"use strict";

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

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

var _completions = require("../completions.js");

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

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

class WidenImplementation {
  _widenArrays(realm, v1, v2) {
    let e = v1 && v1[0] || v2 && v2[0];
    if (e instanceof _index2.Value) return this._widenArraysOfValues(realm, v1, v2);else return this._widenArrayOfsMapEntries(realm, v1, v2);
  }

  _widenArrayOfsMapEntries(realm, a1, a2) {
    let empty = realm.intrinsics.empty;
    let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
    let result = [];
    for (let i = 0; i < n; i++) {
      let { $Key: key1, $Value: val1 } = a1 && a1[i] || { $Key: empty, $Value: empty };
      let { $Key: key2, $Value: val2 } = a2 && a2[i] || { $Key: empty, $Value: empty };
      if (key1 === undefined && key2 === undefined) {
        result[i] = { $Key: undefined, $Value: undefined };
      } else {
        let key3 = this.widenValues(realm, key1, key2);
        (0, _invariant2.default)(key3 instanceof _index2.Value);
        let val3 = this.widenValues(realm, val1, val2);
        (0, _invariant2.default)(val3 === undefined || val3 instanceof _index2.Value);
        result[i] = { $Key: key3, $Value: val3 };
      }
    }
    return result;
  }

  _widenArraysOfValues(realm, a1, a2) {
    let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
    let result = [];
    for (let i = 0; i < n; i++) {
      let wv = this.widenValues(realm, a1 && a1[i] || undefined, a2 && a2[i] || undefined);
      (0, _invariant2.default)(wv === undefined || wv instanceof _index2.Value);
      result[i] = wv;
    }
    return result;
  }

  // Returns a new effects summary that includes both e1 and e2.
  widenEffects(realm, e1, e2) {
    let [result1,, bindings1, properties1, createdObj1] = e1;
    let [result2,, bindings2, properties2, createdObj2] = e2;

    let result = this.widenResults(realm, result1, result2);
    let bindings = this.widenBindings(realm, bindings1, bindings2);
    let properties = this.widenPropertyBindings(realm, properties1, properties2, createdObj1, createdObj2);
    let createdObjects = new Set(); // Top, since the empty set knows nothing. There is no other choice for widen.
    let generator = new _generator.Generator(realm, "widen"); // code subject to widening will be generated somewhere else
    return [result, generator, bindings, properties, createdObjects];
  }

  widenResults(realm, result1, result2) {
    (0, _invariant2.default)(!(result1 instanceof _environment.Reference || result2 instanceof _environment.Reference), "loop bodies should not result in refs");
    (0, _invariant2.default)(!(result1 instanceof _completions.AbruptCompletion || result2 instanceof _completions.AbruptCompletion), "if a loop iteration ends abruptly, there is no need for fixed point computation");
    if (result1 instanceof _index2.Value && result2 instanceof _index2.Value) {
      let val = this.widenValues(realm, result1, result2);
      (0, _invariant2.default)(val instanceof _index2.Value);
      return val;
    }
    if (result1 instanceof _completions.PossiblyNormalCompletion || result2 instanceof _completions.PossiblyNormalCompletion) {
      //todo: #1174 figure out how to deal with loops that have embedded conditional exits
      // widen join pathConditions
      // widen normal result and Effects
      // use abrupt part of result2, depend stability to make this safe. See below.
      throw new _errors.FatalError();
    }
    (0, _invariant2.default)(false);
  }

  widenMaps(m1, m2, widen) {
    let m3 = new Map();
    m1.forEach((val1, key, map1) => {
      let val2 = m2.get(key);
      let val3 = widen(key, val1, val2);
      m3.set(key, val3);
    });
    m2.forEach((val2, key, map2) => {
      if (!m1.has(key)) {
        m3.set(key, widen(key, undefined, val2));
      }
    });
    return m3;
  }

