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