UNPKG

prepack

Version:

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

1,319 lines (1,048 loc) 60.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.construct_empty_effects = construct_empty_effects; exports.Realm = exports.ExecutionContext = exports.Tracer = exports.Effects = void 0; var _statistics = require("./statistics.js"); var _errors = require("./errors.js"); var _index = require("./values/index.js"); var _index2 = require("./domains/index.js"); var _environment = require("./environment.js"); var _index3 = require("./methods/index.js"); var _completions = require("./completions.js"); var _invariant = _interopRequireDefault(require("./invariant.js")); var _seedrandom = _interopRequireDefault(require("seedrandom")); var _generator = require("./utils/generator.js"); var _PreludeGenerator = require("./utils/PreludeGenerator.js"); var _singletons = require("./singletons.js"); var _descriptors = require("./descriptors.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } let effects_uid = 0; class Effects { constructor(result, generator, bindings, propertyBindings, createdObjects) { this.result = result; this.generator = generator; this.modifiedBindings = bindings; this.modifiedProperties = propertyBindings; this.createdObjects = createdObjects; this.canBeApplied = true; this._id = effects_uid++; } shallowCloneWithResult(result) { return new Effects(result, this.generator, this.modifiedBindings, this.modifiedProperties, this.createdObjects); } toDisplayString() { return _singletons.Utils.jsonToDisplayString(this, 10); } toDisplayJson(depth = 1) { if (depth <= 0) return `Effects ${this._id}`; return _singletons.Utils.verboseToDisplayJson(this, depth); } } exports.Effects = Effects; class Tracer { beginEvaluateForEffects(state) {} endEvaluateForEffects(state, effects) {} detourCall(F, thisArgument, argumentsList, newTarget, performCall) {} beforeCall(F, thisArgument, argumentsList, newTarget) {} afterCall(F, thisArgument, argumentsList, newTarget, result) {} beginOptimizingFunction(optimizedFunctionId, functionValue) {} endOptimizingFunction(optimizedFunctionId) {} } exports.Tracer = Tracer; class ExecutionContext { setCaller(context) { this.caller = context; } setFunction(F) { if (F instanceof _index.ECMAScriptSourceFunctionValue) this.isStrict = F.$Strict; this.function = F; } setLocation(loc) { if (!loc) return; this.loc = loc; } setRealm(realm) { this.realm = realm; } /* Read-only envs disallow: - creating bindings in their scope - creating or modifying objects when they are current running context */ setReadOnly(value) { let oldReadOnly = this.isReadOnly; if (this.variableEnvironment) this.variableEnvironment.environmentRecord.isReadOnly = value; if (this.lexicalEnvironment) this.lexicalEnvironment.environmentRecord.isReadOnly = value; this.isReadOnly = value; return oldReadOnly; } suspend() {// TODO #712: suspend } resume() { // TODO #712: resume return this.realm.intrinsics.undefined; } } exports.ExecutionContext = ExecutionContext; function construct_empty_effects(realm, c = new _completions.SimpleNormalCompletion(realm.intrinsics.empty)) { return new Effects(c, new _generator.Generator(realm, "construct_empty_effects", realm.pathConditions), new Map(), new Map(), new Set()); } class Realm { constructor(opts, statistics) { _defineProperty(this, "contextStack", []); _defineProperty(this, "MOBILE_JSC_VERSION", "jsc-600-1-4-17"); _defineProperty(this, "suppressDiagnostics", false); _defineProperty(this, "objectCount", 0); _defineProperty(this, "symbolCount", 867501803871088); _defineProperty(this, "functionBodyUniqueTagSeed", 1); _defineProperty(this, "nextGeneratorId", 0); this.statistics = statistics; this.isReadOnly = false; this.useAbstractInterpretation = opts.serialize === true || Array.isArray(opts.check); this.ignoreLeakLogic = false; this.isInPureTryStatement = false; if (opts.mathRandomSeed !== undefined) { this.mathRandomGenerator = (0, _seedrandom.default)(opts.mathRandomSeed); } this.strictlyMonotonicDateNow = !!opts.strictlyMonotonicDateNow; // 0 = disabled this.abstractValueImpliesMax = opts.abstractValueImpliesMax !== undefined ? opts.abstractValueImpliesMax : 0; this.abstractValueImpliesCounter = 0; this.inSimplificationPath = false; this.timeout = opts.timeout; if (this.timeout !== undefined) { // We'll call Date.now for every this.timeoutCounterThreshold'th AST node. // The threshold is there to reduce the cost of the surprisingly expensive Date.now call. this.timeoutCounter = this.timeoutCounterThreshold = 1024; } this.start = Date.now(); this.compatibility = opts.compatibility !== undefined ? opts.compatibility : "browser"; this.remainingCalls = opts.maxStackDepth || 112; this.invariantLevel = opts.invariantLevel || 0; this.invariantMode = opts.invariantMode || "throw"; this.emitConcreteModel = !!opts.emitConcreteModel; this.$TemplateMap = []; this.pathConditions = (0, _singletons.createPathConditions)(); if (this.useAbstractInterpretation) { this.preludeGenerator = new _PreludeGenerator.PreludeGenerator(opts.debugNames, opts.uniqueSuffix); _index.ObjectValue.setupTrackedPropertyAccessors(_index.ObjectValue.trackedPropertyNames); _index.ObjectValue.setupTrackedPropertyAccessors(_index.NativeFunctionValue.trackedPropertyNames); _index.ObjectValue.setupTrackedPropertyAccessors(_index.ProxyValue.trackedPropertyNames); } this.collectedNestedOptimizedFunctionEffects = new Map(); this.tracers = []; // These get initialized in construct_realm to avoid the dependency this.intrinsics = {}; this.$GlobalObject = {}; this.evaluators = Object.create(null); this.$GlobalEnv = undefined; this.derivedIds = new Map(); this.temporalEntryArgToEntries = new Map(); this.temporalEntryCounter = 0; this.instantRender = { enabled: opts.instantRender || false }; this.react = { abstractHints: new WeakMap(), activeReconciler: undefined, classComponentMetadata: new Map(), currentOwner: undefined, defaultPropsHelper: undefined, emptyArray: undefined, emptyObject: undefined, enabled: opts.reactEnabled || false, failOnUnsupportedSideEffects: opts.reactFailOnUnsupportedSideEffects === false ? false : true, hoistableFunctions: new WeakMap(), hoistableReactElements: new WeakMap(), noopFunction: undefined, optimizeNestedFunctions: opts.reactOptimizeNestedFunctions || false, output: opts.reactOutput || "create-element", propsWithNoPartialKeyOrRef: new WeakSet(), reactElements: new WeakMap(), reactElementStringTypeReferences: new Map(), reactProps: new WeakSet(), symbols: new Map(), usedReactElementKeys: new Set(), verbose: opts.reactVerbose || false }; this.reportSideEffectCallbacks = new Set(); this.alreadyDescribedLocations = new WeakMap(); this.stripFlow = opts.stripFlow || false; this.fbLibraries = { other: new Map(), react: undefined, reactDom: undefined, reactDomServer: undefined, reactNative: undefined, reactRelay: undefined }; this.errorHandler = opts.errorHandler; this.globalSymbolRegistry = []; this.activeLexicalEnvironments = new Set(); this._abstractValuesDefined = new Set(); // A set of nameStrings to ensure abstract values have unique names this.debugNames = opts.debugNames; this._checkedObjectIds = new Map(); this.optimizedFunctions = new Map(); this.arrayNestedOptimizedFunctionsEnabled = opts.arrayNestedOptimizedFunctionsEnabled || opts.instantRender || false; } // to force flow to type the annotations isCompatibleWith(compatibility) { return compatibility === this.compatibility; } // Checks if there is a let binding at global scope with the given name // returning it if so getGlobalLetBinding(key) { let globrec = this.$GlobalEnv.environmentRecord; // GlobalEnv should have a GlobalEnvironmentRecord (0, _invariant.default)(globrec instanceof _environment.GlobalEnvironmentRecord); let dclrec = globrec.$DeclarativeRecord; try { return dclrec.HasBinding(key) ? dclrec.GetBindingValue(key, false) : undefined; } catch (e) { if (e instanceof _errors.FatalError) return undefined; throw e; } } /* Read only realms disallow: - using console.log - creating bindings in any existing scopes - modifying object properties in any existing scopes Setting a realm read-only sets all contained environments to read-only, but all new environments (e.g. new ExecutionContexts) will be writeable. */ setReadOnly(readOnlyValue) { let oldReadOnly = this.isReadOnly; this.isReadOnly = readOnlyValue; this.$GlobalEnv.environmentRecord.isReadOnly = readOnlyValue; this.contextStack.forEach(ctx => { ctx.setReadOnly(readOnlyValue); }); return oldReadOnly; } testTimeout() { let timeout = this.timeout; if (timeout !== undefined && ! --this.timeoutCounter) { this.timeoutCounter = this.timeoutCounterThreshold; let total = Date.now() - this.start; if (total > timeout) { let error = new _errors.CompilerDiagnostic(`total time has exceeded the timeout time: ${timeout}`, this.currentLocation, "PP0036", "FatalError"); this.handleError(error); throw new _errors.FatalError("Timed out"); } } } hasRunningContext() { return this.contextStack.length !== 0; } getRunningContext() { let context = this.contextStack[this.contextStack.length - 1]; (0, _invariant.default)(context, "There's no running execution context"); return context; } clearBlockBindings(modifiedBindings, environmentRecord) { if (modifiedBindings === undefined) return; for (let b of modifiedBindings.keys()) { if (b.mightHaveBeenCaptured) continue; if (environmentRecord.bindings[b.name] && environmentRecord.bindings[b.name] === b) modifiedBindings.delete(b); } } // Call when a scope falls out of scope and should be destroyed. // Clears the Bindings corresponding to the disappearing Scope from ModifiedBindings onDestroyScope(lexicalEnvironment) { (0, _invariant.default)(this.activeLexicalEnvironments.has(lexicalEnvironment)); let modifiedBindings = this.modifiedBindings; if (modifiedBindings) { // Don't undo things to global scope because it's needed past its destruction point (for serialization) let environmentRecord = lexicalEnvironment.environmentRecord; if (environmentRecord instanceof _environment.DeclarativeEnvironmentRecord) { this.clearBlockBindings(modifiedBindings, environmentRecord); } } // Ensures if we call onDestroyScope too early, there will be a failure. this.activeLexicalEnvironments.delete(lexicalEnvironment); lexicalEnvironment.destroy(); } startCall() { if (this.remainingCalls === 0) { let error = new _errors.CompilerDiagnostic("Maximum stack depth exceeded", this.currentLocation, "PP0045", "FatalError"); this.handleError(error); throw new _errors.FatalError(); } this.remainingCalls--; } endCall() { this.remainingCalls++; } pushContext(context) { this.contextStack.push(context); } markVisibleLocalBindingsAsPotentiallyCaptured() { let context = this.getRunningContext(); if (context.function === undefined) return; let lexEnv = context.lexicalEnvironment; while (lexEnv != null) { let envRec = lexEnv.environmentRecord; if (envRec instanceof _environment.DeclarativeEnvironmentRecord) { let bindings = envRec.bindings; for (let name in bindings) { let binding = bindings[name]; binding.mightHaveBeenCaptured = true; } } lexEnv = lexEnv.parent; } } clearFunctionBindings(modifiedBindings, funcVal) { if (modifiedBindings === undefined) return; for (let b of modifiedBindings.keys()) { if (b.mightHaveBeenCaptured) continue; if (b.environment instanceof _environment.FunctionEnvironmentRecord && b.environment.$FunctionObject === funcVal) modifiedBindings.delete(b); } } popContext(context) { let funcVal = context.function; if (funcVal) { this.clearFunctionBindings(this.modifiedBindings, funcVal); } let c = this.contextStack.pop(); (0, _invariant.default)(c === context); } wrapInGlobalEnv(callback) { let context = new ExecutionContext(); context.isStrict = this.isStrict; context.lexicalEnvironment = this.$GlobalEnv; context.variableEnvironment = this.$GlobalEnv; context.realm = this; this.pushContext(context); try { return callback(); } finally { this.popContext(context); } } assignToGlobal(name, value) { this.wrapInGlobalEnv(() => this.$GlobalEnv.assignToGlobal(name, value)); } deleteGlobalBinding(name) { this.$GlobalEnv.environmentRecord.DeleteBinding(name); } neverCheckProperty(object, P) { return P.startsWith("__") || object === this.$GlobalObject && P === "global" || object.intrinsicName !== undefined && object.intrinsicName.startsWith("__"); } _getCheckedBindings() { let globalObject = this.$GlobalObject; (0, _invariant.default)(globalObject instanceof _index.ObjectValue); let binding = globalObject.properties.get("__checkedBindings"); (0, _invariant.default)(binding !== undefined); let checkedBindingsObject = binding.descriptor && binding.descriptor.throwIfNotConcrete(this).value; (0, _invariant.default)(checkedBindingsObject instanceof _index.ObjectValue); return checkedBindingsObject; } markPropertyAsChecked(object, P) { (0, _invariant.default)(!this.neverCheckProperty(object, P)); let objectId = this._checkedObjectIds.get(object); if (objectId === undefined) this._checkedObjectIds.set(object, objectId = this._checkedObjectIds.size); let id = `__propertyHasBeenChecked__${objectId}:${P}`; let checkedBindings = this._getCheckedBindings(); checkedBindings.$Set(id, this.intrinsics.true, checkedBindings); } hasBindingBeenChecked(object, P) { if (this.neverCheckProperty(object, P)) return true; let objectId = this._checkedObjectIds.get(object); if (objectId === undefined) return false; let id = `__propertyHasBeenChecked__${objectId}:${P}`; let binding = this._getCheckedBindings().properties.get(id); if (binding === undefined) return false; let value = binding.descriptor && binding.descriptor.throwIfNotConcrete(this).value; return value instanceof _index.Value && !value.mightNotBeTrue(); } // Evaluate a context as if it won't have any side-effects outside of any objects // that it created itself. This promises that any abstract functions inside of it // also won't have effects on any objects or bindings that weren't created in this // call. evaluatePure(f, bubbleSideEffectReports, reportSideEffectFunc) { let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks; let saved_reportSideEffectCallbacks; // Track all objects (including function closures) created during // this call. This will be used to make the assumption that every // *other* object is unchanged (pure). These objects are marked // as leaked if they're passed to abstract functions. this.createdObjectsTrackedForLeaks = new Set(); if (reportSideEffectFunc !== null) { if (!bubbleSideEffectReports) { saved_reportSideEffectCallbacks = this.reportSideEffectCallbacks; this.reportSideEffectCallbacks = new Set(); } this.reportSideEffectCallbacks.add(reportSideEffectFunc); } try { return f(); } finally { if (saved_createdObjectsTrackedForLeaks === undefined) { this.createdObjectsTrackedForLeaks = undefined; } else { // Add any created objects from the child evaluatePure's tracked objects set to the // current tracked objects set. if (this.createdObjectsTrackedForLeaks !== undefined) { for (let obj of this.createdObjectsTrackedForLeaks) { saved_createdObjectsTrackedForLeaks.add(obj); } } this.createdObjectsTrackedForLeaks = saved_createdObjectsTrackedForLeaks; } if (reportSideEffectFunc !== null) { if (!bubbleSideEffectReports && saved_reportSideEffectCallbacks !== undefined) { this.reportSideEffectCallbacks = saved_reportSideEffectCallbacks; } this.reportSideEffectCallbacks.delete(reportSideEffectFunc); } } } isInPureScope() { return !!this.createdObjectsTrackedForLeaks; } evaluateWithoutLeakLogic(f) { (0, _invariant.default)(!this.ignoreLeakLogic, "Nesting evaluateWithoutLeakLogic() calls is not supported."); this.ignoreLeakLogic = true; try { return f(); } finally { this.ignoreLeakLogic = false; } } // Evaluate some code that might generate temporal values knowing that it might end in an abrupt // completion. We only need to support ThrowCompletion for now but this can be expanded to support other // abrupt completions. evaluateWithPossibleThrowCompletion(f, thrownTypes, thrownValues) { // The cases when we need this are only when we might invoke unknown code such as abstract // funtions, getters, custom coercion etc. It is possible we can use this in other cases // where something might throw a built-in error but can never issue arbitrary code such as // calling something that might not be a function. For now we only use it in pure functions. (0, _invariant.default)(this.isInPureScope(), "only abstract abrupt completion in pure functions"); // TODO(1264): We should create a new generator for this scope and wrap it in a try/catch. // We could use the outcome of that as the join condition for a JoinedNormalAndAbruptCompletions. // We should then compose that with the saved completion and move on to the normal route. // Currently we just issue a recoverable error instead if this might matter. let value = f(); if (this.isInPureTryStatement) { let diag = new _errors.CompilerDiagnostic("Possible throw inside try/catch is not yet supported", this.currentLocation, "PP0021", "RecoverableError"); if (this.handleError(diag) !== "Recover") throw new _errors.FatalError(); } return value; } // Evaluate the given ast in a sandbox and return the evaluation results // in the form of a completion, a code generator, a map of changed variable // bindings and a map of changed property bindings. evaluateNodeForEffects(ast, strictCode, env, state, generatorName = "evaluateNodeForEffects") { return this.evaluateForEffects(() => env.evaluateCompletionDeref(ast, strictCode), state, generatorName); } evaluateForEffectsInGlobalEnv(func, state, generatorName = "evaluateForEffectsInGlobalEnv") { return this.wrapInGlobalEnv(() => this.evaluateForEffects(func, state, generatorName)); } // NB: does not apply generators because there's no way to cleanly revert them. // func should not return undefined withEffectsAppliedInGlobalEnv(func, effects) { let result; this.evaluateForEffectsInGlobalEnv(() => { try { this.applyEffects(effects, "", false); result = func(effects); return this.intrinsics.undefined; } finally { this.restoreBindings(effects.modifiedBindings); this.restoreProperties(effects.modifiedProperties); (0, _invariant.default)(!effects.canBeApplied); effects.canBeApplied = true; } }); (0, _invariant.default)(result !== undefined, "If we get here, func must have returned undefined."); return result; } withNewOptimizedFunction(func, optimizedFunction) { let result; let previousOptimizedFunction = this.currentOptimizedFunction; this.currentOptimizedFunction = optimizedFunction; try { result = func(); } finally { this.currentOptimizedFunction = previousOptimizedFunction; } return result; } evaluateNodeForEffectsInGlobalEnv(node, state, generatorName) { return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName)); } // Use this to evaluate code for internal purposes, so that the tracked state does not get polluted evaluateWithoutEffects(f) { // Save old state and set up undefined state let savedGenerator = this.generator; let savedBindings = this.modifiedBindings; let savedProperties = this.modifiedProperties; let savedCreatedObjects = this.createdObjects; let saved_completion = this.savedCompletion; try { this.generator = new _generator.Generator(this, "evaluateIgnoringEffects", this.pathConditions); this.modifiedBindings = undefined; this.modifiedProperties = undefined; this.createdObjects = undefined; this.savedCompletion = undefined; return f(); } finally { this.generator = savedGenerator; this.modifiedBindings = savedBindings; this.modifiedProperties = savedProperties; this.createdObjects = savedCreatedObjects; this.savedCompletion = saved_completion; } } evaluateForEffects(f, state, generatorName) { // Save old state and set up empty state let [savedBindings, savedProperties] = this.getAndResetModifiedMaps(); let saved_generator = this.generator; let saved_createdObjects = this.createdObjects; let saved_completion = this.savedCompletion; let saved_abstractValuesDefined = this._abstractValuesDefined; this.generator = new _generator.Generator(this, generatorName, this.pathConditions); this.createdObjects = new Set(); this.savedCompletion = undefined; // while in this call, we only explore the normal path. this._abstractValuesDefined = new Set(saved_abstractValuesDefined); let result; try { for (let t1 of this.tracers) t1.beginEvaluateForEffects(state); let c; try { try { c = f(); if (c instanceof _environment.Reference) c = _singletons.Environment.GetValue(this, c);else if (c instanceof _completions.SimpleNormalCompletion) c = c.value; } catch (e) { if (e instanceof _completions.AbruptCompletion) c = e;else throw e; } // This is a join point for any normal completions inside realm.savedCompletion c = _singletons.Functions.incorporateSavedCompletion(this, c); (0, _invariant.default)(c !== undefined); (0, _invariant.default)(this.generator !== undefined); (0, _invariant.default)(this.modifiedBindings !== undefined); (0, _invariant.default)(this.modifiedProperties !== undefined); (0, _invariant.default)(this.createdObjects !== undefined); let astGenerator = this.generator; let astBindings = this.modifiedBindings; let astProperties = this.modifiedProperties; let astCreatedObjects = this.createdObjects; /* TODO #1615: The following invariant should hold. // Check invariant that modified bindings to not refer to environment record belonging to // newly created closure objects. for (let binding of astBindings.keys()) if (binding.environment instanceof FunctionEnvironmentRecord) invariant(!astCreatedObjects.has(binding.environment.$FunctionObject)); */ // Return the captured state changes and evaluation result if (c instanceof _index.Value) c = new _completions.SimpleNormalCompletion(c); result = new Effects(c, astGenerator, astBindings, astProperties, astCreatedObjects); return result; } finally { // Roll back the state changes if (result !== undefined) { this.restoreBindings(result.modifiedBindings); this.restoreProperties(result.modifiedProperties); } else { this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); let completion = this.savedCompletion; while (completion !== undefined) { const { savedEffects } = completion; if (savedEffects !== undefined) { this.restoreBindings(savedEffects.modifiedBindings); this.restoreProperties(savedEffects.modifiedProperties); } completion = completion.composedWith; } } this.generator = saved_generator; this.modifiedBindings = savedBindings; this.modifiedProperties = savedProperties; this.createdObjects = saved_createdObjects; this.savedCompletion = saved_completion; this._abstractValuesDefined = saved_abstractValuesDefined; } } finally { for (let t2 of this.tracers) t2.endEvaluateForEffects(state, result); } } evaluateWithUndo(f, defaultValue = this.intrinsics.undefined) { if (!this.useAbstractInterpretation) return f(); let oldErrorHandler = this.errorHandler; this.errorHandler = d => { if (d.severity === "Information" || d.severity === "Warning") return "Recover"; return "Fail"; }; try { let effects = this.evaluateForEffects(() => { try { return f(); } catch (e) { if (e instanceof _completions.Completion) { return defaultValue; } else if (e instanceof _errors.FatalError) { return defaultValue; } else { throw e; } } }, undefined, "evaluateWithUndo"); return effects.result instanceof _completions.SimpleNormalCompletion ? effects.result.value : defaultValue; } finally { this.errorHandler = oldErrorHandler; } } evaluateWithUndoForDiagnostic(f) { if (!this.useAbstractInterpretation) return f(); let savedHandler = this.errorHandler; let diagnostic; try { this.errorHandler = d => { diagnostic = d; return "Fail"; }; let effects = this.evaluateForEffects(f, undefined, "evaluateWithUndoForDiagnostic"); this.applyEffects(effects); let resultVal = effects.result; if (resultVal instanceof _completions.AbruptCompletion) throw resultVal; return resultVal.value; } catch (e) { if (diagnostic !== undefined) return diagnostic; throw e; } finally { this.errorHandler = savedHandler; } } evaluateForFixpointEffects(iteration) { try { let test; let f = () => { let result; [test, result] = iteration(); if (!(test instanceof _index.AbstractValue)) throw new _errors.FatalError("loop terminates before fixed point"); (0, _invariant.default)(result instanceof _completions.Completion); return result; }; let effects1 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/1"); while (true) { this.restoreBindings(effects1.modifiedBindings); this.restoreProperties(effects1.modifiedProperties); let effects2 = this.evaluateForEffects(f, undefined, "evaluateForFixpointEffects/2"); this.restoreBindings(effects1.modifiedBindings); this.restoreProperties(effects1.modifiedProperties); if (_singletons.Widen.containsEffects(effects1, effects2)) { // effects1 includes every value present in effects2, so doing another iteration using effects2 will not // result in any more values being added to abstract domains and hence a fixpoint has been reached. // Generate code using effects2 because its expressions have not been widened away. const e2 = effects2; this._applyPropertiesToNewlyCreatedObjects(e2.modifiedProperties, e2.createdObjects); this._emitPropertyAssignments(e2.generator, e2.modifiedProperties, e2.createdObjects); this._emitLocalAssignments(e2.generator, e2.modifiedBindings, e2.createdObjects); (0, _invariant.default)(test instanceof _index.AbstractValue); let cond = e2.generator.deriveAbstract(test.types, test.values, [test], (0, _generator.createOperationDescriptor)("SINGLE_ARG"), { skipInvariant: true }); return [effects1, effects2, cond]; } effects1 = _singletons.Widen.widenEffects(this, effects1, effects2); } } catch (e) { if (e instanceof _errors.FatalError) return undefined; throw e; } } evaluateWithAbstractConditional(condValue, consequentEffectsFunc, alternateEffectsFunc) { let effects; if (_singletons.Path.implies(condValue)) { effects = consequentEffectsFunc(); } else if (_singletons.Path.impliesNot(condValue)) { effects = alternateEffectsFunc(); } else { // Join effects let effects1; try { effects1 = _singletons.Path.withCondition(condValue, consequentEffectsFunc); } catch (e) { if (!(e instanceof _errors.InfeasiblePathError)) throw e; } let effects2; try { effects2 = _singletons.Path.withInverseCondition(condValue, alternateEffectsFunc); } catch (e) { if (!(e instanceof _errors.InfeasiblePathError)) throw e; } if (effects1 === undefined || effects2 === undefined) { if (effects1 === undefined && effects2 === undefined) throw new _errors.InfeasiblePathError(); effects = effects1 || effects2; (0, _invariant.default)(effects !== undefined); } else { // Join the effects, creating an abstract view of what happened, regardless // of the actual value of condValue. effects = _singletons.Join.joinEffects(condValue, effects1, effects2); } } this.applyEffects(effects); return condValue.$Realm.returnOrThrowCompletion(effects.result); } _applyPropertiesToNewlyCreatedObjects(modifiedProperties, newlyCreatedObjects) { if (modifiedProperties === undefined) return; modifiedProperties.forEach((desc, propertyBinding, m) => { if (newlyCreatedObjects.has(propertyBinding.object)) { propertyBinding.descriptor = desc; } }); } // populate the loop body generator with assignments that will update the phiNodes _emitLocalAssignments(gen, bindings, newlyCreatedObjects) { let tvalFor = new Map(); bindings.forEach((binding, key, map) => { let val = binding.value; if (val instanceof _index.AbstractValue) { (0, _invariant.default)(val.operationDescriptor !== undefined); let tval = gen.deriveAbstract(val.types, val.values, [val], (0, _generator.createOperationDescriptor)("SINGLE_ARG")); tvalFor.set(key, tval); } }); bindings.forEach((binding, key, map) => { let val = binding.value; if (val instanceof _index.AbstractValue) { let phiNode = key.phiNode; let tval = tvalFor.get(key); (0, _invariant.default)(tval !== undefined); gen.emitStatement([tval], (0, _generator.createOperationDescriptor)("LOCAL_ASSIGNMENT", { value: phiNode })); } if (val instanceof _index.ObjectValue && newlyCreatedObjects.has(val)) { let phiNode = key.phiNode; gen.emitStatement([val], (0, _generator.createOperationDescriptor)("LOCAL_ASSIGNMENT", { value: phiNode })); } }); } // populate the loop body generator with assignments that will update properties modified inside the loop _emitPropertyAssignments(gen, pbindings, newlyCreatedObjects) { let tvalFor = new Map(); pbindings.forEach((val, key, map) => { if (newlyCreatedObjects.has(key.object) || key.object.refuseSerialization) { return; } let value = val && val.throwIfNotConcrete(this).value; if (value instanceof _index.AbstractValue) { (0, _invariant.default)(value.operationDescriptor !== undefined); let tval = gen.deriveAbstract(value.types, value.values, [key.object, value], (0, _generator.createOperationDescriptor)("LOGICAL_PROPERTY_ASSIGNMENT", { propertyBinding: key, value }), { skipInvariant: true }); tvalFor.set(key, tval); } }); pbindings.forEach((val, key, map) => { if (newlyCreatedObjects.has(key.object) || key.object.refuseSerialization) { return; } let path = key.pathNode; let tval = tvalFor.get(key); (0, _invariant.default)(val !== undefined); let value = val.throwIfNotConcrete(this).value; (0, _invariant.default)(value instanceof _index.Value); let keyKey = key.key; if (typeof keyKey === "string") { if (path !== undefined) { gen.emitStatement([key.object, tval || value, this.intrinsics.empty, new _index.StringValue(this, keyKey)], (0, _generator.createOperationDescriptor)("CONDITIONAL_PROPERTY_ASSIGNMENT", { path, value })); } else {// RH value was not widened, so it must have been a constant. We don't need to assign that inside the loop. // Note, however, that if the LH side is a property of an intrinsic object, then an assignment will // have been emitted to the generator. } } else { // TODO: What if keyKey is undefined? (0, _invariant.default)(keyKey instanceof _index.Value); gen.emitStatement([key.object, keyKey, tval || value, this.intrinsics.empty], (0, _generator.createOperationDescriptor)("PROPERTY_ASSIGNMENT", { path })); } }); } returnOrThrowCompletion(completion) { if (completion instanceof _index.Value) completion = new _completions.SimpleNormalCompletion(completion); if (completion instanceof _completions.AbruptCompletion) { let c = _singletons.Functions.incorporateSavedCompletion(this, completion); (0, _invariant.default)(c instanceof _completions.Completion); completion = c; } let cc = this.composeWithSavedCompletion(completion); if (cc instanceof _completions.AbruptCompletion) throw cc; return cc.value; } composeWithSavedCompletion(completion) { if (this.savedCompletion === undefined) { if (completion instanceof _completions.JoinedNormalAndAbruptCompletions) { this.savedCompletion = completion; this.pushPathConditionsLeadingToNormalCompletions(completion); this.captureEffects(completion); } return completion; } else { let cc = _singletons.Join.composeCompletions(this.savedCompletion, completion); if (cc instanceof _completions.JoinedNormalAndAbruptCompletions) { this.savedCompletion = cc; this.pushPathConditionsLeadingToNormalCompletions(completion); if (cc.savedEffects === undefined) this.captureEffects(cc); } else { this.savedCompletion = undefined; } return cc; } } pushPathConditionsLeadingToNormalCompletions(completion) { let realm = this; let bottomValue = realm.intrinsics.__bottomValue; // Note that if a completion of type CompletionType has a value is that is bottom, that completion is unreachable // and pushing its corresponding path condition would cause an InfeasiblePathError to be thrown. if (completion instanceof _completions.JoinedNormalAndAbruptCompletions && completion.composedWith !== undefined) this.pushPathConditionsLeadingToNormalCompletions(completion.composedWith); if (completion instanceof _completions.JoinedAbruptCompletions || completion instanceof _completions.JoinedNormalAndAbruptCompletions) { let jc = completion.joinCondition; if (completion.consequent.value === bottomValue || allPathsAreOfType(_completions.AbruptCompletion, completion.consequent)) { if (completion.alternate.value === bottomValue || allPathsAreOfType(_completions.AbruptCompletion, completion.alternate)) return; _singletons.Path.pushInverseAndRefine(completion.joinCondition); this.pushPathConditionsLeadingToNormalCompletions(completion.alternate); } else if (completion.alternate.value === bottomValue || allPathsAreOfType(_completions.AbruptCompletion, completion.alternate)) { if (completion.consequent.value === bottomValue) return; _singletons.Path.pushAndRefine(completion.joinCondition); this.pushPathConditionsLeadingToNormalCompletions(completion.consequent); } else if (allPathsAreOfType(_completions.NormalCompletion, completion.consequent)) { if (!allPathsAreOfType(_completions.NormalCompletion, completion.alternate)) { let alternatePC = getNormalPathConditions(completion.alternate); let disjunct = _index.AbstractValue.createFromLogicalOp(realm, "||", jc, alternatePC, undefined, true, true); _singletons.Path.pushAndRefine(disjunct); } } else if (allPathsAreOfType(_completions.NormalCompletion, completion.alternate)) { let consequentPC = getNormalPathConditions(completion.consequent); let inverse = _index.AbstractValue.createFromUnaryOp(realm, "!", jc, true, undefined, true, true); let disjunct = _index.AbstractValue.createFromLogicalOp(realm, "||", inverse, consequentPC, undefined, true, true); _singletons.Path.pushAndRefine(disjunct); } else { let cpc = _index.AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditions(completion.consequent), undefined, true, true); let ijc = _index.AbstractValue.createFromUnaryOp(realm, "!", jc, true, undefined, true, true); let apc = _index.AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditions(completion.alternate), undefined, true, true); let disjunct = _index.AbstractValue.createFromLogicalOp(realm, "||", cpc, apc, undefined, true, true); _singletons.Path.pushAndRefine(disjunct); } } return; function allPathsAreOfType(CompletionType, c) { if (c instanceof _completions.JoinedNormalAndAbruptCompletions) { if (c.composedWith !== undefined && !allPathsAreOfType(CompletionType, c.composedWith)) return false; return allPathsAreOfType(CompletionType, c.consequent) && allPathsAreOfType(CompletionType, c.alternate); } else if (c instanceof _completions.JoinedAbruptCompletions) { return allPathsAreOfType(CompletionType, c.consequent) && allPathsAreOfType(CompletionType, c.alternate); } else { return c instanceof CompletionType; } } function getNormalPathConditions(c) { let pathCondToComposeWith; if (c instanceof _completions.JoinedNormalAndAbruptCompletions && c.composedWith !== undefined) pathCondToComposeWith = getNormalPathConditions(c.composedWith); if (!(c instanceof _completions.JoinedAbruptCompletions || c instanceof _completions.JoinedNormalAndAbruptCompletions)) { return c instanceof _completions.AbruptCompletion ? realm.intrinsics.false : realm.intrinsics.true; } let pathCond; if (c.consequent.value === bottomValue || allPathsAreOfType(_completions.AbruptCompletion, c.consequent)) { if (!allPathsAreOfType(_completions.AbruptCompletion, c.alternate)) { let inverse = _index.AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition, true, undefined, true, true); if (allPathsAreOfType(_completions.NormalCompletion, c.alternate)) pathCond = inverse;else pathCond = _index.AbstractValue.createFromLogicalOp(realm, "&&", inverse, getNormalPathConditions(c.alternate), undefined, true, true); } } else if (c.alternate.value === bottomValue || allPathsAreOfType(_completions.AbruptCompletion, c.alternate)) { if (!allPathsAreOfType(_completions.AbruptCompletion, c.consequent)) { if (allPathsAreOfType(_completions.NormalCompletion, c.consequent)) { pathCond = c.joinCondition; } else { let jc = c.joinCondition; pathCond = _index.AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditions(c.consequent), undefined, true, true); } } } else { let jc = c.joinCondition; let consequentPC = _index.AbstractValue.createFromLogicalOp(realm, "&&", jc, getNormalPathConditions(c.consequent), undefined, true, true); let ijc = _index.AbstractValue.createFromUnaryOp(realm, "!", jc, true, undefined, true, true); let alternatePC = _index.AbstractValue.createFromLogicalOp(realm, "&&", ijc, getNormalPathConditions(c.alternate), undefined, true, true); pathCond = _index.AbstractValue.createFromLogicalOp(realm, "||", consequentPC, alternatePC, undefined, true, true); } if (pathCondToComposeWith === undefined && pathCond === undefined) return realm.intrinsics.false; if (pathCondToComposeWith === undefined) { (0, _invariant.default)(pathCond !== undefined); return pathCond; } if (pathCond === undefined) return pathCondToComposeWith; return _index.AbstractValue.createFromLogicalOp(realm, "&&", pathCondToComposeWith, pathCond, undefined, true, true); } } captureEffects(completion) { (0, _invariant.default)(completion.savedEffects === undefined); completion.savedEffects = new Effects(new _completions.SimpleNormalCompletion(this.intrinsics.undefined), this.generator, this.modifiedBindings, this.modifiedProperties, this.createdObjects); this.generator = new _generator.Generator(this, "captured", this.pathConditions); this.modifiedBindings = new Map(); this.modifiedProperties = new Map(); this.createdObjects = new Set(); } getCapturedEffects(v = this.intrinsics.undefined) { (0, _invariant.default)(this.generator !== undefined); (0, _invariant.default)(this.modifiedBindings !== undefined); (0, _invariant.default)(this.modifiedProperties !== undefined); (0, _invariant.default)(this.createdObjects !== undefined); return new Effects(v instanceof _completions.Completion ? v : new _completions.SimpleNormalCompletion(v), this.generator, this.modifiedBindings, this.modifiedProperties, this.createdObjects); } stopEffectCaptureAndUndoEffects(completion) { // Roll back the state changes this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); // Restore saved state if (completion.savedEffects !== undefined) { const savedEffects = completion.savedEffects; completion.savedEffects = undefined; this.generator = savedEffects.generator; this.modifiedBindings = savedEffects.modifiedBindings; this.modifiedProperties = savedEffects.modifiedProperties; this.createdObjects = savedEffects.createdObjects; } else { (0, _invariant.default)(false); } } // Apply the given effects to the global state applyEffects(effects, leadingComment = "", appendGenerator = true) { (0, _invariant.default)(effects.canBeApplied, "Effects have been applied and not properly reverted. It is not safe to apply them a second time."); effects.canBeApplied = false; let { generator, modifiedBindings, modifiedProperties, createdObjects } = effects; // Add generated code for property modifications if (appendGenerator) this.appendGenerator(generator, leadingComment); // Restore modifiedBindings this.restoreBindings(modifiedBindings); this.restoreProperties(modifiedProperties); // track modifiedBindings let realmModifiedBindings = this.modifiedBindings; if (realmModifiedBindings !== undefined) { modifiedBindings.forEach((val, key, m) => { (0, _invariant.default)(realmModifiedBindings !== undefined); if (!realmModifiedBindings.has(key)) { realmModifiedBindings.set(key, val); } }); } let realmModifiedProperties = this.modifiedProperties; if (realmModifiedProperties !== undefined) { modifiedProperties.forEach((desc, propertyBinding, m) => { (0, _invariant.default)(realmModifiedProperties !== undefined); if (!realmModifiedProperties.has(propertyBinding)) { realmModifiedProperties.set(propertyBinding, desc); } }); } // add created objects if (createdObjects.size > 0) { let realmCreatedObjects = this.createdObjects; if (realmCreatedObjects === undefined) this.createdObjects = new Set(createdObjects);else { createdObjects.forEach((ob, a) => { (0, _invariant.default)(realmCreatedObjects !== undefined); realmCreatedObjects.add(ob); }); } } } outputToConsole(method, args) { if (this.isReadOnly) { // This only happens during speculative execution and is reported elsewhere throw new _errors.FatalError("Trying to create console output in read-only realm"); } if (this.useAbstractInterpretation) { (0, _invariant.default)(this.generator !== undefined); this.generator.emitConsoleLog(method, args); } else { // $FlowFixMe: Flow doesn't have type data for all the console methods yet console[method](getString(this, args)); } function getString(realm, values) { let res = ""; while (values.length) { let next = values.shift(); let nextString = _singletons.To.ToString(realm, next); res += nextString; } return res; } } // Record the current value of binding in this.modifiedBindings unless // there is already an entry for binding. recordModifiedBinding(binding, value) { const isDefinedInsidePureFn = root => { let context = this.getRunningContext(); let { lexicalEnvironment: env } = context; while (env !== null) { if (env.environmentRecord === root) { // We can look at whether the lexical environment of the binding was destroyed to // determine if it was defined outside the current pure running context. return !env.destroyed; } env = env.parent; } return false; }; if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding) && value !== undefined && this.isInPureScope()) { let env = binding.environment; if (!(env instanceof _environment.DeclarativeEnvironmentRecord) || env instanceof _environment.DeclarativeEnvironmentRecord && !isDefinedInsidePureFn(env)) { for (let callback of this.reportSideEffectCallbacks) { callback("MODIFIED_BINDING", binding, value.expressionLocation); } } } if (binding.environment.isReadOnly) { // This only happens during speculative execution and is reported elsewhere throw new _errors.FatalError("Trying to modify a binding in read-only realm"); } if (this.modifiedBindings !== undefined && !this.modifiedBindings.has(binding)) { this.modifiedBindings.set(binding, { hasLeaked: binding.hasLeaked, value: binding.value }); } return binding; } callReportObjectGetOwnProperties(ob) { if (this.reportObjectGetOwnProperties !== undefined) { this.reportObjectGetOwnProperties(ob); } } callReportPropertyAccess(binding, isWrite) { if (this.reportPropertyAccess !== undefined) { this.reportPropertyAccess(binding, isWrite); } } // Record the current value of binding in this.modifiedProperties unless // there is already an entry for binding. recordModifiedProperty(binding) { if (binding === undefined) return; if (this.isInPureScope()) { let object = binding.object; (0, _invariant.default)(object instanceof _index.ObjectValue); const createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks; if (createdObjectsTrackedForLeaks !== undefined && !createdObjectsTrackedForLeaks.has(object) && ( // __markPropertyAsChecked__ is set by realm.markPropertyAsChecked typeof binding.key !== "string" || !binding.key.includes("__propertyHasBeenChecked__")) && binding.key !== "_temporalAlias") { if (binding.object === this.$GlobalObject) { for (let callback of this.reportSideEffectCallbacks) { callback("MODIFIED_GLOBAL", binding, object.expressionLocation); } } else { for (let callback of this.reportSideEffectCallbacks) { callback("MODIFIED_PROPERTY", binding, object.expressionLocation); } } } } if (this.isReadOnly && (this.getRunningContext().isReadOnly || !this.isNewObject(binding.object))) { // This only happens during speculative execution and is reported elsewhere throw new _errors.FatalError("Trying to modify a property in read-only realm"); } this.callReportPropertyAccess(binding, true); if (this.modifiedProperties !== undefined && !this.modifiedProperties.has(binding)) { let clone; let desc = binding.descriptor; if (desc === undefined) { clone = undefined; } else if (desc instanceof _descriptors.AbstractJoinedDescriptor) { clone = new _descriptors.AbstractJoinedDescriptor(desc.joinCondition, desc.descriptor1, desc.descriptor2); } else if (desc instanceof _descripto