Skip to content

Commit 40865c5

Browse files
committed
lib: bound the generated source map cache
Signed-off-by: Lazizbek Ergashev <lazerg2@gmail.com>
1 parent 4e207b1 commit 40865c5

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

doc/api/module.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,10 @@ added:
19241924
`path` is the resolved path for the file for which a corresponding source map
19251925
should be fetched.
19261926
1927+
Source maps of code generated by `eval` or `new Function` are held in a cache of
1928+
limited capacity, so the oldest of them are dropped once enough generated code
1929+
has been evaluated.
1930+
19271931
### `module.setSourceMapsSupport(enabled[, options])`
19281932
19291933
<!-- YAML

lib/internal/source_map/source_map_cache.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ const getModuleSourceMapCache = getLazy(() => {
3636
return new SourceMapCacheMap();
3737
});
3838

39-
// The generated source module/script instance is not accessible, so we can use
40-
// a Map without memory concerns. Separate generated source entries with the module
41-
// source entries to avoid overriding the module source entries with arbitrary
42-
// source url magic comments.
39+
// The generated source module/script instance is not accessible, so these entries
40+
// cannot be keyed weakly like the module source entries are. The cache is bounded
41+
// instead, or generated sources evaluated under a changing source url would
42+
// retain every payload they ever mapped.
43+
// Separate generated source entries with the module source entries to avoid
44+
// overriding the module source entries with arbitrary source url magic comments.
45+
const kGeneratedSourceMapCacheLimit = 256;
4346
const generatedSourceMapCache = new SafeMap();
4447
const kLeadingProtocol = /^\w+:\/\//;
4548
const kSourceMappingURLMagicComment = /\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/g;
@@ -189,7 +192,12 @@ function maybeCacheSourceMap(filename, content, moduleInstance, isGeneratedSourc
189192
};
190193

191194
if (isGeneratedSource) {
195+
// Delete first so that re-evaluating a source moves it back to the newest end.
196+
generatedSourceMapCache.delete(filename);
192197
generatedSourceMapCache.set(filename, entry);
198+
if (generatedSourceMapCache.size > kGeneratedSourceMapCacheLimit) {
199+
generatedSourceMapCache.delete(generatedSourceMapCache.keys().next().value);
200+
}
193201
return;
194202
}
195203
// If it is not a generated source, we assume we are in a "cjs/esm"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Flags: --enable-source-maps
2+
'use strict';
3+
4+
/**
5+
* This test verifies that the cache of source maps of generated sources is
6+
* bounded, so evaluating code under a new source url evicts the oldest entry.
7+
*/
8+
9+
require('../common');
10+
const assert = require('node:assert');
11+
const { findSourceMap } = require('node:module');
12+
13+
const payload = Buffer.from(JSON.stringify({
14+
version: 3,
15+
sources: ['a.js'],
16+
names: [],
17+
mappings: 'AAAA',
18+
})).toString('base64');
19+
20+
function evaluate(id) {
21+
eval(`(() => {})\n` +
22+
`//# sourceURL=file:///generated-${id}.js\n` +
23+
`//# sourceMappingURL=data:application/json;base64,${payload}`);
24+
}
25+
26+
evaluate(0);
27+
assert.ok(findSourceMap('file:///generated-0.js'));
28+
29+
// More generated sources than the limit in
30+
// internal/source_map/source_map_cache.js, so that the first one is evicted.
31+
for (let i = 1; i <= 300; i++) {
32+
evaluate(i);
33+
}
34+
35+
assert.strictEqual(findSourceMap('file:///generated-0.js'), undefined);
36+
37+
const sourceMap = findSourceMap('file:///generated-300.js');
38+
assert.ok(sourceMap);
39+
assert.strictEqual(sourceMap.findEntry(0, 0).originalLine, 0);

0 commit comments

Comments
 (0)