UNPKG

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
"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