経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ

「JSのオブジェクトの分割代入で既定値が割り当てられるのはundefinedの場合のみ」ということは、MDNのAssigning to new variable names and providing default valuesAssigned a default value in case the unpacked value is undefined.と書いてあることや、13 ECMAScript Language: Expressionsの13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation4. If Initializeropt is present and v is undefined, thenとあることから分かる。

ここからNode.jsのfs.readを使ってみていく。

node/lib/fs.js#L605~ に以下のコードがある。

function read(fd, buffer, offsetOrOptions, length, position, callback) {
...
      } else if (arguments.length === 3) {
      // This is fs.read(fd, bufferOrParams, callback)
        if (!isArrayBufferView(buffer)) {
        // This is fs.read(fd, params, callback)
          params = buffer;
          ({ buffer = Buffer.alloc(16384) } = params ?? kEmptyObject);
        }
        ...
...
   ({
      offset = 0,
      length = buffer.byteLength - offset,
      position = null,
    } = params ?? kEmptyObject);
...
   validateBuffer(buffer);
...

例えばfs.read(fd, {offset: 1}, callback)というように呼び出すと、
params = {offset: 1}
({ buffer = Buffer.alloc(16384) } = {offset: 1}
buffer = Buffer.alloc(16384)
というように処理される。

もしfs.read(fd, {buffer: null}, callback)というように呼び出すと、
params = {buffer: null}
({ buffer = Buffer.alloc(16384) } = {buffer: null}
buffer = null
というように処理される。
この場合、validateBufferが呼びされる前にlength = buffer.byteLength - offsetbuffer = nullが参照されてしまう。

これを解決したのがこのPR。
github.com

ちなみにdeno_stdは現時点でNodeのv18.8.0との互換を保っているため、以下のように意図的にnullを参照するようにしている。
github.com

// @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87
length = opt.buffer.byteLength;