From 7b33dfd7ea1a3b5c7ac168b47f3440b8076f940c Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Sun, 19 Apr 2026 19:40:37 +0900 Subject: [PATCH 1/4] 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 f493f69d12884c62b6f9eb1fca83f2c9b9803bcf Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Thu, 23 Apr 2026 19:36:22 +0900 Subject: [PATCH 2/4] 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..8ef4f3f2cff76f 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 @@ -2054,6 +2057,7 @@ returned object contains the following keys: [`NODE_COMPILE_CACHE_PORTABLE=1`]: cli.md#node_compile_cache_portable1 [`NODE_DISABLE_COMPILE_CACHE=1`]: cli.md#node_disable_compile_cache1 [`NODE_V8_COVERAGE=dir`]: cli.md#node_v8_coveragedir +[`Object.freeze()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze [`SourceMap`]: #class-modulesourcemap [`initialize`]: #initialize [`module.constants.compileCacheStatus`]: #moduleconstantscompilecachestatus From d3e4534ba0f2f217f5eb1b09250fd0efe480f97d Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Fri, 24 Apr 2026 02:52:24 +0900 Subject: [PATCH 3/4] doc: add change metadata for validator message Co-authored-by: Chengzhong Wu --- doc/api/module.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/api/module.md b/doc/api/module.md index 8ef4f3f2cff76f..5791eb9de31e0a 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -1965,6 +1965,11 @@ Creates a new `sourceMap` instance. generated code. #### `sourceMap.payload` + * Returns: {Object}