Skip to content

Commit 85a6fbf

Browse files
committed
[compiler] Examples of invalid code reassigning locals outside of render
ghstack-source-id: c48d0d0b86e47e2449982263c88cc2cfd44e326b Pull Request resolved: #30106
1 parent ef0f44e commit 85a6fbf

7 files changed

+385
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
## Input
3+
4+
```javascript
5+
import { useEffect } from "react";
6+
7+
function Component() {
8+
let local;
9+
10+
const reassignLocal = (newValue) => {
11+
local = newValue;
12+
};
13+
14+
const onMount = (newValue) => {
15+
reassignLocal("hello");
16+
17+
if (local === newValue) {
18+
// Without React Compiler, `reassignLocal` is freshly created
19+
// on each render, capturing a binding to the latest `local`,
20+
// such that invoking reassignLocal will reassign the same
21+
// binding that we are observing in the if condition, and
22+
// we reach this branch
23+
console.log("`local` was updated!");
24+
} else {
25+
// With React Compiler enabled, `reassignLocal` is only created
26+
// once, capturing a binding to `local` in that render pass.
27+
// Therefore, calling `reassignLocal` will reassign the wrong
28+
// version of `local`, and not update the binding we are checking
29+
// in the if condition.
30+
//
31+
// To protect against this, we disallow reassigning locals from
32+
// functions that escape
33+
throw new Error("`local` not updated!");
34+
}
35+
};
36+
37+
useEffect(() => {
38+
onMount();
39+
}, [onMount]);
40+
41+
return "ok";
42+
}
43+
44+
```
45+
46+
## Code
47+
48+
```javascript
49+
import { c as _c } from "react/compiler-runtime";
50+
import { useEffect } from "react";
51+
52+
function Component() {
53+
const $ = _c(4);
54+
let local;
55+
let t0;
56+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
57+
t0 = (newValue) => {
58+
local = newValue;
59+
};
60+
$[0] = t0;
61+
} else {
62+
t0 = $[0];
63+
}
64+
const reassignLocal = t0;
65+
let t1;
66+
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
67+
t1 = (newValue_0) => {
68+
reassignLocal("hello");
69+
if (local === newValue_0) {
70+
console.log("`local` was updated!");
71+
} else {
72+
throw new Error("`local` not updated!");
73+
}
74+
};
75+
$[1] = t1;
76+
} else {
77+
t1 = $[1];
78+
}
79+
const onMount = t1;
80+
let t2;
81+
let t3;
82+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
83+
t2 = () => {
84+
onMount();
85+
};
86+
t3 = [onMount];
87+
$[2] = t2;
88+
$[3] = t3;
89+
} else {
90+
t2 = $[2];
91+
t3 = $[3];
92+
}
93+
useEffect(t2, t3);
94+
return "ok";
95+
}
96+
97+
```
98+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useEffect } from "react";
2+
3+
function Component() {
4+
let local;
5+
6+
const reassignLocal = (newValue) => {
7+
local = newValue;
8+
};
9+
10+
const onMount = (newValue) => {
11+
reassignLocal("hello");
12+
13+
if (local === newValue) {
14+
// Without React Compiler, `reassignLocal` is freshly created
15+
// on each render, capturing a binding to the latest `local`,
16+
// such that invoking reassignLocal will reassign the same
17+
// binding that we are observing in the if condition, and
18+
// we reach this branch
19+
console.log("`local` was updated!");
20+
} else {
21+
// With React Compiler enabled, `reassignLocal` is only created
22+
// once, capturing a binding to `local` in that render pass.
23+
// Therefore, calling `reassignLocal` will reassign the wrong
24+
// version of `local`, and not update the binding we are checking
25+
// in the if condition.
26+
//
27+
// To protect against this, we disallow reassigning locals from
28+
// functions that escape
29+
throw new Error("`local` not updated!");
30+
}
31+
};
32+
33+
useEffect(() => {
34+
onMount();
35+
}, [onMount]);
36+
37+
return "ok";
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
## Input
3+
4+
```javascript
5+
import { useEffect } from "react";
6+
import { useIdentity } from "shared-runtime";
7+
8+
function Component() {
9+
let local;
10+
11+
const reassignLocal = (newValue) => {
12+
local = newValue;
13+
};
14+
15+
const callback = (newValue) => {
16+
reassignLocal("hello");
17+
18+
if (local === newValue) {
19+
// Without React Compiler, `reassignLocal` is freshly created
20+
// on each render, capturing a binding to the latest `local`,
21+
// such that invoking reassignLocal will reassign the same
22+
// binding that we are observing in the if condition, and
23+
// we reach this branch
24+
console.log("`local` was updated!");
25+
} else {
26+
// With React Compiler enabled, `reassignLocal` is only created
27+
// once, capturing a binding to `local` in that render pass.
28+
// Therefore, calling `reassignLocal` will reassign the wrong
29+
// version of `local`, and not update the binding we are checking
30+
// in the if condition.
31+
//
32+
// To protect against this, we disallow reassigning locals from
33+
// functions that escape
34+
throw new Error("`local` not updated!");
35+
}
36+
};
37+
38+
useIdentity(() => {
39+
callback();
40+
});
41+
42+
return "ok";
43+
}
44+
45+
```
46+
47+
## Code
48+
49+
```javascript
50+
import { c as _c } from "react/compiler-runtime";
51+
import { useEffect } from "react";
52+
import { useIdentity } from "shared-runtime";
53+
54+
function Component() {
55+
const $ = _c(3);
56+
let local;
57+
let t0;
58+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
59+
t0 = (newValue) => {
60+
local = newValue;
61+
};
62+
$[0] = t0;
63+
} else {
64+
t0 = $[0];
65+
}
66+
const reassignLocal = t0;
67+
let t1;
68+
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
69+
t1 = (newValue_0) => {
70+
reassignLocal("hello");
71+
if (local === newValue_0) {
72+
console.log("`local` was updated!");
73+
} else {
74+
throw new Error("`local` not updated!");
75+
}
76+
};
77+
$[1] = t1;
78+
} else {
79+
t1 = $[1];
80+
}
81+
const callback = t1;
82+
let t2;
83+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
84+
t2 = () => {
85+
callback();
86+
};
87+
$[2] = t2;
88+
} else {
89+
t2 = $[2];
90+
}
91+
useIdentity(t2);
92+
return "ok";
93+
}
94+
95+
```
96+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useEffect } from "react";
2+
import { useIdentity } from "shared-runtime";
3+
4+
function Component() {
5+
let local;
6+
7+
const reassignLocal = (newValue) => {
8+
local = newValue;
9+
};
10+
11+
const callback = (newValue) => {
12+
reassignLocal("hello");
13+
14+
if (local === newValue) {
15+
// Without React Compiler, `reassignLocal` is freshly created
16+
// on each render, capturing a binding to the latest `local`,
17+
// such that invoking reassignLocal will reassign the same
18+
// binding that we are observing in the if condition, and
19+
// we reach this branch
20+
console.log("`local` was updated!");
21+
} else {
22+
// With React Compiler enabled, `reassignLocal` is only created
23+
// once, capturing a binding to `local` in that render pass.
24+
// Therefore, calling `reassignLocal` will reassign the wrong
25+
// version of `local`, and not update the binding we are checking
26+
// in the if condition.
27+
//
28+
// To protect against this, we disallow reassigning locals from
29+
// functions that escape
30+
throw new Error("`local` not updated!");
31+
}
32+
};
33+
34+
useIdentity(() => {
35+
callback();
36+
});
37+
38+
return "ok";
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
2+
## Input
3+
4+
```javascript
5+
function Component() {
6+
let local;
7+
8+
const reassignLocal = (newValue) => {
9+
local = newValue;
10+
};
11+
12+
const onClick = (newValue) => {
13+
reassignLocal("hello");
14+
15+
if (local === newValue) {
16+
// Without React Compiler, `reassignLocal` is freshly created
17+
// on each render, capturing a binding to the latest `local`,
18+
// such that invoking reassignLocal will reassign the same
19+
// binding that we are observing in the if condition, and
20+
// we reach this branch
21+
console.log("`local` was updated!");
22+
} else {
23+
// With React Compiler enabled, `reassignLocal` is only created
24+
// once, capturing a binding to `local` in that render pass.
25+
// Therefore, calling `reassignLocal` will reassign the wrong
26+
// version of `local`, and not update the binding we are checking
27+
// in the if condition.
28+
//
29+
// To protect against this, we disallow reassigning locals from
30+
// functions that escape
31+
throw new Error("`local` not updated!");
32+
}
33+
};
34+
35+
return <button onClick={onClick}>Submit</button>;
36+
}
37+
38+
```
39+
40+
## Code
41+
42+
```javascript
43+
import { c as _c } from "react/compiler-runtime";
44+
function Component() {
45+
const $ = _c(2);
46+
let local;
47+
let t0;
48+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
49+
t0 = (newValue) => {
50+
local = newValue;
51+
};
52+
$[0] = t0;
53+
} else {
54+
t0 = $[0];
55+
}
56+
const reassignLocal = t0;
57+
let t1;
58+
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
59+
const onClick = (newValue_0) => {
60+
reassignLocal("hello");
61+
if (local === newValue_0) {
62+
console.log("`local` was updated!");
63+
} else {
64+
throw new Error("`local` not updated!");
65+
}
66+
};
67+
68+
t1 = <button onClick={onClick}>Submit</button>;
69+
$[1] = t1;
70+
} else {
71+
t1 = $[1];
72+
}
73+
return t1;
74+
}
75+
76+
```
77+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
function Component() {
2+
let local;
3+
4+
const reassignLocal = (newValue) => {
5+
local = newValue;
6+
};
7+
8+
const onClick = (newValue) => {
9+
reassignLocal("hello");
10+
11+
if (local === newValue) {
12+
// Without React Compiler, `reassignLocal` is freshly created
13+
// on each render, capturing a binding to the latest `local`,
14+
// such that invoking reassignLocal will reassign the same
15+
// binding that we are observing in the if condition, and
16+
// we reach this branch
17+
console.log("`local` was updated!");
18+
} else {
19+
// With React Compiler enabled, `reassignLocal` is only created
20+
// once, capturing a binding to `local` in that render pass.
21+
// Therefore, calling `reassignLocal` will reassign the wrong
22+
// version of `local`, and not update the binding we are checking
23+
// in the if condition.
24+
//
25+
// To protect against this, we disallow reassigning locals from
26+
// functions that escape
27+
throw new Error("`local` not updated!");
28+
}
29+
};
30+
31+
return <button onClick={onClick}>Submit</button>;
32+
}

0 commit comments

Comments
 (0)