"use strict";

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

var _errors = require("../errors.js");

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

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

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

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

var t = _interopRequireWildcard(_babelTypes);

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

var _babelTraverse2 = _interopRequireDefault(_babelTraverse);

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

var _invariant2 = _interopRequireDefault(_invariant);

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

function visitName(path, state, name, read, write) {
  // Is the name bound to some local identifier? If so, we don't need to do anything
  if (path.scope.hasBinding(name, /*noGlobals*/true)) return;

  // Otherwise, let's record that there's an unbound identifier
  if (read) state.unboundReads.add(name);
  if (write) state.unboundWrites.add(name);
} /**
   * 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.
   */

function ignorePath(path) {
  let parent = path.parent;
  return t.isLabeledStatement(parent) || t.isBreakStatement(parent) || t.isContinueStatement(parent);
}

let HavocedClosureRefVisitor = {
  ReferencedIdentifier(path, state) {
    if (ignorePath(path)) return;

    let innerName = path.node.name;
    if (innerName === "arguments") {
      return;
    }
    visitName(path, state, innerName, true, false);
  },

  "AssignmentExpression|UpdateExpression"(path, state) {
    let doesRead = path.node.operator !== "=";
    for (let name in path.getBindingIdentifiers()) {
      visitName(path, state, name, doesRead, true);
    }
  }
};

function getHavocedFunctionInfo(value) {
  // TODO: This should really be cached on a per AST basis in case we have
  // many uses of the same closure. It should ideally share this cache
  // and data with ResidualHeapVisitor.
  (0, _invariant2.default)(value instanceof _index.ECMAScriptSourceFunctionValue);
  (0, _invariant2.default)(value.constructor === _index.ECMAScriptSourceFunctionValue);
  let functionInfo = {
    unboundReads: new Set(),
    unboundWrites: new Set()
  };
  let formalParameters = value.$FormalParameters;
  (0, _invariant2.default)(formalParameters != null);
  let code = value.$ECMAScriptCode;
  (0, _invariant2.default)(code != null);

  (0, _babelTraverse2.default)(t.file(t.program([t.expressionStatement(t.functionExpression(null, formalParameters, code))])), HavocedClosureRefVisitor, null, functionInfo);
  return functionInfo;
}

class ObjectValueHavocingVisitor {
  // ObjectValues to visit if they're reachable.
  constructor(objectsTrackedForHavoc) {
    this.objectsTrackedForHavoc = objectsTrackedForHavoc;
    this.visitedValues = new Set();
  }
  // Values that has been visited.


  mustVisit(val) {
    if (val instanceof _index.ObjectValue) {
      // For Objects we only need to visit it if it is tracked
      // as a newly created object that might still be mutated.
      // Abstract values gets their arguments visited.
      if (!this.objectsTrackedForHavoc.has(val)) return false;
    }
    if (this.visitedValues.has(val)) return false;
    this.visitedValues.add(val);
    return true;
  }

  visitObjectProperty(binding) {
    let desc = binding.descriptor;
    if (desc === undefined) return; //deleted
    this.visitDescriptor(desc);
  }

  visitObjectProperties(obj, kind) {
    // visit symbol properties
    for (let [, propertyBindingValue] of obj.symbols) {
      (0, _invariant2.default)(propertyBindingValue);
      this.visitObjectProperty(propertyBindingValue);
    }

    // visit string properties
    for (let [, propertyBindingValue] of obj.properties) {
      (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 _index.AbstractValue);
        this.visitObjectPropertiesWithComputedNames(val);
      }
    }

    // prototype
    this.visitObjectPrototype(obj);

    if ((0, _index2.TestIntegrityLevel)(obj.$Realm, obj, "frozen") || obj.isFinalObject()) return;