  widenBindings(realm, m1, m2) {
    let widen = (b, b1, b2) => {
      let l1 = b1 === undefined ? b.hasLeaked : b1.hasLeaked;
      let l2 = b2 === undefined ? b.hasLeaked : b2.hasLeaked;
      let hasLeaked = l1 || l2; // If either has leaked, then this binding has leaked.
      let v1 = b1 === undefined || b1.value === undefined ? b.value : b1.value;
      (0, _invariant2.default)(b2 !== undefined); // Local variables are not going to get deleted as a result of widening
      let v2 = b2.value;
      (0, _invariant2.default)(v2 !== undefined);
      let result = this.widenValues(realm, v1, v2);
      if (result instanceof _index2.AbstractValue && result.kind === "widened") {
        let phiNode = b.phiNode;
        if (phiNode === undefined) {
          // Create a temporal location for binding
          let generator = realm.generator;
          (0, _invariant2.default)(generator !== undefined);
          phiNode = generator.derive(result.types, result.values, [b.value || realm.intrinsics.undefined], ([n]) => n, {
            skipInvariant: true
          });
          b.phiNode = phiNode;
        }
        // Let the widened value be a reference to the phiNode of the binding
        (0, _invariant2.default)(phiNode.intrinsicName !== undefined);
        let phiName = phiNode.intrinsicName;
        result.intrinsicName = phiName;
        result._buildNode = args => t.identifier(phiName);
      }
      (0, _invariant2.default)(result instanceof _index2.Value);
      return { hasLeaked, value: result };
    };
    return this.widenMaps(m1, m2, widen);
  }

  // Returns an abstract value that includes both v1 and v2 as potential values.
  widenValues(realm, v1, v2) {
    if (Array.isArray(v1) || Array.isArray(v2)) {
      (0, _invariant2.default)(v1 === undefined || Array.isArray(v1));
      (0, _invariant2.default)(v2 === undefined || Array.isArray(v2));
      return this._widenArrays(realm, v1, v2);
    }
    (0, _invariant2.default)(v1 === undefined || v1 instanceof _index2.Value);
    (0, _invariant2.default)(v2 === undefined || v2 instanceof _index2.Value);
    if (v1 !== undefined && v2 !== undefined && !(v1 instanceof _index2.AbstractValue) && !(v2 instanceof _index2.AbstractValue) && (0, _index.StrictEqualityComparison)(realm, v1.throwIfNotConcrete(), v2.throwIfNotConcrete())) {
      return v1; // no need to widen a loop invariant value
    } else {
      return _index2.AbstractValue.createFromWidening(realm, v1 || realm.intrinsics.empty, v2 || realm.intrinsics.undefined);
    }
  }

