Skip to content

Commit b85cf6a

Browse files
authored
[compiler] Fix VariableDeclarator source location (facebook#35348)
Putting up facebook#35129 again Reverted in facebook#35346 after breaking main before security patch This change impacts output formatting in a lot of snaps, so is very sensitive to additions in main to the fixtures resulting in broken tests after merging, so we should try merge quickly after rebasing or do a fast follow to the merge with a snap update.
1 parent b45bb33 commit b85cf6a

File tree

175 files changed

+459
-399
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

175 files changed

+459
-399
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4026,6 +4026,7 @@ function lowerAssignment(
40264026
pattern: {
40274027
kind: 'ArrayPattern',
40284028
items,
4029+
loc: lvalue.node.loc ?? GeneratedSource,
40294030
},
40304031
},
40314032
value,
@@ -4203,6 +4204,7 @@ function lowerAssignment(
42034204
pattern: {
42044205
kind: 'ObjectPattern',
42054206
properties,
4207+
loc: lvalue.node.loc ?? GeneratedSource,
42064208
},
42074209
},
42084210
value,

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,11 +694,13 @@ export type SpreadPattern = {
694694
export type ArrayPattern = {
695695
kind: 'ArrayPattern';
696696
items: Array<Place | SpreadPattern | Hole>;
697+
loc: SourceLocation;
697698
};
698699

699700
export type ObjectPattern = {
700701
kind: 'ObjectPattern';
701702
properties: Array<ObjectProperty | SpreadPattern>;
703+
loc: SourceLocation;
702704
};
703705

704706
export type ObjectPropertyKey =

compiler/packages/babel-plugin-react-compiler/src/Optimization/OutlineJsx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ function emitDestructureProps(
515515
pattern: {
516516
kind: 'ObjectPattern',
517517
properties,
518+
loc: GeneratedSource,
518519
},
519520
kind: InstructionKind.Let,
520521
},

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ function codegenReactiveScope(
702702
outputComments.push(name.name);
703703
if (!cx.hasDeclared(identifier)) {
704704
statements.push(
705-
t.variableDeclaration('let', [t.variableDeclarator(name)]),
705+
t.variableDeclaration('let', [createVariableDeclarator(name, null)]),
706706
);
707707
}
708708
cacheLoads.push({name, index, value: wrapCacheDep(cx, name)});
@@ -1387,7 +1387,7 @@ function codegenInstructionNullable(
13871387
suggestions: null,
13881388
});
13891389
return createVariableDeclaration(instr.loc, 'const', [
1390-
t.variableDeclarator(codegenLValue(cx, lvalue), value),
1390+
createVariableDeclarator(codegenLValue(cx, lvalue), value),
13911391
]);
13921392
}
13931393
case InstructionKind.Function: {
@@ -1451,7 +1451,7 @@ function codegenInstructionNullable(
14511451
suggestions: null,
14521452
});
14531453
return createVariableDeclaration(instr.loc, 'let', [
1454-
t.variableDeclarator(codegenLValue(cx, lvalue), value),
1454+
createVariableDeclarator(codegenLValue(cx, lvalue), value),
14551455
]);
14561456
}
14571457
case InstructionKind.Reassign: {
@@ -1691,6 +1691,9 @@ function withLoc<T extends (...args: Array<any>) => t.Node>(
16911691
};
16921692
}
16931693

1694+
const createIdentifier = withLoc(t.identifier);
1695+
const createArrayPattern = withLoc(t.arrayPattern);
1696+
const createObjectPattern = withLoc(t.objectPattern);
16941697
const createBinaryExpression = withLoc(t.binaryExpression);
16951698
const createExpressionStatement = withLoc(t.expressionStatement);
16961699
const _createLabelledStatement = withLoc(t.labeledStatement);
@@ -1722,6 +1725,31 @@ const createTryStatement = withLoc(t.tryStatement);
17221725
const createBreakStatement = withLoc(t.breakStatement);
17231726
const createContinueStatement = withLoc(t.continueStatement);
17241727

