diff --git a/lib/internal/source_map/source_map.js b/lib/internal/source_map/source_map.js index 32fe43ac8f68cb..d66230ec37f312 100644 --- a/lib/internal/source_map/source_map.js +++ b/lib/internal/source_map/source_map.js @@ -303,8 +303,19 @@ function decodeVLQ(stringCharIterator) { // Fix the sign. const negative = result & 1; - result >>= 1; - return negative ? -result : result; + // Use unsigned right shift, so that the 32nd bit is properly shifted to the + // 31st, and the 32nd becomes unset. + result >>>= 1; + if (!negative) { + return result; + } + + // We need to OR here to ensure the 32nd bit (the sign bit in an Int32) is + // always set for negative numbers. If `result` were 1, (meaning `negate` is + // true and all other bits were zeros), `result` would now be 0. But -0 + // doesn't flip the 32nd bit as intended. All other numbers will successfully + // set the 32nd bit without issue, so doing this is a noop for them. + return -result | (1 << 31); } /** diff --git a/test/parallel/test-source-map-api.js b/test/parallel/test-source-map-api.js index e7704cf45cf68e..2bfbc08809e9a1 100644 --- a/test/parallel/test-source-map-api.js +++ b/test/parallel/test-source-map-api.js @@ -82,3 +82,45 @@ const { readFileSync } = require('fs'); assert.strictEqual(payload.sources[0], sourceMap.payload.sources[0]); assert.notStrictEqual(payload.sources, sourceMap.payload.sources); } + +// Test various known decodings to ensure decodeVLQ works correctly. +{ + function makeMinimalMap(column) { + return { + sources: ['test.js'], + // Mapping from the 0th line, 0th column of the output file to the 0th + // source file, 0th line, ${column}th column. + mappings: `AAA${column}`, + }; + } + const knownDecodings = { + 'A': 0, + 'B': -2147483648, + 'C': 1, + 'D': -1, + 'E': 2, + 'F': -2, + + // 2^31 - 1, maximum values + '+/////D': 2147483647, + '8/////D': 2147483646, + '6/////D': 2147483645, + '4/////D': 2147483644, + '2/////D': 2147483643, + '0/////D': 2147483642, + + // -2^31 + 1, minimum values + '//////D': -2147483647, + '9/////D': -2147483646, + '7/////D': -2147483645, + '5/////D': -2147483644, + '3/////D': -2147483643, + '1/////D': -2147483642, + }; + + for (const column in knownDecodings) { + const sourceMap = new SourceMap(makeMinimalMap(column)); + const { originalColumn } = sourceMap.findEntry(0, 0); + assert.strictEqual(originalColumn, knownDecodings[column]); + } +}