  widenPropertyBindings(realm, m1, m2, c1, c2) {
    let widen = (b, d1, d2) => {
      if (d1 === undefined && d2 === undefined) return undefined;
      // If the PropertyBinding object has been freshly allocated do not widen (that happens in AbstractObjectValue)
      if (d1 === undefined) {
        if (b.object instanceof _index2.ObjectValue && c2.has(b.object)) return d2; // no widen
        if (b.descriptor !== undefined && m1.has(b)) {
          // property was present in (n-1)th iteration and deleted in nth iteration
          d1 = (0, _index.cloneDescriptor)(b.descriptor);
          (0, _invariant2.default)(d1 !== undefined);
          d1.value = realm.intrinsics.empty;
        } else {
          // no write to property in nth iteration, use the value from the (n-1)th iteration
          d1 = b.descriptor;
          if (d1 === undefined) {
            d1 = (0, _index.cloneDescriptor)(d2);
            (0, _invariant2.default)(d1 !== undefined);
            d1.value = realm.intrinsics.empty;
          }
        }
      }
      if (d2 === undefined) {
        if (b.object instanceof _index2.ObjectValue && c1.has(b.object)) return d1; // no widen
        if (m2.has(b)) {
          // property was present in nth iteration and deleted in (n+1)th iteration
          d2 = (0, _index.cloneDescriptor)(d1);
          (0, _invariant2.default)(d2 !== undefined);
          d2.value = realm.intrinsics.empty;
        } else {
          // no write to property in (n+1)th iteration, use the value from the nth iteration
          d2 = d1;
        }
        (0, _invariant2.default)(d2 !== undefined);
      }
      let result = this.widenDescriptors(realm, d1, d2);
      if (result && result.value instanceof _index2.AbstractValue && result.value.kind === "widened") {
        let rval = result.value;
        let pathNode = b.pathNode;
        if (pathNode === undefined) {
          //Since properties already have mutable storage locations associated with them, we do not
          //need phi nodes. What we need is an abstract value with a build node that results in a memberExpression
          //that resolves to the storage location of the property.

          // For now, we only handle loop invariant properties
          //i.e. properties where the member expresssion does not involve any values written to inside the loop.
          let key = b.key;
          if (typeof key === "string" || !(key.mightNotBeString() && key.mightNotBeNumber())) {
            if (typeof key === "string") {
              pathNode = _index2.AbstractValue.createFromWidenedProperty(realm, rval, [b.object], ([o]) => t.memberExpression(o, t.identifier(key)));
            } else {
              pathNode = _index2.AbstractValue.createFromWidenedProperty(realm, rval, [b.object, key], ([o, p]) => {
                return t.memberExpression(o, p, true);
              });
            }
            // The value of the property at the start of the loop needs to be written to the property
            // before the loop commences, otherwise the memberExpression will result in an undefined value.
            let generator = realm.generator;
            (0, _invariant2.default)(generator !== undefined);
            let initVal = b.descriptor && b.descriptor.value || realm.intrinsics.empty;
            if (!(initVal instanceof _index2.Value)) throw new _errors.FatalError("todo: handle internal properties");
            if (!(initVal instanceof _index2.EmptyValue)) {
              if (key === "length" && b.object instanceof _index2.ArrayValue) {
                // do nothing, the array length will already be initialized
              } else if (typeof key === "string") {
                generator.emitVoidExpression(rval.types, rval.values, [b.object, initVal], ([o, v]) => t.assignmentExpression("=", t.memberExpression(o, t.identifier(key)), v));
              } else {
                generator.emitVoidExpression(rval.types, rval.values, [b.object, b.key, initVal], ([o, p, v]) => t.assignmentExpression("=", t.memberExpression(o, p, true), v));
              }
            }
          } else {
            throw new _errors.FatalError("todo: handle the case where key is an abstract value");
          }
          b.pathNode = pathNode;
        }
        result.value = pathNode;
      }
      return result;
    };
    return this.widenMaps(m1, m2, widen);
  }

  widenDescriptors(realm, d1, d2) {
    if (d1 === undefined) {
      // d2 is a property written to only in the (n+1)th iteration
      if (!(0, _index.IsDataDescriptor)(realm, d2)) return d2; // accessor properties need not be widened.
      let dc = (0, _index.cloneDescriptor)(d2);
      (0, _invariant2.default)(dc !== undefined);
      dc.value = this.widenValues(realm, d2.value, d2.value);
      return dc;
    } else {
      if ((0, _index.equalDescriptors)(d1, d2)) {
        if (!(0, _index.IsDataDescriptor)(realm, d1)) return d1; // identical accessor properties need not be widened.
        let dc = (0, _index.cloneDescriptor)(d1);
        (0, _invariant2.default)(dc !== undefined);
        dc.value = this.widenValues(realm, d1.value, d2.value);
        return dc;
      }
      //todo: #1174 if we get here, the loop body contains a call to create a property and different iterations
      // create them differently. That seems beyond what a fixpoint computation can reasonably handle without
      // losing precision. Report an error here.
      throw new _errors.FatalError();
    }
  }

  // If e2 is the result of a loop iteration starting with effects e1 and it has a subset of elements of e1,
  // then we have reached a fixed point and no further calls to widen are needed. e1/e2 represent a general
  // summary of the loop, regardless of how many iterations will be performed at runtime.
  containsEffects(e1, e2) {
    let [result1,, bindings1, properties1, createdObj1] = e1;
    let [result2,, bindings2, properties2, createdObj2] = e2;

    if (!this.containsResults(result1, result2)) return false;
    if (!this.containsBindings(bindings1, bindings2)) return false;
    if (!this.containsPropertyBindings(properties1, properties2, createdObj1, createdObj2)) return false;
    return true;
  }

