@samitouri / QOS-React / commits / cebe42e245

[compiler] Add fault tolerance test fixtures (#35879)

--- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35879). * #35888 * #35884 * #35883 * #35882 * #35881 * #35880 * __->__ #35879

Joseph Savona committed Feb 23, 2026 at 16:06 UTC cebe42e24521ce02bf427fd482009d01e1466277
11 files changed +443
compiler/fault-tolerance-overview.md
+1
@@ -327,4 +327,5 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
327 * **Phase 3 (BuildHIR) revealed that most error sites already used `builder.errors.push()` for accumulation.** The existing lowering code was designed to accumulate errors rather than throw. The main changes were: (1) changing `lower()` return type from `Result` to `HIRFunction`, (2) recording builder errors on env, (3) adding a try/catch around body lowering to catch thrown CompilerErrors from sub-calls like `resolveBinding()`, (4) treating `var` as `let` instead of skipping declarations, and (5) fixing ForStatement init/test handling to produce valid CFG structure.
328 * **Partial HIR can trigger downstream invariants.** When lowering skips or partially handles constructs (e.g., unreachable hoisted functions, `var` declarations before the fix), downstream passes like `InferMutationAliasingEffects` may encounter uninitialized identifiers and throw invariants. This is acceptable since the function still correctly bails out of compilation, but error messages may be less specific. The fix for `var` (treating as `let`) demonstrates how to avoid this: continue lowering with a best-effort representation rather than skipping entirely.
329 * **Errors accumulated on `env` are lost when an invariant propagates out of the pipeline.** Since invariant CompilerErrors always re-throw through `tryRecord()`, they exit the pipeline as exceptions. The caller only sees the invariant error, not any errors previously recorded on `env`. This is a design limitation that could be addressed by aggregating env errors with caught exceptions in `tryCompileFunction()`.
330 +* **Dedicated fault tolerance test fixtures** were added in `__tests__/fixtures/compiler/fault-tolerance/`. Each fixture combines two or more errors from different passes to verify the compiler reports all of them rather than short-circuiting on the first. Coverage includes: `var`+props mutation (BuildHIR→InferMutationAliasingEffects), `var`+ref access (BuildHIR→ValidateNoRefAccessInRender), `try/finally`+props mutation (BuildHIR→InferMutationAliasingEffects), `try/finally`+ref access (BuildHIR→ValidateNoRefAccessInRender), and a 3-error test combining try/finally+ref access+props mutation.
331
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-and-mutation-of-props.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +/**
6 + * Fault tolerance test: two independent errors should both be reported.
7 + *
8 + * Error 1 (BuildHIR): `try/finally` is not supported
9 + * Error 2 (InferMutationAliasingEffects): Mutation of frozen props
10 + */
11 +function Component(props) {
12 + // Error: try/finally (Todo from BuildHIR)
13 + try {
14 + doWork();
15 + } finally {
16 + doCleanup();
17 + }
18 +
19 + // Error: mutating frozen props
20 + props.value = 1;
21 +
22 + return <div>{props.value}</div>;
23 +}
24 +
25 +```
26 +
27 +
28 +## Error
29 +
30 +```
31 +Found 2 errors:
32 +
33 +Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
34 +
35 +error.try-finally-and-mutation-of-props.ts:9:2
36 + 7 | function Component(props) {
37 + 8 | // Error: try/finally (Todo from BuildHIR)
38 +> 9 | try {
39 + | ^^^^^
40 +> 10 | doWork();
41 + | ^^^^^^^^^^^^^
42 +> 11 | } finally {
43 + | ^^^^^^^^^^^^^
44 +> 12 | doCleanup();
45 + | ^^^^^^^^^^^^^
46 +> 13 | }
47 + | ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
48 + 14 |
49 + 15 | // Error: mutating frozen props
50 + 16 | props.value = 1;
51 +
52 +Error: This value cannot be modified
53 +
54 +Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
55 +
56 +error.try-finally-and-mutation-of-props.ts:16:2
57 + 14 |
58 + 15 | // Error: mutating frozen props
59 +> 16 | props.value = 1;
60 + | ^^^^^ value cannot be modified
61 + 17 |
62 + 18 | return <div>{props.value}</div>;
63 + 19 | }
64 +```
65 +
66 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-and-mutation-of-props.js new
+19
@@ -0,0 +1,19 @@
1 +/**
2 + * Fault tolerance test: two independent errors should both be reported.
3 + *
4 + * Error 1 (BuildHIR): `try/finally` is not supported
5 + * Error 2 (InferMutationAliasingEffects): Mutation of frozen props
6 + */
7 +function Component(props) {
8 + // Error: try/finally (Todo from BuildHIR)
9 + try {
10 + doWork();
11 + } finally {
12 + doCleanup();
13 + }
14 +
15 + // Error: mutating frozen props
16 + props.value = 1;
17 +
18 + return <div>{props.value}</div>;
19 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-and-ref-access.expect.md new
+69
@@ -0,0 +1,69 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +/**
7 + * Fault tolerance test: two independent errors should both be reported.
8 + *
9 + * Error 1 (BuildHIR): `try/finally` is not supported
10 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
11 + */
12 +function Component() {
13 + const ref = useRef(null);
14 +
15 + // Error: try/finally (Todo from BuildHIR)
16 + try {
17 + doSomething();
18 + } finally {
19 + cleanup();
20 + }
21 +
22 + // Error: reading ref during render
23 + const value = ref.current;
24 +
25 + return <div>{value}</div>;
26 +}
27 +
28 +```
29 +
30 +
31 +## Error
32 +
33 +```
34 +Found 2 errors:
35 +
36 +Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
37 +
38 +error.try-finally-and-ref-access.ts:12:2
39 + 10 |
40 + 11 | // Error: try/finally (Todo from BuildHIR)
41 +> 12 | try {
42 + | ^^^^^
43 +> 13 | doSomething();
44 + | ^^^^^^^^^^^^^^^^^^
45 +> 14 | } finally {
46 + | ^^^^^^^^^^^^^^^^^^
47 +> 15 | cleanup();
48 + | ^^^^^^^^^^^^^^^^^^
49 +> 16 | }
50 + | ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
51 + 17 |
52 + 18 | // Error: reading ref during render
53 + 19 | const value = ref.current;
54 +
55 +Error: Cannot access refs during render
56 +
57 +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
58 +
59 +error.try-finally-and-ref-access.ts:19:16
60 + 17 |
61 + 18 | // Error: reading ref during render
62 +> 19 | const value = ref.current;
63 + | ^^^^^^^^^^^ Cannot access ref value during render
64 + 20 |
65 + 21 | return <div>{value}</div>;
66 + 22 | }
67 +```
68 +
69 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-and-ref-access.js new
+22
@@ -0,0 +1,22 @@
1 +// @validateRefAccessDuringRender
2 +/**
3 + * Fault tolerance test: two independent errors should both be reported.
4 + *
5 + * Error 1 (BuildHIR): `try/finally` is not supported
6 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
7 + */
8 +function Component() {
9 + const ref = useRef(null);
10 +
11 + // Error: try/finally (Todo from BuildHIR)
12 + try {
13 + doSomething();
14 + } finally {
15 + cleanup();
16 + }
17 +
18 + // Error: reading ref during render
19 + const value = ref.current;
20 +
21 + return <div>{value}</div>;
22 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-ref-access-and-mutation.expect.md new
+86
@@ -0,0 +1,86 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +/**
7 + * Fault tolerance test: three independent errors should all be reported.
8 + *
9 + * Error 1 (BuildHIR): `try/finally` is not supported
10 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
11 + * Error 3 (InferMutationAliasingEffects): Mutation of frozen props
12 + */
13 +function Component(props) {
14 + const ref = useRef(null);
15 +
16 + // Error: try/finally (Todo from BuildHIR)
17 + try {
18 + doWork();
19 + } finally {
20 + cleanup();
21 + }
22 +
23 + // Error: reading ref during render
24 + const value = ref.current;
25 +
26 + // Error: mutating frozen props
27 + props.items = [];
28 +
29 + return <div>{value}</div>;
30 +}
31 +
32 +```
33 +
34 +
35 +## Error
36 +
37 +```
38 +Found 3 errors:
39 +
40 +Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
41 +
42 +error.try-finally-ref-access-and-mutation.ts:13:2
43 + 11 |
44 + 12 | // Error: try/finally (Todo from BuildHIR)
45 +> 13 | try {
46 + | ^^^^^
47 +> 14 | doWork();
48 + | ^^^^^^^^^^^^^
49 +> 15 | } finally {
50 + | ^^^^^^^^^^^^^
51 +> 16 | cleanup();
52 + | ^^^^^^^^^^^^^
53 +> 17 | }
54 + | ^^^^ (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
55 + 18 |
56 + 19 | // Error: reading ref during render
57 + 20 | const value = ref.current;
58 +
59 +Error: This value cannot be modified
60 +
61 +Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
62 +
63 +error.try-finally-ref-access-and-mutation.ts:23:2
64 + 21 |
65 + 22 | // Error: mutating frozen props
66 +> 23 | props.items = [];
67 + | ^^^^^ value cannot be modified
68 + 24 |
69 + 25 | return <div>{value}</div>;
70 + 26 | }
71 +
72 +Error: Cannot access refs during render
73 +
74 +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
75 +
76 +error.try-finally-ref-access-and-mutation.ts:20:16
77 + 18 |
78 + 19 | // Error: reading ref during render
79 +> 20 | const value = ref.current;
80 + | ^^^^^^^^^^^ Cannot access ref value during render
81 + 21 |
82 + 22 | // Error: mutating frozen props
83 + 23 | props.items = [];
84 +```
85 +
86 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.try-finally-ref-access-and-mutation.js new
+26
@@ -0,0 +1,26 @@
1 +// @validateRefAccessDuringRender
2 +/**
3 + * Fault tolerance test: three independent errors should all be reported.
4 + *
5 + * Error 1 (BuildHIR): `try/finally` is not supported
6 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
7 + * Error 3 (InferMutationAliasingEffects): Mutation of frozen props
8 + */
9 +function Component(props) {
10 + const ref = useRef(null);
11 +
12 + // Error: try/finally (Todo from BuildHIR)
13 + try {
14 + doWork();
15 + } finally {
16 + cleanup();
17 + }
18 +
19 + // Error: reading ref during render
20 + const value = ref.current;
21 +
22 + // Error: mutating frozen props
23 + props.items = [];
24 +
25 + return <div>{value}</div>;
26 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.var-declaration-and-mutation-of-props.expect.md new
+54
@@ -0,0 +1,54 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +/**
6 + * Fault tolerance test: two independent errors should both be reported.
7 + *
8 + * Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
9 + * Error 2 (InferMutationAliasingEffects): Mutation of frozen props
10 + */
11 +function Component(props) {
12 + // Error: var declaration (Todo from BuildHIR)
13 + var items = props.items;
14 +
15 + // Error: mutating frozen props (detected during inference)
16 + props.items = [];
17 +
18 + return <div>{items.length}</div>;
19 +}
20 +
21 +```
22 +
23 +
24 +## Error
25 +
26 +```
27 +Found 2 errors:
28 +
29 +Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
30 +
31 +error.var-declaration-and-mutation-of-props.ts:9:2
32 + 7 | function Component(props) {
33 + 8 | // Error: var declaration (Todo from BuildHIR)
34 +> 9 | var items = props.items;
35 + | ^^^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
36 + 10 |
37 + 11 | // Error: mutating frozen props (detected during inference)
38 + 12 | props.items = [];
39 +
40 +Error: This value cannot be modified
41 +
42 +Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
43 +
44 +error.var-declaration-and-mutation-of-props.ts:12:2
45 + 10 |
46 + 11 | // Error: mutating frozen props (detected during inference)
47 +> 12 | props.items = [];
48 + | ^^^^^ value cannot be modified
49 + 13 |
50 + 14 | return <div>{items.length}</div>;
51 + 15 | }
52 +```
53 +
54 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.var-declaration-and-mutation-of-props.js new
+15
@@ -0,0 +1,15 @@
1 +/**
2 + * Fault tolerance test: two independent errors should both be reported.
3 + *
4 + * Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
5 + * Error 2 (InferMutationAliasingEffects): Mutation of frozen props
6 + */
7 +function Component(props) {
8 + // Error: var declaration (Todo from BuildHIR)
9 + var items = props.items;
10 +
11 + // Error: mutating frozen props (detected during inference)
12 + props.items = [];
13 +
14 + return <div>{items.length}</div>;
15 +}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.var-declaration-and-ref-access.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateRefAccessDuringRender
6 +/**
7 + * Fault tolerance test: two independent errors should both be reported.
8 + *
9 + * Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
10 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
11 + */
12 +function Component() {
13 + const ref = useRef(null);
14 +
15 + // Error: var declaration (Todo from BuildHIR)
16 + var items = [1, 2, 3];
17 +
18 + // Error: reading ref during render
19 + const value = ref.current;
20 +
21 + return (
22 + <div>
23 + {value}
24 + {items.length}
25 + </div>
26 + );
27 +}
28 +
29 +```
30 +
31 +
32 +## Error
33 +
34 +```
35 +Found 2 errors:
36 +
37 +Todo: (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
38 +
39 +error.var-declaration-and-ref-access.ts:12:2
40 + 10 |
41 + 11 | // Error: var declaration (Todo from BuildHIR)
42 +> 12 | var items = [1, 2, 3];
43 + | ^^^^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration
44 + 13 |
45 + 14 | // Error: reading ref during render
46 + 15 | const value = ref.current;
47 +
48 +Error: Cannot access refs during render
49 +
50 +React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef).
51 +
52 +error.var-declaration-and-ref-access.ts:15:16
53 + 13 |
54 + 14 | // Error: reading ref during render
55 +> 15 | const value = ref.current;
56 + | ^^^^^^^^^^^ Cannot access ref value during render
57 + 16 |
58 + 17 | return (
59 + 18 | <div>
60 +```
61 +
62 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fault-tolerance/error.var-declaration-and-ref-access.js new
+23
@@ -0,0 +1,23 @@
1 +// @validateRefAccessDuringRender
2 +/**
3 + * Fault tolerance test: two independent errors should both be reported.
4 + *
5 + * Error 1 (BuildHIR): `var` declarations are not supported (treated as `let`)
6 + * Error 2 (ValidateNoRefAccessInRender): reading ref.current during render
7 + */
8 +function Component() {
9 + const ref = useRef(null);
10 +
11 + // Error: var declaration (Todo from BuildHIR)
12 + var items = [1, 2, 3];
13 +
14 + // Error: reading ref during render
15 + const value = ref.current;
16 +
17 + return (
18 + <div>
19 + {value}
20 + {items.length}
21 + </div>
22 + );
23 +}