More tests for effect enforcement

Adds test cases to ensure we're correctly inferring mutative builtin operations 
— property store, computed property store, property deletion, and computed 
property deletion — as definite mutation and that we're rejecting inputs where 
these operations are used on immutable/frozen values.
This commit is contained in:
Joe Savona
2023-05-25 14:33:02 -07:00
parent 9766e19805
commit 75a1972584
11 changed files with 134 additions and 9 deletions
+6 -3
View File
@@ -863,7 +863,10 @@ export enum Effect {
Store = "store",
}
export function isMutableEffect(effect: Effect): boolean {
export function isMutableEffect(
effect: Effect,
location: SourceLocation
): boolean {
switch (effect) {
case Effect.Capture:
case Effect.Store:
@@ -874,11 +877,11 @@ export function isMutableEffect(effect: Effect): boolean {
// All conditional mutations should be resolved into some other effect after InferReferenceEffects
CompilerError.invariant(
"Unexpected conditional mutation effect",
GeneratedSource
location
);
}
case Effect.Unknown: {
CompilerError.invariant("Unexpected unknown effect", GeneratedSource);
CompilerError.invariant("Unexpected unknown effect", location);
}
case Effect.Read:
case Effect.Freeze: {
@@ -699,7 +699,7 @@ function inferBlock(
operand,
operand.effect === Effect.Unknown ? Effect.Read : operand.effect
);
hasMutableOperand ||= isMutableEffect(operand.effect);
hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc);
}
// If a closure did not capture any mutable values, then we can consider it to be
// frozen, which allows it to be independently memoized.
@@ -808,7 +808,7 @@ function inferBlock(
case "PropertyDelete": {
// `delete` returns a boolean (immutable) and modifies the object
valueKind = ValueKind.Immutable;
effectKind = Effect.ConditionallyMutate;
effectKind = Effect.Mutate;
break;
}
case "PropertyLoad": {
@@ -837,7 +837,7 @@ function inferBlock(
state.reference(instrValue.object, Effect.Mutate);
state.reference(instrValue.property, Effect.Read);
state.initialize(instrValue, ValueKind.Immutable);
state.reference(instr.lvalue, Effect.ConditionallyMutate);
state.reference(instr.lvalue, Effect.Mutate);
continue;
}
case "ComputedLoad": {
@@ -927,7 +927,10 @@ function inferBlock(
state.alias(lvalue, instrValue.value);
lvalue.effect = Effect.Store;
state.alias(instrValue.lvalue.place, instrValue.value);
// state.reference(instrValue.lvalue.place, Effect.Store);
// NOTE: *not* using state.reference since this is an assignment.
// reference() checks if the effect is valid given the value kind,
// but here the previous value kind doesn't matter since we are
// replacing it
instrValue.lvalue.place.effect = Effect.Store;
continue;
}
@@ -958,7 +961,10 @@ function inferBlock(
lvalue.effect = Effect.Store;
for (const place of eachPatternOperand(instrValue.lvalue.pattern)) {
state.alias(place, instrValue.value);
// state.reference(place, Effect.Store);
// NOTE: *not* using state.reference since this is an assignment.
// reference() checks if the effect is valid given the value kind,
// but here the previous value kind doesn't matter since we are
// replacing it
place.effect = Effect.Store;
}
continue;
@@ -594,7 +594,7 @@ function computeMemoizationInputs(
// reachable from a return value. Any mutable rvalue may alias any other rvalue
const operands = [...eachReactiveValueOperand(value)];
const lvalues = operands
.filter((operand) => isMutableEffect(operand.effect))
.filter((operand) => isMutableEffect(operand.effect, operand.loc))
.map((place) => ({ place, level: MemoizationLevel.Memoized }));
if (lvalue !== null) {
lvalues.push({ place: lvalue, level: MemoizationLevel.Memoized });
@@ -0,0 +1,22 @@
## Input
```javascript
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
x[0] = true;
return x;
}
```
## Error
```
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $22 (frozen) (5:5)
```
@@ -0,0 +1,7 @@
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
x[0] = true;
return x;
}
@@ -0,0 +1,22 @@
## Input
```javascript
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
delete x[y];
return x;
}
```
## Error
```
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $20 (frozen) (5:5)
```
@@ -0,0 +1,7 @@
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
delete x[y];
return x;
}
@@ -0,0 +1,22 @@
## Input
```javascript
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
delete x.y;
return x;
}
```
## Error
```
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $19 (frozen) (5:5)
```
@@ -0,0 +1,7 @@
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
delete x.y;
return x;
}
@@ -0,0 +1,22 @@
## Input
```javascript
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
x.y = true;
return x;
}
```
## Error
```
[ReactForget] InvalidInput: InferReferenceEffects: inferred mutation of known immutable value. Found mutation of $21 (frozen) (5:5)
```
@@ -0,0 +1,7 @@
function Component(props) {
const x = makeObject();
// freeze
<div>{x}</div>;
x.y = true;
return x;
}