prepack
Version:
Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.
387 lines (352 loc) • 17.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WidenImplementation = undefined;
var _errors = require("../errors.js");
var _completions = require("../completions.js");
var _environment = require("../environment.js");
var _index = require("../methods/index.js");
var _generator = require("../utils/generator.js");
var _index2 = require("../values/index.js");
var _invariant = require("../invariant.js");
var _invariant2 = _interopRequireDefault(_invariant);
var _babelTypes = require("babel-types");
var t = _interopRequireWildcard(_babelTypes);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class WidenImplementation {
_widenArrays(realm, v1, v2) {
let e = v1 && v1[0] || v2 && v2[0];
if (e instanceof _index2.Value) return this._widenArraysOfValues(realm, v1, v2);else return this._widenArrayOfsMapEntries(realm, v1, v2);
}
_widenArrayOfsMapEntries(realm, a1, a2) {
let empty = realm.intrinsics.empty;
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 && a1[i] || { $Key: empty, $Value: empty };
let { $Key: key2, $Value: val2 } = a2 && a2[i] || { $Key: empty, $Value: empty };
if (key1 === undefined && key2 === undefined) {
result[i] = { $Key: undefined, $Value: undefined };
} else {
let key3 = this.widenValues(realm, key1, key2);
(0, _invariant2.default)(key3 instanceof _index2.Value);
let val3 = this.widenValues(realm, val1, val2);
(0, _invariant2.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 && a1[i] || undefined, a2 && a2[i] || undefined);
(0, _invariant2.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 [result1,, bindings1, properties1, createdObj1] = e1;
let [result2,, bindings2, properties2, createdObj2] = e2;
let result = this.widenResults(realm, result1, result2);
let bindings = this.widenBindings(realm, bindings1, bindings2);
let properties = this.widenPropertyBindings(realm, properties1, properties2, createdObj1, createdObj2);
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"); // code subject to widening will be generated somewhere else
return [result, generator, bindings, properties, createdObjects];
}
widenResults(realm, result1, result2) {
(0, _invariant2.default)(!(result1 instanceof _environment.Reference || result2 instanceof _environment.Reference), "loop bodies should not result in refs");
(0, _invariant2.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 _index2.Value && result2 instanceof _index2.Value) {
let val = this.widenValues(realm, result1, result2);
(0, _invariant2.default)(val instanceof _index2.Value);
return val;
}
if (result1 instanceof _completions.PossiblyNormalCompletion || result2 instanceof _completions.PossiblyNormalCompletion) {
//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, _invariant2.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, _invariant2.default)(b2 !== undefined); // Local variables are not going to get deleted as a result of widening
let v2 = b2.value;
(0, _invariant2.default)(v2 !== undefined);
let result = this.widenValues(realm, v1, 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, _invariant2.default)(generator !== undefined);
phiNode = generator.derive(result.types, result.values, [b.value || realm.intrinsics.undefined], ([n]) => n, {
skipInvariant: true
});
b.phiNode = phiNode;
}
// Let the widened value be a reference to the phiNode of the binding
(0, _invariant2.default)(phiNode.intrinsicName !== undefined);
let phiName = phiNode.intrinsicName;
result.intrinsicName = phiName;
result._buildNode = args => t.identifier(phiName);
}
(0, _invariant2.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, _invariant2.default)(v1 === undefined || Array.isArray(v1));
(0, _invariant2.default)(v2 === undefined || Array.isArray(v2));
return this._widenArrays(realm, v1, v2);
}
(0, _invariant2.default)(v1 === undefined || v1 instanceof _index2.Value);
(0, _invariant2.default)(v2 === undefined || v2 instanceof _index2.Value);
if (v1 !== undefined && v2 !== undefined && !(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 {
return _index2.AbstractValue.createFromWidening(realm, v1 || realm.intrinsics.empty, v2 || realm.intrinsics.undefined);
}
}
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) {
if (b.object instanceof _index2.ObjectValue && 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, _index.cloneDescriptor)(b.descriptor);
(0, _invariant2.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, _index.cloneDescriptor)(d2);
(0, _invariant2.default)(d1 !== undefined);
d1.value = realm.intrinsics.empty;
}
}
}
if (d2 === undefined) {
if (b.object instanceof _index2.ObjectValue && 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, _index.cloneDescriptor)(d1);
(0, _invariant2.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, _invariant2.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 build node 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.mightNotBeString() && key.mightNotBeNumber())) {
if (typeof key === "string") {
pathNode = _index2.AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) => t.memberExpression(o, t.identifier(key)));
} else {
pathNode = _index2.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;
(0, _invariant2.default)(generator !== undefined);
let initVal = b.descriptor && b.descriptor.value || realm.intrinsics.empty;
if (!(initVal instanceof _index2.Value)) throw new _errors.FatalError("todo: handle internal properties");
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, 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 _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) {
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, _index.cloneDescriptor)(d2);
(0, _invariant2.default)(dc !== undefined);
dc.value = this.widenValues(realm, d2.value, d2.value);
return dc;
} else {
if ((0, _index.equalDescriptors)(d1, d2)) {
if (!(0, _index.IsDataDescriptor)(realm, d1)) return d1; // identical accessor properties need not be widened.
let dc = (0, _index.cloneDescriptor)(d1);
(0, _invariant2.default)(dc !== undefined);
dc.value = this.widenValues(realm, d1.value, d2.value);
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) {
let [result1,, bindings1, properties1, createdObj1] = e1;
let [result2,, bindings2, properties2, createdObj2] = e2;
if (!this.containsResults(result1, result2)) return false;
if (!this.containsBindings(bindings1, bindings2)) return false;
if (!this.containsPropertyBindings(properties1, properties2, createdObj1, createdObj2)) return false;
return true;
}
containsResults(result1, result2) {
if (result1 instanceof _index2.Value && result2 instanceof _index2.Value) return this._containsValues(result1, result2);
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] = [d1 && d1.value, d2 && 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 (key1.object instanceof _index2.ObjectValue && c1.has(key1.object)) {
continue;
}
if (!containsPropertyBinding(val1, val2)) return false;
}
for (const key2 of m2.keys()) {
if (key2.object instanceof _index2.ObjectValue && 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; /**
* 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.
*/
//# sourceMappingURL=widen.js.map