1728+
function createVariableDeclarator(
1729+
id: t.LVal,
1730+
init?: t.Expression | null,
1731+
): t.VariableDeclarator {
1732+
const node = t.variableDeclarator(id, init);
1733+
1734+
/*
1735+
* The variable declarator location is not preserved in HIR, however, we can use the
1736+
* start location of the id and the end location of the init to recreate the
1737+
* exact original variable declarator location.
1738+
*
1739+
* Or if init is null, we likely have a declaration without an initializer, so we can use the id.loc.end as the end location.
1740+
*/
1741+
if (id.loc && (init === null || init?.loc)) {
1742+
node.loc = {
1743+
start: id.loc.start,
1744+
end: init?.loc?.end ?? id.loc.end,
1745+
filename: id.loc.filename,
1746+
identifierName: undefined,
1747+
};
1748+
}
1749+
1750+
return node;
1751+
}
1752+
17251753
function createHookGuard(
17261754
guard: ExternalFunction,
17271755
context: ProgramContext,
@@ -1829,7 +1857,7 @@ function codegenInstruction(
18291857
);
18301858
} else {
18311859
return createVariableDeclaration(instr.loc, 'const', [
1832-
t.variableDeclarator(
1860+
createVariableDeclarator(
18331861
convertIdentifier(instr.lvalue.identifier),
18341862
expressionValue,
18351863
),
@@ -2756,7 +2784,7 @@ function codegenArrayPattern(
27562784
): t.ArrayPattern {
27572785
const hasHoles = !pattern.items.every(e => e.kind !== 'Hole');
27582786
if (hasHoles) {
2759-
const result = t.arrayPattern([]);
2787+
const result = createArrayPattern(pattern.loc, []);
27602788
/*
27612789
* Older versions of babel have a validation bug fixed by
27622790
* https://github.com/babel/babel/pull/10917
@@ -2777,7 +2805,8 @@ function codegenArrayPattern(
27772805
}
27782806
return result;
27792807
} else {
2780-
return t.arrayPattern(
2808+
return createArrayPattern(
2809+
pattern.loc,
27812810
pattern.items.map(item => {
27822811
if (item.kind === 'Hole') {
27832812
return null;
@@ -2797,7 +2826,8 @@ function codegenLValue(
27972826
return codegenArrayPattern(cx, pattern);
27982827
}
27992828
case 'ObjectPattern': {
2800-
return t.objectPattern(
2829+
return createObjectPattern(
2830+
pattern.loc,
28012831
pattern.properties.map(property => {
28022832
if (property.kind === 'ObjectProperty') {
28032833
const key = codegenObjectPropertyKey(cx, property.key);
@@ -2916,7 +2946,7 @@ function convertIdentifier(identifier: Identifier): t.Identifier {
29162946
suggestions: null,
29172947
},
29182948
);
2919-
return t.identifier(identifier.name.value);
2949+
return createIdentifier(identifier.loc, identifier.name.value);
29202950
}
29212951

29222952
function compareScopeDependency(

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateSourceLocations.ts

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ import {Result} from '../Utils/Result';
2727

2828
/**
2929
* Some common node types that are important for coverage tracking.
30-
* Based on istanbul-lib-instrument
30+
* Based on istanbul-lib-instrument + some other common nodes we expect to be present in the generated AST.
31+
*
32+
* Note: For VariableDeclaration, VariableDeclarator, and Identifier, we enforce stricter validation
33+
* that requires both the source location AND node type to match in the generated AST. This ensures
34+
* that variable declarations maintain their structural integrity through compilation.
3135
*/
3236
const IMPORTANT_INSTRUMENTED_TYPES = new Set([
3337
'ArrowFunctionExpression',
@@ -54,6 +58,14 @@ const IMPORTANT_INSTRUMENTED_TYPES = new Set([
5458
'LabeledStatement',
5559
'ConditionalExpression',
5660
'LogicalExpression',
61+
62+
/**
63+
* Note: these aren't important for coverage tracking,
64+
* but we still want to track them to ensure we aren't regressing them when
65+
* we fix the source location tracking for other nodes.
66+
*/
67+
'VariableDeclaration',
68+
'Identifier',
5769
]);
5870

5971
/**
@@ -114,10 +126,13 @@ export function validateSourceLocations(
114126
): Result<void, CompilerError> {
115127
const errors = new CompilerError();
116128

117-
// Step 1: Collect important locations from the original source
129+
/*
130+
* Step 1: Collect important locations from the original source
131+
* Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier)
132+
*/
118133
const importantOriginalLocations = new Map<
119134
string,
120-
{loc: t.SourceLocation; nodeType: string}
135+
{loc: t.SourceLocation; nodeTypes: Set<string>}
121136
>();
122137

123138
func.traverse({
@@ -137,20 +152,31 @@ export function validateSourceLocations(
137152
// Collect the location if it exists
138153
if (node.loc) {
139154
const key = locationKey(node.loc);
140-
importantOriginalLocations.set(key, {
141-
loc: node.loc,
142-
nodeType: node.type,
143-
});
155+
const existing = importantOriginalLocations.get(key);
156+
if (existing) {
157+
existing.nodeTypes.add(node.type);
158+
} else {
159+
importantOriginalLocations.set(key, {
160+
loc: node.loc,
161+
nodeTypes: new Set([node.type]),
162+
});
163+
}
144164
}
145165
},
146166
});
147167

148-
// Step 2: Collect all locations from the generated AST
149-
const generatedLocations = new Set<string>();
168+
// Step 2: Collect all locations from the generated AST with their node types
169+
const generatedLocations = new Map<string, Set<string>>();
150170

151171
function collectGeneratedLocations(node: t.Node): void {
152172
if (node.loc) {
153-
generatedLocations.add(locationKey(node.loc));
173+
const key = locationKey(node.loc);
174+
const nodeTypes = generatedLocations.get(key);
175+
if (nodeTypes) {
176+
nodeTypes.add(node.type);
177+
} else {
178+
generatedLocations.set(key, new Set([node.type]));
179+
}
154180
}
155181

156182
// Use Babel's VISITOR_KEYS to traverse only actual node properties
@@ -183,22 +209,86 @@ export function validateSourceLocations(
183209
collectGeneratedLocations(outlined.fn.body);
184210
}
185211

186-
// Step 3: Validate that all important locations are preserved
187-
for (const [key, {loc, nodeType}] of importantOriginalLocations) {
188-
if (!generatedLocations.has(key)) {
189-
errors.pushDiagnostic(
190-
CompilerDiagnostic.create({
191-
category: ErrorCategory.Todo,
192-
reason: 'Important source location missing in generated code',
193-
description:
194-
`Source location for ${nodeType} is missing in the generated output. This can cause coverage instrumentation ` +
195-
`to fail to track this code properly, resulting in inaccurate coverage reports.`,
196-
}).withDetails({
197-
kind: 'error',
198-
loc,
199-
message: null,
200-
}),
201-
);
212+
/*
213+
* Step 3: Validate that all important locations are preserved
214+
* For certain node types, also validate that the node type matches
215+
*/
216+
const strictNodeTypes = new Set([
217+
'VariableDeclaration',
218+
'VariableDeclarator',
219+
'Identifier',
220+
]);
221+
222+
const reportMissingLocation = (
223+
loc: t.SourceLocation,
224+
nodeType: string,
225+
): void => {
226+
errors.pushDiagnostic(
227+
CompilerDiagnostic.create({
228+
category: ErrorCategory.Todo,
229+
reason: 'Important source location missing in generated code',
230+
description:
231+
`Source location for ${nodeType} is missing in the generated output. This can cause coverage instrumentation ` +
232+
`to fail to track this code properly, resulting in inaccurate coverage reports.`,
233+
}).withDetails({
234+
kind: 'error',
235+
loc,
236+
message: null,
237+
}),
238+
);
239+
};
240+
241+
const reportWrongNodeType = (
242+
loc: t.SourceLocation,
243+
expectedType: string,
244+
actualTypes: Set<string>,
245+
): void => {
246+
errors.pushDiagnostic(
247+
CompilerDiagnostic.create({
248+
category: ErrorCategory.Todo,
249+
reason:
250+
'Important source location has wrong node type in generated code',
251+
description:
252+
`Source location for ${expectedType} exists in the generated output but with wrong node type(s): ${Array.from(actualTypes).join(', ')}. ` +
253+
`This can cause coverage instrumentation to fail to track this code properly, resulting in inaccurate coverage reports.`,
254+
}).withDetails({
255+
kind: 'error',
256+
loc,
257+
message: null,
258+
}),
259+
);
260+
};
261+
262+
for (const [key, {loc, nodeTypes}] of importantOriginalLocations) {
263+
const generatedNodeTypes = generatedLocations.get(key);
264+
265+
if (!generatedNodeTypes) {
266+
// Location is completely missing
267+
reportMissingLocation(loc, Array.from(nodeTypes).join(', '));
268+
} else {
269+
// Location exists, check each node type
270+
for (const nodeType of nodeTypes) {
271+
if (
272+
strictNodeTypes.has(nodeType) &&
273+
!generatedNodeTypes.has(nodeType)
274+
) {
275+
/*
276+
* For strict node types, the specific node type must be present
277+
* Check if any generated node type is also an important original node type
278+
*/
279+
const hasValidNodeType = Array.from(generatedNodeTypes).some(
280+
genType => nodeTypes.has(genType),
281+
);
282+
283+
if (hasValidNodeType) {
284+
// At least one generated node type is valid (also in original), so this is just missing
285+
reportMissingLocation(loc, nodeType);
286+
} else {
287+
// None of the generated node types are in original - this is wrong node type
288+
reportWrongNodeType(loc, nodeType, generatedNodeTypes);
289+
}
290+
}
291+
}
202292
}
203293
}
204294

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver-and-mutate.expect.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ function Component() {
3535
let t0;
3636
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
3737
const a = makeObject_Primitives();
38-
3938
const x = [];
4039
x.push(a);
41-
4240
mutate(x);
4341
t0 = [x, a];
4442
$[0] = t0;

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/alias-capture-in-method-receiver.expect.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ function Component() {
3333
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
3434
const x = [];
3535
x.push(a);
36-
3736
t1 = [x, a];
3837
$[1] = t1;
3938
} else {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-fn-expr.expect.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,10 @@ function Component(t0) {
8585
let t1;
8686
if ($[0] !== prop) {
8787
const obj = shallowCopy(prop);
88-
8988
const aliasedObj = identity(obj);
90-
9189
const getId = () => obj.id;
92-
9390
mutate(aliasedObj);
9491
setPropertyByKey(aliasedObj, "id", prop.id + 1);
95-
9692
t1 = <Stringify getId={getId} shouldInvokeFns={true} />;
9793
$[0] = prop;
9894
$[1] = t1;

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/aliased-nested-scope-truncated-dep.expect.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,9 @@ function Component(t0) {
181181
if ($[0] !== prop) {
182182
const obj = shallowCopy(prop);
183183
const aliasedObj = identity(obj);
184-
185184
const id = [obj.id];
186-
187185
mutate(aliasedObj);
188186
setPropertyByKey(aliasedObj, "id", prop.id + 1);
189-
190187
t1 = <Stringify id={id} />;
191188
$[0] = prop;
192189
$[1] = t1;

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/align-scopes-within-nested-valueblock-in-array.expect.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ function Foo(t0) {
5454
let t1;
5555
if ($[0] !== cond1 || $[1] !== cond2) {
5656
const arr = makeArray({ a: 2 }, 2, []);
57-
5857
t1 = cond1 ? (
5958
<>
6059
<div>{identity("foo")}</div>

0 commit comments

Comments
 (0)