"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ResidualHeapVisitor = undefined;

var _environment = require("../environment.js");

var _realm = require("../realm.js");

var _index = require("../methods/index.js");

var _index2 = require("../values/index.js");

var _Error = require("../intrinsics/ecma262/Error.js");

var _babelTypes = require("babel-types");

var t = _interopRequireWildcard(_babelTypes);

var _generator = require("../utils/generator.js");

var _babelTraverse = require("babel-traverse");

var _babelTraverse2 = _interopRequireDefault(_babelTraverse);

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

var _visitors = require("./visitors.js");

var _logger = require("../utils/logger.js");

var _modules = require("../utils/modules.js");

var _ResidualHeapInspector = require("./ResidualHeapInspector.js");

var _Referentializer = require("./Referentializer.js");

var _utils = require("./utils.js");

var _singletons = require("../singletons.js");

var _utils2 = require("../react/utils.js");

var _hoisting = require("../react/hoisting.js");

var _ReactElementSet = require("../react/ReactElementSet.js");

var _ReactElementSet2 = _interopRequireDefault(_ReactElementSet);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

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; } }

/* This class visits all values that are reachable in the residual heap.
   In particular, this "filters out" values that are:
   - captured by a DeclarativeEnvironmentRecord, but not actually used by any closure.
   - Unmodified prototype objects
   TODO #680: Figure out minimal set of values that need to be kept alive for WeakSet and WeakMap instances.
*/
class ResidualHeapVisitor {
  constructor(realm, logger, modules, additionalFunctionValuesAndEffects,
  // Referentializer is null if we're just checking what values exist
  referentializer) {
    (0, _invariant2.default)(realm.useAbstractInterpretation);
    this.realm = realm;
    this.logger = logger;
    this.modules = modules;
    this.referentializer = referentializer === "NO_REFERENTIALIZE" ? undefined : referentializer;

    this.declarativeEnvironmentRecordsBindings = new Map();
    this.globalBindings = new Map();
    this.functionInfos = new Map();
    this.classMethodInstances = new Map();
    this.functionInstances = new Map();
    this.values = new Map();
    let generator = this.realm.generator;
    (0, _invariant2.default)(generator);
    this.scope = this.globalGenerator = generator;
    this.inspector = new _ResidualHeapInspector.ResidualHeapInspector(realm, logger);
    this.referencedDeclaredValues = new Map();
    this.delayedVisitGeneratorEntries = [];
    this.someReactElement = undefined;
    this.additionalFunctionValuesAndEffects = additionalFunctionValuesAndEffects;
    this.equivalenceSet = new _index.HashSet();
    this.reactElementEquivalenceSet = new _ReactElementSet2.default(realm, this.equivalenceSet);
    this.additionalFunctionValueInfos = new Map();
    this.functionToCapturedScopes = new Map();
    this.generatorParents = new Map();
    let environment = realm.$GlobalEnv.environmentRecord;
    (0, _invariant2.default)(environment instanceof _environment.GlobalEnvironmentRecord);
    this.globalEnvironmentRecord = environment;
    this.createdObjects = new Map();
  }

  // Caches that ensure one ResidualFunctionBinding exists per (record, name) pair


  // Going backwards from the current scope, find either the containing
  // additional function, or if there isn't one, return the global generator.
  _getCommonScope() {
    let s = this.scope;
    while (true) {
      if (s instanceof _generator.Generator) s = this.generatorParents.get(s);else if (s instanceof _index2.FunctionValue) {
        // Did we find an additional function?
        if (this.additionalFunctionValuesAndEffects.has(s)) return s;

        // Did the function itself get created by a generator we can chase?
        s = this.createdObjects.get(s) || "GLOBAL";
      } else {
        (0, _invariant2.default)(s === "GLOBAL");
        let generator = this.globalGenerator;
        (0, _invariant2.default)(generator);
        return generator;
      }
    }
    (0, _invariant2.default)(false);
  }

  // If the current scope has a containing additional function, retrieve it.
  _getAdditionalFunctionOfScope() {
    let s = this._getCommonScope();
    return s instanceof _index2.FunctionValue ? s : undefined;
  }

  // When a value has been created by some generator that is unrelated
  // to the current common scope, visit the value in the scope it was
  // created --- this causes the value later to be serialized in its
  // creation scope, ensuring that the value has the right creation / life time.
  _registerAdditionalRoot(value) {
    let generator = this.createdObjects.get(value);
    if (generator !== undefined) {
      let s = generator;
      while (s instanceof _generator.Generator) {
        s = this.generatorParents.get(s);
      }
      (0, _invariant2.default)(s === "GLOBAL" || s instanceof _index2.FunctionValue);
      let additionalFunction = this._getAdditionalFunctionOfScope();
      if (additionalFunction === s) return;
    } else {
      let additionalFunction = this._getAdditionalFunctionOfScope();
      if (additionalFunction === undefined) return;
      generator = this.globalGenerator;
    }

    this._visitInUnrelatedScope(generator, value);
  }

