Fix ValidateNoSetStateInRender for loops
Joe Savona committed
Nov 14, 2023 at 11:33 UTC
696434fed83215829c6d907999ad7d636aa0baf5
7 files changed
+121
-7
compiler/packages/babel-plugin-react-forget/src/Validation/ValidateNoSetStateInRender.ts
+8
-7
@@ -16,7 +16,6 @@ import {
16
} from "../HIR";
17
import { PostDominator } from "../HIR/Dominator";
18
import { eachInstructionValueOperand } from "../HIR/visitors";
19
-import { findBlocksWithBackEdges } from "../Optimization/DeadCodeElimination";
19
import { Err, Ok, Result } from "../Utils/Result";
20
21
/**
@@ -60,17 +59,19 @@ function validateNoSetStateInRenderImpl(
59
): Result<PostDominator<BlockId>, CompilerError> {
60
// Construct the set of blocks that is always reachable from the entry block.
61
const unconditionalBlocks = new Set<BlockId>();
63
- const blocksWithBackEdges = findBlocksWithBackEdges(fn);
62
const dominators = computePostDominatorTree(fn, {
63
includeThrowsAsExitNode: false,
64
});
65
const exit = dominators.exit;
66
let current: BlockId | null = fn.body.entry;
69
- while (
70
- current !== null &&
71
- current !== exit &&
72
- !blocksWithBackEdges.has(current)
73
- ) {
67
+ while (current !== null && current !== exit) {
68
+ CompilerError.invariant(!unconditionalBlocks.has(current), {
69
+ reason:
70
+ "Internal error: non-terminating loop in ValidateNoSetStateInRender",
71
+ loc: null,
72
+ suggestions: null,
73
+ description: null,
74
+ });
75
unconditionalBlocks.add(current);
76
current = dominators.get(current);
77
}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md
new
+28
@@ -0,0 +1,28 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateNoSetStateInRender
6
+function Component(props) {
7
+ const [state, setState] = useState(false);
8
+ for (const _ of props) {
9
+ if (props.cond) {
10
+ break;
11
+ } else {
12
+ continue;
13
+ }
14
+ }
15
+ setState(true);
16
+ return state;
17
+}
18
+
19
+```
20
+
21
+
22
+## Error
23
+
24
+```
25
+[ReactForget] InvalidReact: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) (11:11)
26
+```
27
+
28
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.js
new
+13
@@ -0,0 +1,13 @@
1
+// @validateNoSetStateInRender
2
+function Component(props) {
3
+ const [state, setState] = useState(false);
4
+ for (const _ of props) {
5
+ if (props.cond) {
6
+ break;
7
+ } else {
8
+ continue;
9
+ }
10
+ }
11
+ setState(true);
12
+ return state;
13
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md
new
+23
@@ -0,0 +1,23 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateNoSetStateInRender
6
+function Component(props) {
7
+ const [state, setState] = useState(false);
8
+ for (const _ of props) {
9
+ }
10
+ setState(true);
11
+ return state;
12
+}
13
+
14
+```
15
+
16
+
17
+## Error
18
+
19
+```
20
+[ReactForget] InvalidReact: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) (6:6)
21
+```
22
+
23
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.js
new
+8
@@ -0,0 +1,8 @@
1
+// @validateNoSetStateInRender
2
+function Component(props) {
3
+ const [state, setState] = useState(false);
4
+ for (const _ of props) {
5
+ }
6
+ setState(true);
7
+ return state;
8
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md
new
+28
@@ -0,0 +1,28 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateNoSetStateInRender
6
+function Component(props) {
7
+ const [state, setState] = useState(false);
8
+ for (const _ of props) {
9
+ if (props.cond) {
10
+ break;
11
+ } else {
12
+ throw new Error("bye!");
13
+ }
14
+ }
15
+ setState(true);
16
+ return state;
17
+}
18
+
19
+```
20
+
21
+
22
+## Error
23
+
24
+```
25
+[ReactForget] InvalidReact: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) (11:11)
26
+```
27
+
28
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.js
new
+13
@@ -0,0 +1,13 @@
1
+// @validateNoSetStateInRender
2
+function Component(props) {
3
+ const [state, setState] = useState(false);
4
+ for (const _ of props) {
5
+ if (props.cond) {
6
+ break;
7
+ } else {
8
+ throw new Error("bye!");
9
+ }
10
+ }
11
+ setState(true);
12
+ return state;
13
+}