From 73ffd8ae11eddefac43dfa0aa283f4b9c256a64a Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Sun, 19 Apr 2026 19:40:37 +0900 Subject: [PATCH 1/2] lib: use Object.freeze to avoid defensive cloning in SourceMap --- lib/internal/source_map/source_map.js | 14 ++++++-------- test/parallel/test-source-map-api.js | 13 +++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index dbfc05e8925d46..42e7bca3c4c5c0 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -71,6 +71,7 @@ const { ArrayPrototypePush, ArrayPrototypeSlice, ArrayPrototypeSort, + ObjectFreeze, ObjectPrototypeHasOwnProperty, StringPrototypeCharAt, Symbol, @@ -144,7 +145,7 @@ class SourceMap { this.#payload = cloneSourceMapV3(payload); this.#parseMappingPayload(); if (ArrayIsArray(lineLengths) && lineLengths.length) { - this.#lineLengths = lineLengths; + this.#lineLengths = ObjectFreeze(ArrayPrototypeSlice(lineLengths)); } } @@ -152,7 +153,7 @@ class SourceMap { * @returns {object} raw source map v3 payload. */ get payload() { - return cloneSourceMapV3(this.#payload); + return this.#payload; } get [kMappings]() { @@ -163,10 +164,7 @@ class SourceMap { * @returns {number[] | undefined} line lengths of generated source code */ get lineLengths() { - if (this.#lineLengths) { - return ArrayPrototypeSlice(this.#lineLengths); - } - return undefined; + return this.#lineLengths; } #parseMappingPayload = () => { @@ -366,10 +364,10 @@ function cloneSourceMapV3(payload) { for (const key in payload) { if (ObjectPrototypeHasOwnProperty(payload, key) && ArrayIsArray(payload[key])) { - payload[key] = ArrayPrototypeSlice(payload[key]); + payload[key] = ObjectFreeze(ArrayPrototypeSlice(payload[key])); } } - return payload; + return ObjectFreeze(payload); } /** diff --git a/test/parallel/test-source-map-api.js b/test/parallel/test-source-map-api.js index 8c965891536072..bc09d2ca2b8775 100644 --- a/test/parallel/test-source-map-api.js +++ b/test/parallel/test-source-map-api.js @@ -140,6 +140,14 @@ const { readFileSync } = require('fs'); assert.notStrictEqual(payload, sourceMap.payload); assert.strictEqual(payload.sources[0], sourceMap.payload.sources[0]); assert.notStrictEqual(payload.sources, sourceMap.payload.sources); + // The payload and its arrays should be frozen to avoid unnecessary cloning: + assert(Object.isFrozen(sourceMap.payload)); + assert(Object.isFrozen(sourceMap.payload.sources)); + // The same frozen object is returned on each call: + assert.strictEqual(sourceMap.payload, sourceMap.payload); + // lineLengths should be frozen and return the same reference each call: + assert(Object.isFrozen(sourceMap.lineLengths)); + assert.strictEqual(sourceMap.lineLengths, sourceMap.lineLengths); } // findEntry() and findOrigin() must return empty object instead of @@ -178,6 +186,11 @@ const { readFileSync } = require('fs'); assert.notStrictEqual(payload, sourceMap.payload); assert.strictEqual(payload.sources[0], sourceMap.payload.sources[0]); assert.notStrictEqual(payload.sources, sourceMap.payload.sources); + // The payload and its arrays should be frozen to avoid unnecessary cloning: + assert(Object.isFrozen(sourceMap.payload)); + assert(Object.isFrozen(sourceMap.payload.sources)); + // The same frozen object is returned on each call: + assert.strictEqual(sourceMap.payload, sourceMap.payload); } // Test various known decodings to ensure decodeVLQ works correctly. From ab627d745c2d9ce09671b96123ff71e7419fdac4 Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Wed, 22 Apr 2026 13:36:15 +0900 Subject: [PATCH 2/2] doc: clarify sourceMap.payload behavior and immutability --- doc/api/module.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api/module.md b/doc/api/module.md index ac08d9af3e4f14..5f90b047ac7a37 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -1970,6 +1970,9 @@ generated code. Getter for the payload used to construct the [`SourceMap`][] instance. +The returned object is frozen with [`Object.freeze()`][], and the same +reference is returned on every access. Do not mutate the returned object. + #### `sourceMap.findEntry(lineOffset, columnOffset)` * `lineOffset` {number} The zero-indexed line number offset in @@ -2065,6 +2068,7 @@ returned object contains the following keys: [`module`]: #the-module-object [`os.tmpdir()`]: os.md#ostmpdir [`register`]: #moduleregisterspecifier-parenturl-options +[`Object.freeze()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze [`util.TextDecoder`]: util.md#class-utiltextdecoder [accepted final formats]: #accepted-final-formats-returned-by-load [asynchronous `load` hook]: #asynchronous-loadurl-context-nextload