@samitouri / QOS-React-2 / commits / 696434fed8

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