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