Skip to content

buffer: Prevent Buffer constructor deopt

The Buffer constructor will generally get inlined, but any call to the Buffer constructor for a string without encoding will cause an eager deoptimization of any function that inlined the Buffer constructor. This is due to a an out-of-bounds read on arguments[1]. This change prevents that deopt.

This example script demonstrates the deoptimization:

function main() {
  function makeBuffer() {
    var b = new Buffer(10).fill(0);
  }

  for (var i = 0; i < 1e5; i++) {
    makeBuffer()
  }

  b = new Buffer('abcd');

  makeBuffer();
}

main();

If run with --trace-deopt:

$ ./node --trace-deopt t.js
[deoptimizing (DEOPT eager): begin 0x850afb4d981 <JS Function main (SharedFunctionInfo 0x850afb2ecb1)> (opt #8) @18, FP to SP delta: 152]
            ;;; deoptimize at 1156: out of bounds
  reading input frame main => node=1, args=68, height=3; inputs:
      0: 0x850afb4d981 ; (frame function) 0x850afb4d981 <JS Function main (SharedFunctionInfo 0x850afb2ecb1)>
      1: 0x257b60004189 ; [fp - 72] 0x257b60004189 <undefined>
      2: 0x257b600b62d1 ; [fp - 64] 0x257b600b62d1 <FixedArray[164]>
      3: 0x850afb4d939 ; [fp - 56] 0x850afb4d939 <JS Function makeBuffer (SharedFunctionInfo 0x850afb2f659)>
      4: 0x257b60004189 ; (literal 4) 0x257b60004189 <undefined>
  reading construct stub frame main => height=2; inputs:
      0: 0x31834c93da41 ; (literal 8) 0x31834c93da41 <JS Function Buffer (SharedFunctionInfo 0x257b600f50d9)>
      1: 0x28bb10dd7b1 ; rbx 0x28bb10dd7b1 <a Buffer with map 0x1ac5dac178c9>
      2: 0x850afb2ec31 ; (literal 13) 0x850afb2ec31 <String[4]: abcd>
  reading input frame Buffer => node=2, args=74, height=2; inputs:
      0: 0x31834c93da41 ; (literal 8) 0x31834c93da41 <JS Function Buffer (SharedFunctionInfo 0x257b600f50d9)>
      1: 0x28bb10dd7b1 ; rbx 0x28bb10dd7b1 <a Buffer with map 0x1ac5dac178c9>
      2: 0x850afb2ec31 ; (literal 13) 0x850afb2ec31 <String[4]: abcd>
      3: 0x31834c93cea9 ; (literal 10) 0x31834c93cea9 <FixedArray[26]>
      4: argumets object #0 (length = 1)
           0x850afb2ec31 ; (literal 13) 0x850afb2ec31 <String[4]: abcd>
  translating frame main => node=68, height=16
    0x7ffe14593378: [top + 48] <- 0x257b60004189 ;  0x257b60004189 <undefined>  (input #1)
    0x7ffe14593370: [top + 40] <- 0x3fdb34b4ce86 ;  caller's pc
    0x7ffe14593368: [top + 32] <- 0x7ffe145933a0 ;  caller's fp
    0x7ffe14593360: [top + 24] <- 0x257b600b62d1 ;  context    0x257b600b62d1 <FixedArray[164]>  (input #2)
    0x7ffe14593358: [top + 16] <- 0x850afb4d981 ;  function    0x850afb4d981 <JS Function main (SharedFunctionInfo 0x850afb2ecb1)>  (input #0)
    0x7ffe14593350: [top + 8] <- 0x850afb4d939 ;  0x850afb4d939 <JS Function makeBuffer (SharedFunctionInfo 0x850afb2f659)>  (input #3)
    0x7ffe14593348: [top + 0] <- 0x257b60004189 ;  0x257b60004189 <undefined>  (input #4)
  translating construct stub => height=16
    0x7ffe14593340: [top + 80] <- 0x28bb10dd7b1 ;  0x28bb10dd7b1 <a Buffer with map 0x1ac5dac178c9>  (input #1)
    0x7ffe14593338: [top + 72] <- 0x850afb2ec31 ;  0x850afb2ec31 <String[4]: abcd>  (input #2)
    0x7ffe14593330: [top + 64] <- 0x3fdb34b4d4ed ;  caller's pc
    0x7ffe14593328: [top + 56] <- 0x7ffe14593368 ;  caller's fp
    0x7ffe14593320: [top + 48] <- 0x257b600b62d1 ;  context
    0x7ffe14593318: [top + 40] <- 0x900000000 ;  function (construct sentinel)
    0x7ffe14593310: [top + 32] <- 0x3fdb34a2eea1 ;  code object
    0x7ffe14593308: [top + 24] <- 0x257b60004189 ;  allocation site
    0x7ffe14593300: [top + 16] <- 0x100000000 ;  argc (1)
    0x7ffe145932f8: [top + 8] <- 0x257b60004189 ;  new.target
    0x7ffe145932f0: [top + 0] <- 0x28bb10dd7b1 ;  allocated receiver
  translating frame Buffer => node=74, height=8
    0x7ffe145932e8: [top + 48] <- 0x28bb10dd7b1 ;  0x28bb10dd7b1 <a Buffer with map 0x1ac5dac178c9>  (input #1)
    0x7ffe145932e0: [top + 40] <- 0x850afb2ec31 ;  0x850afb2ec31 <String[4]: abcd>  (input #2)
    0x7ffe145932d8: [top + 32] <- 0x3fdb34a2f0a6 ;  caller's pc
    0x7ffe145932d0: [top + 24] <- 0x7ffe14593328 ;  caller's fp
    0x7ffe145932c8: [top + 16] <- 0x31834c93cea9 ;  context    0x31834c93cea9 <FixedArray[26]>  (input #3)
    0x7ffe145932c0: [top + 8] <- 0x31834c93da41 ;  function    0x31834c93da41 <JS Function Buffer (SharedFunctionInfo 0x257b600f50d9)>  (input #0)
    0x7ffe145932b8: [top + 0] <- 0x257b600043d1 ;  0x257b600043d1 <Odd Oddball>  (input #4)
[deoptimizing (eager): end 0x850afb4d981 <JS Function main (SharedFunctionInfo 0x850afb2ecb1)> @18 => node=74, pc=0x3fdb34b48b68, state=NO_REGISTERS, alignment=no padding, took 0.089 ms]
Materialization [0x7ffe145932b8] <- 0x28bb10dd7c9 ;  0x28bb10dd7c9 <an Arguments with map 0x36dcdfa0b271>
[removing optimized code for: main]

Here is the output of IRHydra showing the deopts:

We can see that the Buffer constructor has been inlined: selection_062

The inlined Buffer constructor has been deoptimized due to arguments[1] out-of-bounds read: selection_063

After this patch, the Buffer constructor no longer will get deoptimized for out-of-bounds arguments reads:

./node --trace-deopt t.js

(no output)

cc: @trevnorris

Merge request reports

Loading