From 69543f48458d8439e3da9e737dbce1b8060cfb12 Mon Sep 17 00:00:00 2001 From: Dusan Stanojevic Date: Mon, 8 Jun 2026 13:41:02 -0500 Subject: [PATCH] module: don't read a phantom name on the last mapping segment SourceMap#parseMap reads the optional name VLQ guarded only by !isSeparator(peek()). At the end of the mappings string peek() returns '', which is not a separator, so the parser decodes a phantom VLQ (0), keeps the previous nameIndex, and assigns names[nameIndex] to a segment that has no name field. SourceMap.findEntry() then returns a stale name, which surfaces as the wrong function name on the bottom global frame of --enable-source-maps stack traces. Guard the name read with hasNext() so the phantom field is not read at end of string. A trailing segment that genuinely carries a name still has characters left to read, so it is unaffected. Refs: https://github.com/nodejs/node/issues/63795 Signed-off-by: Dusan Stanojevic --- lib/internal/source_map/source_map.js | 3 +- .../parallel/test-source-map-trailing-name.js | 34 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-source-map-trailing-name.js diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index 42e7bca3c4c5c0..cda2dabc9ab7a5 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -300,7 +300,8 @@ class SourceMap { sourceColumnNumber += decodeVLQ(stringCharIterator); let name; - if (!isSeparator(stringCharIterator.peek())) { + if (stringCharIterator.hasNext() && + !isSeparator(stringCharIterator.peek())) { nameIndex += decodeVLQ(stringCharIterator); name = map.names?.[nameIndex]; } diff --git a/test/parallel/test-source-map-trailing-name.js b/test/parallel/test-source-map-trailing-name.js new file mode 100644 index 00000000000000..55867b811b0010 --- /dev/null +++ b/test/parallel/test-source-map-trailing-name.js @@ -0,0 +1,34 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { SourceMap } = require('node:module'); + +// Refs: https://github.com/nodejs/node/issues/63795 + +// A mapping whose final segment has 4 fields (no name index) must not +// inherit the previous segment's name. +{ + const sm = new SourceMap({ + version: 3, + sources: ['a.js'], + names: ['foo'], + mappings: 'AAAAA,CAAC', + }); + + assert.strictEqual(sm.findEntry(0, 0).name, 'foo'); + assert.strictEqual(sm.findEntry(0, 1).name, undefined); +} + +// A mapping whose final segment legitimately carries a name index must still +// resolve it: the end-of-string guard must not suppress a real trailing name. +{ + const sm = new SourceMap({ + version: 3, + sources: ['a.js'], + names: ['foo'], + mappings: 'AAAA,CAAAA', + }); + + assert.strictEqual(sm.findEntry(0, 0).name, undefined); + assert.strictEqual(sm.findEntry(0, 1).name, 'foo'); +}