UNPKG

prepack

Version:

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

1,168 lines (1,034 loc) 46.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Realm = exports.ExecutionContext = exports.Tracer = undefined; exports.construct_empty_effects = construct_empty_effects; var _errors = require("./errors.js"); var _index = require("./values/index.js"); var _environment = require("./environment.js"); var _index2 = require("./methods/index.js"); var _completions = require("./completions.js"); var _invariant = require("./invariant.js"); var _invariant2 = _interopRequireDefault(_invariant); var _seedrandom = require("seedrandom"); var _seedrandom2 = _interopRequireDefault(_seedrandom); var _generator = require("./utils/generator.js"); var _internalizer = require("./utils/internalizer.js"); var _singletons = require("./singletons.js"); 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 }; } /** * 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 Tracer { beginEvaluateForEffects(state) {} endEvaluateForEffects(state, effects) {} detourCall(F, thisArgument, argumentsList, newTarget, performCall) {} beforeCall(F, thisArgument, argumentsList, newTarget) {} afterCall(F, thisArgument, argumentsList, newTarget, result) {} } 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) { return [realm.intrinsics.empty, new _generator.Generator(realm, "construct_empty_effects"), new Map(), new Map(), new Set()]; } class Realm { constructor(opts) { this.contextStack = []; this.MOBILE_JSC_VERSION = "jsc-600-1-4-17"; this.objectCount = 0; this.symbolCount = 867501803871088; this.functionBodyUniqueTagSeed = 1; this.nextGeneratorId = 0; this.isReadOnly = false; this.useAbstractInterpretation = !!opts.serialize || !!opts.residual || !!opts.check; this.trackLeaks = !!opts.abstractEffectsInAdditionalFunctions; this.ignoreLeakLogic = false; this.isInPureTryStatement = false; if (opts.mathRandomSeed !== undefined) { this.mathRandomGenerator = (0, _seedrandom2.default)(opts.mathRandomSeed); } this.strictlyMonotonicDateNow = !!opts.strictlyMonotonicDateNow; this.timeout = opts.timeout; if (this.timeout) { // 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 || "browser"; this.maxStackDepth = opts.maxStackDepth || 225; this.omitInvariants = !!opts.omitInvariants; this.emitConcreteModel = !!opts.emitConcreteModel; this.$TemplateMap = []; if (this.useAbstractInterpretation) { this.preludeGenerator = new _generator.PreludeGenerator(opts.debugNames, opts.uniqueSuffix); this.pathConditions = []; _index.ObjectValue.setupTrackedPropertyAccessors(_index.ObjectValue.trackedPropertyNames); _index.ObjectValue.setupTrackedPropertyAccessors(_index.NativeFunctionValue.trackedPropertyNames); _index.ObjectValue.setupTrackedPropertyAccessors(_index.ProxyValue.trackedPropertyNames); } this.tracers = []; // These get initialized in construct_realm to avoid the dependency this.intrinsics = {}; this.$GlobalObject = {}; this.evaluators = Object.create(null); this.partialEvaluators = Object.create(null); this.$GlobalEnv = undefined; this.react = { abstractHints: new WeakMap(), classComponentMetadata: new Map(), currentOwner: undefined, enabled: opts.reactEnabled || false, output: opts.reactOutput || "create-element", hoistableFunctions: new WeakMap(), hoistableReactElements: new WeakMap(), reactElements: new WeakSet(), symbols: new Map(), verbose: opts.reactVerbose || false }; this.stripFlow = opts.stripFlow || false; this.fbLibraries = { other: new Map(), react: 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; } // TODO(1264): Remove this once we implement proper exception handling in abstract calls. // A list of abstract conditions that are known to be true in the current execution path. // For example, the abstract condition of an if statement is known to be true inside its true branch. // Unique tag for identifying function body ast node. It is neeeded // instead of ast node itself because we may perform ast tree deep clone // during serialization which changes the ast identity. // 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, _invariant2.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) { this.isReadOnly = readOnlyValue; this.$GlobalEnv.environmentRecord.isReadOnly = readOnlyValue; this.contextStack.forEach(ctx => { ctx.setReadOnly(readOnlyValue); }); } testTimeout() { let timeout = this.timeout; if (timeout && ! --this.timeoutCounter) { this.timeoutCounter = this.timeoutCounterThreshold; let total = Date.now() - this.start; if (total > timeout) { throw new _errors.FatalError("Timed out"); } } } hasRunningContext() { return this.contextStack.length !== 0; } getRunningContext() { let context = this.contextStack[this.contextStack.length - 1]; (0, _invariant2.default)(context, "There's no running execution context"); return context; } clearBlockBindings(modifiedBindings, environmentRecord) { if (modifiedBindings === undefined) return; for (let b of modifiedBindings.keys()) if (environmentRecord.bindings[b.name] && environmentRecord.bindings[b.name] === b) modifiedBindings.delete(b); } clearBlockBindingsFromCompletion(completion, environmentRecord) { if (completion instanceof _completions.PossiblyNormalCompletion) { this.clearBlockBindings(completion.alternateEffects[2], environmentRecord); this.clearBlockBindings(completion.consequentEffects[2], environmentRecord); if (completion.savedEffects !== undefined) this.clearBlockBindings(completion.savedEffects[2], environmentRecord); if (completion.alternate instanceof _completions.Completion) this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord); if (completion.consequent instanceof _completions.Completion) this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord); } else if (completion instanceof _completions.JoinedAbruptCompletions) { this.clearBlockBindings(completion.alternateEffects[2], environmentRecord); this.clearBlockBindings(completion.consequentEffects[2], environmentRecord); if (completion.alternate instanceof _completions.Completion) this.clearBlockBindingsFromCompletion(completion.alternate, environmentRecord); if (completion.consequent instanceof _completions.Completion) this.clearBlockBindingsFromCompletion(completion.consequent, environmentRecord); } } // 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, _invariant2.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); if (this.savedCompletion !== undefined) this.clearBlockBindingsFromCompletion(this.savedCompletion, environmentRecord); } } // Ensures if we call onDestroyScope too early, there will be a failure. this.activeLexicalEnvironments.delete(lexicalEnvironment); lexicalEnvironment.destroy(); } pushContext(context) { if (this.contextStack.length >= this.maxStackDepth) { throw new _errors.FatalError("Maximum stack depth exceeded"); } this.contextStack.push(context); } clearFunctionBindings(modifiedBindings, funcVal) { if (modifiedBindings === undefined) return; for (let b of modifiedBindings.keys()) { if (b.environment.$FunctionObject === funcVal) modifiedBindings.delete(b); } } clearFunctionBindingsFromCompletion(completion, funcVal) { if (completion instanceof _completions.PossiblyNormalCompletion) { this.clearFunctionBindings(completion.alternateEffects[2], funcVal); this.clearFunctionBindings(completion.consequentEffects[2], funcVal); if (completion.savedEffects !== undefined) this.clearFunctionBindings(completion.savedEffects[2], funcVal); if (completion.alternate instanceof _completions.Completion) this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal); if (completion.consequent instanceof _completions.Completion) this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal); } else if (completion instanceof _completions.JoinedAbruptCompletions) { this.clearFunctionBindings(completion.alternateEffects[2], funcVal); this.clearFunctionBindings(completion.consequentEffects[2], funcVal); if (completion.alternate instanceof _completions.Completion) this.clearFunctionBindingsFromCompletion(completion.alternate, funcVal); if (completion.consequent instanceof _completions.Completion) this.clearFunctionBindingsFromCompletion(completion.consequent, funcVal); } } popContext(context) { let funcVal = context.function; if (funcVal) { this.clearFunctionBindings(this.modifiedBindings, funcVal); if (this.savedCompletion !== undefined) this.clearFunctionBindingsFromCompletion(this.savedCompletion, funcVal); } let c = this.contextStack.pop(); (0, _invariant2.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); } // 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) { if (!this.trackLeaks) { return f(); } let saved_createdObjectsTrackedForLeaks = this.createdObjectsTrackedForLeaks; // 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(); try { return f(); } finally { this.createdObjectsTrackedForLeaks = saved_createdObjectsTrackedForLeaks; } } isInPureScope() { return !!this.createdObjectsTrackedForLeaks; } evaluateWithoutLeakLogic(f) { (0, _invariant2.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, _invariant2.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 PossiblyNormalCompletion. // 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) { return this.evaluateForEffects(() => env.evaluateCompletionDeref(ast, strictCode), state, generatorName || "evaluateNodeForEffects"); } evaluateForEffectsInGlobalEnv(func, state, generatorName) { return this.wrapInGlobalEnv(() => this.evaluateForEffects(func, state, generatorName || "evaluateForEffectsInGlobalEnv")); } // 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); result = func(effects); return this.intrinsics.undefined; } finally { this.restoreBindings(effects[2]); this.restoreProperties(effects[3]); } }); (0, _invariant2.default)(result !== undefined, "If we get here, func must have returned undefined."); return result; } evaluateNodeForEffectsInGlobalEnv(node, state, generatorName) { return this.wrapInGlobalEnv(() => this.evaluateNodeForEffects(node, false, this.$GlobalEnv, state, generatorName)); } partiallyEvaluateNodeForEffects(ast, strictCode, env) { let nodeAst, nodeIO; function partialEval() { let result; [result, nodeAst, nodeIO] = env.partiallyEvaluateCompletionDeref(ast, strictCode); return result; } let effects = this.evaluateForEffects(partialEval, undefined, "partiallyEvaluateNodeForEffects"); (0, _invariant2.default)(nodeAst !== undefined && nodeIO !== undefined); return [effects, nodeAst, nodeIO]; } evaluateForEffects(f, state, generatorName) { // Save old state and set up empty state for ast let [savedBindings, savedProperties] = this.getAndResetModifiedMaps(); let saved_generator = this.generator; let saved_createdObjects = this.createdObjects; let saved_completion = this.savedCompletion; this.generator = new _generator.Generator(this, generatorName); this.createdObjects = new Set(); this.savedCompletion = undefined; // while in this call, we only explore the normal path. 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); } catch (e) { if (e instanceof _completions.AbruptCompletion) c = e;else throw e; } // This is a join point for the normal branch of a PossiblyNormalCompletion. if (c instanceof _index.Value || c instanceof _completions.AbruptCompletion) c = _singletons.Functions.incorporateSavedCompletion(this, c); (0, _invariant2.default)(c !== undefined); if (c instanceof _completions.PossiblyNormalCompletion) { // The current state may have advanced since the time control forked into the various paths recorded in c. // Update the normal path and restore the global state to what it was at the time of the fork. let subsequentEffects = this.getCapturedEffects(c, c.value); (0, _invariant2.default)(subsequentEffects !== undefined); this.stopEffectCaptureAndUndoEffects(c); _singletons.Join.updatePossiblyNormalCompletionWithSubsequentEffects(this, c, subsequentEffects); this.savedCompletion = undefined; } (0, _invariant2.default)(this.generator !== undefined); (0, _invariant2.default)(this.modifiedBindings !== undefined); (0, _invariant2.default)(this.modifiedProperties !== undefined); (0, _invariant2.default)(this.createdObjects !== undefined); let astGenerator = this.generator; let astBindings = this.modifiedBindings; let astProperties = this.modifiedProperties; let astCreatedObjects = this.createdObjects; // 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 _environment.FunctionEnvironmentRecord) (0, _invariant2.default)(!astCreatedObjects.has(binding.environment.$FunctionObject)); // Return the captured state changes and evaluation result result = [c, astGenerator, astBindings, astProperties, astCreatedObjects]; return result; } finally { // Roll back the state changes if (this.savedCompletion !== undefined) this.stopEffectCaptureAndUndoEffects(this.savedCompletion); if (result !== undefined) { this.restoreBindings(result[2]); this.restoreProperties(result[3]); } else { this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); } this.generator = saved_generator; this.modifiedBindings = savedBindings; this.modifiedProperties = savedProperties; this.createdObjects = saved_createdObjects; this.savedCompletion = saved_completion; } } 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[0] instanceof _index.Value ? effects[0] : 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[0]; if (resultVal instanceof _completions.AbruptCompletion) throw resultVal; if (resultVal instanceof _completions.PossiblyNormalCompletion) { // in this case one of the branches may complete abruptly, which means that // not all control flow branches join into one flow at this point. // Consequently we have to continue tracking changes until the point where // all the branches come together into one. resultVal = this.composeWithSavedCompletion(resultVal); } (0, _invariant2.default)(resultVal instanceof _index.Value); return resultVal; } catch (e) { if (diagnostic !== undefined) return diagnostic; throw e; } finally { this.errorHandler = savedHandler; } } evaluateForFixpointEffects(loopContinueTest, loopBody) { try { let effects1 = this.evaluateForEffects(loopBody, undefined, "evaluateForFixpointEffects/1"); while (true) { this.restoreBindings(effects1[2]); this.restoreProperties(effects1[3]); let effects2 = this.evaluateForEffects(() => { let test = loopContinueTest(); if (!(test instanceof _index.AbstractValue)) throw new _errors.FatalError("loop terminates before fixed point"); return loopBody(); }, undefined, "evaluateForFixpointEffects/2"); this.restoreBindings(effects1[2]); this.restoreProperties(effects1[3]); 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. let [, gen, bindings2, pbindings2, createdObjects2] = effects2; this._applyPropertiesToNewlyCreatedObjects(pbindings2, createdObjects2); this._emitPropertAssignments(gen, pbindings2, createdObjects2); this._emitLocalAssignments(gen, bindings2, createdObjects2); return [effects1, effects2]; } effects1 = _singletons.Widen.widenEffects(this, effects1, effects2); } } catch (e) { return undefined; } } _applyPropertiesToNewlyCreatedObjects(modifiedProperties, newlyCreatedObjects) { if (modifiedProperties === undefined) return; modifiedProperties.forEach((desc, propertyBinding, m) => { if (propertyBinding.object instanceof _index.ObjectValue && 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, _invariant2.default)(val._buildNode !== undefined); let tval = gen.derive(val.types, val.values, [val], ([n]) => n, { skipInvariant: true }); 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, _invariant2.default)(tval !== undefined); gen.emitStatement([tval], ([v]) => { (0, _invariant2.default)(phiNode !== undefined); let id = phiNode.buildNode([]); return t.expressionStatement(t.assignmentExpression("=", id, v)); }); } if (val instanceof _index.ObjectValue && newlyCreatedObjects.has(val)) { let phiNode = key.phiNode; gen.emitStatement([val], ([v]) => { (0, _invariant2.default)(phiNode !== undefined); let id = phiNode.buildNode([]); return t.expressionStatement(t.assignmentExpression("=", id, v)); }); } }); } // populate the loop body generator with assignments that will update properties modified inside the loop _emitPropertAssignments(gen, pbindings, newlyCreatedObjects) { function isSelfReferential(value, pathNode) { if (value === pathNode) return true; if (value instanceof _index.AbstractValue && pathNode !== undefined) { for (let v of value.args) { if (isSelfReferential(v, pathNode)) return true; } } return false; } let tvalFor = new Map(); pbindings.forEach((val, key, map) => { if (key.object instanceof _index.ObjectValue && newlyCreatedObjects.has(key.object)) { return; } let value = val && val.value; if (value instanceof _index.AbstractValue) { (0, _invariant2.default)(value._buildNode !== undefined); let tval = gen.derive(value.types, value.values, [key.object, value], ([o, n]) => { (0, _invariant2.default)(value instanceof _index.Value); if (typeof key.key === "string" && value.mightHaveBeenDeleted() && isSelfReferential(value, key.pathNode)) { let inTest = t.binaryExpression("in", t.stringLiteral(key.key), o); let addEmpty = t.conditionalExpression(inTest, n, _internalizer.emptyExpression); n = t.logicalExpression("||", n, addEmpty); } return n; }, { skipInvariant: true }); tvalFor.set(key, tval); } }); pbindings.forEach((val, key, map) => { if (key.object instanceof _index.ObjectValue && newlyCreatedObjects.has(key.object)) { return; } let path = key.pathNode; let tval = tvalFor.get(key); (0, _invariant2.default)(val !== undefined); let value = val.value; (0, _invariant2.default)(value instanceof _index.Value); let mightHaveBeenDeleted = value.mightHaveBeenDeleted(); let mightBeUndefined = value.mightBeUndefined(); if (typeof key.key === "string") { gen.emitStatement([key.object, tval || value, this.intrinsics.empty], ([o, v, e]) => { (0, _invariant2.default)(path !== undefined); let lh = path.buildNode([o, t.identifier(key.key)]); let r = t.expressionStatement(t.assignmentExpression("=", lh, v)); if (mightHaveBeenDeleted) { // If v === __empty || (v === undefined && !(key.key in o)) then delete it let emptyTest = t.binaryExpression("===", v, e); let undefinedTest = t.binaryExpression("===", v, _internalizer.voidExpression); let inTest = t.unaryExpression("!", t.binaryExpression("in", t.stringLiteral(key.key), o)); let guard = t.logicalExpression("||", emptyTest, t.logicalExpression("&&", undefinedTest, inTest)); let deleteIt = t.expressionStatement(t.unaryExpression("delete", lh)); return t.ifStatement(mightBeUndefined ? emptyTest : guard, deleteIt, r); } return r; }); } else { gen.emitStatement([key.object, key.key, tval || value, this.intrinsics.empty], ([o, p, v, e]) => { (0, _invariant2.default)(path !== undefined); let lh = path.buildNode([o, p]); return t.expressionStatement(t.assignmentExpression("=", lh, v)); }); } }); } composeEffects(priorEffects, subsequentEffects) { let [, pg, pb, pp, po] = priorEffects; let [sc, sg, sb, sp, so] = subsequentEffects; let result = construct_empty_effects(this); let [,, rb, rp, ro] = result; result[0] = sc; result[1] = _singletons.Join.composeGenerators(this, pg || result[1], sg); if (pb) { pb.forEach((val, key, m) => rb.set(key, val)); } sb.forEach((val, key, m) => rb.set(key, val)); if (pp) { pp.forEach((desc, propertyBinding, m) => rp.set(propertyBinding, desc)); } sp.forEach((val, key, m) => rp.set(key, val)); if (po) { po.forEach((ob, a) => ro.add(ob)); } so.forEach((ob, a) => ro.add(ob)); return result; } updateAbruptCompletions(priorEffects, c) { if (c.consequent instanceof _completions.AbruptCompletion) { c.consequentEffects = this.composeEffects(priorEffects, c.consequentEffects); let alternate = c.alternate; if (alternate instanceof _completions.PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, alternate); } else { (0, _invariant2.default)(c.alternate instanceof _completions.AbruptCompletion); c.alternateEffects = this.composeEffects(priorEffects, c.alternateEffects); let consequent = c.consequent; if (consequent instanceof _completions.PossiblyNormalCompletion) this.updateAbruptCompletions(priorEffects, consequent); } } composeWithSavedCompletion(completion) { if (this.savedCompletion === undefined) { this.savedCompletion = completion; this.savedCompletion.savedPathConditions = this.pathConditions; this.pathConditions = [].concat(this.pathConditions); this.captureEffects(completion); } else { this.savedCompletion = _singletons.Join.composePossiblyNormalCompletions(this, this.savedCompletion, completion); } pushPathConditionsLeadingToNormalCompletion(completion); return completion.value; function pushPathConditionsLeadingToNormalCompletion(c) { if (c.consequent instanceof _completions.AbruptCompletion) { _singletons.Path.pushInverseAndRefine(c.joinCondition); if (c.alternate instanceof _completions.PossiblyNormalCompletion) pushPathConditionsLeadingToNormalCompletion(c.alternate); } else if (c.alternate instanceof _completions.AbruptCompletion) { _singletons.Path.pushAndRefine(c.joinCondition); if (c.consequent instanceof _completions.PossiblyNormalCompletion) pushPathConditionsLeadingToNormalCompletion(c.consequent); } } } incorporatePriorSavedCompletion(priorCompletion) { if (priorCompletion === undefined) return; if (this.savedCompletion === undefined) { this.savedCompletion = priorCompletion; this.captureEffects(priorCompletion); } else { (0, _invariant2.default)(priorCompletion.savedEffects !== undefined); let savedEffects = this.savedCompletion.savedEffects; (0, _invariant2.default)(savedEffects !== undefined); this.restoreBindings(savedEffects[2]); this.restoreProperties(savedEffects[3]); _singletons.Join.updatePossiblyNormalCompletionWithSubsequentEffects(this, priorCompletion, savedEffects); this.restoreBindings(savedEffects[2]); this.restoreProperties(savedEffects[3]); (0, _invariant2.default)(this.savedCompletion !== undefined); this.savedCompletion.savedEffects = undefined; this.savedCompletion = _singletons.Join.composePossiblyNormalCompletions(this, priorCompletion, this.savedCompletion); } } captureEffects(completion) { if (completion.savedEffects !== undefined) { // Already called captureEffects, just carry on return; } completion.savedEffects = [this.intrinsics.undefined, this.generator, this.modifiedBindings, this.modifiedProperties, this.createdObjects]; this.generator = new _generator.Generator(this, "captured"); this.modifiedBindings = new Map(); this.modifiedProperties = new Map(); this.createdObjects = new Set(); } getCapturedEffects(completion, v) { if (completion.savedEffects === undefined) return undefined; if (v === undefined) v = this.intrinsics.undefined; (0, _invariant2.default)(this.generator !== undefined); (0, _invariant2.default)(this.modifiedBindings !== undefined); (0, _invariant2.default)(this.modifiedProperties !== undefined); (0, _invariant2.default)(this.createdObjects !== undefined); return [v, this.generator, this.modifiedBindings, this.modifiedProperties, this.createdObjects]; } stopEffectCapture(completion) { let e = this.getCapturedEffects(completion); if (e !== undefined) { this.stopEffectCaptureAndUndoEffects(completion); this.applyEffects(e); } } stopEffectCaptureAndUndoEffects(completion) { // Roll back the state changes this.restoreBindings(this.modifiedBindings); this.restoreProperties(this.modifiedProperties); // Restore saved state if (completion.savedEffects !== undefined) { let [c, g, b, p, o] = completion.savedEffects; c; completion.savedEffects = undefined; this.generator = g; this.modifiedBindings = b; this.modifiedProperties = p; this.createdObjects = o; } else { (0, _invariant2.default)(false); } } // Apply the given effects to the global state applyEffects(effects, leadingComment = "") { let [, generator, bindings, properties, createdObjects] = effects; // Add generated code for property modifications this.appendGenerator(generator, leadingComment); // Restore bindings this.restoreBindings(bindings); this.restoreProperties(properties); // track bindings let realmModifiedBindings = this.modifiedBindings; if (realmModifiedBindings !== undefined) { bindings.forEach((val, key, m) => { (0, _invariant2.default)(realmModifiedBindings !== undefined); if (!realmModifiedBindings.has(key)) { realmModifiedBindings.set(key, val); } }); } let realmModifiedProperties = this.modifiedProperties; if (realmModifiedProperties !== undefined) { properties.forEach((desc, propertyBinding, m) => { (0, _invariant2.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, _invariant2.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, _invariant2.default)(this.generator !== undefined); this.generator.emitConsoleLog(method, args); } else { 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) { 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) { if (this.reportPropertyAccess !== undefined) { this.reportPropertyAccess(binding); } } // 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.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); if (this.modifiedProperties !== undefined && !this.modifiedProperties.has(binding)) { this.modifiedProperties.set(binding, (0, _index2.cloneDescriptor)(binding.descriptor)); } } isNewObject(object) { if (object instanceof _index.AbstractObjectValue) return false; return this.createdObjects === undefined || this.createdObjects.has(object); } recordNewObject(object) { if (this.createdObjects !== undefined) { this.createdObjects.add(object); } if (this.createdObjectsTrackedForLeaks !== undefined) { this.createdObjectsTrackedForLeaks.add(object); } } // Returns the current values of modifiedBindings and modifiedProperties // and then assigns new empty maps to them. getAndResetModifiedMaps() { let result = [this.modifiedBindings, this.modifiedProperties]; this.modifiedBindings = new Map(); this.modifiedProperties = new Map(); return result; } // Restores each Binding in the given map to the value it // had when it was entered into the map and updates the map to record // the value the Binding had just before the call to this method. restoreBindings(modifiedBindings) { if (modifiedBindings === undefined) return; modifiedBindings.forEach(({ hasLeaked, value }, binding, m) => { let l = binding.hasLeaked; let v = binding.value; binding.hasLeaked = hasLeaked; binding.value = value; m.set(binding, { hasLeaked: l, value: v }); }); } // Restores each PropertyBinding in the given map to the value it // had when it was entered into the map and updates the map to record // the value the Binding had just before the call to this method. restoreProperties(modifiedProperties) { if (modifiedProperties === undefined) return; modifiedProperties.forEach((desc, propertyBinding, m) => { let d = propertyBinding.descriptor; propertyBinding.descriptor = desc; m.set(propertyBinding, d); }); } // Provide the realm with maps in which to track modifications. // A map can be set to undefined if no tracking is required. setModifiedMaps(modifiedBindings, modifiedProperties) { this.modifiedBindings = modifiedBindings; this.modifiedProperties = modifiedProperties; } rebuildObjectProperty(object, key, propertyValue, path) { if (!(propertyValue instanceof _index.AbstractValue)) return; if (propertyValue.kind === "abstractConcreteUnion") { let absVal = propertyValue.args.find(e => e instanceof _index.AbstractValue); (0, _invariant2.default)(absVal instanceof _index.AbstractValue); propertyValue = absVal; } if (!propertyValue.isIntrinsic()) { propertyValue.intrinsicName = `${path}.${key}`; propertyValue.kind = "rebuiltProperty"; propertyValue.args = [object]; propertyValue._buildNode = ([node]) => t.memberExpression(node, t.identifier(key)); this.rebuildNestedProperties(propertyValue, propertyValue.intrinsicName); } } rebuildNestedProperties(abstractValue, path) { if (!(abstractValue instanceof _index.AbstractObjectValue)) return; if (abstractValue.values.isTop()) return; let template = abstractValue.getTemplate(); (0, _invariant2.default)(!template.intrinsicName || template.intrinsicName === path); template.intrinsicName = path; template.intrinsicNameGenerated = true; for (let [key, binding] of template.properties) { if (binding === undefined || binding.descriptor === undefined) continue; // deleted (0, _invariant2.default)(binding.descriptor !== undefined); let value = binding.descriptor.value; _singletons.Properties.ThrowIfMightHaveBeenDeleted(value); if (value === undefined) { _index.AbstractValue.reportIntrospectionError(abstractValue, key); throw new _errors.FatalError(); } (0, _invariant2.default)(value instanceof _index.Value); this.rebuildObjectProperty(abstractValue, key, value, path); } } createExecutionContext() { let context = new ExecutionContext(); let loc = this.nextContextLocation; if (loc) { context.setLocation(loc); this.nextContextLocation = null; } return context; } setNextExecutionContextLocation(loc) { if (!loc) return; //if (this.nextContextLocation) { // throw new ThrowCompletion( // Construct(this, this.intrinsics.TypeError, [new StringValue(this, "Already have a context location that we haven't used yet")]) // ); //} else { this.nextContextLocation = loc; //} } reportIntrospectionError(message) { if (message === undefined) message = ""; if (typeof message === "string") message = new _index.StringValue(this, message); (0, _invariant2.default)(message instanceof _index.StringValue); this.nextContextLocation = this.currentLocation; let error = new _errors.CompilerDiagnostic(message.value, this.currentLocation, "PP0001", "FatalError"); this.handleError(error); } createErrorThrowCompletion(type, message) { (0, _invariant2.default)(type !== this.intrinsics.__IntrospectionError); if (message === undefined) message = ""; if (typeof message === "string") message = new _index.StringValue(this, message); (0, _invariant2.default)(message instanceof _index.StringValue); this.nextContextLocation = this.currentLocation; return new _completions.ThrowCompletion((0, _index2.Construct)(this, type, [message]), this.currentLocation); } appendGenerator(generator, leadingComment = "") { let realmGenerator = this.generator; if (realmGenerator === undefined) { (0, _invariant2.default)(generator.empty()); return; } realmGenerator.appendGenerator(generator, leadingComment); } // Pass the error to the realm's error-handler // Return value indicates whether the caller should try to recover from the error or not. handleError(diagnostic) { if (!diagnostic.callStack && this.contextStack.length > 0) { let error = (0, _index2.Construct)(this, this.intrinsics.Error); let stack = error.$Get("stack", error); if (stack instanceof _index.StringValue) diagnostic.callStack = stack.value; } // Default behaviour is to bail on the first error let errorHandler = this.errorHandler; if (!errorHandler) { let msg = `${diagnostic.errorCode}: ${diagnostic.message}`; if (diagnostic.location) { let loc_start = diagnostic.location.start; let loc_end = diagnostic.location.end; msg += ` at ${loc_start.line}:${loc_start.column} to ${loc_end.line}:${loc_end.column}`; } try { switch (diagnostic.severity) { case "Information": console.log(`Info: ${msg}`); return "Recover"; case "Warning": console.warn(`Warn: ${msg}`); return "Recover"; case "RecoverableError": console.error(`Error: ${msg}`); return "Fail"; case "FatalError": console.error(`Fatal Error: ${msg}`); return "Fail"; default: (0, _invariant2.default)(false, "Unexpected error type"); } } finally { console.log(diagnostic.callStack); } } return errorHandler(diagnostic); } saveNameString(nameString) { this._abstractValuesDefined.add(nameString); } isNameStringUnique(nameString) { return !this._abstractValuesDefined.has(nameString); } } exports.Realm = Realm; //# sourceMappingURL=realm.js.map