Skip to content

Commit 80b81fe

Browse files
authored
[compiler] Repro for aliased captures within inner function expressions (#31770)
see fixture
1 parent e30872a commit 80b81fe

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {makeArray, mutate} from 'shared-runtime';
6+
7+
/**
8+
* Bug repro:
9+
* Found differences in evaluator results
10+
* Non-forget (expected):
11+
* (kind: ok)
12+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
13+
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
14+
* Forget:
15+
* (kind: ok)
16+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
17+
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
18+
*
19+
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
20+
* aliasing `y` via `[y]`, we make an opaque call.
21+
*
22+
* Note that the bug here is that we don't infer that `a = makeArray(y)`
23+
* potentially captures a context variable into a local variable. As a result,
24+
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
25+
* currently inferring that this lambda captures `y` (for a potential later
26+
* mutation) and simply reads `x`.
27+
*
28+
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
29+
* used when we analyze CallExpressions.
30+
*/
31+
function Component({foo, bar}: {foo: number; bar: number}) {
32+
let x = {foo};
33+
let y: {bar: number; x?: {foo: number}} = {bar};
34+
const f0 = function () {
35+
let a = makeArray(y); // a = [y]
36+
let b = x;
37+
// this writes y.x = x
38+
a[0].x = b;
39+
};
40+
f0();
41+
mutate(y.x);
42+
return y;
43+
}
44+
45+
export const FIXTURE_ENTRYPOINT = {
46+
fn: Component,
47+
params: [{foo: 3, bar: 4}],
48+
sequentialRenders: [
49+
{foo: 3, bar: 4},
50+
{foo: 3, bar: 5},
51+
],
52+
};
53+
54+
```
55+
56+
## Code
57+
58+
```javascript
59+
import { c as _c } from "react/compiler-runtime";
60+
import { makeArray, mutate } from "shared-runtime";
61+
62+
/**
63+
* Bug repro:
64+
* Found differences in evaluator results
65+
* Non-forget (expected):
66+
* (kind: ok)
67+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
68+
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
69+
* Forget:
70+
* (kind: ok)
71+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
72+
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
73+
*
74+
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
75+
* aliasing `y` via `[y]`, we make an opaque call.
76+
*
77+
* Note that the bug here is that we don't infer that `a = makeArray(y)`
78+
* potentially captures a context variable into a local variable. As a result,
79+
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
80+
* currently inferring that this lambda captures `y` (for a potential later
81+
* mutation) and simply reads `x`.
82+
*
83+
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
84+
* used when we analyze CallExpressions.
85+
*/
86+
function Component(t0) {
87+
const $ = _c(5);
88+
const { foo, bar } = t0;
89+
let t1;
90+
if ($[0] !== foo) {
91+
t1 = { foo };
92+
$[0] = foo;
93+
$[1] = t1;
94+
} else {
95+
t1 = $[1];
96+
}
97+
const x = t1;
98+
let y;
99+
if ($[2] !== bar || $[3] !== x) {
100+
y = { bar };
101+
const f0 = function () {
102+
const a = makeArray(y);
103+
const b = x;
104+
105+
a[0].x = b;
106+
};
107+
108+
f0();
109+
mutate(y.x);
110+
$[2] = bar;
111+
$[3] = x;
112+
$[4] = y;
113+
} else {
114+
y = $[4];
115+
}
116+
return y;
117+
}
118+
119+
export const FIXTURE_ENTRYPOINT = {
120+
fn: Component,
121+
params: [{ foo: 3, bar: 4 }],
122+
sequentialRenders: [
123+
{ foo: 3, bar: 4 },
124+
{ foo: 3, bar: 5 },
125+
],
126+
};
127+
128+
```
129+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {makeArray, mutate} from 'shared-runtime';
2+
3+
/**
4+
* Bug repro:
5+
* Found differences in evaluator results
6+
* Non-forget (expected):
7+
* (kind: ok)
8+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
9+
* {"bar":5,"x":{"foo":3,"wat0":"joe"}}
10+
* Forget:
11+
* (kind: ok)
12+
* {"bar":4,"x":{"foo":3,"wat0":"joe"}}
13+
* {"bar":5,"x":{"foo":3,"wat0":"joe","wat1":"joe"}}
14+
*
15+
* Fork of `capturing-func-alias-captured-mutate`, but instead of directly
16+
* aliasing `y` via `[y]`, we make an opaque call.
17+
*
18+
* Note that the bug here is that we don't infer that `a = makeArray(y)`
19+
* potentially captures a context variable into a local variable. As a result,
20+
* we don't understand that `a[0].x = b` captures `x` into `y` -- instead, we're
21+
* currently inferring that this lambda captures `y` (for a potential later
22+
* mutation) and simply reads `x`.
23+
*
24+
* Concretely `InferReferenceEffects.hasContextRefOperand` is incorrectly not
25+
* used when we analyze CallExpressions.
26+
*/
27+
function Component({foo, bar}: {foo: number; bar: number}) {
28+
let x = {foo};
29+
let y: {bar: number; x?: {foo: number}} = {bar};
30+
const f0 = function () {
31+
let a = makeArray(y); // a = [y]
32+
let b = x;
33+
// this writes y.x = x
34+
a[0].x = b;
35+
};
36+
f0();
37+
mutate(y.x);
38+
return y;
39+
}
40+
41+
export const FIXTURE_ENTRYPOINT = {
42+
fn: Component,
43+
params: [{foo: 3, bar: 4}],
44+
sequentialRenders: [
45+
{foo: 3, bar: 4},
46+
{foo: 3, bar: 5},
47+
],
48+
};

compiler/packages/snap/src/SproutTodoFilter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ const skipFilter = new Set([
479479
// bugs
480480
'fbt/bug-fbt-plural-multiple-function-calls',
481481
'fbt/bug-fbt-plural-multiple-mixed-call-tag',
482+
`bug-capturing-func-maybealias-captured-mutate`,
482483
'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr',
483484
'bug-invalid-hoisting-functionexpr',
484485
'bug-aliased-capture-aliased-mutate',

0 commit comments

Comments
 (0)