    // if this object wasn't already havoced, we need mark it as havoced
    // so that any mutation and property access get tracked after this.
    if (!obj.isHavocedObject()) {
      obj.havoc();
    }
  }

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

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

  visitDescriptor(desc) {
    (0, _invariant2.default)(desc.value === undefined || desc.value instanceof _index.Value);
    if (desc.value !== undefined) this.visitValue(desc.value);
    if (desc.get !== undefined) this.visitValue(desc.get);
    if (desc.set !== undefined) this.visitValue(desc.set);
  }

  visitDeclarativeEnvironmentRecordBinding(record, remainingHavocedBindings) {
    let bindings = record.bindings;
    for (let bindingName of Object.keys(bindings)) {
      let binding = bindings[bindingName];
      // Check if this binding is referenced, and if so delete it from the set.
      let isRead = remainingHavocedBindings.unboundReads.delete(bindingName);
      let isWritten = remainingHavocedBindings.unboundWrites.delete(bindingName);
      if (isRead) {
        // If this binding can be read from the closure, its value has now havoced.
        let value = binding.value;
        if (value) {
          this.visitValue(value);
        }
      }
      if (isWritten || isRead) {
        // If this binding could have been mutated from the closure, then the
        // binding itself has now leaked, but not necessarily the value in it.
        // TODO: We could tag a leaked binding as read and/or write. That way
        // we don't have to havoc values written to this binding if only the binding
        // has been written to. We also don't have to havoc reads from this binding
        // if it is only read from.
        (0, _environment.havocBinding)(binding);
      }
    }
  }

  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) {
    if (val.isHavocedObject()) {
      return;
    }
    this.visitObjectProperties(val);

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

    (0, _invariant2.default)(!(val instanceof _index.NativeFunctionValue), "all native function values should have already been created outside this pure function");

    let remainingHavocedBindings = getHavocedFunctionInfo(val);

    let environment = val.$Environment.parent;
    while (environment) {
      let record = environment.environmentRecord;
      if (record instanceof _environment.ObjectEnvironmentRecord) {
        this.visitValue(record.object);
        continue;
      }
      if (record instanceof _environment.GlobalEnvironmentRecord) {
        break;
      }

      (0, _invariant2.default)(record instanceof _environment.DeclarativeEnvironmentRecord);
      this.visitDeclarativeEnvironmentRecordBinding(record, remainingHavocedBindings);

      if (record instanceof _environment.FunctionEnvironmentRecord) {
        // If this is a function environment, which is not tracked for havocs,
        // we can bail out because its bindings should not be mutated in a
        // pure function.
        let fn = record.$FunctionObject;
        if (!this.objectsTrackedForHavoc.has(fn)) {
          break;
        }
      }
      environment = environment.parent;
    }
  }

  visitValueObject(val) {
    if (val.isHavocedObject()) {
      return;
    }

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

    switch (kind) {
      case "RegExp":
      case "Number":
      case "String":
      case "Boolean":
      case "ReactElement":
      case "ArrayBuffer":
      case "Array":
        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:
        (0, _invariant2.default)(kind === "Object", `Object of kind ${kind} is not supported in calls to abstract functions.`);
        (0, _invariant2.default)(val.$ParameterMap === undefined, `Arguments object is not supported in calls to abstract functions.`);
        return;
    }
  }

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

  visitAbstractValue(val) {
    for (let i = 0, n = val.args.length; i < n; i++) {
      this.visitValue(val.args[i]);
    }
  }

  visitValue(val) {
    if (val instanceof _index.AbstractValue) {
      if (this.mustVisit(val)) this.visitAbstractValue(val);
    } else if (val.isIntrinsic()) {
      // All intrinsic values exist from the beginning of time...
      // ...except for a few that come into existance as templates for abstract objects.
      this.mustVisit(val);
    } else if (val instanceof _index.EmptyValue) {
      this.mustVisit(val);
    } else if (val instanceof _index.PrimitiveValue) {
      this.mustVisit(val);
    } else if (val instanceof _index.ProxyValue) {
      if (this.mustVisit(val)) this.visitValueProxy(val);
    } else if (val instanceof _index.FunctionValue) {
      (0, _invariant2.default)(val instanceof _index.FunctionValue);
      if (this.mustVisit(val)) this.visitValueFunction(val);
    } else {
      (0, _invariant2.default)(val instanceof _index.ObjectValue);
      if (val.originalConstructor !== undefined) {
        (0, _invariant2.default)(val instanceof _index.ObjectValue);
        if (this.mustVisit(val)) this.visitValueObject(val);
      } else {
        if (this.mustVisit(val)) this.visitValueObject(val);
      }
    }
  }
}

function ensureFrozenValue(realm, value, loc) {
  // TODO: This should really check if it is recursively immutability.
  if (value instanceof _index.ObjectValue && !(0, _index2.TestIntegrityLevel)(realm, value, "frozen") && !value.isFinalObject()) {
    let diag = new _errors.CompilerDiagnostic("Unfrozen object leaked before end of global code", loc || realm.currentLocation, "PP0017", "RecoverableError");
    if (realm.handleError(diag) !== "Recover") throw new _errors.FatalError();
  }
}

// Ensure that a value is immutable. If it is not, set all its properties to abstract values
// and all reachable bindings to abstract values.
class HavocImplementation {
  value(realm, value, loc) {
    let objectsTrackedForHavoc = realm.createdObjectsTrackedForLeaks;
    if (objectsTrackedForHavoc === undefined) {
      // We're not tracking a pure function. That means that we would track
      // everything as havoced. We'll assume that any object argument
      // is invalid unless it's frozen.
      ensureFrozenValue(realm, value, loc);
    } else {
      // If we're tracking a pure function, we can assume that only newly
      // created objects and bindings, within it, are mutable. Any other
      // object can safely be assumed to be deeply immutable as far as this
      // pure function is concerned. However, any mutable object needs to
      // be tainted as possibly having changed to anything.
      let visitor = new ObjectValueHavocingVisitor(objectsTrackedForHavoc);
      visitor.visitValue(value);
    }
  }
}
exports.HavocImplementation = HavocImplementation;
//# sourceMappingURL=havoc.js.map