@samitouri / QOS-React / commits / e047db8d03

[repro] Repro for control flow bug in PropagateScopeDependency

--- Thanks to @josephsavona for finding this bug. This is another example of why we really want hir-everywhere. Forget output currently nullthrows because we believe `obj.a` is run unconditionally in source (missing the break/returns out of this scope)

Mofei Zhang committed Mar 21, 2024 at 17:44 UTC e047db8d037f63884a41575ea733c27fe8bd1f14
9 files changed +357 -4
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug-reduce-reactive-deps-break-in-scope.expect.md new
+63
@@ -0,0 +1,63 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo({ obj, objIsNull }) {
6 + const x = [];
7 + b0: {
8 + if (objIsNull) {
9 + break b0;
10 + }
11 + x.push(obj.a);
12 + }
13 + return x;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [{ obj: null, objIsNull: true }],
19 + sequentialRenders: [
20 + { obj: null, objIsNull: true },
21 + { obj: { a: 2 }, objIsNull: false },
22 + ],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { unstable_useMemoCache as useMemoCache } from "react";
31 +function useFoo(t0) {
32 + const $ = useMemoCache(3);
33 + const { obj, objIsNull } = t0;
34 + let x;
35 + if ($[0] !== objIsNull || $[1] !== obj.a) {
36 + x = [];
37 + bb1: {
38 + if (objIsNull) {
39 + break bb1;
40 + }
41 +
42 + x.push(obj.a);
43 + }
44 + $[0] = objIsNull;
45 + $[1] = obj.a;
46 + $[2] = x;
47 + } else {
48 + x = $[2];
49 + }
50 + return x;
51 +}
52 +
53 +export const FIXTURE_ENTRYPOINT = {
54 + fn: useFoo,
55 + params: [{ obj: null, objIsNull: true }],
56 + sequentialRenders: [
57 + { obj: null, objIsNull: true },
58 + { obj: { a: 2 }, objIsNull: false },
59 + ],
60 +};
61 +
62 +```
63 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug-reduce-reactive-deps-break-in-scope.ts new
+19
@@ -0,0 +1,19 @@
1 +function useFoo({ obj, objIsNull }) {
2 + const x = [];
3 + b0: {
4 + if (objIsNull) {
5 + break b0;
6 + }
7 + x.push(obj.a);
8 + }
9 + return x;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [{ obj: null, objIsNull: true }],
15 + sequentialRenders: [
16 + { obj: null, objIsNull: true },
17 + { obj: { a: 2 }, objIsNull: false },
18 + ],
19 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug-reduce-reactive-deps-return-in-scope.expect.md new
+69
@@ -0,0 +1,69 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo({ obj, objIsNull }) {
6 + const x = [];
7 + if (objIsNull) {
8 + return;
9 + }
10 + x.push(obj.b);
11 + return x;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useFoo,
16 + params: [{ obj: null, objIsNull: true }],
17 + sequentialRenders: [
18 + { obj: null, objIsNull: true },
19 + { obj: { a: 2 }, objIsNull: false },
20 + ],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { unstable_useMemoCache as useMemoCache } from "react";
29 +function useFoo(t0) {
30 + const $ = useMemoCache(4);
31 + const { obj, objIsNull } = t0;
32 + let x;
33 + let t1;
34 + if ($[0] !== objIsNull || $[1] !== obj.b) {
35 + t1 = Symbol.for("react.early_return_sentinel");
36 + bb7: {
37 + x = [];
38 + if (objIsNull) {
39 + t1 = undefined;
40 + break bb7;
41 + }
42 +
43 + x.push(obj.b);
44 + }
45 + $[0] = objIsNull;
46 + $[1] = obj.b;
47 + $[2] = x;
48 + $[3] = t1;
49 + } else {
50 + x = $[2];
51 + t1 = $[3];
52 + }
53 + if (t1 !== Symbol.for("react.early_return_sentinel")) {
54 + return t1;
55 + }
56 + return x;
57 +}
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: useFoo,
61 + params: [{ obj: null, objIsNull: true }],
62 + sequentialRenders: [
63 + { obj: null, objIsNull: true },
64 + { obj: { a: 2 }, objIsNull: false },
65 + ],
66 +};
67 +
68 +```
69 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/bug-reduce-reactive-deps-return-in-scope.ts new
+17
@@ -0,0 +1,17 @@
1 +function useFoo({ obj, objIsNull }) {
2 + const x = [];
3 + if (objIsNull) {
4 + return;
5 + }
6 + x.push(obj.b);
7 + return x;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: useFoo,
12 + params: [{ obj: null, objIsNull: true }],
13 + sequentialRenders: [
14 + { obj: null, objIsNull: true },
15 + { obj: { a: 2 }, objIsNull: false },
16 + ],
17 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.expect.md new
+70
@@ -0,0 +1,70 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo({ obj, objIsNull }) {
6 + const x = [];
7 + b0: {
8 + if (objIsNull) {
9 + break b0;
10 + } else {
11 + x.push(obj.a);
12 + }
13 + x.push(obj.b);
14 + }
15 + return x;
16 +}
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useFoo,
20 + params: [{ obj: null, objIsNull: true }],
21 + sequentialRenders: [
22 + { obj: null, objIsNull: true },
23 + { obj: { a: 2 }, objIsNull: false },
24 + ],
25 +};
26 +
27 +```
28 +
29 +## Code
30 +
31 +```javascript
32 +import { unstable_useMemoCache as useMemoCache } from "react";
33 +function useFoo(t0) {
34 + const $ = useMemoCache(3);
35 + const { obj, objIsNull } = t0;
36 + let x;
37 + if ($[0] !== objIsNull || $[1] !== obj) {
38 + x = [];
39 + bb1: {
40 + if (objIsNull) {
41 + break bb1;
42 + } else {
43 + x.push(obj.a);
44 + }
45 +
46 + x.push(obj.b);
47 + }
48 + $[0] = objIsNull;
49 + $[1] = obj;
50 + $[2] = x;
51 + } else {
52 + x = $[2];
53 + }
54 + return x;
55 +}
56 +
57 +export const FIXTURE_ENTRYPOINT = {
58 + fn: useFoo,
59 + params: [{ obj: null, objIsNull: true }],
60 + sequentialRenders: [
61 + { obj: null, objIsNull: true },
62 + { obj: { a: 2 }, objIsNull: false },
63 + ],
64 +};
65 +
66 +```
67 +
68 +### Eval output
69 +(kind: ok) []
70 +[2,null]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-break-in-scope.ts new
+21
@@ -0,0 +1,21 @@
1 +function useFoo({ obj, objIsNull }) {
2 + const x = [];
3 + b0: {
4 + if (objIsNull) {
5 + break b0;
6 + } else {
7 + x.push(obj.a);
8 + }
9 + x.push(obj.b);
10 + }
11 + return x;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: useFoo,
16 + params: [{ obj: null, objIsNull: true }],
17 + sequentialRenders: [
18 + { obj: null, objIsNull: true },
19 + { obj: { a: 2 }, objIsNull: false },
20 + ],
21 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.expect.md new
+76
@@ -0,0 +1,76 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function useFoo({ obj, objIsNull }) {
6 + const x = [];
7 + if (objIsNull) {
8 + return;
9 + } else {
10 + x.push(obj.a);
11 + }
12 + x.push(obj.b);
13 + return x;
14 +}
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useFoo,
18 + params: [{ obj: null, objIsNull: true }],
19 + sequentialRenders: [
20 + { obj: null, objIsNull: true },
21 + { obj: { a: 2 }, objIsNull: false },
22 + ],
23 +};
24 +
25 +```
26 +
27 +## Code
28 +
29 +```javascript
30 +import { unstable_useMemoCache as useMemoCache } from "react";
31 +function useFoo(t0) {
32 + const $ = useMemoCache(4);
33 + const { obj, objIsNull } = t0;
34 + let x;
35 + let t1;
36 + if ($[0] !== objIsNull || $[1] !== obj) {
37 + t1 = Symbol.for("react.early_return_sentinel");
38 + bb8: {
39 + x = [];
40 + if (objIsNull) {
41 + t1 = undefined;
42 + break bb8;
43 + } else {
44 + x.push(obj.a);
45 + }
46 +
47 + x.push(obj.b);
48 + }
49 + $[0] = objIsNull;
50 + $[1] = obj;
51 + $[2] = x;
52 + $[3] = t1;
53 + } else {
54 + x = $[2];
55 + t1 = $[3];
56 + }
57 + if (t1 !== Symbol.for("react.early_return_sentinel")) {
58 + return t1;
59 + }
60 + return x;
61 +}
62 +
63 +export const FIXTURE_ENTRYPOINT = {
64 + fn: useFoo,
65 + params: [{ obj: null, objIsNull: true }],
66 + sequentialRenders: [
67 + { obj: null, objIsNull: true },
68 + { obj: { a: 2 }, objIsNull: false },
69 + ],
70 +};
71 +
72 +```
73 +
74 +### Eval output
75 +(kind: ok)
76 +[2,null]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-cond-deps-return-in-scope.ts new
+19
@@ -0,0 +1,19 @@
1 +function useFoo({ obj, objIsNull }) {
2 + const x = [];
3 + if (objIsNull) {
4 + return;
5 + } else {
6 + x.push(obj.a);
7 + }
8 + x.push(obj.b);
9 + return x;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useFoo,
14 + params: [{ obj: null, objIsNull: true }],
15 + sequentialRenders: [
16 + { obj: null, objIsNull: true },
17 + { obj: { a: 2 }, objIsNull: false },
18 + ],
19 +};
compiler/packages/snap/src/SproutTodoFilter.ts
+3 -4
@@ -531,10 +531,9 @@ const skipFilter = new Set([
531 "rules-of-hooks/rules-of-hooks-93dc5d5e538a",
532 "rules-of-hooks/rules-of-hooks-69521d94fa03",
533
534 - // bug
535 - "bug-jsx-memberexpr-tag-in-lambda",
536 - "bug-invalid-code-when-bailout",
537 - "component-syntax-ref-gating.flow",
534 + // bugs
535 + "bug-reduce-reactive-deps-return-in-scope",
536 + "bug-reduce-reactive-deps-break-in-scope",
537
538 // 'react-forget-runtime' not yet supported
539 "flag-enable-emit-hook-guards",