  // Careful!
  // Only use _withScope when you know that the currently applied effects makes sense for the given (nested) scope!
  _withScope(scope, f) {
    let oldScope = this.scope;
    this.scope = scope;
    try {
      f();
    } finally {
      this.scope = oldScope;
    }
  }

  // Queues up an action to be later processed in some arbitrary scope.
  _withUnrelatedScope(scope, action) {
    let generator;
    if (scope instanceof _index2.FunctionValue) generator = this.createdObjects.get(scope) || this.globalGenerator;else if (scope === "GLOBAL") generator = this.globalGenerator;else {
      (0, _invariant2.default)(scope instanceof _generator.Generator);
      generator = scope;
    }
    this.delayedVisitGeneratorEntries.push({ generator, action });
  }

  // Queues up visiting a value in some arbitrary scope.
  _visitInUnrelatedScope(scope, val) {
    let scopes = this.values.get(val);
    if (scopes !== undefined && scopes.has(scope)) return;
    this._withUnrelatedScope(scope, () => this.visitValue(val));
  }

  visitObjectProperty(binding) {
    let desc = binding.descriptor;
    if (desc === undefined) return; //deleted
    let obj = binding.object;
    if (obj instanceof _index2.AbstractObjectValue || !this.inspector.canIgnoreProperty(obj, binding.key)) {
      this.visitDescriptor(desc);
    }
  }

  visitObjectProperties(obj, kind) {
    let { skipPrototype, constructor } = (0, _utils.getObjectPrototypeMetadata)(this.realm, obj);
    if (obj.temporalAlias !== undefined) return;

    // visit properties
    if (!(0, _utils2.isReactElement)(obj)) {
      for (let [symbol, propertyBinding] of obj.symbols) {
        (0, _invariant2.default)(propertyBinding);
        let desc = propertyBinding.descriptor;
        if (desc === undefined) continue; //deleted
        this.visitDescriptor(desc);
        this.visitValue(symbol);
      }
    }

    // visit properties
    for (let [propertyBindingKey, propertyBindingValue] of obj.properties) {
      // we don't want to visit these as we handle the serialization ourselves
      // via a different logic route for classes
      let descriptor = propertyBindingValue.descriptor;
      if (obj instanceof _index2.ECMAScriptFunctionValue && obj.$FunctionKind === "classConstructor" && (_utils.ClassPropertiesToIgnore.has(propertyBindingKey) || propertyBindingKey === "length" && (0, _utils.canIgnoreClassLengthProperty)(obj, descriptor, this.logger))) {
        continue;
      }
      if (propertyBindingValue.pathNode !== undefined) continue; // property is written to inside a loop
      (0, _invariant2.default)(propertyBindingValue);
      this.visitObjectProperty(propertyBindingValue);
    }

    // inject properties with computed names
    if (obj.unknownProperty !== undefined) {
      let desc = obj.unknownProperty.descriptor;
      if (desc !== undefined) {
        let val = desc.value;
        (0, _invariant2.default)(val instanceof _index2.AbstractValue);
        this.visitObjectPropertiesWithComputedNames(val);
      }
    }

    // prototype
    if (!(0, _utils2.isReactElement)(obj) && !skipPrototype) {
      // we don't want to the ReactElement prototype visited
      // as this is contained within the JSXElement, otherwise
      // they we be need to be emitted during serialization
      this.visitObjectPrototype(obj);
    }
    if (obj instanceof _index2.FunctionValue) {
      this.visitConstructorPrototype(constructor ? constructor : obj);
    } else if (obj instanceof _index2.ObjectValue && skipPrototype && constructor) {
      this.visitValue(constructor);
    }
  }

  visitObjectPrototype(obj) {
    let proto = obj.$Prototype;

    let kind = obj.getKind();
    if (proto === this.realm.intrinsics[kind + "Prototype"]) return;

    if (!obj.$IsClassPrototype || proto !== this.realm.intrinsics.null) {
      this.visitValue(proto);
    }
  }

  visitConstructorPrototype(func) {
    // If the original prototype object was mutated,
    // request its serialization here as this might be observable by
    // residual code.
    (0, _invariant2.default)(func instanceof _index2.FunctionValue);
    let prototype = _ResidualHeapInspector.ResidualHeapInspector.getPropertyValue(func, "prototype");
    if (prototype instanceof _index2.ObjectValue && prototype.originalConstructor === func && !this.inspector.isDefaultPrototype(prototype)) {
      this.visitValue(prototype);
    }
  }

