Skip to content

Commit 96c6b89

Browse files
motiz88facebook-github-bot
authored andcommitted
Terminate module source maps with a null mapping
Summary: Changelog: * **[Fix]:** Guard against invalid symbolication in Chrome DevTools by terminating each module's source map with a null mapping. ## The bug Metro's bundles include a bit of top-level generated code at the end, which today has no source mappings whatsoever. In Chrome DevTools, this results in stack traces that look corrupt if they include any frames in that region of the bundle: this is because Chrome falls back to the nearest preceding mapping, which happens to come from a different module entirely. ## The fix Here, we add code to `metro-transform-worker` to ensure that we always emit an extra mapping (with no embedded source location) to mark the end of the last line of each module. When concatenated into a bundle, this causes Chrome DevTools to return this null mapping for all generated locations in the bottom of the bundle. This is strictly better than returning a random incorrect source file, regardless of what the "ideal" way to represent that code might be. NOTE: The Metro server's source map infra is hacky and outdated, preventing us from using safe abstractions like `BundleBuilder` that have this type of automatic termination built-in (as well as from emitting any actual mappings for the trailing code in the bundle). We'll likely want to follow up with a deeper refactor there. Reviewed By: GijsWeterings Differential Revision: D55066677 fbshipit-source-id: 879524b0b49cfb1c137daac2083aeafe24f0e193
1 parent 79c10e9 commit 96c6b89

File tree

4 files changed

+68
-4
lines changed

4 files changed

+68
-4
lines changed

packages/metro-transform-worker/src/__tests__/__snapshots__/index-test.js.snap

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ Array [
123123
4,
124124
23,
125125
],
126+
Array [
127+
9,
128+
3,
129+
],
126130
]
127131
`;
128132

@@ -175,6 +179,10 @@ Array [
175179
1,
176180
15,
177181
],
182+
Array [
183+
3,
184+
3,
185+
],
178186
]
179187
`;
180188

@@ -227,6 +235,10 @@ Array [
227235
1,
228236
25,
229237
],
238+
Array [
239+
3,
240+
140,
241+
],
230242
]
231243
`;
232244

@@ -315,6 +327,10 @@ Array [
315327
1,
316328
20,
317329
],
330+
Array [
331+
5,
332+
3,
333+
],
318334
]
319335
`;
320336

packages/metro-transform-worker/src/__tests__/index-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ it('transforms an es module with asyncToGenerator', async () => {
192192

193193
expect(result.output[0].type).toBe('js/module');
194194
expect(result.output[0].data.code).toMatchSnapshot();
195-
expect(result.output[0].data.map).toHaveLength(13);
195+
expect(result.output[0].data.map).toHaveLength(14);
196196
expect(result.output[0].data.functionMap).toMatchSnapshot();
197197
expect(result.dependencies).toEqual([
198198
{

packages/metro-transform-worker/src/index.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const {
4444
toSegmentTuple,
4545
} = require('metro-source-map');
4646
const metroTransformPlugins = require('metro-transform-plugins');
47-
const countLines = require('metro/src/lib/countLines');
4847
const collectDependencies = require('metro/src/ModuleGraph/worker/collectDependencies');
4948
const {
5049
InvalidRequireCallError: InternalInvalidRequireCallError,
@@ -441,11 +440,14 @@ async function transformJS(
441440
));
442441
}
443442

443+
let lineCount;
444+
({lineCount, map} = countLinesAndTerminateMap(code, map));
445+
444446
const output: Array<JsOutput> = [
445447
{
446448
data: {
447449
code,
448-
lineCount: countLines(code),
450+
lineCount,
449451
map,
450452
functionMap: file.functionMap,
451453
},
@@ -552,9 +554,11 @@ async function transformJSON(
552554
jsType = 'js/module';
553555
}
554556

557+
let lineCount;
558+
({lineCount, map} = countLinesAndTerminateMap(code, map));
555559
const output: Array<JsOutput> = [
556560
{
557-
data: {code, lineCount: countLines(code), map, functionMap: null},
561+
data: {code, lineCount, map, functionMap: null},
558562
type: jsType,
559563
},
560564
];
@@ -652,6 +656,7 @@ module.exports = {
652656
const {babelTransformerPath, minifierPath, ...remainingConfig} = config;
653657

654658
const filesKey = getCacheKey([
659+
__filename,
655660
require.resolve(babelTransformerPath),
656661
require.resolve(minifierPath),
657662
require.resolve('./utils/getMinifier'),
@@ -670,3 +675,42 @@ module.exports = {
670675
].join('$');
671676
},
672677
};
678+
679+
function countLinesAndTerminateMap(
680+
code: string,
681+
map: $ReadOnlyArray<MetroSourceMapSegmentTuple>,
682+
): {
683+
lineCount: number,
684+
map: Array<MetroSourceMapSegmentTuple>,
685+
} {
686+
const NEWLINE = /\r\n?|\n|\u2028|\u2029/g;
687+
let lineCount = 1;
688+
let lastLineStart = 0;
689+
690+
// Count lines and keep track of where the last line starts
691+
for (const match of code.matchAll(NEWLINE)) {
692+
lineCount++;
693+
lastLineStart = match.index + match[0].length;
694+
}
695+
const lastLineLength = code.length - lastLineStart;
696+
const lastLineIndex1Based = lineCount;
697+
const lastLineNextColumn0Based = lastLineLength;
698+
699+
// If there isn't a mapping at one-past-the-last column of the last line,
700+
// add one that maps to nothing. This ensures out-of-bounds lookups hit the
701+
// null mapping rather than aliasing to whichever mapping happens to be last.
702+
// ASSUMPTION: Mappings are generated in order of increasing line and column.
703+
const lastMapping = map[map.length - 1];
704+
const terminatingMapping = [lastLineIndex1Based, lastLineNextColumn0Based];
705+
if (
706+
!lastMapping ||
707+
lastMapping[0] !== terminatingMapping[0] ||
708+
lastMapping[1] !== terminatingMapping[1]
709+
) {
710+
return {
711+
lineCount,
712+
map: map.concat([terminatingMapping]),
713+
};
714+
}
715+
return {lineCount, map: [...map]};
716+
}

packages/metro/src/integration_tests/__tests__/__snapshots__/buildGraph-test.js.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,10 @@ Array [
389389
20,
390390
40,
391391
],
392+
Array [
393+
24,
394+
3,
395+
],
392396
],
393397
},
394398
"type": "js/module",

0 commit comments

Comments
 (0)