prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
529 lines (420 loc) • 19.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WidenImplementation = void 0;
var _errors = require("../errors.js");
var _realm = require("../realm.js");
var _descriptors = require("../descriptors.js");
var _completions = require("../completions.js");
var _environment = require("../environment.js");
var _index = require("./index.js");
var _generator = require("../utils/generator.js");
var _index2 = require("../values/index.js");
var _invariant = _interopRequireDefault(require("../invariant.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
class WidenImplementation {
_widenArrays(realm, v1, v2) {
if (v1[0] instanceof _index2.Value) {
(0, _invariant.default)(v2[0] instanceof _index2.Value);
return this._widenArraysOfValues(realm, v1, v2);
}
(0, _invariant.default)(!(v2[0] instanceof _index2.Value));
return this._widenArrayOfsMapEntries(realm, v1, v2);
}
_widenArrayOfsMapEntries(realm, a1, a2) {
let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
let result = [];
for (let i = 0; i < n; i++) {
let {
$Key: key1,
$Value: val1
} = a1[i] || {
$Key: undefined,
$Value: undefined
};
let {
$Key: key2,
$Value: val2
} = a2[i] || {
$Key: undefined,
$Value: undefined
};
if (key1 === undefined && key2 === undefined) {
result[i] = {
$Key: undefined,
$Value: undefined
};
} else {
if (key1 === undefined) key1 = key2;else if (key2 === undefined) key2 = key1;
(0, _invariant.default)(key1 !== undefined);
(0, _invariant.default)(key2 !== undefined);
let key3 = this.widenValues(realm, key1, key2);
(0, _invariant.default)(key3 instanceof _index2.Value);
if (val1 === undefined && val2 === undefined) {
result[i] = {
$Key: key3,
$Value: undefined
};
} else {
if (val1 === undefined) val1 = val2;else if (val2 === undefined) val2 = val1;
(0, _invariant.default)(val1 !== undefined);
(0, _invariant.default)(val2 !== undefined);
let val3 = this.widenValues(realm, val1, val2);
(0, _invariant.default)(val3 === undefined || val3 instanceof _index2.Value);
result[i] = {
$Key: key3,
$Value: val3
};
}
}
}
return result;
}
_widenArraysOfValues(realm, a1, a2) {
let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
let result = [];
for (let i = 0; i < n; i++) {
let wv = this.widenValues(realm, a1[i], a2[i]);
(0, _invariant.default)(wv === undefined || wv instanceof _index2.Value);
result[i] = wv;
}
return result;
} // Returns a new effects summary that includes both e1 and e2.
widenEffects(realm, e1, e2) {
let result = this.widenResults(realm, e1.result, e2.result);
let bindings = this.widenBindings(realm, e1.modifiedBindings, e2.modifiedBindings);
let properties = this.widenPropertyBindings(realm, e1.modifiedProperties, e2.modifiedProperties, e1.createdObjects, e2.createdObjects);
let createdObjects = new Set(); // Top, since the empty set knows nothing. There is no other choice for widen.
let generator = new _generator.Generator(realm, "widen", realm.pathConditions); // code subject to widening will be generated somewhere else
return new _realm.Effects(result, generator, bindings, properties, createdObjects);
}
widenResults(realm, result1, result2) {
(0, _invariant.default)(!(result1 instanceof _environment.Reference || result2 instanceof _environment.Reference), "loop bodies should not result in refs");
(0, _invariant.default)(!(result1 instanceof _completions.AbruptCompletion || result2 instanceof _completions.AbruptCompletion), "if a loop iteration ends abruptly, there is no need for fixed point computation");
if (result1 instanceof _completions.SimpleNormalCompletion && result2 instanceof _completions.SimpleNormalCompletion) {
let val = this.widenValues(realm, result1.value, result2.value);
(0, _invariant.default)(val instanceof _index2.Value);
return new _completions.SimpleNormalCompletion(val);
}
if (result1 instanceof _completions.JoinedNormalAndAbruptCompletions || result2 instanceof _completions.JoinedNormalAndAbruptCompletions) {
//todo: #1174 figure out how to deal with loops that have embedded conditional exits
// widen join pathConditions
// widen normal result and Effects
// use abrupt part of result2, depend stability to make this safe. See below.
throw new _errors.FatalError();
}
(0, _invariant.default)(false);
}
widenMaps(m1, m2, widen) {
let m3 = new Map();
m1.forEach((val1, key, map1) => {
let val2 = m2.get(key);
let val3 = widen(key, val1, val2);
m3.set(key, val3);
});
m2.forEach((val2, key, map2) => {
if (!m1.has(key)) {
m3.set(key, widen(key, undefined, val2));
}
});
return m3;
}
widenBindings(realm, m1, m2) {
let widen = (b, b1, b2) => {
let l1 = b1 === undefined ? b.hasLeaked : b1.hasLeaked;
let l2 = b2 === undefined ? b.hasLeaked : b2.hasLeaked;
let hasLeaked = l1 || l2; // If either has leaked, then this binding has leaked.
let v1 = b1 === undefined || b1.value === undefined ? b.value : b1.value;
(0, _invariant.default)(b2 !== undefined); // Local variables are not going to get deleted as a result of widening
let v2 = b2.value;
(0, _invariant.default)(v2 !== undefined);
let result = this.widenValues(realm, v1 || realm.intrinsics.undefined, v2);
if (result instanceof _index2.AbstractValue && result.kind === "widened") {
let phiNode = b.phiNode;
if (phiNode === undefined) {
// Create a temporal location for binding
let generator = realm.generator;
(0, _invariant.default)(generator !== undefined);
phiNode = generator.deriveAbstract(result.types, result.values, [b.value || realm.intrinsics.undefined], (0, _generator.createOperationDescriptor)("SINGLE_ARG"), {
skipInvariant: true
});
b.phiNode = phiNode;
} // Let the widened value be a reference to the phiNode of the binding
(0, _invariant.default)(phiNode.intrinsicName !== undefined);
let phiName = phiNode.intrinsicName;
result.intrinsicName = phiName;
result.operationDescriptor = (0, _generator.createOperationDescriptor)("WIDENED_IDENTIFIER", {
id: phiName
});
}
(0, _invariant.default)(result instanceof _index2.Value);
return {
hasLeaked,
value: result
};
};
return this.widenMaps(m1, m2, widen);
} // Returns an abstract value that includes both v1 and v2 as potential values.
widenValues(realm, v1, v2) {
if (Array.isArray(v1) || Array.isArray(v2)) {
(0, _invariant.default)(Array.isArray(v1));
(0, _invariant.default)(Array.isArray(v2));
return this._widenArrays(realm, v1, v2);
}
(0, _invariant.default)(v1 instanceof _index2.Value);
(0, _invariant.default)(v2 instanceof _index2.Value);
if (!(v1 instanceof _index2.AbstractValue) && !(v2 instanceof _index2.AbstractValue) && (0, _index.StrictEqualityComparison)(realm, v1.throwIfNotConcrete(), v2.throwIfNotConcrete())) {
return v1; // no need to widen a loop invariant value
} else {
(0, _invariant.default)(v1 && v2);
return _index2.AbstractValue.createFromWidening(realm, v1, v2);
}
}
widenPropertyBindings(realm, m1, m2, c1, c2) {
let widen = (b, d1, d2) => {
if (d1 === undefined && d2 === undefined) return undefined; // If the PropertyBinding object has been freshly allocated do not widen (that happens in AbstractObjectValue)
if (d1 === undefined) {
(0, _invariant.default)(d2 !== undefined);
if (c2.has(b.object)) return d2; // no widen
if (b.descriptor !== undefined && m1.has(b)) {
// property was present in (n-1)th iteration and deleted in nth iteration
d1 = (0, _descriptors.cloneDescriptor)(b.descriptor.throwIfNotConcrete(realm));
(0, _invariant.default)(d1 !== undefined);
d1.value = realm.intrinsics.empty;
} else {
// no write to property in nth iteration, use the value from the (n-1)th iteration
d1 = b.descriptor;
if (d1 === undefined) {
d1 = (0, _descriptors.cloneDescriptor)(d2.throwIfNotConcrete(realm));
(0, _invariant.default)(d1 !== undefined);
d1.value = realm.intrinsics.empty;
}
}
}
if (d2 === undefined) {
if (c1.has(b.object)) return d1; // no widen
if (m2.has(b)) {
// property was present in nth iteration and deleted in (n+1)th iteration
d2 = (0, _descriptors.cloneDescriptor)(d1.throwIfNotConcrete(realm));
(0, _invariant.default)(d2 !== undefined);
d2.value = realm.intrinsics.empty;
} else {
// no write to property in (n+1)th iteration, use the value from the nth iteration
d2 = d1;
}
(0, _invariant.default)(d2 !== undefined);
}
let result = this.widenDescriptors(realm, d1, d2);
if (result && result.value instanceof _index2.AbstractValue && result.value.kind === "widened") {
let rval = result.value;
let pathNode = b.pathNode;
if (pathNode === undefined) {
//Since properties already have mutable storage locations associated with them, we do not
//need phi nodes. What we need is an abstract value with a operation descriptor that results in a memberExpression
//that resolves to the storage location of the property.
// 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.
let key = b.key;
if (typeof key === "string" || key instanceof _index2.AbstractValue && !(key.mightNotBeString() && key.mightNotBeNumber())) {
if (typeof key === "string") {
pathNode = _index2.AbstractValue.createFromWidenedProperty(realm, rval, [b.object, new _index2.StringValue(realm, key)], (0, _generator.createOperationDescriptor)("WIDEN_PROPERTY"));
} else {
(0, _invariant.default)(key instanceof _index2.AbstractValue);
pathNode = _index2.AbstractValue.createFromWidenedProperty(realm, rval, [b.object, key], (0, _generator.createOperationDescriptor)("WIDEN_PROPERTY"));
} // 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;
(0, _invariant.default)(generator !== undefined);
let initVal = b.descriptor && b.descriptor.throwIfNotConcrete(realm).value || realm.intrinsics.empty;
if (!(initVal instanceof _index2.EmptyValue)) {
if (key === "length" && b.object instanceof _index2.ArrayValue) {// do nothing, the array length will already be initialized
} else if (typeof key === "string") {
generator.emitVoidExpression(rval.types, rval.values, [b.object, new _index2.StringValue(realm, key), initVal], (0, _generator.createOperationDescriptor)("WIDEN_PROPERTY_ASSIGNMENT"));
} else {
(0, _invariant.default)(key instanceof _index2.AbstractValue);
generator.emitVoidExpression(rval.types, rval.values, [b.object, key, initVal], (0, _generator.createOperationDescriptor)("WIDEN_PROPERTY_ASSIGNMENT"));
}
}
} else {
throw new _errors.FatalError("todo: handle the case where key is an abstract value");
}
b.pathNode = pathNode;
}
result.value = pathNode;
}
return result;
};
return this.widenMaps(m1, m2, widen);
}
widenDescriptors(realm, d1, d2) {
d2 = d2.throwIfNotConcrete(realm);
if (d1 === undefined) {
// d2 is a property written to only in the (n+1)th iteration
if (!(0, _index.IsDataDescriptor)(realm, d2)) return d2; // accessor properties need not be widened.
let dc = (0, _descriptors.cloneDescriptor)(d2);
(0, _invariant.default)(dc !== undefined);
let d2value = dc.value;
(0, _invariant.default)(d2value !== undefined); // because IsDataDescriptor is true for d2/dc
let dcValue = this.widenValues(realm, d2value, d2value);
(0, _invariant.default)(dcValue instanceof _index2.Value);
dc.value = dcValue;
return dc;
} else {
d1 = d1.throwIfNotConcrete(realm);
if ((0, _descriptors.equalDescriptors)(d1, d2)) {
if (!(0, _index.IsDataDescriptor)(realm, d1)) return d1; // identical accessor properties need not be widened.
// equalDescriptors plus IsDataDescriptor guarantee that both have value props and if you have a value prop is value is defined.
let dc = (0, _descriptors.cloneDescriptor)(d1);
(0, _invariant.default)(dc !== undefined);
let d1value = d1.value;
(0, _invariant.default)(d1value !== undefined);
let d2value = d2.value;
(0, _invariant.default)(d2value !== undefined);
let dcValue = this.widenValues(realm, d1value, d2value);
(0, _invariant.default)(dcValue instanceof _index2.Value);
dc.value = dcValue;
return dc;
} //todo: #1174 if we get here, the loop body contains a call to create a property and different iterations
// create them differently. That seems beyond what a fixpoint computation can reasonably handle without
// losing precision. Report an error here.
throw new _errors.FatalError();
}
} // If e2 is the result of a loop iteration starting with effects e1 and it has a subset of elements of e1,
// then we have reached a fixed point and no further calls to widen are needed. e1/e2 represent a general
// summary of the loop, regardless of how many iterations will be performed at runtime.
containsEffects(e1, e2) {
if (!this.containsResults(e1.result, e2.result)) return false;
if (!this.containsBindings(e1.modifiedBindings, e2.modifiedBindings)) return false;
if (!this.containsPropertyBindings(e1.modifiedProperties, e2.modifiedProperties, e1.createdObjects, e2.createdObjects)) return false;
return true;
}
containsResults(result1, result2) {
if (result1 instanceof _completions.SimpleNormalCompletion && result2 instanceof _completions.SimpleNormalCompletion) return this._containsValues(result1.value, result2.value);
return false;
}
containsMap(m1, m2, f) {
for (const [key1, val1] of m1.entries()) {
if (val1 === undefined) continue; // deleted
let val2 = m2.get(key1);
if (val2 === undefined) continue; // A key that disappears has been widened away into the unknown key
if (!f(val1, val2)) return false;
}
for (const key2 of m2.keys()) {
if (!m1.has(key2)) return false;
}
return true;
}
containsBindings(m1, m2) {
let containsBinding = (b1, b2) => {
if (b1 === undefined || b2 === undefined || b1.value === undefined || b2.value === undefined || !this._containsValues(b1.value, b2.value) || b1.hasLeaked !== b2.hasLeaked) {
return false;
}
return true;
};
return this.containsMap(m1, m2, containsBinding);
}
containsPropertyBindings(m1, m2, c1, c2) {
let containsPropertyBinding = (d1, d2) => {
let v1, v2;
if (d1 instanceof _descriptors.InternalSlotDescriptor || d2 instanceof _descriptors.InternalSlotDescriptor) {
if (d1 !== undefined) {
(0, _invariant.default)(d1 instanceof _descriptors.InternalSlotDescriptor);
v1 = d1.value;
}
if (d2 !== undefined) {
(0, _invariant.default)(d2 instanceof _descriptors.InternalSlotDescriptor);
v2 = d2.value;
}
}
if (d1 instanceof _descriptors.PropertyDescriptor) {
v1 = d1.value;
}
if (d2 instanceof _descriptors.PropertyDescriptor) {
v2 = d2.value;
}
if (v1 === undefined) {
return v2 === undefined;
}
if (v1 instanceof _index2.Value && v2 instanceof _index2.Value) return this._containsValues(v1, v2);
if (Array.isArray(v1) && Array.isArray(v2)) {
return this._containsArray(v1, v2);
}
return v2 === undefined;
};
for (const [key1, val1] of m1.entries()) {
if (val1 === undefined) continue; // deleted
let val2 = m2.get(key1);
if (val2 === undefined) continue; // A key that disappears has been widened away into the unknown key
if (c1.has(key1.object)) {
continue;
}
if (!containsPropertyBinding(val1, val2)) return false;
}
for (const key2 of m2.keys()) {
if (c2.has(key2.object)) {
continue;
}
if (!m1.has(key2)) return false;
}
return true;
}
_containsArray(v1, v2) {
let e = v1 && v1[0] || v2 && v2[0];
if (e instanceof _index2.Value) return this.containsArraysOfValue(v1, v2);else return this._containsArrayOfsMapEntries(v1, v2);
}
_containsArrayOfsMapEntries(realm, a1, a2) {
let empty = realm.intrinsics.empty;
let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
for (let i = 0; i < n; i++) {
let {
$Key: key1,
$Value: val1
} = a1 && a1[i] || {
$Key: empty,
$Value: empty
};
let {
$Key: key2,
$Value: val2
} = a2 && a2[i] || {
$Key: empty,
$Value: empty
};
if (key1 === undefined) {
if (key2 !== undefined) return false;
} else {
if (key1 instanceof _index2.Value && key2 instanceof _index2.Value && key1.equals(key2)) {
if (val1 instanceof _index2.Value && val2 instanceof _index2.Value && this._containsValues(val1, val2)) continue;
}
return false;
}
}
return true;
}
containsArraysOfValue(realm, a1, a2) {
let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
for (let i = 0; i < n; i++) {
let [val1, val2] = [a1 && a1[i], a2 && a2[i]];
if (val1 instanceof _index2.Value && val2 instanceof _index2.Value && !this._containsValues(val1, val2)) return false;
}
return true;
}
_containsValues(val1, val2) {
if (val1 instanceof _index2.AbstractValue) {
if (!_index2.Value.isTypeCompatibleWith(val2.getType(), val1.getType()) && !_index2.Value.isTypeCompatibleWith(val1.getType(), val2.getType())) return false;
return val1.values.containsValue(val2);
}
return val1.equals(val2);
}
}
exports.WidenImplementation = WidenImplementation;
//# sourceMappingURL=widen.js.map