"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.ignoreLeakLogic = false;
    this.isInPureTryStatement = false;
    if (opts.mathRandomSeed !== undefined) {
      this.mathRandomGenerator = (0, _seedrandom2.default)(opts.mathRandomSeed);
    }
    this.strictlyMonotonicDateNow = !!opts.strictlyMonotonicDateNow;

    // 0 = disabled
    this.abstractValueImpliesMax = opts.abstractValueImpliesMax || 0;
    this.abstractValueImpliesCounter = 0;
    this.inSimplificationPath = false;

    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.alreadyDescribedLocations = new WeakMap();
    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 instanceof _environment.FunctionEnvironmentRecord && 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) {
    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, "", false);
        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;

        /* 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
        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 {
      (0, _invariant2.default)(this.savedCompletion.savedEffects !== undefined);
      (0, _invariant2.default)(this.generator !== undefined);
      this.savedCompletion.savedEffects[1].appendGenerator(this.generator, "composeWithSavedCompletion");
      this.generator = new _generator.Generator(this, "composeWithSavedCompletion");
      (0, _invariant2.default)(this.savedCompletion !== undefined);
      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 = "", appendGenerator = true) {
    let [, generator, bindings, properties, createdObjects] = effects;

    // Add generated code for property modifications
    if (appendGenerator) 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