[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
+}