mirror of
https://github.com/facebookarchive/prepack.git
synced 2024-09-19 02:58:08 +03:00
Handle computed property names using loop variables
Summary: Initial bit of support for dealing with abstract loops where the loop body contains assignments to properties with computed names involving loop controlled variables. Closes https://github.com/facebook/prepack/pull/1286 Differential Revision: D6628885 Pulled By: hermanventer fbshipit-source-id: 960fa469e3c18e55e5447ac441235a80df2b1629
This commit is contained in:
parent
1bdf429281
commit
83508ef1ee
@ -43,7 +43,7 @@
|
||||
"test-node-cli-mode": "bash < scripts/test-node-cli-mode.sh",
|
||||
"test-std-in": "bash < scripts/test-std-in.sh",
|
||||
"test-react": "jest scripts/test-react",
|
||||
"test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-react && yarn test-internal",
|
||||
"test": "yarn test-residual && yarn test-serializer && yarn test-sourcemaps && yarn test-error-handler && yarn test-std-in && yarn test-test262 && yarn test-internal && yarn test-react",
|
||||
"test-coverage-most": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/remap-istanbul -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
||||
"test-all-coverage": "./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover ./lib/multi-runner.js --dir coverage.most && ./node_modules/.bin/istanbul --stack_size=10000 --max_old_space_size=16384 cover -- ./lib/test262-runner.js --timeout 50 --singleThreaded && ./node_modules/.bin/remap-istanbul -i coverage/coverage.json -i coverage.most/coverage.json -o coverage-sourcemapped -t html",
|
||||
"repl": "node lib/repl-cli.js",
|
||||
|
@ -71,6 +71,14 @@ export default class ValuesDomain {
|
||||
return true;
|
||||
}
|
||||
|
||||
containsValue(x: Value): boolean {
|
||||
let elems = this._elements;
|
||||
if (elems === undefined) return true; // Top contains everything
|
||||
if (x instanceof AbstractValue) return this.contains(x.values);
|
||||
invariant(x instanceof ConcreteValue);
|
||||
return elems.has(x);
|
||||
}
|
||||
|
||||
isTop() {
|
||||
return this._elements === undefined;
|
||||
}
|
||||
|
@ -1352,7 +1352,7 @@ export class Reference {
|
||||
);
|
||||
this.base = base;
|
||||
this.referencedName = refName;
|
||||
invariant(!(refName instanceof AbstractValue) || !refName.mightNotBeString());
|
||||
invariant(!(refName instanceof AbstractValue) || !(refName.mightNotBeString() && refName.mightNotBeNumber()));
|
||||
this.strict = strict;
|
||||
this.thisValue = thisValue;
|
||||
invariant(thisValue === undefined || !(base instanceof EnvironmentRecord));
|
||||
|
@ -33,10 +33,6 @@ import {
|
||||
Value,
|
||||
} from "../values/index.js";
|
||||
import invariant from "../invariant.js";
|
||||
import buildExpressionTemplate from "../utils/builder.js";
|
||||
|
||||
const tsTemplateSrc = "(A).toString()";
|
||||
const tsTemplate = buildExpressionTemplate(tsTemplateSrc);
|
||||
|
||||
type ElementConvType = {
|
||||
Int8: (Realm, numberOrValue) => number,
|
||||
@ -773,12 +769,7 @@ export class ToImplementation {
|
||||
|
||||
ToPropertyKeyPartial(realm: Realm, arg: Value): AbstractValue | SymbolValue | string /* but not StringValue */ {
|
||||
if (arg instanceof ConcreteValue) return this.ToPropertyKey(realm, arg);
|
||||
if (arg.mightNotBeString()) {
|
||||
if (!arg.mightNotBeNumber()) {
|
||||
return AbstractValue.createFromTemplate(realm, tsTemplate, StringValue, [arg], tsTemplateSrc);
|
||||
}
|
||||
arg.throwIfNotConcrete();
|
||||
}
|
||||
if (arg.mightNotBeString() && arg.mightNotBeNumber()) arg.throwIfNotConcrete();
|
||||
invariant(arg instanceof AbstractValue);
|
||||
return arg;
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import { AbruptCompletion, PossiblyNormalCompletion } from "../completions.js";
|
||||
import { Reference } from "../environment.js";
|
||||
import { cloneDescriptor, equalDescriptors, IsDataDescriptor, StrictEqualityComparison } from "../methods/index.js";
|
||||
import { Generator } from "../utils/generator.js";
|
||||
import { AbstractValue, EmptyValue, ObjectValue, Value } from "../values/index.js";
|
||||
import { AbstractValue, ArrayValue, EmptyValue, ObjectValue, Value } from "../values/index.js";
|
||||
|
||||
import invariant from "../invariant.js";
|
||||
import * as t from "babel-types";
|
||||
@ -236,12 +236,17 @@ export class WidenImplementation {
|
||||
|
||||
// For now, we only handle loop invariant properties
|
||||
//i.e. properties where the member expresssion does not involve any values written to inside the loop.
|
||||
//todo: handle the case where key is an abstract value.
|
||||
let key = b.key;
|
||||
if (typeof key === "string") {
|
||||
pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) =>
|
||||
t.memberExpression(o, t.identifier(key))
|
||||
);
|
||||
if (typeof key === "string" || !(key.mightNotBeString() && key.mightNotBeNumber())) {
|
||||
if (typeof key === "string") {
|
||||
pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) =>
|
||||
t.memberExpression(o, t.identifier(key))
|
||||
);
|
||||
} else {
|
||||
pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object, key], ([o, p]) => {
|
||||
return t.memberExpression(o, p, true);
|
||||
});
|
||||
}
|
||||
// The value of the property at the start of the loop needs to be written to the property
|
||||
// before the loop commences, otherwise the memberExpression will result in an undefined value.
|
||||
let generator = realm.generator;
|
||||
@ -249,9 +254,17 @@ export class WidenImplementation {
|
||||
let initVal = (b.descriptor && b.descriptor.value) || realm.intrinsics.empty;
|
||||
if (!(initVal instanceof Value)) throw new FatalError("todo: handle internal properties");
|
||||
if (!(initVal instanceof EmptyValue)) {
|
||||
generator.emitVoidExpression(rval.types, rval.values, [b.object, initVal], ([o, v]) =>
|
||||
t.assignmentExpression("=", t.memberExpression(o, t.identifier(key)), v)
|
||||
);
|
||||
if (key === "length" && b.object instanceof ArrayValue) {
|
||||
// do nothing, the array length will already be initialized
|
||||
} else if (typeof key === "string") {
|
||||
generator.emitVoidExpression(rval.types, rval.values, [b.object, initVal], ([o, v]) =>
|
||||
t.assignmentExpression("=", t.memberExpression(o, t.identifier(key)), v)
|
||||
);
|
||||
} else {
|
||||
generator.emitVoidExpression(rval.types, rval.values, [b.object, b.key, initVal], ([o, p, v]) =>
|
||||
t.assignmentExpression("=", t.memberExpression(o, p, true), v)
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new FatalError("todo: handle the case where key is an abstract value");
|
||||
@ -390,9 +403,9 @@ export class WidenImplementation {
|
||||
}
|
||||
|
||||
_containsValues(val1: Value, val2: Value) {
|
||||
if (val1 instanceof AbstractValue && val2 instanceof AbstractValue) {
|
||||
if (val1.getType() !== val2.getType()) return false;
|
||||
return val1.values.contains(val2.values);
|
||||
if (val1 instanceof AbstractValue) {
|
||||
if (!Value.isTypeCompatibleWith(val2.getType(), val1.getType())) return false;
|
||||
return val1.values.containsValue(val2);
|
||||
}
|
||||
return val1.equals(val2);
|
||||
}
|
||||
|
44
src/realm.js
44
src/realm.js
@ -572,8 +572,8 @@ export class Realm {
|
||||
// result in any more values being added to abstract domains and hence a fixpoint has been reached.
|
||||
// Generate code using effects2 because its expressions have not been widened away.
|
||||
let [, gen, bindings2, pbindings2] = effects2;
|
||||
this._emitLocalAssignments(gen, bindings2);
|
||||
this._emitPropertAssignments(gen, pbindings2);
|
||||
this._emitLocalAssignments(gen, bindings2);
|
||||
return [effects1, effects2];
|
||||
}
|
||||
effects1 = Widen.widenEffects(this, effects1, effects2);
|
||||
@ -634,7 +634,7 @@ export class Realm {
|
||||
[key.object, value],
|
||||
([o, n]) => {
|
||||
invariant(value instanceof Value);
|
||||
if (value.mightHaveBeenDeleted() && isSelfReferential(value, key.pathNode)) {
|
||||
if (typeof key.key === "string" && value.mightHaveBeenDeleted() && isSelfReferential(value, key.pathNode)) {
|
||||
let inTest = t.binaryExpression("in", t.stringLiteral(key.key), o);
|
||||
let addEmpty = t.conditionalExpression(inTest, n, emptyExpression);
|
||||
n = t.logicalExpression("||", n, addEmpty);
|
||||
@ -651,28 +651,34 @@ export class Realm {
|
||||
pbindings.forEach((val, key, map) => {
|
||||
let path = key.pathNode;
|
||||
let tval = tvalFor.get(key);
|
||||
invariant(tval !== undefined);
|
||||
invariant(val !== undefined);
|
||||
let value = val.value;
|
||||
invariant(value instanceof Value);
|
||||
let mightHaveBeenDeleted = value.mightHaveBeenDeleted();
|
||||
let mightBeUndefined = value.mightBeUndefined();
|
||||
gen.emitStatement([key.object, tval, this.intrinsics.empty], ([o, v, e]) => {
|
||||
invariant(path !== undefined);
|
||||
let lh = path.buildNode([o]);
|
||||
let r = t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
|
||||
if (mightHaveBeenDeleted) {
|
||||
invariant(typeof key.key === "string"); // for now
|
||||
// If v === __empty || (v === undefined && !(key.key in o)) then delete it
|
||||
let emptyTest = t.binaryExpression("===", v, e);
|
||||
let undefinedTest = t.binaryExpression("===", v, voidExpression);
|
||||
let inTest = t.unaryExpression("!", t.binaryExpression("in", t.stringLiteral(key.key), o));
|
||||
let guard = t.logicalExpression("||", emptyTest, t.logicalExpression("&&", undefinedTest, inTest));
|
||||
let deleteIt = t.expressionStatement(t.unaryExpression("delete", (lh: any)));
|
||||
return t.ifStatement(mightBeUndefined ? emptyTest : guard, deleteIt, r);
|
||||
}
|
||||
return r;
|
||||
});
|
||||
if (typeof key.key === "string") {
|
||||
gen.emitStatement([key.object, tval || value, this.intrinsics.empty], ([o, v, e]) => {
|
||||
invariant(path !== undefined);
|
||||
let lh = path.buildNode([o, t.identifier(key.key)]);
|
||||
let r = t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
|
||||
if (mightHaveBeenDeleted) {
|
||||
// If v === __empty || (v === undefined && !(key.key in o)) then delete it
|
||||
let emptyTest = t.binaryExpression("===", v, e);
|
||||
let undefinedTest = t.binaryExpression("===", v, voidExpression);
|
||||
let inTest = t.unaryExpression("!", t.binaryExpression("in", t.stringLiteral(key.key), o));
|
||||
let guard = t.logicalExpression("||", emptyTest, t.logicalExpression("&&", undefinedTest, inTest));
|
||||
let deleteIt = t.expressionStatement(t.unaryExpression("delete", (lh: any)));
|
||||
return t.ifStatement(mightBeUndefined ? emptyTest : guard, deleteIt, r);
|
||||
}
|
||||
return r;
|
||||
});
|
||||
} else {
|
||||
gen.emitStatement([key.object, key.key, tval || value, this.intrinsics.empty], ([o, p, v, e]) => {
|
||||
invariant(path !== undefined);
|
||||
let lh = path.buildNode([o, p]);
|
||||
return t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -331,6 +331,7 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
|
||||
_getNestedAbstractValues(absVal: AbstractValue, values: Array<Value>): Array<Value> {
|
||||
if (absVal.kind === "widened property") return values;
|
||||
invariant(absVal.args.length === 3);
|
||||
let cond = absVal.args[0];
|
||||
invariant(cond instanceof AbstractValue);
|
||||
@ -356,6 +357,7 @@ export class ResidualHeapSerializer {
|
||||
}
|
||||
|
||||
_emitPropertiesWithComputedNames(obj: ObjectValue, absVal: AbstractValue) {
|
||||
if (absVal.kind === "widened property") return;
|
||||
invariant(absVal.args.length === 3);
|
||||
let cond = absVal.args[0];
|
||||
invariant(cond instanceof AbstractValue);
|
||||
@ -870,15 +872,17 @@ export class ResidualHeapSerializer {
|
||||
// 2. array length is concrete, but different from number of index properties
|
||||
// we put into initialization list.
|
||||
if (lenProperty instanceof AbstractValue || To.ToLength(realm, lenProperty) !== numberOfIndexProperties) {
|
||||
this.emitter.emitNowOrAfterWaitingForDependencies([val], () => {
|
||||
this._assignProperty(
|
||||
() => t.memberExpression(this.getSerializeObjectIdentifier(val), t.identifier("length")),
|
||||
() => {
|
||||
return this.serializeValue(lenProperty);
|
||||
},
|
||||
false /*mightHaveBeenDeleted*/
|
||||
);
|
||||
});
|
||||
if (!(lenProperty instanceof AbstractValue) || lenProperty.kind !== "widened property") {
|
||||
this.emitter.emitNowOrAfterWaitingForDependencies([val], () => {
|
||||
this._assignProperty(
|
||||
() => t.memberExpression(this.getSerializeObjectIdentifier(val), t.identifier("length")),
|
||||
() => {
|
||||
return this.serializeValue(lenProperty);
|
||||
},
|
||||
false /*mightHaveBeenDeleted*/
|
||||
);
|
||||
});
|
||||
}
|
||||
remainingProperties.delete("length");
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +211,7 @@ export class ResidualHeapVisitor {
|
||||
}
|
||||
|
||||
visitObjectPropertiesWithComputedNames(absVal: AbstractValue): void {
|
||||
if (absVal.kind === "widened property") return;
|
||||
invariant(absVal.args.length === 3);
|
||||
let cond = absVal.args[0];
|
||||
invariant(cond instanceof AbstractValue);
|
||||
|
@ -47,6 +47,16 @@ import {
|
||||
import { Join, Properties } from "../singletons.js";
|
||||
import invariant from "../invariant.js";
|
||||
import type { typeAnnotation } from "babel-types";
|
||||
import * as t from "babel-types";
|
||||
|
||||
function isWidenedValue(v: void | Value) {
|
||||
if (!(v instanceof AbstractValue)) return false;
|
||||
if (v.kind === "widened" || v.kind === "widened property") return true;
|
||||
for (let a of v.args) {
|
||||
if (isWidenedValue(a)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default class ObjectValue extends ConcreteValue {
|
||||
constructor(
|
||||
@ -541,13 +551,18 @@ export default class ObjectValue extends ConcreteValue {
|
||||
$GetPartial(P: AbstractValue | PropertyKeyValue, Receiver: Value): Value {
|
||||
if (!(P instanceof AbstractValue)) return this.$Get(P, Receiver);
|
||||
// We assume that simple objects have no getter/setter properties.
|
||||
if (this !== Receiver || !this.isSimpleObject() || P.mightNotBeString()) {
|
||||
if (this !== Receiver || !this.isSimpleObject() || (P.mightNotBeString() && P.mightNotBeNumber())) {
|
||||
AbstractValue.reportIntrospectionError(P, "TODO: #1021");
|
||||
throw new FatalError();
|
||||
}
|
||||
// If all else fails, use this expression
|
||||
let result;
|
||||
if (this.isPartialObject()) {
|
||||
if (isWidenedValue(P)) {
|
||||
return AbstractValue.createTemporalFromBuildFunction(this.$Realm, Value, [this, P], ([o, p]) =>
|
||||
t.memberExpression(o, p, true)
|
||||
);
|
||||
}
|
||||
result = AbstractValue.createFromType(this.$Realm, Value, "sentinel member expression");
|
||||
result.args = [this, P];
|
||||
} else {
|
||||
@ -586,6 +601,17 @@ export default class ObjectValue extends ConcreteValue {
|
||||
}
|
||||
|
||||
specializeJoin(absVal: AbstractValue, propName: Value): Value {
|
||||
if (absVal.kind === "widened property") {
|
||||
let ob = absVal.args[0];
|
||||
if (propName instanceof StringValue) {
|
||||
let pName = propName.value;
|
||||
let pNumber = +pName;
|
||||
if (pName === pNumber + "") propName = new NumberValue(this.$Realm, pNumber);
|
||||
}
|
||||
return AbstractValue.createTemporalFromBuildFunction(this.$Realm, absVal.getType(), [ob, propName], ([o, p]) => {
|
||||
return t.memberExpression(o, p, true);
|
||||
});
|
||||
}
|
||||
invariant(absVal.args.length === 3 && absVal.kind === "conditional");
|
||||
let generic_cond = absVal.args[0];
|
||||
invariant(generic_cond instanceof AbstractValue);
|
||||
@ -611,6 +637,8 @@ export default class ObjectValue extends ConcreteValue {
|
||||
|
||||
$SetPartial(P: AbstractValue | PropertyKeyValue, V: Value, Receiver: Value): boolean {
|
||||
if (!(P instanceof AbstractValue)) return this.$Set(P, V, Receiver);
|
||||
let pIsLoopVar = isWidenedValue(P);
|
||||
let pIsNumeric = Value.isTypeCompatibleWith(P.getType(), NumberValue);
|
||||
|
||||
function createTemplate(realm: Realm, propName: AbstractValue) {
|
||||
return AbstractValue.createFromBinaryOp(
|
||||
@ -625,7 +653,7 @@ export default class ObjectValue extends ConcreteValue {
|
||||
|
||||
// We assume that simple objects have no getter/setter properties and
|
||||
// that all properties are writable.
|
||||
if (this !== Receiver || !this.isSimpleObject() || P.mightNotBeString()) {
|
||||
if (this !== Receiver || !this.isSimpleObject() || (P.mightNotBeString() && P.mightNotBeNumber())) {
|
||||
AbstractValue.reportIntrospectionError(P, "TODO #1021");
|
||||
throw new FatalError();
|
||||
}
|
||||
@ -635,7 +663,7 @@ export default class ObjectValue extends ConcreteValue {
|
||||
prop = {
|
||||
descriptor: undefined,
|
||||
object: this,
|
||||
key: "",
|
||||
key: P,
|
||||
};
|
||||
this.unknownProperty = prop;
|
||||
} else {
|
||||
@ -645,7 +673,7 @@ export default class ObjectValue extends ConcreteValue {
|
||||
let desc = prop.descriptor;
|
||||
if (desc === undefined) {
|
||||
let newVal = V;
|
||||
if (!(V instanceof UndefinedValue)) {
|
||||
if (!(V instanceof UndefinedValue) && !isWidenedValue(P)) {
|
||||
// join V with undefined, using a property name test as the condition
|
||||
let cond = createTemplate(this.$Realm, P);
|
||||
newVal = Join.joinValuesAsConditional(this.$Realm, cond, V, this.$Realm.intrinsics.undefined);
|
||||
@ -662,15 +690,28 @@ export default class ObjectValue extends ConcreteValue {
|
||||
invariant(oldVal instanceof Value);
|
||||
let newVal = oldVal;
|
||||
if (!(V instanceof UndefinedValue)) {
|
||||
let cond = createTemplate(this.$Realm, P);
|
||||
newVal = Join.joinValuesAsConditional(this.$Realm, cond, V, oldVal);
|
||||
if (isWidenedValue(P)) {
|
||||
newVal = V; // It will be widened later on
|
||||
} else {
|
||||
let cond = createTemplate(this.$Realm, P);
|
||||
newVal = Join.joinValuesAsConditional(this.$Realm, cond, V, oldVal);
|
||||
}
|
||||
}
|
||||
desc.value = newVal;
|
||||
}
|
||||
|
||||
// Since we don't know the name of the property we are writing to, we also need
|
||||
// to perform weak updates of all of the known properties.
|
||||
// First clear out this.unknownProperty so that helper routines know its OK to update the properties
|
||||
let savedUnknownProperty = this.unknownProperty;
|
||||
this.unknownProperty = undefined;
|
||||
for (let [key, propertyBinding] of this.properties) {
|
||||
if (pIsLoopVar && pIsNumeric) {
|
||||
// Delete numeric properties and don't do weak updates on other properties.
|
||||
if (key !== +key + "") continue;
|
||||
this.properties.delete(key);
|
||||
continue;
|
||||
}
|
||||
let oldVal = this.$Realm.intrinsics.empty;
|
||||
if (propertyBinding.descriptor && propertyBinding.descriptor.value) {
|
||||
oldVal = propertyBinding.descriptor.value;
|
||||
@ -680,6 +721,7 @@ export default class ObjectValue extends ConcreteValue {
|
||||
let newVal = Join.joinValuesAsConditional(this.$Realm, cond, V, oldVal);
|
||||
Properties.OrdinarySet(this.$Realm, this, key, newVal, Receiver);
|
||||
}
|
||||
this.unknownProperty = savedUnknownProperty;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
10
test/serializer/abstract/DoWhile7.js
Normal file
10
test/serializer/abstract/DoWhile7.js
Normal file
@ -0,0 +1,10 @@
|
||||
let n = global.__abstract ? __abstract("number", "10") : 10;
|
||||
let i = 0;
|
||||
let ob = { };
|
||||
do {
|
||||
i++;
|
||||
ob[i] = 1;
|
||||
} while (i < n);
|
||||
let x = ob[5];
|
||||
|
||||
inspect = function() { return i + " " + x + " " + JSON.stringify(ob); }
|
11
test/serializer/abstract/DoWhile8.js
Normal file
11
test/serializer/abstract/DoWhile8.js
Normal file
@ -0,0 +1,11 @@
|
||||
let a = global.__abstract ? __abstract([], "[1, 2, 3]") : [1, 2, 3];
|
||||
let n = global.__abstract ? __abstract("number", "(3)") : 3;
|
||||
let i = 0;
|
||||
let b = [];
|
||||
do {
|
||||
b[i] = a[i++];
|
||||
} while (i < n);
|
||||
let x = b[1];
|
||||
let j = b.length;
|
||||
|
||||
inspect = function() { return i + " " + j + " " + x + " " + JSON.stringify(b); }
|
Loading…
Reference in New Issue
Block a user