mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix temporaries accessed outside of their defining scope
Fixed a bug identified in repro cases earlier in the stack. The case is where some later value is composed of several values, say A and B, where A is an identifier that is reassigned within B. Also, the mutable range of B surrounds the evaluation of A. In this case, the reference to A gets lowered to a temporary (say a t0 = LoadLocal A), and that temporary is created within the reactive scope for B. PropagateScopeDependencies bypasses LoadLocal indirections, and considers the reference to the temporary (t0) as if it was a reference to the identifier (A). That breaks the whole reason we lower Identifiers to temporaries - to preserve evaluation order. This PR fixes the bug by promoting temporaries to names values if they are referenced outside their defining scope. So, the reference to t0 stays a reference to t0, which correctly preserves the value of A at the right point in time. This is all much easier to see in the new test case.
This commit is contained in:
@@ -71,7 +71,8 @@ class Context {
|
||||
// ReactiveScope (B) that uses the produced temporary.
|
||||
// - codegen will inline these PropertyLoads back into scope (B)
|
||||
#properties: Map<Identifier, ReactiveScopePropertyDependency> = new Map();
|
||||
#temporaries: Map<Identifier, Place> = new Map();
|
||||
#temporaries: Map<Identifier, { place: Place; scope: ReactiveScope | null }> =
|
||||
new Map();
|
||||
#inConditionalWithinScope: boolean = false;
|
||||
// Reactive dependencies used unconditionally in the current conditional.
|
||||
// Composed of dependencies:
|
||||
@@ -184,8 +185,22 @@ class Context {
|
||||
this.#reassignments.set(identifier, decl);
|
||||
}
|
||||
|
||||
declareTemporary(lvalue: Place, value: Place): void {
|
||||
this.#temporaries.set(lvalue.identifier, value);
|
||||
declareTemporary(lvalue: Place, place: Place): void {
|
||||
this.#temporaries.set(lvalue.identifier, {
|
||||
place,
|
||||
scope: this.currentScope.value,
|
||||
});
|
||||
}
|
||||
|
||||
resolveTemporary(place: Place): Place {
|
||||
const temporary = this.#temporaries.get(place.identifier);
|
||||
if (
|
||||
temporary !== undefined &&
|
||||
(temporary.scope === null || this.#isScopeActive(temporary.scope))
|
||||
) {
|
||||
return temporary.place;
|
||||
}
|
||||
return place;
|
||||
}
|
||||
|
||||
#getProperty(
|
||||
@@ -193,7 +208,7 @@ class Context {
|
||||
property: string,
|
||||
isConditional: boolean
|
||||
): ReactiveScopePropertyDependency {
|
||||
const resolvedObject = this.#temporaries.get(object.identifier) ?? object;
|
||||
const resolvedObject = this.resolveTemporary(object);
|
||||
const resolvedDependency = this.#properties.get(resolvedObject.identifier);
|
||||
let objectDependency: ReactiveScopePropertyDependency;
|
||||
// (1) Create the base property dependency as either a LoadLocal (from a temporary)
|
||||
@@ -267,7 +282,7 @@ class Context {
|
||||
}
|
||||
|
||||
visitOperand(place: Place): void {
|
||||
const resolved = this.#temporaries.get(place.identifier) ?? place;
|
||||
const resolved = this.resolveTemporary(place);
|
||||
// if this operand is a temporary created for a property load, try to resolve it to
|
||||
// the expanded Place. Fall back to using the operand as-is.
|
||||
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
let Tag = props.component;
|
||||
// NOTE: the order of evaluation in the lowering is incorrect:
|
||||
// the jsx element's tag observes `Tag` after reassignment, but should observe
|
||||
// it before the reassignment.
|
||||
return (
|
||||
<Tag>
|
||||
{((Tag = props.alternateComponent), maybeMutate(maybeMutable))}
|
||||
<Tag />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Invariant: [Codegen] No value found for temporary. Value for 'read $33' was not set in the codegen context (8:8)
|
||||
```
|
||||
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
let Tag = props.component;
|
||||
// NOTE: the order of evaluation in the lowering is incorrect:
|
||||
// the jsx element's tag observes `Tag` after reassignment, but should observe
|
||||
// it before the reassignment.
|
||||
return (
|
||||
<Tag>
|
||||
{((Tag = props.alternateComponent), maybeMutate(maybeMutable))}
|
||||
<Tag />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import * as React from "react";
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(13);
|
||||
const c_0 = $[0] !== props.component;
|
||||
const c_1 = $[1] !== props.alternateComponent;
|
||||
let Tag;
|
||||
let t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if (c_0 || c_1) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
Tag = props.component;
|
||||
|
||||
t0 = Tag;
|
||||
t1 = "\n ";
|
||||
Tag = props.alternateComponent;
|
||||
t2 = maybeMutate(maybeMutable);
|
||||
$[0] = props.component;
|
||||
$[1] = props.alternateComponent;
|
||||
$[2] = Tag;
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
Tag = $[2];
|
||||
t0 = $[3];
|
||||
t1 = $[4];
|
||||
t2 = $[5];
|
||||
}
|
||||
const c_6 = $[6] !== Tag;
|
||||
let t3;
|
||||
if (c_6) {
|
||||
t3 = <Tag />;
|
||||
$[6] = Tag;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
const c_8 = $[8] !== t0;
|
||||
const c_9 = $[9] !== t1;
|
||||
const c_10 = $[10] !== t2;
|
||||
const c_11 = $[11] !== t3;
|
||||
let t4;
|
||||
if (c_8 || c_9 || c_10 || c_11) {
|
||||
t4 = (
|
||||
<t0>
|
||||
{t1}
|
||||
{t2}
|
||||
{t3}
|
||||
</t0>
|
||||
);
|
||||
$[8] = t0;
|
||||
$[9] = t1;
|
||||
$[10] = t2;
|
||||
$[11] = t3;
|
||||
$[12] = t4;
|
||||
} else {
|
||||
t4 = $[12];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
let x = props.value;
|
||||
return [x, maybeMutate(maybeMutable)];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import * as React from "react";
|
||||
function Component(props) {
|
||||
const $ = React.unstable_useMemoCache(6);
|
||||
const c_0 = $[0] !== props.value;
|
||||
let t0;
|
||||
let t1;
|
||||
if (c_0) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
const x = props.value;
|
||||
t0 = x;
|
||||
t1 = maybeMutate(maybeMutable);
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t1 = $[2];
|
||||
}
|
||||
const c_3 = $[3] !== t0;
|
||||
const c_4 = $[4] !== t1;
|
||||
let t2;
|
||||
if (c_3 || c_4) {
|
||||
t2 = [t0, t1];
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
function Component(props) {
|
||||
const maybeMutable = new MaybeMutable();
|
||||
let x = props.value;
|
||||
return [x, maybeMutate(maybeMutable)];
|
||||
}
|
||||
Reference in New Issue
Block a user