  containsResults(result1, result2) {
    if (result1 instanceof _index2.Value && result2 instanceof _index2.Value) return this._containsValues(result1, result2);
    return false;
  }

  containsMap(m1, m2, f) {
    for (const [key1, val1] of m1.entries()) {
      if (val1 === undefined) continue; // deleted
      let val2 = m2.get(key1);
      if (val2 === undefined) continue; // A key that disappears has been widened away into the unknown key
      if (!f(val1, val2)) return false;
    }
    for (const key2 of m2.keys()) {
      if (!m1.has(key2)) return false;
    }
    return true;
  }

  containsBindings(m1, m2) {
    let containsBinding = (b1, b2) => {
      if (b1 === undefined || b2 === undefined || b1.value === undefined || b2.value === undefined || !this._containsValues(b1.value, b2.value) || b1.hasLeaked !== b2.hasLeaked) {
        return false;
      }
      return true;
    };
    return this.containsMap(m1, m2, containsBinding);
  }

  containsPropertyBindings(m1, m2, c1, c2) {
    let containsPropertyBinding = (d1, d2) => {
      let [v1, v2] = [d1 && d1.value, d2 && d2.value];
      if (v1 === undefined) {
        return v2 === undefined;
      }
      if (v1 instanceof _index2.Value && v2 instanceof _index2.Value) return this._containsValues(v1, v2);
      if (Array.isArray(v1) && Array.isArray(v2)) {
        return this._containsArray(v1, v2);
      }
      return v2 === undefined;
    };
    for (const [key1, val1] of m1.entries()) {
      if (val1 === undefined) continue; // deleted
      let val2 = m2.get(key1);
      if (val2 === undefined) continue; // A key that disappears has been widened away into the unknown key
      if (key1.object instanceof _index2.ObjectValue && c1.has(key1.object)) {
        continue;
      }
      if (!containsPropertyBinding(val1, val2)) return false;
    }
    for (const key2 of m2.keys()) {
      if (key2.object instanceof _index2.ObjectValue && c2.has(key2.object)) {
        continue;
      }
      if (!m1.has(key2)) return false;
    }
    return true;
  }

  _containsArray(v1, v2) {
    let e = v1 && v1[0] || v2 && v2[0];
    if (e instanceof _index2.Value) return this.containsArraysOfValue(v1, v2);else return this._containsArrayOfsMapEntries(v1, v2);
  }

  _containsArrayOfsMapEntries(realm, a1, a2) {
    let empty = realm.intrinsics.empty;
    let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
    for (let i = 0; i < n; i++) {
      let { $Key: key1, $Value: val1 } = a1 && a1[i] || { $Key: empty, $Value: empty };
      let { $Key: key2, $Value: val2 } = a2 && a2[i] || { $Key: empty, $Value: empty };
      if (key1 === undefined) {
        if (key2 !== undefined) return false;
      } else {
        if (key1 instanceof _index2.Value && key2 instanceof _index2.Value && key1.equals(key2)) {
          if (val1 instanceof _index2.Value && val2 instanceof _index2.Value && this._containsValues(val1, val2)) continue;
        }
        return false;
      }
    }
    return true;
  }

  containsArraysOfValue(realm, a1, a2) {
    let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
    for (let i = 0; i < n; i++) {
      let [val1, val2] = [a1 && a1[i], a2 && a2[i]];
      if (val1 instanceof _index2.Value && val2 instanceof _index2.Value && !this._containsValues(val1, val2)) return false;
    }
    return true;
  }

  _containsValues(val1, val2) {
    if (val1 instanceof _index2.AbstractValue) {
      if (!_index2.Value.isTypeCompatibleWith(val2.getType(), val1.getType()) && !_index2.Value.isTypeCompatibleWith(val1.getType(), val2.getType())) return false;
      return val1.values.containsValue(val2);
    }
    return val1.equals(val2);
  }
}
exports.WidenImplementation = WidenImplementation; /**
                                                    * 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=widen.js.map