  visitObjectPropertiesWithComputedNames(absVal) {
    if (absVal.kind === "widened property") return;
    (0, _invariant2.default)(absVal.args.length === 3);
    let cond = absVal.args[0];
    (0, _invariant2.default)(cond instanceof _index2.AbstractValue);
    if (cond.kind === "template for property name condition") {
      let P = cond.args[0];
      (0, _invariant2.default)(P instanceof _index2.AbstractValue);
      let V = absVal.args[1];
      let earlier_props = absVal.args[2];
      if (earlier_props instanceof _index2.AbstractValue) this.visitObjectPropertiesWithComputedNames(earlier_props);
      this.visitValue(P);
      this.visitValue(V);
    } else {
      // conditional assignment
      absVal.args[0] = this.visitEquivalentValue(cond);
      let consequent = absVal.args[1];
      (0, _invariant2.default)(consequent instanceof _index2.AbstractValue);
      let alternate = absVal.args[2];
      (0, _invariant2.default)(alternate instanceof _index2.AbstractValue);
      this.visitObjectPropertiesWithComputedNames(consequent);
      this.visitObjectPropertiesWithComputedNames(alternate);
    }
  }

  visitDescriptor(desc) {
    (0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index2.Value);
    if (desc.joinCondition !== undefined) {
      desc.joinCondition = this.visitEquivalentValue(desc.joinCondition);
      if (desc.descriptor1 !== undefined) this.visitDescriptor(desc.descriptor1);
      if (desc.descriptor2 !== undefined) this.visitDescriptor(desc.descriptor2);
      return;
    }
    if (desc.value !== undefined) desc.value = this.visitEquivalentValue(desc.value);
    if (desc.get !== undefined) this.visitValue(desc.get);
    if (desc.set !== undefined) this.visitValue(desc.set);
  }

  visitValueArray(val) {
    this._registerAdditionalRoot(val);

    this.visitObjectProperties(val);
    const realm = this.realm;
    let lenProperty;
    if (val.isHavocedObject()) {
      lenProperty = this.realm.evaluateWithoutLeakLogic(() => (0, _index.Get)(realm, val, "length"));
    } else {
      lenProperty = (0, _index.Get)(realm, val, "length");
    }
    if (lenProperty instanceof _index2.AbstractValue ? lenProperty.kind !== "widened property" : _singletons.To.ToLength(realm, lenProperty) !== (0, _utils.getSuggestedArrayLiteralLength)(realm, val)) {
      this.visitValue(lenProperty);
    }
  }

  visitValueMap(val) {
    let kind = val.getKind();

    let entries;
    if (kind === "Map") {
      entries = val.$MapData;
    } else {
      (0, _invariant2.default)(kind === "WeakMap");
      entries = val.$WeakMapData;
    }
    (0, _invariant2.default)(entries !== undefined);
    let len = entries.length;

    for (let i = 0; i < len; i++) {
      let entry = entries[i];
      let key = entry.$Key;
      let value = entry.$Value;
      if (key === undefined || value === undefined) continue;
      this.visitValue(key);
      this.visitValue(value);
    }
  }

  visitValueSet(val) {
    let kind = val.getKind();

    let entries;
    if (kind === "Set") {
      entries = val.$SetData;
    } else {
      (0, _invariant2.default)(kind === "WeakSet");
      entries = val.$WeakSetData;
    }
    (0, _invariant2.default)(entries !== undefined);
    let len = entries.length;

    for (let i = 0; i < len; i++) {
      let entry = entries[i];
      if (entry === undefined) continue;
      this.visitValue(entry);
    }
  }

  visitValueFunction(val, parentScope) {
    let isClass = false;

    this._registerAdditionalRoot(val);
    if (val instanceof _index2.ECMAScriptFunctionValue && val.$FunctionKind === "classConstructor") {
      (0, _invariant2.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
      let homeObject = val.$HomeObject;
      if (homeObject instanceof _index2.ObjectValue && homeObject.$IsClassPrototype) {
        isClass = true;
      }
    }
    this.visitObjectProperties(val);

    if (val instanceof _index2.BoundFunctionValue) {
      this.visitValue(val.$BoundTargetFunction);
      this.visitValue(val.$BoundThis);
      for (let boundArg of val.$BoundArguments) this.visitValue(boundArg);
      return;
    }

    (0, _invariant2.default)(!(val instanceof _index2.NativeFunctionValue), "all native function values should be intrinsics");

    (0, _invariant2.default)(val instanceof _index2.ECMAScriptSourceFunctionValue);
    (0, _invariant2.default)(val.constructor === _index2.ECMAScriptSourceFunctionValue);
    let formalParameters = val.$FormalParameters;
    let code = val.$ECMAScriptCode;

    let functionInfo = this.functionInfos.get(code);
    let residualFunctionBindings = new Map();
    this.functionInstances.set(val, {
      residualFunctionBindings,
      initializationStatements: [],
      functionValue: val,
      scopeInstances: new Map()
    });

    if (!functionInfo) {
      functionInfo = {
        depth: 0,
        unbound: new Set(),
        modified: new Set(),
        usesArguments: false,
        usesThis: false
      };
      let state = {
        tryQuery: this.logger.tryQuery.bind(this.logger),
        val,
        functionInfo,
        realm: this.realm
      };

      (0, _babelTraverse2.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), _visitors.ClosureRefVisitor, null, state);
      this.functionInfos.set(code, functionInfo);

      if (val.isResidual && functionInfo.unbound.size) {
        if (!val.isUnsafeResidual) {
          this.logger.logError(val, `residual function ${(0, _Error.describeLocation)(this.realm, val, undefined, code.loc) || "(unknown)"} refers to the following identifiers defined outside of the local scope: ${Object.keys(functionInfo.unbound).join(", ")}`);
        }
      }
    }

    let additionalFunctionEffects = this.additionalFunctionValuesAndEffects.get(val);
    if (additionalFunctionEffects) {
      this._visitAdditionalFunction(val, additionalFunctionEffects, parentScope);
    } else {
      this._withScope(val, () => {
        (0, _invariant2.default)(functionInfo);
        for (let innerName of functionInfo.unbound) {
          let environment = this.resolveBinding(val, innerName);
          let residualBinding = this.getBinding(val, environment, innerName);
          this.visitBinding(val, residualBinding);
          (0, _invariant2.default)(residualBinding !== undefined);
          residualFunctionBindings.set(innerName, residualBinding);
          if (functionInfo.modified.has(innerName)) {
            residualBinding.modified = true;
          }
        }
      });
    }
    if (isClass && val.$HomeObject instanceof _index2.ObjectValue) {
      this._visitClass(val, val.$HomeObject);
    }
  }

