UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

422 lines (366 loc) 14.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HavocImplementation = undefined; var _errors = require("../errors.js"); var _environment = require("../environment.js"); var _index = require("../values/index.js"); var _index2 = require("../methods/index.js"); var _babelTypes = require("babel-types"); var t = _interopRequireWildcard(_babelTypes); var _babelTraverse = require("babel-traverse"); var _babelTraverse2 = _interopRequireDefault(_babelTraverse); var _invariant = require("../invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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 visitName(path, state, name, read, write) { // Is the name bound to some local identifier? If so, we don't need to do anything if (path.scope.hasBinding(name, /*noGlobals*/true)) return; // Otherwise, let's record that there's an unbound identifier if (read) state.unboundReads.add(name); if (write) state.unboundWrites.add(name); } /** * 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. */ function ignorePath(path) { let parent = path.parent; return t.isLabeledStatement(parent) || t.isBreakStatement(parent) || t.isContinueStatement(parent); } let HavocedClosureRefVisitor = { ReferencedIdentifier(path, state) { if (ignorePath(path)) return; let innerName = path.node.name; if (innerName === "arguments") { return; } visitName(path, state, innerName, true, false); }, "AssignmentExpression|UpdateExpression"(path, state) { let doesRead = path.node.operator !== "="; for (let name in path.getBindingIdentifiers()) { visitName(path, state, name, doesRead, true); } } }; function getHavocedFunctionInfo(value) { // TODO: This should really be cached on a per AST basis in case we have // many uses of the same closure. It should ideally share this cache // and data with ResidualHeapVisitor. (0, _invariant2.default)(value instanceof _index.ECMAScriptSourceFunctionValue); (0, _invariant2.default)(value.constructor === _index.ECMAScriptSourceFunctionValue); let functionInfo = { unboundReads: new Set(), unboundWrites: new Set() }; let formalParameters = value.$FormalParameters; (0, _invariant2.default)(formalParameters != null); let code = value.$ECMAScriptCode; (0, _invariant2.default)(code != null); (0, _babelTraverse2.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), HavocedClosureRefVisitor, null, functionInfo); return functionInfo; } class ObjectValueHavocingVisitor { // ObjectValues to visit if they're reachable. constructor(objectsTrackedForHavoc) { this.objectsTrackedForHavoc = objectsTrackedForHavoc; this.visitedValues = new Set(); } // Values that has been visited. mustVisit(val) { if (val instanceof _index.ObjectValue) { // For Objects we only need to visit it if it is tracked // as a newly created object that might still be mutated. // Abstract values gets their arguments visited. if (!this.objectsTrackedForHavoc.has(val)) return false; } if (this.visitedValues.has(val)) return false; this.visitedValues.add(val); return true; } visitObjectProperty(binding) { let desc = binding.descriptor; if (desc === undefined) return; //deleted this.visitDescriptor(desc); } visitObjectProperties(obj, kind) { // visit symbol properties for (let [, propertyBindingValue] of obj.symbols) { (0, _invariant2.default)(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } // visit string properties for (let [, propertyBindingValue] of obj.properties) { (0, _invariant2.default)(propertyBindingValue); this.visitObjectProperty(propertyBindingValue); } // inject properties with computed names if (obj.unknownProperty !== undefined) { let desc = obj.unknownProperty.descriptor; if (desc !== undefined) { let val = desc.value; (0, _invariant2.default)(val instanceof _index.AbstractValue); this.visitObjectPropertiesWithComputedNames(val); } } // prototype this.visitObjectPrototype(obj); if ((0, _index2.TestIntegrityLevel)(obj.$Realm, obj, "frozen")) return; // if this object wasn't already havoced, we need mark it as havoced // so that any mutation and property access get tracked after this. if (!obj.isHavocedObject()) { obj.havoc(); } } visitObjectPrototype(obj) { let proto = obj.$Prototype; this.visitValue(proto); } visitObjectPropertiesWithComputedNames(absVal) { (0, _invariant2.default)(absVal.args.length === 3); let cond = absVal.args[0]; (0, _invariant2.default)(cond instanceof _index.AbstractValue); if (cond.kind === "template for property name condition") { let P = cond.args[0]; (0, _invariant2.default)(P instanceof _index.AbstractValue); let V = absVal.args[1]; let earlier_props = absVal.args[2]; if (earlier_props instanceof _index.AbstractValue) this.visitObjectPropertiesWithComputedNames(earlier_props); this.visitValue(P); this.visitValue(V); } else { // conditional assignment this.visitValue(cond); let consequent = absVal.args[1]; (0, _invariant2.default)(consequent instanceof _index.AbstractValue); let alternate = absVal.args[2]; (0, _invariant2.default)(alternate instanceof _index.AbstractValue); this.visitObjectPropertiesWithComputedNames(consequent); this.visitObjectPropertiesWithComputedNames(alternate); } } visitDescriptor(desc) { (0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index.Value); if (desc.value !== undefined) this.visitValue(desc.value); if (desc.get !== undefined) this.visitValue(desc.get); if (desc.set !== undefined) this.visitValue(desc.set); } visitDeclarativeEnvironmentRecordBinding(record, remainingHavocedBindings) { let bindings = record.bindings; for (let bindingName of Object.keys(bindings)) { let binding = bindings[bindingName]; // Check if this binding is referenced, and if so delete it from the set. let isRead = remainingHavocedBindings.unboundReads.delete(bindingName); let isWritten = remainingHavocedBindings.unboundWrites.delete(bindingName); if (isRead) { // If this binding can be read from the closure, its value has now havoced. let value = binding.value; if (value) { this.visitValue(value); } } if (isWritten || isRead) { // If this binding could have been mutated from the closure, then the // binding itself has now leaked, but not necessarily the value in it. // TODO: We could tag a leaked binding as read and/or write. That way // we don't have to havoc values written to this binding if only the binding // has been written to. We also don't have to havoc reads from this binding // if it is only read from. (0, _environment.havocBinding)(binding); } } } visitValueMap(val) { let kind = val.getKind(); let entries; if (kind === "Map") { entries = val.$MapData; } else { (0, _invariant2.default)(kind === "WeakMap"); entries = val.$WeakMapData; } (0, _invariant2.default)(entries !== undefined); let len = entries.length; for (let i = 0; i < len; i++) { let entry = entries[i]; let key = entry.$Key; let value = entry.$Value; if (key === undefined || value === undefined) continue; this.visitValue(key); this.visitValue(value); } } visitValueSet(val) { let kind = val.getKind(); let entries; if (kind === "Set") { entries = val.$SetData; } else { (0, _invariant2.default)(kind === "WeakSet"); entries = val.$WeakSetData; } (0, _invariant2.default)(entries !== undefined); let len = entries.length; for (let i = 0; i < len; i++) { let entry = entries[i]; if (entry === undefined) continue; this.visitValue(entry); } } visitValueFunction(val) { if (val.isHavocedObject()) { return; } this.visitObjectProperties(val); if (val instanceof _index.BoundFunctionValue) { this.visitValue(val.$BoundTargetFunction); this.visitValue(val.$BoundThis); for (let boundArg of val.$BoundArguments) this.visitValue(boundArg); return; } (0, _invariant2.default)(!(val instanceof _index.NativeFunctionValue), "all native function values should have already been created outside this pure function"); let remainingHavocedBindings = getHavocedFunctionInfo(val); let environment = val.$Environment.parent; while (environment) { let record = environment.environmentRecord; if (record instanceof _environment.ObjectEnvironmentRecord) { this.visitValue(record.object); continue; } if (record instanceof _environment.GlobalEnvironmentRecord) { break; } (0, _invariant2.default)(record instanceof _environment.DeclarativeEnvironmentRecord); this.visitDeclarativeEnvironmentRecordBinding(record, remainingHavocedBindings); if (record instanceof _environment.FunctionEnvironmentRecord) { // If this is a function environment, which is not tracked for havocs, // we can bail out because its bindings should not be mutated in a // pure function. let fn = record.$FunctionObject; if (!this.objectsTrackedForHavoc.has(fn)) { break; } } environment = environment.parent; } } visitValueObject(val) { if (val.isHavocedObject()) { return; } let kind = val.getKind(); this.visitObjectProperties(val, kind); switch (kind) { case "RegExp": case "Number": case "String": case "Boolean": case "ReactElement": case "ArrayBuffer": case "Array": return; case "Date": let dateValue = val.$DateValue; (0, _invariant2.default)(dateValue !== undefined); this.visitValue(dateValue); return; case "Float32Array": case "Float64Array": case "Int8Array": case "Int16Array": case "Int32Array": case "Uint8Array": case "Uint16Array": case "Uint32Array": case "Uint8ClampedArray": case "DataView": let buf = val.$ViewedArrayBuffer; (0, _invariant2.default)(buf !== undefined); this.visitValue(buf); return; case "Map": case "WeakMap": this.visitValueMap(val); return; case "Set": case "WeakSet": this.visitValueSet(val); return; default: (0, _invariant2.default)(kind === "Object", `Object of kind ${kind} is not supported in calls to abstract functions.`); (0, _invariant2.default)(this.$ParameterMap === undefined, `Arguments object is not supported in calls to abstract functions.`); return; } } visitValueProxy(val) { this.visitValue(val.$ProxyTarget); this.visitValue(val.$ProxyHandler); } visitAbstractValue(val) { for (let i = 0, n = val.args.length; i < n; i++) { this.visitValue(val.args[i]); } } visitValue(val) { if (val instanceof _index.AbstractValue) { if (this.mustVisit(val)) this.visitAbstractValue(val); } else if (val.isIntrinsic()) { // All intrinsic values exist from the beginning of time... // ...except for a few that come into existance as templates for abstract objects. this.mustVisit(val); } else if (val instanceof _index.EmptyValue) { this.mustVisit(val); } else if (val instanceof _index.PrimitiveValue) { this.mustVisit(val); } else if (val instanceof _index.ProxyValue) { if (this.mustVisit(val)) this.visitValueProxy(val); } else if (val instanceof _index.FunctionValue) { (0, _invariant2.default)(val instanceof _index.FunctionValue); if (this.mustVisit(val)) this.visitValueFunction(val); } else { (0, _invariant2.default)(val instanceof _index.ObjectValue); if (val.originalConstructor !== undefined) { (0, _invariant2.default)(val instanceof _index.ObjectValue); if (this.mustVisit(val)) this.visitValueObject(val); } else { if (this.mustVisit(val)) this.visitValueObject(val); } } } } function ensureFrozenValue(realm, value, loc) { // TODO: This should really check if it is recursively immutability. if (value instanceof _index.ObjectValue && !(0, _index2.TestIntegrityLevel)(realm, value, "frozen")) { let diag = new _errors.CompilerDiagnostic("Unfrozen object leaked before end of global code", loc || realm.currentLocation, "PP0017", "RecoverableError"); if (realm.handleError(diag) !== "Recover") throw new _errors.FatalError(); } } // Ensure that a value is immutable. If it is not, set all its properties to abstract values // and all reachable bindings to abstract values. class HavocImplementation { value(realm, value, loc) { let objectsTrackedForHavoc = realm.createdObjectsTrackedForLeaks; if (objectsTrackedForHavoc === undefined) { // We're not tracking a pure function. That means that we would track // everything as havoced. We'll assume that any object argument // is invalid unless it's frozen. ensureFrozenValue(realm, value, loc); } else { // If we're tracking a pure function, we can assume that only newly // created objects and bindings, within it, are mutable. Any other // object can safely be assumed to be deeply immutable as far as this // pure function is concerned. However, any mutable object needs to // be tainted as possibly having changed to anything. let visitor = new ObjectValueHavocingVisitor(objectsTrackedForHavoc); visitor.visitValue(value); } } } exports.HavocImplementation = HavocImplementation; //# sourceMappingURL=havoc.js.map