Deal with loops where early iterations do not assign to a property

Summary:
Release note: none

This is the first among a number of step to deal with the gnarly edge cases that arise because a property can be absent (empty) rather than just undefined.
Closes https://github.com/facebook/prepack/pull/1278

Differential Revision: D6596190

Pulled By: hermanventer

fbshipit-source-id: f18f181e617a50e1bb39015378330dee14d71815
This commit is contained in:
Herman Venter 2017-12-19 11:04:09 -08:00 committed by Facebook Github Bot
parent 104fd40103
commit 92979e2c91
7 changed files with 67 additions and 14 deletions

View File

@ -338,10 +338,6 @@ export class PropertiesImplementation {
// iii. Let valueDesc be the PropertyDescriptor{[[Value]]: V}.
let valueDesc = { value: V };
if (weakDeletion) {
valueDesc = existingDescriptor;
valueDesc.value = V;
}
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
if (weakDeletion || existingDescValue.mightHaveBeenDeleted()) {
@ -350,6 +346,8 @@ export class PropertiesImplementation {
// and that redefining the property with valueDesc will not change the
// attributes of the property, so we delete it to make things nice for $DefineOwnProperty.
Receiver.$Delete(P);
valueDesc = existingDescriptor;
valueDesc.value = V;
}
return Receiver.$DefineOwnProperty(P, valueDesc);
} else {

View File

@ -18,7 +18,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, ObjectValue, Value } from "../values/index.js";
import { AbstractValue, EmptyValue, ObjectValue, Value } from "../values/index.js";
import invariant from "../invariant.js";
import * as t from "babel-types";
@ -128,7 +128,7 @@ export class WidenImplementation {
// Create a temporal location for binding
let generator = realm.generator;
invariant(generator !== undefined);
phiNode = generator.derive(result.types, result.values, [v1 || realm.intrinsics.undefined], ([n]) => n, {
phiNode = generator.derive(result.types, result.values, [b.value || realm.intrinsics.undefined], ([n]) => n, {
skipInvariant: true,
});
b.phiNode = phiNode;
@ -167,11 +167,7 @@ export class WidenImplementation {
) {
return v1; // no need to widen a loop invariant value
} else {
return AbstractValue.createFromWidening(
realm,
v1 || realm.intrinsics.undefined,
v2 || realm.intrinsics.undefined
);
return AbstractValue.createFromWidening(realm, v1 || realm.intrinsics.empty, v2 || realm.intrinsics.undefined);
}
}
@ -195,6 +191,11 @@ export class WidenImplementation {
} else {
// no write to property in nth iteration, use the value from the (n-1)th iteration
d1 = b.descriptor;
if (d1 === undefined) {
d1 = cloneDescriptor(d2);
invariant(d1 !== undefined);
d1.value = realm.intrinsics.empty;
}
}
}
if (d2 === undefined) {
@ -227,6 +228,17 @@ export class WidenImplementation {
pathNode = AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) =>
t.memberExpression(o, t.identifier(key))
);
// 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;
invariant(generator !== undefined);
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)
);
}
} else {
throw new FatalError("todo: handle the case where key is an abstract value");
}

View File

@ -283,7 +283,12 @@ export class Realm {
invariant(globrec instanceof GlobalEnvironmentRecord);
let dclrec = globrec.$DeclarativeRecord;
return dclrec.HasBinding(key) ? dclrec.GetBindingValue(key, false) : undefined;
try {
return dclrec.HasBinding(key) ? dclrec.GetBindingValue(key, false) : undefined;
} catch (e) {
if (e instanceof FatalError) return undefined;
throw e;
}
}
/*
@ -564,6 +569,7 @@ export class Realm {
let tval = gen.derive(value.types, value.values, [value], ([n]) => n, {
skipInvariant: true,
});
tval.mightBeEmpty = value.mightBeEmpty;
tvalFor.set(key, tval);
}
});
@ -571,10 +577,21 @@ export class Realm {
let path = key.pathNode;
let tval = tvalFor.get(key);
invariant(tval !== undefined);
let mightBeEmpty = tval.mightBeEmpty;
gen.emitStatement([key.object, tval], ([o, v]) => {
invariant(path !== undefined);
let lh = path.buildNode([o]);
return t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
let r = t.expressionStatement(t.assignmentExpression("=", (lh: any), v));
if (mightBeEmpty) {
// If o does not have property key.key and v === undefined, omit the assignment (at runtime)
// if (v !== undefined || key.key in o) r
invariant(typeof key.key === "string"); // for now
let inTest = t.binaryExpression("in", t.stringLiteral(key.key), o);
let vDefined = t.binaryExpression("!==", v, t.unaryExpression("void", t.numericLiteral(0)));
let guard = t.logicalExpression("||", vDefined, inTest);
return t.ifStatement(guard, r);
}
return r;
});
});
}

View File

@ -1073,7 +1073,11 @@ export class ResidualHeapSerializer {
if (prop.joinCondition !== undefined) return false;
if ((obj instanceof FunctionValue && key === "prototype") || (obj.getKind() === "RegExp" && key === "lastIndex"))
return !!prop.writable && !prop.configurable && !prop.enumerable && !prop.set && !prop.get;
else return !!prop.writable && !!prop.configurable && !!prop.enumerable && !prop.set && !prop.get;
else if (!!prop.writable && !!prop.configurable && !!prop.enumerable && !prop.set && !prop.get) {
return !(prop.value instanceof AbstractValue && prop.value.kind === "widened property");
} else {
return false;
}
}
_findLastObjectPrototype(obj: ObjectValue): ObjectValue {
@ -1097,6 +1101,7 @@ export class ResidualHeapSerializer {
const dummyProperties = new Set();
let props = [];
for (let [key, propertyBinding] of val.properties) {
if (propertyBinding.pathNode !== undefined) continue; // written to inside loop
let descriptor = propertyBinding.descriptor;
if (descriptor === undefined || descriptor.value === undefined) continue; // deleted
if (this._canEmbedProperty(val, key, descriptor)) {

View File

@ -616,6 +616,7 @@ export default class AbstractValue extends Value {
let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue;
let result = new Constructor(realm, types, values, hash, args, buildFunction);
result.kind = "widened property";
result.mightBeEmpty = resultTemplate.mightBeEmpty;
result.expressionLocation = resultTemplate.expressionLocation;
return result;
}
@ -628,6 +629,7 @@ export default class AbstractValue extends Value {
let Constructor = Value.isTypeCompatibleWith(types.getType(), ObjectValue) ? AbstractObjectValue : AbstractValue;
let result = new Constructor(realm, types, values, hash, []);
result.kind = "widened";
result.mightBeEmpty = value1.mightHaveBeenDeleted() || value2.mightHaveBeenDeleted();
result.expressionLocation = value1.expressionLocation;
return result;
}

View File

@ -0,0 +1,9 @@
let n = global.__abstract ? __abstract("number", "1") : 1;
let i = 0;
let ob = { j: 0 };
do {
i++;
if (i > 1) ob.j = i;
} while (i < n);
inspect = function() { return i + " " + ob.j; }

View File

@ -0,0 +1,10 @@
let n = global.__abstract ? __abstract("number", "1") : 1;
let i = 0;
let ob = { };
do {
i++;
if (i > 1) ob.j = i;
} while (i < n);
let k = ob.j;
inspect = function() { return k + " " + JSON.stringify(Reflect.ownKeys(ob)); }