  // Addresses the case:
  // let x = [];
  // let y = [];
  // function a() { x.push("hi"); }
  // function b() { y.push("bye"); }
  // function c() { return x.length + y.length; }
  // Here we need to make sure that a and b both initialize x and y because x and y will be in the same
  // captured scope because c captures both x and y.
  _recordBindingVisitedAndRevisit(val, residualFunctionBinding) {
    let refScope = this._getAdditionalFunctionOfScope() || "GLOBAL";
    (0, _invariant2.default)(!(refScope instanceof _generator.Generator));
    let funcToScopes = (0, _utils.getOrDefault)(this.functionToCapturedScopes, refScope, () => new Map());
    let envRec = residualFunctionBinding.declarativeEnvironmentRecord;
    (0, _invariant2.default)(envRec !== null);
    let bindingState = (0, _utils.getOrDefault)(funcToScopes, envRec, () => ({
      capturedBindings: new Set(),
      capturingFunctions: new Set()
    }));
    // If the binding is new for this bindingState, have all functions capturing bindings from that scope visit it
    if (!bindingState.capturedBindings.has(residualFunctionBinding)) {
      let value = residualFunctionBinding.value;
      if (value) {
        for (let functionValue of bindingState.capturingFunctions) {
          this._visitInUnrelatedScope(functionValue, value);
        }
      }
      bindingState.capturedBindings.add(residualFunctionBinding);
    }
    // If the function is new for this bindingState, visit all existent bindings in this scope
    if (!bindingState.capturingFunctions.has(val)) {
      for (let residualBinding of bindingState.capturedBindings) {
        if (residualBinding.value) this.visitValue(residualBinding.value);
      }
      bindingState.capturingFunctions.add(val);
    }
  }

  resolveBinding(val, name) {
    let doesNotMatter = true;
    let reference = this.logger.tryQuery(() => _singletons.Environment.ResolveBinding(this.realm, name, doesNotMatter, val.$Environment), undefined);
    if (reference === undefined || _singletons.Environment.IsUnresolvableReference(this.realm, reference) || reference.base === this.globalEnvironmentRecord || reference.base === this.globalEnvironmentRecord.$DeclarativeRecord) {
      return this.globalEnvironmentRecord;
    } else {
      (0, _invariant2.default)(!_singletons.Environment.IsUnresolvableReference(this.realm, reference));
      let referencedBase = reference.base;
      let referencedName = reference.referencedName;
      (0, _invariant2.default)(referencedName === name);
      (0, _invariant2.default)(referencedBase instanceof _environment.DeclarativeEnvironmentRecord);
      return referencedBase;
    }
  }

  // Visits a binding, returns a ResidualFunctionBinding
  getBinding(val, environment, name) {
    if (environment === this.globalEnvironmentRecord.$DeclarativeRecord) environment = this.globalEnvironmentRecord;

    if (environment === this.globalEnvironmentRecord) {
      // Global Binding
      return (0, _utils.getOrDefault)(this.globalBindings, name, () => ({
        value: this.realm.getGlobalLetBinding(name),
        modified: true,
        declarativeEnvironmentRecord: null,
        potentialReferentializationScopes: new Set()
      }));
    } else {
      (0, _invariant2.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
      // DeclarativeEnvironmentRecord binding
      let residualFunctionBindings = (0, _utils.getOrDefault)(this.declarativeEnvironmentRecordsBindings, environment, () => new Map());
      return (0, _utils.getOrDefault)(residualFunctionBindings, name, () => {
        (0, _invariant2.default)(environment instanceof _environment.DeclarativeEnvironmentRecord);
        let binding = environment.bindings[name];
        (0, _invariant2.default)(binding !== undefined);
        (0, _invariant2.default)(!binding.deletable);
        return {
          value: binding.initialized && binding.value || this.realm.intrinsics.undefined,
          modified: false,
          declarativeEnvironmentRecord: environment,
          potentialReferentializationScopes: new Set()
        };
      });
    }
  }

  // Visits a binding, returns a ResidualFunctionBinding
  visitBinding(val, residualFunctionBinding) {
    if (residualFunctionBinding.declarativeEnvironmentRecord !== null) {
      let refScope = this._getAdditionalFunctionOfScope() || "GLOBAL";
      residualFunctionBinding.potentialReferentializationScopes.add(refScope);
      this._recordBindingVisitedAndRevisit(val, residualFunctionBinding);
    }
    if (residualFunctionBinding.value) {
      let equivalentValue = this.visitEquivalentValue(residualFunctionBinding.value);
      residualFunctionBinding.value = equivalentValue;
    }
    return residualFunctionBinding;
  }

  _visitClass(classFunc, classPrototype) {
    let visitClassMethod = (propertyNameOrSymbol, methodFunc, methodType, isStatic) => {
      if (methodFunc instanceof _index2.ECMAScriptSourceFunctionValue) {
        // if the method does not have a $HomeObject, it's not a class method
        if (methodFunc.$HomeObject !== undefined) {
          if (methodFunc !== classFunc) {
            this._visitClassMethod(methodFunc, methodType, classPrototype, !!isStatic);
          }
        }
      }
    };
    for (let [propertyName, method] of classPrototype.properties) {
      (0, _utils.withDescriptorValue)(propertyName, method.descriptor, visitClassMethod);
    }
    for (let [symbol, method] of classPrototype.symbols) {
      (0, _utils.withDescriptorValue)(symbol, method.descriptor, visitClassMethod);
    }

    // handle class inheritance
    if (!(classFunc.$Prototype instanceof _index2.NativeFunctionValue)) {
      this.visitValue(classFunc.$Prototype);
    }

    if (classPrototype.properties.has("constructor")) {
      let constructor = classPrototype.properties.get("constructor");

      (0, _invariant2.default)(constructor !== undefined);
      // check if the constructor was deleted, as it can't really be deleted
      // it just gets set to empty (the default again)
      if (constructor.descriptor === undefined) {
        classFunc.$HasEmptyConstructor = true;
      } else {
        let visitClassProperty = (propertyNameOrSymbol, methodFunc, methodType) => {
          visitClassMethod(propertyNameOrSymbol, methodFunc, methodType, true);
        };
        // check if we have any static methods we need to include
        let constructorFunc = (0, _index.Get)(this.realm, classPrototype, "constructor");
        (0, _invariant2.default)(constructorFunc instanceof _index2.ObjectValue);
        for (let [propertyName, method] of constructorFunc.properties) {
          if (!_utils.ClassPropertiesToIgnore.has(propertyName) && method.descriptor !== undefined && !(propertyName === "length" && (0, _utils.canIgnoreClassLengthProperty)(constructorFunc, method.descriptor, this.logger))) {
            (0, _utils.withDescriptorValue)(propertyName, method.descriptor, visitClassProperty);
          }
        }
      }
    }
    this.classMethodInstances.set(classFunc, {
      classPrototype,
      methodType: "constructor",
      classSuperNode: undefined,
      classMethodIsStatic: false,
      classMethodKeyNode: undefined,
      classMethodComputed: false
    });
  }

  _visitClassMethod(methodFunc, methodType, classPrototype, isStatic) {
    this.classMethodInstances.set(methodFunc, {
      classPrototype,
      methodType: methodType === "value" ? "method" : methodType,
      classSuperNode: undefined,
      classMethodIsStatic: isStatic,
      classMethodKeyNode: undefined,
      classMethodComputed: !!methodFunc.$HasComputedName
    });
  }

  visitValueObject(val) {
    this._registerAdditionalRoot(val);
    let kind = val.getKind();
    this.visitObjectProperties(val, kind);

    // If this object is a prototype object that was implicitly created by the runtime
    // for a constructor, then we can obtain a reference to this object
    // in a special way that's handled alongside function serialization.
    let constructor = val.originalConstructor;
    if (constructor !== undefined) {
      this.visitValue(constructor);
      return;
    }

    switch (kind) {
      case "RegExp":
      case "Number":
      case "String":
      case "Boolean":
      case "ArrayBuffer":
        return;
      case "ReactElement":
        if (this.realm.react.output === "create-element") {
          this.someReactElement = val;
        }
        // check we can hoist a React Element
        (0, _hoisting.canHoistReactElement)(this.realm, val, this);
        return;
      case "Date":
        let dateValue = val.$DateValue;
        (0, _invariant2.default)(dateValue !== undefined);
        this.visitValue(dateValue);
        return;
      case "Float32Array":
      case "Float64Array":
      case "Int8Array":
      case "Int16Array":
      case "Int32Array":
      case "Uint8Array":
      case "Uint16Array":
      case "Uint32Array":
      case "Uint8ClampedArray":
      case "DataView":
        let buf = val.$ViewedArrayBuffer;
        (0, _invariant2.default)(buf !== undefined);
        this.visitValue(buf);
        return;
      case "Map":
      case "WeakMap":
        this.visitValueMap(val);
        return;
      case "Set":
      case "WeakSet":
        this.visitValueSet(val);
        return;
      default:
        if (kind !== "Object") this.logger.logError(val, `Object of kind ${kind} is not supported in residual heap.`);
        if (this.realm.react.enabled && (0, _utils2.valueIsReactLibraryObject)(this.realm, val, this.logger)) {
          this.realm.fbLibraries.react = val;
        }
        return;
    }
  }

  visitValueSymbol(val) {
    if (val.$Description) this.visitValue(val.$Description);
  }

  visitValueProxy(val) {
    this._registerAdditionalRoot(val);

    this.visitValue(val.$ProxyTarget);
    this.visitValue(val.$ProxyHandler);
  }

  visitAbstractValue(val) {
    if (val.kind === "sentinel member expression") this.logger.logError(val, "expressions of type o[p] are not yet supported for partially known o and unknown p");
    for (let i = 0, n = val.args.length; i < n; i++) {
      val.args[i] = this.visitEquivalentValue(val.args[i]);
    }
  }

  // Overridable hook for pre-visiting the value.
  // Return false will tell visitor to skip visiting children of this node.
  preProcessValue(val) {
    return this._mark(val);
  }

  // Overridable hook for post-visiting the value.
  postProcessValue(val) {}

  _mark(val) {
    let scopes = this.values.get(val);
    if (scopes === undefined) this.values.set(val, scopes = new Set());
    if (scopes.has(this.scope)) return false;
    scopes.add(this.scope);
    return true;
  }

  visitEquivalentValue(val) {
    if (val instanceof _index2.AbstractValue) {
      let equivalentValue = this.equivalenceSet.add(val);
      if (this.preProcessValue(equivalentValue)) this.visitAbstractValue(equivalentValue);
      this.postProcessValue(equivalentValue);
      return equivalentValue;
    }
    if (val instanceof _index2.ObjectValue && (0, _utils2.isReactElement)(val)) {
      let equivalentReactElementValue = this.reactElementEquivalenceSet.add(val);
      if (this._mark(equivalentReactElementValue)) this.visitValueObject(equivalentReactElementValue);
      return equivalentReactElementValue;
    }
    this.visitValue(val);
    return val;
  }

  visitValue(val) {
    (0, _invariant2.default)(!(val instanceof _index2.ObjectValue && val.refuseSerialization));
    if (val instanceof _index2.AbstractValue) {
      if (this.preProcessValue(val)) this.visitAbstractValue(val);
    } else if (val.isIntrinsic()) {
      // All intrinsic values exist from the beginning of time...
      // ...except for a few that come into existence as templates for abstract objects via executable code.
      if (val instanceof _index2.ObjectValue && val._isScopedTemplate) this.preProcessValue(val);else this._withScope(this._getCommonScope(), () => {
        this.preProcessValue(val);
      });
    } else if (val instanceof _index2.EmptyValue) {
      this.preProcessValue(val);
    } else if (_ResidualHeapInspector.ResidualHeapInspector.isLeaf(val)) {
      this.preProcessValue(val);
    } else if ((0, _index.IsArray)(this.realm, val)) {
      (0, _invariant2.default)(val instanceof _index2.ObjectValue);
      if (this.preProcessValue(val)) this.visitValueArray(val);
    } else if (val instanceof _index2.ProxyValue) {
      if (this.preProcessValue(val)) this.visitValueProxy(val);
    } else if (val instanceof _index2.FunctionValue) {
      // Function declarations should get hoisted in common scope so that instances only get allocated once
      let parentScope = this.scope;
      // Every function references itself through arguments, prevent the recursive double-visit
      let commonScope = this._getCommonScope();
      if (this.scope !== val && commonScope !== val) {
        this._withScope(commonScope, () => {
          (0, _invariant2.default)(val instanceof _index2.FunctionValue);
          if (this.preProcessValue(val)) this.visitValueFunction(val, parentScope);
        });
      } else {
        // We didn't call preProcessValue, so let's avoid calling postProcessValue.
        return;
      }
    } else if (val instanceof _index2.SymbolValue) {
      if (this.preProcessValue(val)) this.visitValueSymbol(val);
    } else {
      (0, _invariant2.default)(val instanceof _index2.ObjectValue);

      // Prototypes are reachable via function declarations, and those get hoisted, so we need to move
      // prototype initialization to the common scope code as well.
      if (val.originalConstructor !== undefined) {
        this._withScope(this._getCommonScope(), () => {
          (0, _invariant2.default)(val instanceof _index2.ObjectValue);
          if (this.preProcessValue(val)) this.visitValueObject(val);
        });
      } else {
        if (this.preProcessValue(val)) this.visitValueObject(val);
      }
    }
    this.postProcessValue(val);
  }

  createGeneratorVisitCallbacks(additionalFunctionInfo) {
    let callbacks = {
      visitValues: values => {
        for (let i = 0, n = values.length; i < n; i++) values[i] = this.visitEquivalentValue(values[i]);
      },
      visitGenerator: (generator, parent) => {
        // TODO: The serializer assumes that each generator has a unique parent; however, in the presence of conditional exceptions that is not actually true.
        // invariant(!this.generatorParents.has(generator));
        this.visitGenerator(generator, parent, additionalFunctionInfo);
      },
      canSkip: value => {
        return !this.referencedDeclaredValues.has(value) && !this.values.has(value);
      },
      recordDeclaration: value => {
        this.referencedDeclaredValues.set(value, this._getAdditionalFunctionOfScope());
      },
      recordDelayedEntry: (generator, entry) => {
        this.delayedVisitGeneratorEntries.push({
          generator,
          action: () => entry.visit(callbacks, generator)
        });
      },
      visitObjectProperty: binding => {
        this.visitObjectProperty(binding);
      },
      visitModifiedBinding: (modifiedBinding, previousValue) => {
        (0, _invariant2.default)(additionalFunctionInfo);
        let { functionValue } = additionalFunctionInfo;
        (0, _invariant2.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
        let residualBinding;
        residualBinding = this.getBinding(functionValue, modifiedBinding.environment, modifiedBinding.name);
        this.visitBinding(functionValue, residualBinding);
        (0, _invariant2.default)(residualBinding !== undefined);
        // named functions inside an additional function that have a global binding
        // can be skipped, as we don't want them to bind to the global
        if (residualBinding.declarativeEnvironmentRecord === null && modifiedBinding.value instanceof _index2.ECMAScriptSourceFunctionValue) {
          return;
        }
        let funcInstance = additionalFunctionInfo.instance;
        (0, _invariant2.default)(funcInstance !== undefined);
        funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
        let newValue = modifiedBinding.value;
        (0, _invariant2.default)(newValue);
        this.visitValue(newValue);
        residualBinding.modified = true;
        // This should be enforced by checkThatFunctionsAreIndependent
        (0, _invariant2.default)(!residualBinding.additionalFunctionOverridesValue || residualBinding.additionalFunctionOverridesValue === functionValue, "We should only have one additional function value modifying any given residual binding");
        if (previousValue) residualBinding.additionalFunctionOverridesValue = functionValue;
        additionalFunctionInfo.modifiedBindings.set(modifiedBinding, residualBinding);
        return residualBinding;
      }
    };
    return callbacks;
  }

  visitGenerator(generator, parent, additionalFunctionInfo) {
    this.generatorParents.set(generator, parent);
    if (generator.effectsToApply) for (const createdObject of generator.effectsToApply[4]) {
      // TODO: Unfortunately, the following invariant doesn't hold. This is concerning.
      // invariant(!this.createdObjects.has(createdObject) || this.createdObjects.get(createdObject) === generator);
      if (!this.createdObjects.has(createdObject)) this.createdObjects.set(createdObject, generator);
    }

    this._withScope(generator, () => {
      generator.visit(this.createGeneratorVisitCallbacks(additionalFunctionInfo));
    });

    // We don't bother purging created objects
  }

  // result -- serialized as a return statement
  // Generator -- visit all entries
  // Bindings -- (modifications to named variables) only need to serialize bindings if they're
  //             captured by a residual function
  //          -- need to apply them and maybe need to revisit functions in ancestors to make sure
  //             we don't overwrite anything they capture
  // PropertyBindings -- (property modifications) visit any property bindings to pre-existing objects
  // CreatedObjects -- should take care of itself
  _visitAdditionalFunction(functionValue, additionalEffects, parentScope) {
    // Get Instance + Info
    (0, _invariant2.default)(functionValue instanceof _index2.ECMAScriptSourceFunctionValue);
    let code = functionValue.$ECMAScriptCode;
    let functionInfo = this.functionInfos.get(code);
    (0, _invariant2.default)(functionInfo !== undefined);
    let funcInstance = this.functionInstances.get(functionValue);
    (0, _invariant2.default)(funcInstance !== undefined);

    // Set Visitor state
    // Allows us to emit function declarations etc. inside of this additional
    // function instead of adding them at global scope
    let oldEquivalenceSet = this.equivalenceSet;
    this.equivalenceSet = new _index.HashSet();
    let oldReactElementEquivalenceSet = this.reactElementEquivalenceSet;
    this.reactElementEquivalenceSet = new _ReactElementSet2.default(this.realm, this.equivalenceSet);

    let modifiedBindingInfo = new Map();
    let [result] = additionalEffects.effects;

    (0, _invariant2.default)(funcInstance !== undefined);
    (0, _invariant2.default)(functionInfo !== undefined);
    let additionalFunctionInfo = {
      functionValue,
      captures: functionInfo.unbound,
      modifiedBindings: modifiedBindingInfo,
      instance: funcInstance,
      hasReturn: !(result instanceof _index2.UndefinedValue)
    };
    this.additionalFunctionValueInfos.set(functionValue, additionalFunctionInfo);

    let effectsGenerator = additionalEffects.generator;
    this.visitGenerator(effectsGenerator, functionValue, additionalFunctionInfo);

    // Cleanup
    this.reactElementEquivalenceSet = oldReactElementEquivalenceSet;
    this.equivalenceSet = oldEquivalenceSet;
    this._withScope(parentScope,
    // Re-visit any bindings corresponding to unbound values or values closed over from outside additional function
    // they're serialized in the correct scope
    () => {
      (0, _invariant2.default)(functionInfo !== undefined);
      (0, _invariant2.default)(funcInstance !== undefined);
      for (let [modifiedBinding, oldValue] of additionalEffects.generator.getModifiedBindingOldValues()) {
        (0, _invariant2.default)(oldValue !== undefined);
        let residualBinding = this.getBinding(functionValue, modifiedBinding.environment, modifiedBinding.name);
        // Fixup the binding to have the correct value
        // No previousValue means this is a binding for a nested function
        residualBinding.value = this.visitEquivalentValue(oldValue);
        this.visitBinding(functionValue, residualBinding);
        (0, _invariant2.default)(residualBinding !== undefined);
        funcInstance.residualFunctionBindings.set(modifiedBinding.name, residualBinding);
        // TODO nested optimized functions: revisit adding GLOBAL as outer optimized function
        residualBinding.potentialReferentializationScopes.add("GLOBAL");
      }
    });
  }

  visitRoots() {
    this.visitGenerator(this.globalGenerator, "GLOBAL");
    for (let moduleValue of this.modules.initializedModules.values()) this.visitValue(moduleValue);
    if (this.realm.react.enabled && this.someReactElement !== undefined) {
      this._visitReactLibrary(this.someReactElement);
    }

    // Do a fixpoint over all pure generator entries to make sure that we visit
    // arguments of only BodyEntries that are required by some other residual value
    let progress = true;
    while (progress) {
      let oldDelayedEntries = this.delayedVisitGeneratorEntries.reverse();
      this.delayedVisitGeneratorEntries = [];
      progress = false;
      for (let _ref of oldDelayedEntries) {
        let { generator, action } = _ref;

        let withEffectsAppliedInGlobalEnv = f => f();
        let s = generator;
        let visited = new Set();
        while (s !== "GLOBAL") {
          (0, _invariant2.default)(!visited.has(s));
          visited.add(s);
          if (s instanceof _generator.Generator) {
            let effectsToApply = s.effectsToApply;
            if (effectsToApply) {
              let outer = withEffectsAppliedInGlobalEnv;
              withEffectsAppliedInGlobalEnv = f => outer(() => {
                this.realm.withEffectsAppliedInGlobalEnv(() => {
                  f();
                  return null;
                }, effectsToApply);
              });
            }
            s = this.generatorParents.get(s);
          } else if (s instanceof _index2.FunctionValue) {
            (0, _invariant2.default)(this.additionalFunctionValuesAndEffects.has(s));
            s = this.createdObjects.get(s) || "GLOBAL";
          }
          (0, _invariant2.default)(s instanceof _generator.Generator || s instanceof _index2.FunctionValue || s === "GLOBAL");
        }

        this._withScope(generator, () => withEffectsAppliedInGlobalEnv(() => {
          if (action() !== false) progress = true;
        }));
      }
    }

    // Make sure to visit all global bindings in global scope
    this._withScope(this.globalGenerator, () => {
      for (let binding of this.globalBindings.values()) if (binding.value) this.visitValue(binding.value);
    });

    let referentializer = this.referentializer;
    if (referentializer !== undefined) for (let instance of this.functionInstances.values()) referentializer.referentialize(instance);
  }

  _visitReactLibrary(someReactElement) {
    // find and visit the React library
    let reactLibraryObject = this.realm.fbLibraries.react;
    if (this.realm.react.output === "jsx") {
      // React might not be defined in scope, i.e. another library is using JSX
      // we don't throw an error as we should support JSX stand-alone
      if (reactLibraryObject !== undefined) {
        this.visitValue(reactLibraryObject);
      }
    } else if (this.realm.react.output === "create-element") {
      let logError = () => {
        this.logger.logError(someReactElement, "unable to visit createElement due to React not being referenced in scope");
      };
      // createElement output needs React in scope
      if (reactLibraryObject === undefined) {
        logError();
      } else {
        (0, _invariant2.default)(reactLibraryObject instanceof _index2.ObjectValue);
        let createElement = reactLibraryObject.properties.get("createElement");
        if (createElement === undefined || createElement.descriptor === undefined) {
          logError();
        } else {
          let reactCreateElement = (0, _index.Get)(this.realm, reactLibraryObject, "createElement");
          this.visitValue(reactCreateElement);
        }
      }
    }
  }
}
exports.ResidualHeapVisitor = ResidualHeapVisitor; /**
                                                    * 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.
                                                    */
//# sourceMappingURL=ResidualHeapVisitor.js.map