Commit message: [CVE-2020-1911] Look up HostObject computed properties on the right object in the prototype chain.

Summary:
The change in the hermes repository fixes the security vulnerability
CVE-2020-1911.  This vulnerability only affects applications which
allow evaluation of uncontrolled, untrusted JavaScript code not
shipped with the app, so React Native apps will generally not be affected.

This revision includes a test for the bug.  The test is generic JSI
code, so it is included in the hermes and react-native repositories.

Changelog: [Internal]

Reviewed By: tmikov

Differential Revision: D23322992

fbshipit-source-id: 4e88c974afe1ad33a263f9cac03e9dc98d33649a
CVE: CVE-2020-1911
CWE: CWE-125
Vulnerability description: A type confusion vulnerability when resolving properties of JavaScript objects with specially-crafted prototype chains in Facebook Hermes prior to commit fe52854cdf6725c2eaa9e125995da76e6ceb27da allows attackers to potentially execute arbitrary code via crafted JavaScript. Note that this is only exploitable if the application using Hermes permits evaluation of untrusted JavaScript. Hence, most React Native applications are not affected.

Project: hermes
File: testlib.cpp

TEST_P(JSITest, HostObjectTest) {
  class ConstantHostObject : public HostObject {
    Value get(Runtime&, const PropNameID& sym) override {
      return 9000;
    }

    void set(Runtime&, const PropNameID&, const Value&) override {}
  };

  Object cho =
      Object::createFromHostObject(rt, std::make_shared<ConstantHostObject>());
  EXPECT_TRUE(function("function (obj) { return obj.someRandomProp == 9000; }")
                  .call(rt, cho)
                  .getBool());
  EXPECT_TRUE(cho.isHostObject(rt));
  EXPECT_TRUE(cho.getHostObject<ConstantHostObject>(rt).get() != nullptr);

  struct SameRuntimeHostObject : HostObject {
    SameRuntimeHostObject(Runtime& rt) : rt_(rt){};

    Value get(Runtime& rt, const PropNameID& sym) override {
      EXPECT_EQ(&rt, &rt_);
      return Value();
    }

    void set(Runtime& rt, const PropNameID& name, const Value& value) override {
      EXPECT_EQ(&rt, &rt_);
    }

    std::vector<PropNameID> getPropertyNames(Runtime& rt) override {
      EXPECT_EQ(&rt, &rt_);
      return {};
    }

    Runtime& rt_;
  };

  Object srho = Object::createFromHostObject(
      rt, std::make_shared<SameRuntimeHostObject>(rt));
  // Test get's Runtime is as expected
  function("function (obj) { return obj.isSame; }").call(rt, srho);
  // ... and set
  function("function (obj) { obj['k'] = 'v'; }").call(rt, srho);
  // ... and getPropertyNames
  function("function (obj) { for (k in obj) {} }").call(rt, srho);

  class TwiceHostObject : public HostObject {
    Value get(Runtime& rt, const PropNameID& sym) override {
      return String::createFromUtf8(rt, sym.utf8(rt) + sym.utf8(rt));
    }

    void set(Runtime&, const PropNameID&, const Value&) override {}
  };

  Object tho =
      Object::createFromHostObject(rt, std::make_shared<TwiceHostObject>());
  EXPECT_TRUE(function("function (obj) { return obj.abc == 'abcabc'; }")
                  .call(rt, tho)
                  .getBool());
  EXPECT_TRUE(function("function (obj) { return obj['def'] == 'defdef'; }")
                  .call(rt, tho)
                  .getBool());
  EXPECT_TRUE(function("function (obj) { return obj[12] === '1212'; }")
                  .call(rt, tho)
                  .getBool());
  EXPECT_TRUE(tho.isHostObject(rt));
  EXPECT_TRUE(
      std::dynamic_pointer_cast<ConstantHostObject>(tho.getHostObject(rt)) ==
      nullptr);
  EXPECT_TRUE(tho.getHostObject<TwiceHostObject>(rt).get() != nullptr);

  class PropNameIDHostObject : public HostObject {
    Value get(Runtime& rt, const PropNameID& sym) override {
      if (PropNameID::compare(rt, sym, PropNameID::forAscii(rt, "undef"))) {
        return Value::undefined();
      } else {
        return PropNameID::compare(
            rt, sym, PropNameID::forAscii(rt, "somesymbol"));
      }
    }

    void set(Runtime&, const PropNameID&, const Value&) override {}
  };

  Object sho = Object::createFromHostObject(
      rt, std::make_shared<PropNameIDHostObject>());
  EXPECT_TRUE(sho.isHostObject(rt));
  EXPECT_TRUE(function("function (obj) { return obj.undef; }")
                  .call(rt, sho)
                  .isUndefined());
  EXPECT_TRUE(function("function (obj) { return obj.somesymbol; }")
                  .call(rt, sho)
                  .getBool());
  EXPECT_FALSE(function("function (obj) { return obj.notsomuch; }")
                   .call(rt, sho)
                   .getBool());

  class BagHostObject : public HostObject {
   public:
    const std::string& getThing() {
      return bag_["thing"];
    }

   private:
    Value get(Runtime& rt, const PropNameID& sym) override {
      if (sym.utf8(rt) == "thing") {
        return String::createFromUtf8(rt, bag_[sym.utf8(rt)]);
      }
      return Value::undefined();
    }

    void set(Runtime& rt, const PropNameID& sym, const Value& val) override {
      std::string key(sym.utf8(rt));
      if (key == "thing") {
        bag_[key] = val.toString(rt).utf8(rt);
      }
    }

    std::unordered_map<std::string, std::string> bag_;
  };

  std::shared_ptr<BagHostObject> shbho = std::make_shared<BagHostObject>();
  Object bho = Object::createFromHostObject(rt, shbho);
  EXPECT_TRUE(bho.isHostObject(rt));
  EXPECT_TRUE(function("function (obj) { return obj.undef; }")
                  .call(rt, bho)
                  .isUndefined());
  EXPECT_EQ(
      function("function (obj) { obj.thing = 'hello'; return obj.thing; }")
          .call(rt, bho)
          .toString(rt)
          .utf8(rt),
      "hello");
  EXPECT_EQ(shbho->getThing(), "hello");

  class ThrowingHostObject : public HostObject {
    Value get(Runtime& rt, const PropNameID& sym) override {
      throw std::runtime_error("Cannot get");
    }

    void set(Runtime& rt, const PropNameID& sym, const Value& val) override {
      throw std::runtime_error("Cannot set");
    }
  };

  Object thro =
      Object::createFromHostObject(rt, std::make_shared<ThrowingHostObject>());
  EXPECT_TRUE(thro.isHostObject(rt));
  std::string exc;
  try {
    function("function (obj) { return obj.thing; }").call(rt, thro);
  } catch (const JSError& ex) {
    exc = ex.what();
  }
  EXPECT_NE(exc.find("Cannot get"), std::string::npos);
  exc = "";
  try {
    function("function (obj) { obj.thing = 'hello'; }").call(rt, thro);
  } catch (const JSError& ex) {
    exc = ex.what();
  }
  EXPECT_NE(exc.find("Cannot set"), std::string::npos);

  class NopHostObject : public HostObject {};
  Object nopHo =
      Object::createFromHostObject(rt, std::make_shared<NopHostObject>());
  EXPECT_TRUE(nopHo.isHostObject(rt));
  EXPECT_TRUE(function("function (obj) { return obj.thing; }")
                  .call(rt, nopHo)
                  .isUndefined());

  std::string nopExc;
  try {
    function("function (obj) { obj.thing = 'pika'; }").call(rt, nopHo);
  } catch (const JSError& ex) {
    nopExc = ex.what();
  }
  EXPECT_NE(nopExc.find("TypeError: "), std::string::npos);

  class HostObjectWithPropertyNames : public HostObject {
    std::vector<PropNameID> getPropertyNames(Runtime& rt) override {
      return PropNameID::names(
          rt, "a_prop", "1", "false", "a_prop", "3", "c_prop");
    }
  };

  Object howpn = Object::createFromHostObject(
      rt, std::make_shared<HostObjectWithPropertyNames>());
  EXPECT_TRUE(
      function(
          "function (o) { return Object.getOwnPropertyNames(o).length == 5 }")
          .call(rt, howpn)
          .getBool());

  auto hasOwnPropertyName = function(
      "function (o, p) {"
      "  return Object.getOwnPropertyNames(o).indexOf(p) >= 0"
      "}");
  EXPECT_TRUE(
      hasOwnPropertyName.call(rt, howpn, String::createFromAscii(rt, "a_prop"))
          .getBool());
  EXPECT_TRUE(
      hasOwnPropertyName.call(rt, howpn, String::createFromAscii(rt, "1"))
          .getBool());
  EXPECT_TRUE(
      hasOwnPropertyName.call(rt, howpn, String::createFromAscii(rt, "false"))
          .getBool());
  EXPECT_TRUE(
      hasOwnPropertyName.call(rt, howpn, String::createFromAscii(rt, "3"))
          .getBool());
  EXPECT_TRUE(
      hasOwnPropertyName.call(rt, howpn, String::createFromAscii(rt, "c_prop"))
          .getBool());
  EXPECT_FALSE(hasOwnPropertyName
                   .call(rt, howpn, String::createFromAscii(rt, "not_existing"))
                   .getBool());
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, JSErrorDoesNotInfinitelyRecurse) {
  Value globalError = rt.global().getProperty(rt, "Error");
  rt.global().setProperty(rt, "Error", Value::undefined());
  try {
    rt.global().getPropertyAsFunction(rt, "NotAFunction");
    FAIL() << "expected exception";
  } catch (const JSError& ex) {
    EXPECT_EQ(
        ex.getMessage(),
        "callGlobalFunction: JS global property 'Error' is undefined, "
        "expected a Function (while raising getPropertyAsObject: "
        "property 'NotAFunction' is undefined, expected an Object)");
  }

  // If Error is missing, this is fundamentally a problem with JS code
  // messing up the global object, so it should present in JS code as
  // a catchable string.  Not an Error (because that's broken), or as
  // a C++ failure.

  auto fails = [](Runtime& rt, const Value&, const Value*, size_t) -> Value {
    return rt.global().getPropertyAsObject(rt, "NotAProperty");
  };
  EXPECT_EQ(
      function("function (f) { try { f(); return 'undefined'; }"
               "catch (e) { return typeof e; } }")
          .call(
              rt,
              Function::createFromHostFunction(
                  rt, PropNameID::forAscii(rt, "fails"), 0, fails))
          .getString(rt)
          .utf8(rt),
      "string");

  rt.global().setProperty(rt, "Error", globalError);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::defineOwnComputedPrimitive(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  assert(
      !nameValHandle->isObject() &&
      "nameValHandle passed to "
      "defineOwnComputedPrimitive() cannot be "
      "an object");
  assert(
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
  assert(
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
      "Cannot set both value and accessor");
  assert(
      (dpFlags.setValue || dpFlags.isAccessor() ||
       valueOrAccessor.get().isUndefined()) &&
      "value must be undefined when all of setValue/setSetter/setGetter are "
      "false");
  assert(
      !dpFlags.enableInternalSetter &&
      "Cannot set internalSetter on a computed property");
#ifndef NDEBUG
  if (dpFlags.isAccessor()) {
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
    assert(
        !dpFlags.setWritable && !dpFlags.writable &&
        "writable must not be set with accessors");
  }
#endif

  // If the name is a valid integer array index, store it here.
  OptValue<uint32_t> arrayIndex;

  // If we have indexed storage, we must attempt to convert the name to array
  // index, even if the conversion is expensive.
  if (selfHandle->flags_.indexedStorage) {
    MutableHandle<StringPrimitive> strPrim{runtime};
    TO_ARRAY_INDEX(runtime, nameValHandle, strPrim, arrayIndex);
  }

  SymbolID id{};

  // If not storing a property with an array index name, or if we don't have
  // indexed storage, just pass to the named routine.
  if (!arrayIndex) {
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
    return defineOwnProperty(
        selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
  }

  // At this point we know that we have indexed storage and that the property
  // has an index-like name.

  // First check if a named property with the same name exists.
  if (selfHandle->clazz_.get(runtime)->getHasIndexLikeProperties()) {
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);

    NamedPropertyDescriptor desc;
    auto pos = findProperty(selfHandle, runtime, id, desc);
    // If we found a named property, update it.
    if (pos) {
      return updateOwnProperty(
          selfHandle,
          runtime,
          id,
          *pos,
          desc,
          dpFlags,
          valueOrAccessor,
          opFlags);
    }
  }

  // Does an indexed property with that index exist?
  auto indexedPropPresent =
      getOwnIndexedPropertyFlags(selfHandle.get(), runtime, *arrayIndex);
  if (indexedPropPresent) {
    // The current value of the property.
    HermesValue curValueOrAccessor =
        getOwnIndexed(selfHandle.get(), runtime, *arrayIndex);

    auto updateStatus = checkPropertyUpdate(
        runtime,
        *indexedPropPresent,
        dpFlags,
        curValueOrAccessor,
        valueOrAccessor,
        opFlags);
    if (updateStatus == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    if (updateStatus->first == PropertyUpdateStatus::failed)
      return false;

    // The property update is valid, but can the property remain an "indexed"
    // property, or do we need to convert it to a named property?
    // If the property flags didn't change, the property remains indexed.
    if (updateStatus->second == *indexedPropPresent) {
      // If the value doesn't change, we are done.
      if (updateStatus->first == PropertyUpdateStatus::done)
        return true;

      // If we successfully updated the value, we are done.
      auto result =
          setOwnIndexed(selfHandle, runtime, *arrayIndex, valueOrAccessor);
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
        return ExecutionStatus::EXCEPTION;
      if (*result)
        return true;

      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError(
            "cannot change read-only property value");
      }

      return false;
    }

    // OK, we need to convert an indexed property to a named one.

    // Check whether to use the supplied value, or to reuse the old one, as we
    // are simply reconfiguring it.
    MutableHandle<> value{runtime};
    if (dpFlags.setValue || dpFlags.isAccessor()) {
      value = valueOrAccessor.get();
    } else {
      value = curValueOrAccessor;
    }

    // Update dpFlags to match the existing property flags.
    dpFlags.setEnumerable = 1;
    dpFlags.setWritable = 1;
    dpFlags.setConfigurable = 1;
    dpFlags.enumerable = updateStatus->second.enumerable;
    dpFlags.writable = updateStatus->second.writable;
    dpFlags.configurable = updateStatus->second.configurable;

    // Delete the existing indexed property.
    if (!deleteOwnIndexed(selfHandle, runtime, *arrayIndex)) {
      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError("Cannot define property");
      }
      return false;
    }

    // Add the new named property.
    LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
    return addOwnProperty(selfHandle, runtime, id, dpFlags, value, opFlags);
  }

  /// Can we add new properties?
  if (!selfHandle->isExtensible()) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          "cannot add a new property"); // TODO: better message.
    }
    return false;
  }

  // This is a new property with an index-like name.
  // Check whether we need to update array's ".length" property.
  bool updateLength = false;
  if (auto arrayHandle = Handle<JSArray>::dyn_vmcast(selfHandle)) {
    if (LLVM_UNLIKELY(*arrayIndex >= JSArray::getLength(*arrayHandle))) {
      NamedPropertyDescriptor lengthDesc;
      bool lengthPresent = getOwnNamedDescriptor(
          arrayHandle,
          runtime,
          Predefined::getSymbolID(Predefined::length),
          lengthDesc);
      (void)lengthPresent;
      assert(lengthPresent && ".length must be present in JSArray");

      if (!lengthDesc.flags.writable) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeError(
              "Cannot assign to read-only 'length' property of array");
        }
        return false;
      }

      updateLength = true;
    }
  }

  bool newIsIndexed = canNewPropertyBeIndexed(dpFlags);
  if (newIsIndexed) {
    auto result = setOwnIndexed(
        selfHandle,
        runtime,
        *arrayIndex,
        dpFlags.setValue ? valueOrAccessor : Runtime::getUndefinedValue());
    if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
      return ExecutionStatus::EXCEPTION;
    if (!*result) {
      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError("Cannot define property");
      }
      return false;
    }
  }

  // If this is an array and we need to update ".length", do so.
  if (updateLength) {
    // This should always succeed since we are simply enlarging the length.
    auto res = JSArray::setLength(
        Handle<JSArray>::vmcast(selfHandle), runtime, *arrayIndex + 1, opFlags);
    (void)res;
    assert(
        res != ExecutionStatus::EXCEPTION && *res &&
        "JSArray::setLength() failed unexpectedly");
  }

  if (newIsIndexed)
    return true;

  // We are adding a new property with an index-like name.
  LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);
  return addOwnProperty(
      selfHandle, runtime, id, dpFlags, valueOrAccessor, opFlags);
}

Project: hermes
File: JSObject.cpp

  static ObjectFlags &flags(JSObject *self) {
    return self->flags_;
  }

Project: hermes
File: testlib.cpp

TEST_P(JSITest, DecoratorTest) {
  struct Count {
    // init here is just to show that a With type does not need to be
    // default constructible.
    explicit Count(int init) : count(init) {}

    // Test optional before method.

    void after() {
      ++count;
    }

    int count;
  };

  static constexpr int kInit = 17;

  class CountRuntime final : public WithRuntimeDecorator<Count> {
   public:
    explicit CountRuntime(std::unique_ptr<Runtime> rt)
        : WithRuntimeDecorator<Count>(*rt, count_),
          rt_(std::move(rt)),
          count_(kInit) {}

    int count() {
      return count_.count;
    }

   private:
    std::unique_ptr<Runtime> rt_;
    Count count_;
  };

  CountRuntime crt(factory());

  crt.description();
  EXPECT_EQ(crt.count(), kInit + 1);

  crt.global().setProperty(crt, "o", Object(crt));
  EXPECT_EQ(crt.count(), kInit + 6);
}

Project: hermes
File: JSObject.cpp

JSObject::_setOwnIndexedImpl(Handle<JSObject>, Runtime *, uint32_t, Handle<>) {
  return false;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, HostObjectProtoTest) {
  class ProtoHostObject : public HostObject {
    Value get(Runtime& rt, const PropNameID&) override {
      return String::createFromAscii(rt, "phoprop");
    }
  };

  rt.global().setProperty(
      rt,
      "pho",
      Object::createFromHostObject(rt, std::make_shared<ProtoHostObject>()));

  EXPECT_EQ(
      eval("({__proto__: pho})[Symbol.toPrimitive]").getString(rt).utf8(rt),
      "phoprop");
}

Project: hermes
File: JSObject.cpp

ObjectID JSObject::getObjectID(JSObject *self, Runtime *runtime) {
  if (LLVM_LIKELY(self->flags_.objectID))
    return self->flags_.objectID;

  // Object ID does not yet exist, get next unique global ID..
  self->flags_.objectID = runtime->generateNextObjectID();
  // Make sure it is not zero.
  if (LLVM_UNLIKELY(!self->flags_.objectID))
    --self->flags_.objectID;
  return self->flags_.objectID;
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::getComputedDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    MutableHandle<JSObject> &propObj,
    ComputedPropertyDescriptor &desc) {
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  return getComputedPrimitiveDescriptor(
      selfHandle, runtime, *converted, propObj, desc);
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::addOwnPropertyImpl(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    PropertyFlags propertyFlags,
    Handle<> valueOrAccessor) {
  assert(
      !selfHandle->flags_.proxyObject &&
      "Internal properties cannot be added to Proxy objects");
  // Add a new property to the class.
  // TODO: if we check for OOM here in the future, we must undo the slot
  // allocation.
  auto addResult = HiddenClass::addProperty(
      runtime->makeHandle(selfHandle->clazz_), runtime, name, propertyFlags);
  if (LLVM_UNLIKELY(addResult == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  selfHandle->clazz_.set(runtime, *addResult->first, &runtime->getHeap());

  allocateNewSlotStorage(
      selfHandle, runtime, addResult->second, valueOrAccessor);

  // If this is an index-like property, we need to clear the fast path flags.
  if (LLVM_UNLIKELY(
          selfHandle->clazz_.getNonNull(runtime)->getHasIndexLikeProperties()))
    selfHandle->flags_.fastIndexProperties = false;

  return ExecutionStatus::RETURNED;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, EqualsTest) {
  EXPECT_TRUE(Object::strictEquals(rt, rt.global(), rt.global()));
  EXPECT_TRUE(Value::strictEquals(rt, 1, 1));
  EXPECT_FALSE(Value::strictEquals(rt, true, 1));
  EXPECT_FALSE(Value::strictEquals(rt, true, false));
  EXPECT_TRUE(Value::strictEquals(rt, false, false));
  EXPECT_FALSE(Value::strictEquals(rt, nullptr, 1));
  EXPECT_TRUE(Value::strictEquals(rt, nullptr, nullptr));
  EXPECT_TRUE(Value::strictEquals(rt, Value::undefined(), Value()));
  EXPECT_TRUE(Value::strictEquals(rt, rt.global(), Value(rt.global())));
  EXPECT_FALSE(Value::strictEquals(
      rt,
      std::numeric_limits<double>::quiet_NaN(),
      std::numeric_limits<double>::quiet_NaN()));
  EXPECT_FALSE(Value::strictEquals(
      rt,
      std::numeric_limits<double>::signaling_NaN(),
      std::numeric_limits<double>::signaling_NaN()));
  EXPECT_TRUE(Value::strictEquals(rt, +0.0, -0.0));
  EXPECT_TRUE(Value::strictEquals(rt, -0.0, +0.0));

  Function noop = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "noop"),
      0,
      [](const Runtime&, const Value&, const Value*, size_t) {
        return Value();
      });
  auto noopDup = Value(rt, noop).getObject(rt);
  EXPECT_TRUE(Object::strictEquals(rt, noop, noopDup));
  EXPECT_TRUE(Object::strictEquals(rt, noopDup, noop));
  EXPECT_FALSE(Object::strictEquals(rt, noop, rt.global()));
  EXPECT_TRUE(Object::strictEquals(rt, noop, noop));
  EXPECT_TRUE(Value::strictEquals(rt, Value(rt, noop), Value(rt, noop)));

  String str = String::createFromAscii(rt, "rick");
  String strDup = String::createFromAscii(rt, "rick");
  String otherStr = String::createFromAscii(rt, "morty");
  EXPECT_TRUE(String::strictEquals(rt, str, str));
  EXPECT_TRUE(String::strictEquals(rt, str, strDup));
  EXPECT_TRUE(String::strictEquals(rt, strDup, str));
  EXPECT_FALSE(String::strictEquals(rt, str, otherStr));
  EXPECT_TRUE(Value::strictEquals(rt, Value(rt, str), Value(rt, str)));
  EXPECT_FALSE(Value::strictEquals(rt, Value(rt, str), Value(rt, noop)));
  EXPECT_FALSE(Value::strictEquals(rt, Value(rt, str), 1.0));
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::getOwnComputedPrimitiveDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    JSObject::IgnoreProxy ignoreProxy,
    ComputedPropertyDescriptor &desc) {
  SymbolID id{};

  return getOwnComputedPrimitiveDescriptorImpl(
      selfHandle, runtime, nameValHandle, ignoreProxy, id, desc);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::preventExtensions(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    PropOpFlags opFlags) {
  if (LLVM_UNLIKELY(selfHandle->isProxyObject())) {
    return JSProxy::preventExtensions(selfHandle, runtime, opFlags);
  }
  JSObject::preventExtensions(*selfHandle);
  return true;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, PreparedJavaScriptSourceTest) {
  rt.evaluateJavaScript(std::make_unique<StringBuffer>("var q = 0;"), "");
  auto prep = rt.prepareJavaScript(std::make_unique<StringBuffer>("q++;"), "");
  EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 0);
  rt.evaluatePreparedJavaScript(prep);
  EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 1);
  rt.evaluatePreparedJavaScript(prep);
  EXPECT_EQ(rt.global().getProperty(rt, "q").getNumber(), 2);
}

Project: hermes
File: JSObject.cpp

void JSObject::preventExtensions(JSObject *self) {
  assert(
      !self->flags_.proxyObject &&
      "[[Extensible]] slot cannot be set directly on Proxy objects");
  self->flags_.noExtend = true;
}

Project: hermes
File: JSObject.cpp

void JSObject::_snapshotAddEdgesImpl(GCCell *cell, GC *gc, HeapSnapshot &snap) {
  auto *const self = vmcast<JSObject>(cell);

  // Add the prototype as a property edge, so it's easy for JS developers to
  // walk the prototype chain on their own.
  if (self->parent_) {
    snap.addNamedEdge(
        HeapSnapshot::EdgeType::Property,
        // __proto__ chosen for similarity to V8.
        "__proto__",
        gc->getObjectID(self->parent_));
  }

  HiddenClass::forEachPropertyNoAlloc(
      self->clazz_.get(gc->getPointerBase()),
      gc->getPointerBase(),
      [self, gc, &snap](SymbolID id, NamedPropertyDescriptor desc) {
        if (InternalProperty::isInternal(id)) {
          // Internal properties aren't user-visible, skip them.
          return;
        }
        // Else, it's a user-visible property.
        GCHermesValue &prop =
            namedSlotRef(self, gc->getPointerBase(), desc.slot);
        const llvh::Optional<HeapSnapshot::NodeID> idForProp =
            gc->getSnapshotID(prop);
        if (!idForProp) {
          return;
        }
        std::string propName = gc->convertSymbolToUTF8(id);
        // If the property name is a valid array index, display it as an
        // "element" instead of a "property". This will put square brackets
        // around the number and sort it numerically rather than
        // alphabetically.
        if (auto index = ::hermes::toArrayIndex(propName)) {
          snap.addIndexedEdge(
              HeapSnapshot::EdgeType::Element,
              index.getValue(),
              idForProp.getValue());
        } else {
          snap.addNamedEdge(
              HeapSnapshot::EdgeType::Property, propName, idForProp.getValue());
        }
      });
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::hasComputed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle) {
  // Try the fast-path first: no "index-like" properties and the "name" already
  // is a valid integer index.
  if (selfHandle->flags_.fastIndexProperties) {
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
      // Do we have this value present in our array storage? If so, return true.
      if (haveOwnIndexed(selfHandle.get(), runtime, *arrayIndex)) {
        return true;
      }
    }
  }

  // If nameValHandle is an object, we should convert it to string now,
  // because toString may have side-effect, and we want to do this only
  // once.
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  auto nameValPrimitiveHandle = *converted;

  ComputedPropertyDescriptor desc;
  MutableHandle<JSObject> propObj{runtime};
  if (getComputedPrimitiveDescriptor(
          selfHandle, runtime, nameValPrimitiveHandle, propObj, desc) ==
      ExecutionStatus::EXCEPTION) {
    return ExecutionStatus::EXCEPTION;
  }
  if (!propObj) {
    return false;
  }
  if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
    if (key == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    return JSProxy::hasComputed(propObj, runtime, *key);
  }
  // For compatibility with polyfills we want to pretend that all HostObject
  // properties are "own" properties in 'in'. Since there is no way to check for
  // a HostObject property, we must always assume success. In practice the
  // property name would have been obtained from enumerating the properties in
  // JS code that looks something like this:
  //    for(key in hostObj) {
  //      if (key in hostObj)
  //        ...
  //    }
  return true;
}

Project: hermes
File: JSObject.cpp

JSObject *JSObject::getNamedDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    PropertyFlags expectedFlags,
    NamedPropertyDescriptor &desc) {
  if (findProperty(selfHandle, runtime, name, expectedFlags, desc))
    return *selfHandle;

  // Check here for host object flag.  This means that "normal" own
  // properties above win over host-defined properties, but there's no
  // cost imposed on own property lookups.  This should do what we
  // need in practice, and we can define host vs js property
  // disambiguation however we want.  This is here in order to avoid
  // impacting perf for the common case where an own property exists
  // in normal storage.
  if (LLVM_UNLIKELY(selfHandle->flags_.hostObject)) {
    desc.flags.hostObject = true;
    desc.flags.writable = true;
    return *selfHandle;
  }

  if (LLVM_UNLIKELY(selfHandle->flags_.lazyObject)) {
    assert(
        !selfHandle->flags_.proxyObject &&
        "Proxy objects should never be lazy");
    // Initialize the object and perform the lookup again.
    JSObject::initializeLazyObject(runtime, selfHandle);

    if (findProperty(selfHandle, runtime, name, expectedFlags, desc))
      return *selfHandle;
  }

  if (LLVM_UNLIKELY(selfHandle->flags_.proxyObject)) {
    desc.flags.proxyObject = true;
    return *selfHandle;
  }

  if (selfHandle->parent_) {
    MutableHandle<JSObject> mutableSelfHandle{
        runtime, selfHandle->parent_.getNonNull(runtime)};

    do {
      // Check the most common case first, at the cost of some code duplication.
      if (LLVM_LIKELY(
              !mutableSelfHandle->flags_.lazyObject &&
              !mutableSelfHandle->flags_.hostObject &&
              !mutableSelfHandle->flags_.proxyObject)) {
      findProp:
        if (findProperty(
                mutableSelfHandle,
                runtime,
                name,
                PropertyFlags::invalid(),
                desc)) {
          assert(
              !selfHandle->flags_.proxyObject &&
              "Proxy object parents should never have own properties");
          return *mutableSelfHandle;
        }
      } else if (LLVM_UNLIKELY(mutableSelfHandle->flags_.lazyObject)) {
        JSObject::initializeLazyObject(runtime, mutableSelfHandle);
        goto findProp;
      } else if (LLVM_UNLIKELY(mutableSelfHandle->flags_.hostObject)) {
        desc.flags.hostObject = true;
        desc.flags.writable = true;
        return *mutableSelfHandle;
      } else {
        assert(
            mutableSelfHandle->flags_.proxyObject &&
            "descriptor flags are impossible");
        desc.flags.proxyObject = true;
        return *mutableSelfHandle;
      }
    } while ((mutableSelfHandle = mutableSelfHandle->parent_.get(runtime)));
  }

  return nullptr;
}

Project: hermes
File: JSObject.cpp

PropertyAccessor::PropertyAccessor(Deserializer &d)
    : GCCell(&d.getRuntime()->getHeap(), &vt) {
  d.readRelocation(&getter, RelocationKind::GCPointer);
  d.readRelocation(&setter, RelocationKind::GCPointer);
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getNamedPropertyValue_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<JSObject> propObj,
    NamedPropertyDescriptor desc) {
  assert(
      !selfHandle->flags_.proxyObject && !propObj->flags_.proxyObject &&
      "getNamedPropertyValue_RJS cannot be used with proxy objects");

  if (LLVM_LIKELY(!desc.flags.accessor))
    return createPseudoHandle(getNamedSlotValue(propObj.get(), runtime, desc));

  auto *accessor =
      vmcast<PropertyAccessor>(getNamedSlotValue(propObj.get(), runtime, desc));
  if (!accessor->getter)
    return createPseudoHandle(HermesValue::encodeUndefinedValue());

  // Execute the accessor on this object.
  return accessor->getter.get(runtime)->executeCall0(
      runtime->makeHandle(accessor->getter), runtime, selfHandle);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::hasNamed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name) {
  NamedPropertyDescriptor desc;
  JSObject *propObj = getNamedDescriptor(selfHandle, runtime, name, desc);
  if (propObj == nullptr) {
    return false;
  }
  if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
    return JSProxy::hasNamed(runtime->makeHandle(propObj), runtime, name);
  }
  return true;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, HostFunctionTest) {
  auto one = std::make_shared<int>(1);
  Function plusOne = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "plusOne"),
      2,
      [one, savedRt = &rt](
          Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
        EXPECT_EQ(savedRt, &rt);
        // We don't know if we're in strict mode or not, so it's either global
        // or undefined.
        EXPECT_TRUE(
            Value::strictEquals(rt, thisVal, rt.global()) ||
            thisVal.isUndefined());
        return *one + args[0].getNumber() + args[1].getNumber();
      });

  EXPECT_EQ(plusOne.call(rt, 1, 2).getNumber(), 4);
  EXPECT_TRUE(checkValue(plusOne.call(rt, 3, 5), "9"));
  rt.global().setProperty(rt, "plusOne", plusOne);
  EXPECT_TRUE(eval("plusOne(20, 300) == 321").getBool());

  Function dot = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "dot"),
      2,
      [](Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
        EXPECT_TRUE(
            Value::strictEquals(rt, thisVal, rt.global()) ||
            thisVal.isUndefined());
        if (count != 2) {
          throw std::runtime_error("expected 2 args");
        }
        std::string ret = args[0].getString(rt).utf8(rt) + "." +
            args[1].getString(rt).utf8(rt);
        return String::createFromUtf8(
            rt, reinterpret_cast<const uint8_t*>(ret.data()), ret.size());
      });

  rt.global().setProperty(rt, "cons", dot);
  EXPECT_TRUE(eval("cons('left', 'right') == 'left.right'").getBool());
  EXPECT_TRUE(eval("cons.name == 'dot'").getBool());
  EXPECT_TRUE(eval("cons.length == 2").getBool());
  EXPECT_TRUE(eval("cons instanceof Function").getBool());

  EXPECT_TRUE(eval("(function() {"
                   "  try {"
                   "    cons('fail'); return false;"
                   "  } catch (e) {"
                   "    return ((e instanceof Error) &&"
                   "            (e.message == 'Exception in HostFunction: ' +"
                   "                          'expected 2 args'));"
                   "  }})()")
                  .getBool());

  Function coolify = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "coolify"),
      0,
      [](Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
        EXPECT_EQ(count, 0);
        std::string ret = thisVal.toString(rt).utf8(rt) + " is cool";
        return String::createFromUtf8(
            rt, reinterpret_cast<const uint8_t*>(ret.data()), ret.size());
      });
  rt.global().setProperty(rt, "coolify", coolify);
  EXPECT_TRUE(eval("coolify.name == 'coolify'").getBool());
  EXPECT_TRUE(eval("coolify.length == 0").getBool());
  EXPECT_TRUE(eval("coolify.bind('R&M')() == 'R&M is cool'").getBool());
  EXPECT_TRUE(eval("(function() {"
                   "  var s = coolify.bind(function(){})();"
                   "  return s.lastIndexOf(' is cool') == (s.length - 8);"
                   "})()")
                  .getBool());

  Function lookAtMe = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "lookAtMe"),
      0,
      [](Runtime& rt, const Value& thisVal, const Value* args, size_t count)
          -> Value {
        EXPECT_TRUE(thisVal.isObject());
        EXPECT_EQ(
            thisVal.getObject(rt)
                .getProperty(rt, "name")
                .getString(rt)
                .utf8(rt),
            "mr.meeseeks");
        return Value();
      });
  rt.global().setProperty(rt, "lookAtMe", lookAtMe);
  EXPECT_TRUE(eval("lookAtMe.bind({'name': 'mr.meeseeks'})()").isUndefined());

  struct Callable {
    Callable(std::string s) : str(s) {}

    Value
    operator()(Runtime& rt, const Value&, const Value* args, size_t count) {
      if (count != 1) {
        return Value();
      }
      return String::createFromUtf8(
          rt, args[0].toString(rt).utf8(rt) + " was called with " + str);
    }

    std::string str;
  };

  Function callable = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "callable"),
      1,
      Callable("std::function::target"));
  EXPECT_EQ(
      function("function (f) { return f('A cat'); }")
          .call(rt, callable)
          .getString(rt)
          .utf8(rt),
      "A cat was called with std::function::target");
  EXPECT_TRUE(callable.isHostFunction(rt));
  EXPECT_NE(callable.getHostFunction(rt).target<Callable>(), nullptr);

  std::string strval = "strval1";
  auto getter = Object(rt);
  getter.setProperty(
      rt,
      "get",
      Function::createFromHostFunction(
          rt,
          PropNameID::forAscii(rt, "getter"),
          1,
          [&strval](
              Runtime& rt,
              const Value& thisVal,
              const Value* args,
              size_t count) -> Value {
            return String::createFromUtf8(rt, strval);
          }));
  auto obj = Object(rt);
  rt.global()
      .getPropertyAsObject(rt, "Object")
      .getPropertyAsFunction(rt, "defineProperty")
      .call(rt, obj, "prop", getter);
  EXPECT_TRUE(function("function(value) { return value.prop == 'strval1'; }")
                  .call(rt, obj)
                  .getBool());
  strval = "strval2";
  EXPECT_TRUE(function("function(value) { return value.prop == 'strval2'; }")
                  .call(rt, obj)
                  .getBool());
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::defineNewOwnProperty(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    PropertyFlags propertyFlags,
    Handle<> valueOrAccessor) {
  assert(
      !selfHandle->flags_.proxyObject &&
      "definedNewOwnProperty cannot be used with proxy objects");
  assert(
      !(propertyFlags.accessor && !valueOrAccessor.get().isPointer()) &&
      "accessor must be non-empty");
  assert(
      !(propertyFlags.accessor && propertyFlags.writable) &&
      "writable must not be set with accessors");
  assert(
      !HiddenClass::debugIsPropertyDefined(
          selfHandle->clazz_.get(runtime), runtime, name) &&
      "new property is already defined");

  return addOwnPropertyImpl(
      selfHandle, runtime, name, propertyFlags, valueOrAccessor);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::defineOwnProperty(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() && "cannot use mustExist with defineOwnProperty");
  assert(
      !(dpFlags.setValue && dpFlags.isAccessor()) &&
      "Cannot set both value and accessor");
  assert(
      (dpFlags.setValue || dpFlags.isAccessor() ||
       valueOrAccessor.get().isUndefined()) &&
      "value must be undefined when all of setValue/setSetter/setGetter are "
      "false");
#ifndef NDEBUG
  if (dpFlags.isAccessor()) {
    assert(valueOrAccessor.get().isPointer() && "accessor must be non-empty");
    assert(
        !dpFlags.setWritable && !dpFlags.writable &&
        "writable must not be set with accessors");
  }
#endif

  // Is it an existing property.
  NamedPropertyDescriptor desc;
  auto pos = findProperty(selfHandle, runtime, name, desc);
  if (pos) {
    return updateOwnProperty(
        selfHandle,
        runtime,
        name,
        *pos,
        desc,
        dpFlags,
        valueOrAccessor,
        opFlags);
  }

  if (LLVM_UNLIKELY(
          selfHandle->flags_.lazyObject || selfHandle->flags_.proxyObject)) {
    if (selfHandle->flags_.proxyObject) {
      return JSProxy::defineOwnProperty(
          selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
    }
    assert(selfHandle->flags_.lazyObject && "descriptor flags are impossible");
    // if the property was not found and the object is lazy we need to
    // initialize it and try again.
    JSObject::initializeLazyObject(runtime, selfHandle);
    return defineOwnProperty(
        selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
  }

  return addOwnProperty(
      selfHandle, runtime, name, dpFlags, valueOrAccessor, opFlags);
}

Project: hermes
File: JSObject.cpp

void JSObject::serializeObjectImpl(
    Serializer &s,
    const GCCell *cell,
    unsigned overlapSlots) {
  auto *self = vmcast<const JSObject>(cell);
  s.writeData(&self->flags_, sizeof(ObjectFlags));
  s.writeRelocation(self->parent_.get(s.getRuntime()));
  s.writeRelocation(self->clazz_.get(s.getRuntime()));
  // propStorage_ : GCPointer<PropStorage> is also ArrayStorage. Serialize
  // *propStorage_ with this JSObject.
  bool hasArray = (bool)self->propStorage_;
  s.writeInt<uint8_t>(hasArray);
  if (hasArray) {
    ArrayStorage::serializeArrayStorage(
        s, self->propStorage_.get(s.getRuntime()));
  }

  // Record the number of overlap slots, so that the deserialization code
  // doesn't need to keep track of it.
  s.writeInt<uint8_t>(overlapSlots);
  for (size_t i = overlapSlots; i < JSObject::DIRECT_PROPERTY_SLOTS; i++) {
    s.writeHermesValue(self->directProps()[i]);
  }
}

Project: hermes
File: testlib.cpp

unsigned countOccurences(const std::string& of, const std::string& in) {
  unsigned occurences = 0;
  std::string::size_type lastOccurence = -1;
  while ((lastOccurence = in.find(of, lastOccurence + 1)) !=
         std::string::npos) {
    occurences++;
  }
  return occurences;
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getNamedOrIndexed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    PropOpFlags opFlags) {
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
    // Note that getStringView can be satisfied without materializing the
    // Identifier.
    const auto strView =
        runtime->getIdentifierTable().getStringView(runtime, name);
    if (auto nameAsIndex = toArrayIndex(strView)) {
      return getComputed_RJS(
          selfHandle,
          runtime,
          runtime->makeHandle(HermesValue::encodeNumberValue(*nameAsIndex)));
    }
    // Here we have indexed properties but the symbol was not index-like.
    // Fall through to getNamed().
  }
  return getNamed_RJS(selfHandle, runtime, name, opFlags);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::deleteNamed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() && "mustExist cannot be specified when deleting");

  // Find the property by name.
  NamedPropertyDescriptor desc;
  auto pos = findProperty(selfHandle, runtime, name, desc);

  // If the property doesn't exist in this object, return success.
  if (!pos) {
    if (LLVM_LIKELY(
            !selfHandle->flags_.lazyObject &&
            !selfHandle->flags_.proxyObject)) {
      return true;
    } else if (selfHandle->flags_.lazyObject) {
      // object is lazy, initialize and read again.
      initializeLazyObject(runtime, selfHandle);
      pos = findProperty(selfHandle, runtime, name, desc);
      if (!pos) // still not there, return true.
        return true;
    } else {
      assert(selfHandle->flags_.proxyObject && "object flags are impossible");
      return proxyOpFlags(
          runtime,
          opFlags,
          "Proxy delete returned false",
          JSProxy::deleteNamed(selfHandle, runtime, name));
    }
  }
  // If the property isn't configurable, fail.
  if (LLVM_UNLIKELY(!desc.flags.configurable)) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          TwineChar16("Property '") +
          runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
          "' is not configurable");
    }
    return false;
  }

  // Clear the deleted property value to prevent memory leaks.
  setNamedSlotValue(
      *selfHandle, runtime, desc, HermesValue::encodeEmptyValue());

  // Perform the actual deletion.
  auto newClazz = HiddenClass::deleteProperty(
      runtime->makeHandle(selfHandle->clazz_), runtime, *pos);
  selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());

  return true;
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getComputedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    Handle<> receiver) {
  // Try the fast-path first: no "index-like" properties and the "name" already
  // is a valid integer index.
  if (selfHandle->flags_.fastIndexProperties) {
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
      // Do we have this value present in our array storage? If so, return it.
      PseudoHandle<> ourValue = createPseudoHandle(
          getOwnIndexed(selfHandle.get(), runtime, *arrayIndex));
      if (LLVM_LIKELY(!ourValue->isEmpty()))
        return ourValue;
    }
  }

  // If nameValHandle is an object, we should convert it to string now,
  // because toString may have side-effect, and we want to do this only
  // once.
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  auto nameValPrimitiveHandle = *converted;

  ComputedPropertyDescriptor desc;

  // Locate the descriptor. propObj contains the object which may be anywhere
  // along the prototype chain.
  MutableHandle<JSObject> propObj{runtime};
  if (LLVM_UNLIKELY(
          getComputedPrimitiveDescriptor(
              selfHandle, runtime, nameValPrimitiveHandle, propObj, desc) ==
          ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }

  if (!propObj)
    return createPseudoHandle(HermesValue::encodeUndefinedValue());

  if (LLVM_LIKELY(
          !desc.flags.accessor && !desc.flags.hostObject &&
          !desc.flags.proxyObject))
    return createPseudoHandle(
        getComputedSlotValue(propObj.get(), runtime, desc));

  if (desc.flags.accessor) {
    auto *accessor = vmcast<PropertyAccessor>(
        getComputedSlotValue(propObj.get(), runtime, desc));
    if (!accessor->getter)
      return createPseudoHandle(HermesValue::encodeUndefinedValue());

    // Execute the accessor on this object.
    return accessor->getter.get(runtime)->executeCall0(
        runtime->makeHandle(accessor->getter), runtime, receiver);
  } else if (desc.flags.hostObject) {
    SymbolID id{};
    LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
    auto propRes = vmcast<HostObject>(propObj.get())->get(id);
    if (propRes == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    return createPseudoHandle(*propRes);
  } else {
    assert(desc.flags.proxyObject && "descriptor flags are impossible");
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
    if (key == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    return JSProxy::getComputed(propObj, runtime, *key, receiver);
  }
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, SymbolTest) {
  if (!rt.global().hasProperty(rt, "Symbol")) {
    // Symbol is an es6 feature which doesn't exist in older VMs.  So
    // the tests which might be elsewhere are all here so they can be
    // skipped.
    return;
  }

  // ObjectTest
  eval("x = {1:2, 'three':Symbol('four')}");
  Object x = rt.global().getPropertyAsObject(rt, "x");
  EXPECT_EQ(x.getPropertyNames(rt).size(rt), 2);
  EXPECT_TRUE(x.hasProperty(rt, "three"));
  EXPECT_EQ(
      x.getProperty(rt, "three").getSymbol(rt).toString(rt), "Symbol(four)");

  // ValueTest
  EXPECT_TRUE(eval("Symbol('sym')").isSymbol());
  EXPECT_EQ(eval("Symbol('sym')").getSymbol(rt).toString(rt), "Symbol(sym)");

  // EqualsTest
  EXPECT_FALSE(Symbol::strictEquals(
      rt,
      eval("Symbol('a')").getSymbol(rt),
      eval("Symbol('a')").getSymbol(rt)));
  EXPECT_TRUE(Symbol::strictEquals(
      rt,
      eval("Symbol.for('a')").getSymbol(rt),
      eval("Symbol.for('a')").getSymbol(rt)));
  EXPECT_FALSE(
      Value::strictEquals(rt, eval("Symbol('a')"), eval("Symbol('a')")));
  EXPECT_TRUE(Value::strictEquals(
      rt, eval("Symbol.for('a')"), eval("Symbol.for('a')")));
  EXPECT_FALSE(Value::strictEquals(rt, eval("Symbol('a')"), eval("'a'")));
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::internalSetter(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    NamedPropertyDescriptor /*desc*/,
    Handle<> value,
    PropOpFlags opFlags) {
  if (vmisa<JSArray>(selfHandle.get())) {
    if (name == Predefined::getSymbolID(Predefined::length)) {
      return JSArray::setLength(
          Handle<JSArray>::vmcast(selfHandle), runtime, value, opFlags);
    }
  }

  llvm_unreachable("unhandled property in Object::internalSetter()");
}

Project: hermes
File: JSObject.cpp

void PropertyAccessorSerialize(Serializer &s, const GCCell *cell) {
  auto *self = vmcast<const PropertyAccessor>(cell);
  s.writeRelocation(self->getter.get(s.getRuntime()));
  s.writeRelocation(self->setter.get(s.getRuntime()));
  s.endObject(cell);
}

Project: hermes
File: JSObject.cpp

bool JSObject::isSealed(PseudoHandle<JSObject> self, Runtime *runtime) {
  if (self->flags_.sealed)
    return true;
  if (!self->flags_.noExtend)
    return false;

  auto selfHandle = runtime->makeHandle(std::move(self));

  if (!HiddenClass::areAllNonConfigurable(
          runtime->makeHandle(selfHandle->clazz_), runtime)) {
    return false;
  }

  if (!checkAllOwnIndexed(
          *selfHandle,
          runtime,
          ObjectVTable::CheckAllOwnIndexedMode::NonConfigurable)) {
    return false;
  }

  // Now that we know we are sealed, set the flag.
  selfHandle->flags_.sealed = true;
  return true;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ObjectTest) {
  eval("x = {1:2, '3':4, 5:'six', 'seven':['eight', 'nine']}");
  Object x = rt.global().getPropertyAsObject(rt, "x");
  EXPECT_EQ(x.getPropertyNames(rt).size(rt), 4);
  EXPECT_TRUE(x.hasProperty(rt, "1"));
  EXPECT_TRUE(x.hasProperty(rt, PropNameID::forAscii(rt, "1")));
  EXPECT_FALSE(x.hasProperty(rt, "2"));
  EXPECT_FALSE(x.hasProperty(rt, PropNameID::forAscii(rt, "2")));
  EXPECT_TRUE(x.hasProperty(rt, "3"));
  EXPECT_TRUE(x.hasProperty(rt, PropNameID::forAscii(rt, "3")));
  EXPECT_TRUE(x.hasProperty(rt, "seven"));
  EXPECT_TRUE(x.hasProperty(rt, PropNameID::forAscii(rt, "seven")));
  EXPECT_EQ(x.getProperty(rt, "1").getNumber(), 2);
  EXPECT_EQ(x.getProperty(rt, PropNameID::forAscii(rt, "1")).getNumber(), 2);
  EXPECT_EQ(x.getProperty(rt, "3").getNumber(), 4);
  Value five = 5;
  EXPECT_EQ(
      x.getProperty(rt, PropNameID::forString(rt, five.toString(rt)))
          .getString(rt)
          .utf8(rt),
      "six");

  x.setProperty(rt, "ten", 11);
  EXPECT_EQ(x.getPropertyNames(rt).size(rt), 5);
  EXPECT_TRUE(eval("x.ten == 11").getBool());

  x.setProperty(rt, "e_as_float", 2.71f);
  EXPECT_TRUE(eval("Math.abs(x.e_as_float - 2.71) < 0.001").getBool());

  x.setProperty(rt, "e_as_double", 2.71);
  EXPECT_TRUE(eval("x.e_as_double == 2.71").getBool());

  uint8_t utf8[] = {0xF0, 0x9F, 0x86, 0x97};
  String nonAsciiName = String::createFromUtf8(rt, utf8, sizeof(utf8));
  x.setProperty(rt, PropNameID::forString(rt, nonAsciiName), "emoji");
  EXPECT_EQ(x.getPropertyNames(rt).size(rt), 8);
  EXPECT_TRUE(eval("x['\\uD83C\\uDD97'] == 'emoji'").getBool());

  Object seven = x.getPropertyAsObject(rt, "seven");
  EXPECT_TRUE(seven.isArray(rt));
  Object evalf = rt.global().getPropertyAsObject(rt, "eval");
  EXPECT_TRUE(evalf.isFunction(rt));

  Object movedX = Object(rt);
  movedX = std::move(x);
  EXPECT_EQ(movedX.getPropertyNames(rt).size(rt), 8);
  EXPECT_EQ(movedX.getProperty(rt, "1").getNumber(), 2);

  Object obj = Object(rt);
  obj.setProperty(rt, "roses", "red");
  obj.setProperty(rt, "violets", "blue");
  Object oprop = Object(rt);
  obj.setProperty(rt, "oprop", oprop);
  obj.setProperty(rt, "aprop", Array(rt, 1));

  EXPECT_TRUE(function("function (obj) { return "
                       "obj.roses == 'red' && "
                       "obj['violets'] == 'blue' && "
                       "typeof obj.oprop == 'object' && "
                       "Array.isArray(obj.aprop); }")
                  .call(rt, obj)
                  .getBool());

  // Check that getPropertyNames doesn't return non-enumerable
  // properties.
  obj = function(
            "function () {"
            "  obj = {};"
            "  obj.a = 1;"
            "  Object.defineProperty(obj, 'b', {"
            "    enumerable: false,"
            "    value: 2"
            "  });"
            "  return obj;"
            "}")
            .call(rt)
            .getObject(rt);
  EXPECT_EQ(obj.getProperty(rt, "a").getNumber(), 1);
  EXPECT_EQ(obj.getProperty(rt, "b").getNumber(), 2);
  Array names = obj.getPropertyNames(rt);
  EXPECT_EQ(names.size(rt), 1);
  EXPECT_EQ(names.getValueAtIndex(rt, 0).getString(rt).utf8(rt), "a");
}

Project: hermes
File: JSObject.cpp

JSObject::JSObject(Deserializer &d, const VTable *vtp)
    : GCCell(&d.getRuntime()->getHeap(), vtp) {
  d.readData(&flags_, sizeof(ObjectFlags));
  d.readRelocation(&parent_, RelocationKind::GCPointer);
  d.readRelocation(&clazz_, RelocationKind::GCPointer);
  if (d.readInt<uint8_t>()) {
    propStorage_.set(
        d.getRuntime(),
        ArrayStorage::deserializeArrayStorage(d),
        &d.getRuntime()->getHeap());
  }

  auto overlapSlots = d.readInt<uint8_t>();
  for (size_t i = overlapSlots; i < JSObject::DIRECT_PROPERTY_SLOTS; i++) {
    d.readHermesValue(&directProps()[i]);
  }
}

Project: hermes
File: JSObject.cpp

std::pair<uint32_t, uint32_t> JSObject::_getOwnIndexedRangeImpl(
    JSObject *self,
    Runtime *runtime) {
  return {0, 0};
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ScopeDoesNotCrashWhenValueEscapes) {
  Value v;
  Scope::callInNewScope(rt, [&]() {
    Object o(rt);
    o.setProperty(rt, "a", 5);
    v = std::move(o);
  });
  EXPECT_EQ(v.getObject(rt).getProperty(rt, "a").getNumber(), 5);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::addOwnProperty(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  /// Can we add more properties?
  if (!selfHandle->isExtensible() && !opFlags.getInternalForce()) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          TwineChar16("Cannot add new property '") +
          runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
          "'");
    }
    return false;
  }

  PropertyFlags flags{};

  // Accessors don't set writeable.
  if (dpFlags.isAccessor()) {
    dpFlags.setWritable = 0;
    flags.accessor = 1;
  }

  // Override the default flags if specified.
  if (dpFlags.setEnumerable)
    flags.enumerable = dpFlags.enumerable;
  if (dpFlags.setWritable)
    flags.writable = dpFlags.writable;
  if (dpFlags.setConfigurable)
    flags.configurable = dpFlags.configurable;
  flags.internalSetter = dpFlags.enableInternalSetter;

  if (LLVM_UNLIKELY(
          addOwnPropertyImpl(
              selfHandle, runtime, name, flags, valueOrAccessor) ==
          ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }

  return true;
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::defineOwnComputed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION))
    return ExecutionStatus::EXCEPTION;
  return defineOwnComputedPrimitive(
      selfHandle, runtime, *converted, dpFlags, valueOrAccessor, opFlags);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, FunctionThisTest) {
  Function checkPropertyFunction =
      function("function() { return this.a === 'a_property' }");

  Object jsObject = Object(rt);
  jsObject.setProperty(rt, "a", String::createFromUtf8(rt, "a_property"));

  class APropertyHostObject : public HostObject {
    Value get(Runtime& rt, const PropNameID& sym) override {
      return String::createFromUtf8(rt, "a_property");
    }

    void set(Runtime&, const PropNameID&, const Value&) override {}
  };
  Object hostObject =
      Object::createFromHostObject(rt, std::make_shared<APropertyHostObject>());

  EXPECT_TRUE(checkPropertyFunction.callWithThis(rt, jsObject).getBool());
  EXPECT_TRUE(checkPropertyFunction.callWithThis(rt, hostObject).getBool());
  EXPECT_FALSE(checkPropertyFunction.callWithThis(rt, Array(rt, 5)).getBool());
  EXPECT_FALSE(checkPropertyFunction.call(rt).getBool());
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<JSObject>> JSObject::getPrototypeOf(
    PseudoHandle<JSObject> selfHandle,
    Runtime *runtime) {
  if (LLVM_LIKELY(!selfHandle->isProxyObject())) {
    return createPseudoHandle(selfHandle->getParent(runtime));
  }

  return JSProxy::getPrototypeOf(
      runtime->makeHandle(std::move(selfHandle)), runtime);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ArrayTest) {
  eval("x = {1:2, '3':4, 5:'six', 'seven':['eight', 'nine']}");

  Object x = rt.global().getPropertyAsObject(rt, "x");
  Array names = x.getPropertyNames(rt);
  EXPECT_EQ(names.size(rt), 4);
  std::unordered_set<std::string> strNames;
  for (size_t i = 0; i < names.size(rt); ++i) {
    Value n = names.getValueAtIndex(rt, i);
    EXPECT_TRUE(n.isString());
    strNames.insert(n.getString(rt).utf8(rt));
  }

  EXPECT_EQ(strNames.size(), 4);
  EXPECT_EQ(strNames.count("1"), 1);
  EXPECT_EQ(strNames.count("3"), 1);
  EXPECT_EQ(strNames.count("5"), 1);
  EXPECT_EQ(strNames.count("seven"), 1);

  Object seven = x.getPropertyAsObject(rt, "seven");
  Array arr = seven.getArray(rt);

  EXPECT_EQ(arr.size(rt), 2);
  EXPECT_EQ(arr.getValueAtIndex(rt, 0).getString(rt).utf8(rt), "eight");
  EXPECT_EQ(arr.getValueAtIndex(rt, 1).getString(rt).utf8(rt), "nine");
  // TODO: test out of range

  EXPECT_EQ(x.getPropertyAsObject(rt, "seven").getArray(rt).size(rt), 2);

  // Check that property access with both symbols and strings can access
  // array values.
  EXPECT_EQ(seven.getProperty(rt, "0").getString(rt).utf8(rt), "eight");
  EXPECT_EQ(seven.getProperty(rt, "1").getString(rt).utf8(rt), "nine");
  seven.setProperty(rt, "1", "modified");
  EXPECT_EQ(seven.getProperty(rt, "1").getString(rt).utf8(rt), "modified");
  EXPECT_EQ(arr.getValueAtIndex(rt, 1).getString(rt).utf8(rt), "modified");
  EXPECT_EQ(
      seven.getProperty(rt, PropNameID::forAscii(rt, "0"))
          .getString(rt)
          .utf8(rt),
      "eight");
  seven.setProperty(rt, PropNameID::forAscii(rt, "0"), "modified2");
  EXPECT_EQ(arr.getValueAtIndex(rt, 0).getString(rt).utf8(rt), "modified2");

  Array alpha = Array(rt, 4);
  EXPECT_TRUE(alpha.getValueAtIndex(rt, 0).isUndefined());
  EXPECT_TRUE(alpha.getValueAtIndex(rt, 3).isUndefined());
  EXPECT_EQ(alpha.size(rt), 4);
  alpha.setValueAtIndex(rt, 0, "a");
  alpha.setValueAtIndex(rt, 1, "b");
  EXPECT_EQ(alpha.length(rt), 4);
  alpha.setValueAtIndex(rt, 2, "c");
  alpha.setValueAtIndex(rt, 3, "d");
  EXPECT_EQ(alpha.size(rt), 4);

  EXPECT_TRUE(
      function(
          "function (arr) { return "
          "arr.length == 4 && "
          "['a','b','c','d'].every(function(v,i) { return v === arr[i]}); }")
          .call(rt, alpha)
          .getBool());

  Array alpha2 = Array(rt, 1);
  alpha2 = std::move(alpha);
  EXPECT_EQ(alpha2.size(rt), 4);
}

Project: hermes
File: JSObject.cpp

HermesValue JSObject::_getOwnIndexedImpl(JSObject *, Runtime *, uint32_t) {
  return HermesValue::encodeEmptyValue();
}

Project: hermes
File: JSObject.cpp

static ExecutionStatus raiseErrorForOverridingStaticBuiltin(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<SymbolID> name) {
  Handle<StringPrimitive> methodNameHnd =
      runtime->makeHandle(runtime->getStringPrimFromSymbolID(name.get()));
  // If the 'name' property does not exist or is an accessor, we don't display
  // the name.
  NamedPropertyDescriptor desc;
  auto *obj = JSObject::getNamedDescriptor(
      selfHandle, runtime, Predefined::getSymbolID(Predefined::name), desc);
  assert(
      !selfHandle->isProxyObject() &&
      "raiseErrorForOverridingStaticBuiltin cannot be used with proxy objects");

  if (!obj || desc.flags.accessor) {
    return runtime->raiseTypeError(
        TwineChar16("Attempting to override read-only builtin method '") +
        TwineChar16(methodNameHnd.get()) + "'");
  }

  // Display the name property of the builtin object if it is a string.
  StringPrimitive *objName = dyn_vmcast<StringPrimitive>(
      JSObject::getNamedSlotValue(selfHandle.get(), runtime, desc));
  if (!objName) {
    return runtime->raiseTypeError(
        TwineChar16("Attempting to override read-only builtin method '") +
        TwineChar16(methodNameHnd.get()) + "'");
  }

  return runtime->raiseTypeError(
      TwineChar16("Attempting to override read-only builtin method '") +
      TwineChar16(objName) + "." + TwineChar16(methodNameHnd.get()) + "'");
}

Project: hermes
File: JSObject.cpp

bool JSObject::_checkAllOwnIndexedImpl(
    JSObject * /*self*/,
    Runtime * /*runtime*/,
    ObjectVTable::CheckAllOwnIndexedMode /*mode*/) {
  return true;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, InstanceOfTest) {
  auto ctor = function("function Rick() { this.say = 'wubalubadubdub'; }");
  auto newObj = function("function (ctor) { return new ctor(); }");
  auto instance = newObj.call(rt, ctor).getObject(rt);
  EXPECT_TRUE(instance.instanceOf(rt, ctor));
  EXPECT_EQ(
      instance.getProperty(rt, "say").getString(rt).utf8(rt), "wubalubadubdub");
  EXPECT_FALSE(Object(rt).instanceOf(rt, ctor));
  EXPECT_TRUE(ctor.callAsConstructor(rt, nullptr, 0)
                  .getObject(rt)
                  .instanceOf(rt, ctor));
}

Project: hermes
File: testlib.cpp

  Value get(Runtime& rt, const PropNameID& name) override {
    EXPECT_EQ(expectedRT_, &rt);
    return Value(17.0);
  }

Project: hermes
File: testlib.cpp

  explicit HO(Runtime* expectedRT) : expectedRT_(expectedRT) {}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, StringTest) {
  EXPECT_TRUE(checkValue(String::createFromAscii(rt, "foobar", 3), "'foo'"));
  EXPECT_TRUE(checkValue(String::createFromAscii(rt, "foobar"), "'foobar'"));

  std::string baz = "baz";
  EXPECT_TRUE(checkValue(String::createFromAscii(rt, baz), "'baz'"));

  uint8_t utf8[] = {0xF0, 0x9F, 0x86, 0x97};
  EXPECT_TRUE(checkValue(
      String::createFromUtf8(rt, utf8, sizeof(utf8)), "'\\uD83C\\uDD97'"));

  EXPECT_EQ(eval("'quux'").getString(rt).utf8(rt), "quux");
  EXPECT_EQ(eval("'\\u20AC'").getString(rt).utf8(rt), "\xe2\x82\xac");

  String quux = String::createFromAscii(rt, "quux");
  String movedQuux = std::move(quux);
  EXPECT_EQ(movedQuux.utf8(rt), "quux");
  movedQuux = String::createFromAscii(rt, "quux2");
  EXPECT_EQ(movedQuux.utf8(rt), "quux2");
}

Project: hermes
File: JSObject.cpp

CallResult<HermesValue> PropertyAccessor::create(
    Runtime *runtime,
    Handle<Callable> getter,
    Handle<Callable> setter) {
  void *mem = runtime->alloc(cellSize<PropertyAccessor>());
  return HermesValue::encodeObjectValue(
      new (mem) PropertyAccessor(runtime, *getter, *setter));
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::setParent(
    JSObject *self,
    Runtime *runtime,
    JSObject *parent,
    PropOpFlags opFlags) {
  if (LLVM_UNLIKELY(self->isProxyObject())) {
    return proxyOpFlags(
        runtime,
        opFlags,
        "Object is not extensible.",
        JSProxy::setPrototypeOf(
            runtime->makeHandle(self), runtime, runtime->makeHandle(parent)));
  }
  // ES9 9.1.2
  // 4.
  if (self->parent_.get(runtime) == parent)
    return true;
  // 5.
  if (!self->isExtensible()) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError("Object is not extensible.");
    } else {
      return false;
    }
  }
  // 6-8. Check for a prototype cycle.
  for (JSObject *cur = parent; cur; cur = cur->parent_.get(runtime)) {
    if (cur == self) {
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError("Prototype cycle detected");
      } else {
        return false;
      }
    } else if (LLVM_UNLIKELY(cur->isProxyObject())) {
      // TODO this branch should also be used for module namespace and
      // immutable prototype exotic objects.
      break;
    }
  }
  // 9.
  self->parent_.set(runtime, parent, &runtime->getHeap());
  // 10.
  return true;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, PropNameIDTest) {
  // This is a little weird to test, because it doesn't really exist
  // in JS yet.  All I can do is create them, compare them, and
  // receive one as an argument to a HostObject.

  PropNameID quux = PropNameID::forAscii(rt, "quux1", 4);
  PropNameID movedQuux = std::move(quux);
  EXPECT_EQ(movedQuux.utf8(rt), "quux");
  movedQuux = PropNameID::forAscii(rt, "quux2");
  EXPECT_EQ(movedQuux.utf8(rt), "quux2");
  PropNameID copiedQuux = PropNameID(rt, movedQuux);
  EXPECT_TRUE(PropNameID::compare(rt, movedQuux, copiedQuux));

  EXPECT_TRUE(PropNameID::compare(rt, movedQuux, movedQuux));
  EXPECT_TRUE(PropNameID::compare(
      rt, movedQuux, PropNameID::forAscii(rt, std::string("quux2"))));
  EXPECT_FALSE(PropNameID::compare(
      rt, movedQuux, PropNameID::forAscii(rt, std::string("foo"))));
  uint8_t utf8[] = {0xF0, 0x9F, 0x86, 0x97};
  PropNameID utf8PropNameID = PropNameID::forUtf8(rt, utf8, sizeof(utf8));
  EXPECT_EQ(utf8PropNameID.utf8(rt), u8"\U0001F197");
  EXPECT_TRUE(PropNameID::compare(
      rt, utf8PropNameID, PropNameID::forUtf8(rt, utf8, sizeof(utf8))));
  PropNameID nonUtf8PropNameID = PropNameID::forUtf8(rt, "meow");
  EXPECT_TRUE(PropNameID::compare(
      rt, nonUtf8PropNameID, PropNameID::forAscii(rt, "meow")));
  EXPECT_EQ(nonUtf8PropNameID.utf8(rt), "meow");
  PropNameID strPropNameID =
      PropNameID::forString(rt, String::createFromAscii(rt, "meow"));
  EXPECT_TRUE(PropNameID::compare(rt, nonUtf8PropNameID, strPropNameID));

  auto names = PropNameID::names(
      rt, "Ala", std::string("ma"), PropNameID::forAscii(rt, "kota"));
  EXPECT_EQ(names.size(), 3);
  EXPECT_TRUE(
      PropNameID::compare(rt, names[0], PropNameID::forAscii(rt, "Ala")));
  EXPECT_TRUE(
      PropNameID::compare(rt, names[1], PropNameID::forAscii(rt, "ma")));
  EXPECT_TRUE(
      PropNameID::compare(rt, names[2], PropNameID::forAscii(rt, "kota")));
}

Project: hermes
File: JSObject.cpp

bool JSObject::_deleteOwnIndexedImpl(Handle<JSObject>, Runtime *, uint32_t) {
  return false;
}

Project: hermes
File: JSObject.cpp

void PropertyAccessorDeserialize(Deserializer &d, CellKind kind) {
  assert(kind == CellKind::PropertyAccessorKind && "Expected PropertyAccessor");
  void *mem = d.getRuntime()->alloc(cellSize<PropertyAccessor>());
  auto *cell = new (mem) PropertyAccessor(d);
  d.endObject(cell);
}

Project: hermes
File: JSObject.cpp

PseudoHandle<JSObject> JSObject::create(
    Runtime *runtime,
    Handle<JSObject> parentHandle) {
  JSObjectAlloc<JSObject> mem{runtime};
  return mem.initToPseudoHandle(new (mem) JSObject(
      runtime,
      &vt.base,
      *parentHandle,
      runtime->getHiddenClassForPrototypeRaw(
          *parentHandle,
          numOverlapSlots<JSObject>() + ANONYMOUS_PROPERTY_SLOTS),
      GCPointerBase::NoBarriers()));
}

Project: hermes
File: JSObject.cpp

std::string JSObject::getNameIfExists(PointerBase *base) {
  // Try "displayName" first, if it is defined.
  if (auto nameVal = tryGetNamedNoAlloc(
          this, base, Predefined::getSymbolID(Predefined::displayName))) {
    if (auto *name = dyn_vmcast<StringPrimitive>(*nameVal)) {
      return converter(name);
    }
  }
  // Next, use "name" if it is defined.
  if (auto nameVal = tryGetNamedNoAlloc(
          this, base, Predefined::getSymbolID(Predefined::name))) {
    if (auto *name = dyn_vmcast<StringPrimitive>(*nameVal)) {
      return converter(name);
    }
  }
  // There is no other way to access the "name" property on an object.
  return "";
}

Project: hermes
File: JSObject.cpp

void ObjectSerialize(Serializer &s, const GCCell *cell) {
  JSObject::serializeObjectImpl(s, cell, JSObject::numOverlapSlots<JSObject>());
  s.endObject(cell);
}

Project: hermes
File: JSObject.cpp

PseudoHandle<JSObject> JSObject::create(
    Runtime *runtime,
    Handle<HiddenClass> clazz) {
  auto obj = JSObject::create(runtime, clazz->getNumProperties());
  obj->clazz_.set(runtime, *clazz, &runtime->getHeap());
  // If the hidden class has index like property, we need to clear the fast path
  // flag.
  if (LLVM_UNLIKELY(obj->clazz_.get(runtime)->getHasIndexLikeProperties()))
    obj->flags_.fastIndexProperties = false;
  return obj;
}

Project: hermes
File: JSObject.cpp

PseudoHandle<JSObject> JSObject::create(Runtime *runtime) {
  JSObjectAlloc<JSObject> mem{runtime};
  JSObject *objProto = runtime->objectPrototypeRawPtr;
  return mem.initToPseudoHandle(new (mem) JSObject(
      runtime,
      &vt.base,
      objProto,
      runtime->getHiddenClassForPrototypeRaw(
          objProto, numOverlapSlots<JSObject>() + ANONYMOUS_PROPERTY_SLOTS),
      GCPointerBase::NoBarriers()));
}

Project: hermes
File: JSObject.cpp

void JSObject::_snapshotAddLocationsImpl(
    GCCell *cell,
    GC *gc,
    HeapSnapshot &snap) {
  auto *const self = vmcast<JSObject>(cell);
  PointerBase *const base = gc->getPointerBase();
  // Add the location of the constructor function for this object, if that
  // constructor is a user-defined JS function.
  if (auto constructorVal = tryGetNamedNoAlloc(
          self, base, Predefined::getSymbolID(Predefined::constructor))) {
    if (constructorVal->isObject()) {
      if (auto *constructor = dyn_vmcast<JSFunction>(*constructorVal)) {
        constructor->addLocationToSnapshot(snap, gc->getObjectID(self));
      }
    }
  }
}

Project: hermes
File: JSObject.cpp

ExecutionStatus setProtoClasses(
    Runtime *runtime,
    Handle<JSObject> obj,
    MutableHandle<BigStorage> &arr) {
  // Layout of a JSArray stored in the for-in cache:
  // [class(proto(obj)), class(proto(proto(obj))), ..., null, prop0, prop1, ...]

  if (!obj->shouldCacheForIn(runtime)) {
    arr->clear(runtime);
    return ExecutionStatus::RETURNED;
  }
  MutableHandle<JSObject> head(runtime, obj->getParent(runtime));
  MutableHandle<> clazz(runtime);
  GCScopeMarkerRAII marker{runtime};
  while (head.get()) {
    if (!head->shouldCacheForIn(runtime)) {
      arr->clear(runtime);
      return ExecutionStatus::RETURNED;
    }
    if (JSObject::Helper::flags(*head).lazyObject) {
      // Ensure all properties have been initialized before caching the hidden
      // class. Not doing this will result in changes to the hidden class
      // when getOwnPropertyKeys is called later.
      JSObject::initializeLazyObject(runtime, head);
    }
    clazz = HermesValue::encodeObjectValue(head->getClass(runtime));
    if (LLVM_UNLIKELY(
            BigStorage::push_back(arr, runtime, clazz) ==
            ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    head = head->getParent(runtime);
    marker.flush();
  }
  clazz = HermesValue::encodeNullValue();
  return BigStorage::push_back(arr, runtime, clazz);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, JSErrorTest) {
  // JSError creation can lead to further errors.  Make sure these
  // cases are handled and don't cause weird crashes or other issues.
  //
  // Getting message property can throw

  EXPECT_THROW(
      eval("var GetMessageThrows = {get message() { throw Error('ex'); }};"
           "throw GetMessageThrows;"),
      JSIException);

  EXPECT_THROW(
      eval("var GetMessageThrows = {get message() { throw GetMessageThrows; }};"
           "throw GetMessageThrows;"),
      JSIException);

  // Converting exception message to String can throw

  EXPECT_THROW(
      eval(
          "Object.defineProperty("
          "  globalThis, 'String', {configurable:true, get() { var e = Error(); e.message = 23; throw e; }});"
          "var e = Error();"
          "e.message = 17;"
          "throw e;"),
      JSIException);

  EXPECT_THROW(
      eval(
          "var e = Error();"
          "Object.defineProperty("
          "  e, 'message', {configurable:true, get() { throw Error('getter'); }});"
          "throw e;"),
      JSIException);

  EXPECT_THROW(
      eval("var e = Error();"
           "String = function() { throw Error('ctor'); };"
           "throw e;"),
      JSIException);

  // Converting an exception message to String can return a non-String

  EXPECT_THROW(
      eval("String = function() { return 42; };"
           "var e = Error();"
           "e.message = 17;"
           "throw e;"),
      JSIException);

  // Exception can be non-Object

  EXPECT_THROW(eval("throw 17;"), JSIException);

  EXPECT_THROW(eval("throw undefined;"), JSIException);

  // Converting exception with no message or stack property to String can throw

  EXPECT_THROW(
      eval("var e = {toString() { throw new Error('errstr'); }};"
           "throw e;"),
      JSIException);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, RuntimeTest) {
  auto v = rt.evaluateJavaScript(std::make_unique<StringBuffer>("1"), "");
  EXPECT_EQ(v.getNumber(), 1);

  rt.evaluateJavaScript(std::make_unique<StringBuffer>("x = 1"), "");
  EXPECT_EQ(rt.global().getProperty(rt, "x").getNumber(), 1);
}

Project: hermes
File: JSObject.cpp

  getOwnIndexedPropertyFlags(JSObject *self, Runtime *runtime, uint32_t index) {
    return JSObject::getOwnIndexedPropertyFlags(self, runtime, index);
  }

Project: hermes
File: JSObject.cpp

void PropertyAccessorBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
  const auto *self = static_cast<const PropertyAccessor *>(cell);
  mb.addField("getter", &self->getter);
  mb.addField("setter", &self->setter);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ScopeDoesNotCrashTest) {
  Scope scope(rt);
  Object o(rt);
}

Project: hermes
File: JSObject.cpp

CallResult<Handle<JSArray>> JSObject::getOwnPropertyKeys(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    OwnKeysFlags okFlags) {
  assert(
      (okFlags.getIncludeNonSymbols() || okFlags.getIncludeSymbols()) &&
      "Can't exclude symbols and strings");
  if (LLVM_UNLIKELY(
          selfHandle->flags_.lazyObject || selfHandle->flags_.proxyObject)) {
    if (selfHandle->flags_.proxyObject) {
      CallResult<PseudoHandle<JSArray>> proxyRes =
          JSProxy::ownPropertyKeys(selfHandle, runtime, okFlags);
      if (LLVM_UNLIKELY(proxyRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      return runtime->makeHandle(std::move(*proxyRes));
    }
    assert(selfHandle->flags_.lazyObject && "descriptor flags are impossible");
    initializeLazyObject(runtime, selfHandle);
  }

  auto range = getOwnIndexedRange(selfHandle.get(), runtime);

  // Estimate the capacity of the output array.  This estimate is only
  // reasonable for the non-symbol case.
  uint32_t capacity = okFlags.getIncludeNonSymbols()
      ? (selfHandle->clazz_.get(runtime)->getNumProperties() + range.second -
         range.first)
      : 0;

  auto arrayRes = JSArray::create(runtime, capacity, 0);
  if (LLVM_UNLIKELY(arrayRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  auto array = runtime->makeHandle(std::move(*arrayRes));

  // Optional array of SymbolIDs reported via host object API
  llvh::Optional<Handle<JSArray>> hostObjectSymbols;
  size_t hostObjectSymbolCount = 0;

  // If current object is a host object we need to deduplicate its properties
  llvh::SmallSet<SymbolID::RawType, 16> dedupSet;

  // Output index.
  uint32_t index = 0;

  // Avoid allocating a new handle per element.
  MutableHandle<> tmpHandle{runtime};

  // Number of indexed properties.
  uint32_t numIndexed = 0;

  // Regular properties with names that are array indexes are stashed here, if
  // encountered.
  llvh::SmallVector<uint32_t, 8> indexNames{};

  // Iterate the named properties excluding those which use Symbols.
  if (okFlags.getIncludeNonSymbols()) {
    // Get host object property names
    if (LLVM_UNLIKELY(selfHandle->flags_.hostObject)) {
      assert(
          range.first == range.second &&
          "Host objects cannot own indexed range");
      auto hostSymbolsRes =
          vmcast<HostObject>(selfHandle.get())->getHostPropertyNames();
      if (hostSymbolsRes == ExecutionStatus::EXCEPTION) {
        return ExecutionStatus::EXCEPTION;
      }
      if ((hostObjectSymbolCount = (**hostSymbolsRes)->getEndIndex()) != 0) {
        Handle<JSArray> hostSymbols = *hostSymbolsRes;
        hostObjectSymbols = std::move(hostSymbols);
        capacity += hostObjectSymbolCount;
      }
    }

    // Iterate the indexed properties.
    GCScopeMarkerRAII marker{runtime};
    for (auto i = range.first; i != range.second; ++i) {
      auto res = getOwnIndexedPropertyFlags(selfHandle.get(), runtime, i);
      if (!res)
        continue;

      // If specified, check whether it is enumerable.
      if (!okFlags.getIncludeNonEnumerable() && !res->enumerable)
        continue;

      tmpHandle = HermesValue::encodeDoubleValue(i);
      JSArray::setElementAt(array, runtime, index++, tmpHandle);
      marker.flush();
    }

    numIndexed = index;

    HiddenClass::forEachProperty(
        runtime->makeHandle(selfHandle->clazz_),
        runtime,
        [runtime,
         okFlags,
         array,
         hostObjectSymbolCount,
         &index,
         &indexNames,
         &tmpHandle,
         &dedupSet](SymbolID id, NamedPropertyDescriptor desc) {
          if (!isPropertyNamePrimitive(id)) {
            return;
          }

          // If specified, check whether it is enumerable.
          if (!okFlags.getIncludeNonEnumerable()) {
            if (!desc.flags.enumerable)
              return;
          }

          // Host properties might overlap with the ones recognized by the
          // hidden class. If we're dealing with a host object then keep track
          // of hidden class properties for the deduplication purposes.
          if (LLVM_UNLIKELY(hostObjectSymbolCount > 0)) {
            dedupSet.insert(id.unsafeGetRaw());
          }

          // Check if this property is an integer index. If it is, we stash it
          // away to deal with it later. This check should be fast since most
          // property names don't start with a digit.
          auto propNameAsIndex = toArrayIndex(
              runtime->getIdentifierTable().getStringView(runtime, id));
          if (LLVM_UNLIKELY(propNameAsIndex)) {
            indexNames.push_back(*propNameAsIndex);
            return;
          }

          tmpHandle = HermesValue::encodeStringValue(
              runtime->getStringPrimFromSymbolID(id));
          JSArray::setElementAt(array, runtime, index++, tmpHandle);
        });

    // Iterate over HostObject properties and append them to the array. Do not
    // append duplicates.
    if (LLVM_UNLIKELY(hostObjectSymbols)) {
      for (size_t i = 0; i < hostObjectSymbolCount; ++i) {
        assert(
            (*hostObjectSymbols)->at(runtime, i).isSymbol() &&
            "Host object needs to return array of SymbolIDs");
        marker.flush();
        SymbolID id = (*hostObjectSymbols)->at(runtime, i).getSymbol();
        if (dedupSet.count(id.unsafeGetRaw()) == 0) {
          dedupSet.insert(id.unsafeGetRaw());

          assert(
              !InternalProperty::isInternal(id) &&
              "host object returned reserved symbol");
          auto propNameAsIndex = toArrayIndex(
              runtime->getIdentifierTable().getStringView(runtime, id));
          if (LLVM_UNLIKELY(propNameAsIndex)) {
            indexNames.push_back(*propNameAsIndex);
            continue;
          }
          tmpHandle = HermesValue::encodeStringValue(
              runtime->getStringPrimFromSymbolID(id));
          JSArray::setElementAt(array, runtime, index++, tmpHandle);
        }
      }
    }
  }

  // Now iterate the named properties again, including only Symbols.
  // We could iterate only once, if we chose to ignore (and disallow)
  // own properties on HostObjects, as we do with Proxies.
  if (okFlags.getIncludeSymbols()) {
    MutableHandle<SymbolID> idHandle{runtime};
    HiddenClass::forEachProperty(
        runtime->makeHandle(selfHandle->clazz_),
        runtime,
        [runtime, okFlags, array, &index, &idHandle](
            SymbolID id, NamedPropertyDescriptor desc) {
          if (!isSymbolPrimitive(id)) {
            return;
          }
          // If specified, check whether it is enumerable.
          if (!okFlags.getIncludeNonEnumerable()) {
            if (!desc.flags.enumerable)
              return;
          }
          idHandle = id;
          JSArray::setElementAt(array, runtime, index++, idHandle);
        });
  }

  // The end (exclusive) of the named properties.
  uint32_t endNamed = index;

  // Properly set the length of the array.
  auto cr = JSArray::setLength(
      array, runtime, endNamed + indexNames.size(), PropOpFlags{});
  (void)cr;
  assert(
      cr != ExecutionStatus::EXCEPTION && *cr && "JSArray::setLength() failed");

  // If we have no index-like names, we are done.
  if (LLVM_LIKELY(indexNames.empty()))
    return array;

  // In the unlikely event that we encountered index-like names, we need to sort
  // them and merge them with the real indexed properties. Note that it is
  // guaranteed that there are no clashes.
  std::sort(indexNames.begin(), indexNames.end());

  // Also make space for the new elements by shifting all the named properties
  // to the right. First, resize the array.
  JSArray::setStorageEndIndex(array, runtime, endNamed + indexNames.size());

  // Shift the non-index property names. The region [numIndexed..endNamed) is
  // moved to [numIndexed+indexNames.size()..array->size()).
  // TODO: optimize this by implementing memcpy-like functionality in ArrayImpl.
  for (uint32_t last = endNamed, toLast = array->getEndIndex();
       last != numIndexed;) {
    --last;
    --toLast;
    tmpHandle = array->at(runtime, last);
    JSArray::setElementAt(array, runtime, toLast, tmpHandle);
  }

  // Now we need to merge the indexes in indexNames and the array
  // [0..numIndexed). We start from the end and copy the larger element from
  // either array.
  // 1+ the destination position to copy into.
  for (uint32_t toLast = numIndexed + indexNames.size(),
                indexNamesLast = indexNames.size();
       toLast != 0;) {
    if (numIndexed) {
      uint32_t a = (uint32_t)array->at(runtime, numIndexed - 1).getNumber();
      uint32_t b;

      if (indexNamesLast && (b = indexNames[indexNamesLast - 1]) > a) {
        tmpHandle = HermesValue::encodeDoubleValue(b);
        --indexNamesLast;
      } else {
        tmpHandle = HermesValue::encodeDoubleValue(a);
        --numIndexed;
      }
    } else {
      assert(indexNamesLast && "prematurely ran out of source values");
      tmpHandle =
          HermesValue::encodeDoubleValue(indexNames[indexNamesLast - 1]);
      --indexNamesLast;
    }

    --toLast;
    JSArray::setElementAt(array, runtime, toLast, tmpHandle);
  }

  return array;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, PreparedJavaScriptURLInBacktrace) {
  std::string sourceURL = "//PreparedJavaScriptURLInBacktrace/Test/URL";
  std::string throwingSource =
      "function thrower() { throw new Error('oops')}"
      "thrower();";
  auto prep = rt.prepareJavaScript(
      std::make_unique<StringBuffer>(throwingSource), sourceURL);
  try {
    rt.evaluatePreparedJavaScript(prep);
    FAIL() << "prepareJavaScript should have thrown an exception";
  } catch (facebook::jsi::JSError err) {
    EXPECT_NE(std::string::npos, err.getStack().find(sourceURL))
        << "Backtrace should contain source URL";
  }
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, MultilevelDecoratedHostObject) {
  // This test will be run for various test instantiations, so initialize these
  // counters.
  RD1::numGets = 0;
  RD2::numGets = 0;

  RD1 rd1(rt);
  RD2 rd2(rd1);
  // We expect the "get" operation of ho to be called with rd2.
  auto ho = std::make_shared<HO>(&rd2);
  auto obj = Object::createFromHostObject(rd2, ho);
  Value v = obj.getProperty(rd2, "p");
  EXPECT_TRUE(v.isNumber());
  EXPECT_EQ(17.0, v.asNumber());
  auto ho2 = obj.getHostObject(rd2);
  EXPECT_EQ(ho, ho2);
  EXPECT_EQ(1, RD1::numGets);
  EXPECT_EQ(1, RD2::numGets);
}

Project: hermes
File: JSObject.cpp

void JSObject::updatePropertyFlagsWithoutTransitions(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    PropertyFlags flagsToClear,
    PropertyFlags flagsToSet,
    OptValue<llvh::ArrayRef<SymbolID>> props) {
  auto newClazz = HiddenClass::updatePropertyFlagsWithoutTransitions(
      runtime->makeHandle(selfHandle->clazz_),
      runtime,
      flagsToClear,
      flagsToSet,
      props);
  selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());
}

Project: hermes
File: JSObject.cpp

void ObjectBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
  // This call is just for debugging and consistency purposes.
  mb.addJSObjectOverlapSlots(JSObject::numOverlapSlots<JSObject>());

  const auto *self = static_cast<const JSObject *>(cell);
  mb.addField("parent", &self->parent_);
  mb.addField("class", &self->clazz_);
  mb.addField("propStorage", &self->propStorage_);

  // Declare the direct properties.
  static const char *directPropName[JSObject::DIRECT_PROPERTY_SLOTS] = {
      "directProp0", "directProp1", "directProp2", "directProp3"};
  for (unsigned i = mb.getJSObjectOverlapSlots();
       i < JSObject::DIRECT_PROPERTY_SLOTS;
       ++i) {
    mb.addField(directPropName[i], self->directProps() + i);
  }
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::updateOwnProperty(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    HiddenClass::PropertyPos propertyPos,
    NamedPropertyDescriptor desc,
    const DefinePropertyFlags dpFlags,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  auto updateStatus = checkPropertyUpdate(
      runtime,
      desc.flags,
      dpFlags,
      getNamedSlotValue(selfHandle.get(), runtime, desc),
      valueOrAccessor,
      opFlags);
  if (updateStatus == ExecutionStatus::EXCEPTION)
    return ExecutionStatus::EXCEPTION;
  if (updateStatus->first == PropertyUpdateStatus::failed)
    return false;

  // If the property flags changed, update them.
  if (updateStatus->second != desc.flags) {
    desc.flags = updateStatus->second;
    auto newClazz = HiddenClass::updateProperty(
        runtime->makeHandle(selfHandle->clazz_),
        runtime,
        propertyPos,
        desc.flags);
    selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());
  }

  if (updateStatus->first == PropertyUpdateStatus::done)
    return true;
  assert(
      updateStatus->first == PropertyUpdateStatus::needSet &&
      "unexpected PropertyUpdateStatus");

  if (dpFlags.setValue) {
    if (LLVM_LIKELY(!desc.flags.internalSetter))
      setNamedSlotValue(selfHandle.get(), runtime, desc, valueOrAccessor.get());
    else
      return internalSetter(
          selfHandle, runtime, name, desc, valueOrAccessor, opFlags);
  } else if (dpFlags.isAccessor()) {
    setNamedSlotValue(selfHandle.get(), runtime, desc, valueOrAccessor.get());
  } else {
    // If checkPropertyUpdate() returned needSet, but there is no value or
    // accessor, clear the value.
    setNamedSlotValue(
        selfHandle.get(), runtime, desc, HermesValue::encodeUndefinedValue());
  }

  return true;
}

Project: hermes
File: JSObject.cpp

void ObjectDeserialize(Deserializer &d, CellKind kind) {
  assert(kind == CellKind::ObjectKind && "Expected JSObject");
  void *mem = d.getRuntime()->alloc</*fixedSize*/ true>(cellSize<JSObject>());
  auto *obj = new (mem) JSObject(d, &JSObject::vt.base);

  d.endObject(obj);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, JSErrorsArePropagatedNicely) {
  unsigned callsBeforeError = 5;

  Function sometimesThrows = function(
      "function sometimesThrows(shouldThrow, callback) {"
      "  if (shouldThrow) {"
      "    throw Error('Omg, what a nasty exception')"
      "  }"
      "  callback(callback);"
      "}");

  Function callback = Function::createFromHostFunction(
      rt,
      PropNameID::forAscii(rt, "callback"),
      0,
      [&sometimesThrows, &callsBeforeError](
          Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
        return sometimesThrows.call(rt, --callsBeforeError == 0, args[0]);
      });

  try {
    sometimesThrows.call(rt, false, callback);
  } catch (JSError& error) {
    EXPECT_EQ(error.getMessage(), "Omg, what a nasty exception");
    EXPECT_EQ(countOccurences("sometimesThrows", error.getStack()), 6);

    // system JSC JSI does not implement host function names
    // EXPECT_EQ(countOccurences("callback", error.getStack(rt)), 5);
  }
}

Project: hermes
File: JSObject.cpp

static bool canNewPropertyBeIndexed(DefinePropertyFlags dpf) {
  return dpf.setEnumerable && dpf.enumerable && dpf.setWritable &&
      dpf.writable && dpf.setConfigurable && dpf.configurable &&
      !dpf.setSetter && !dpf.setGetter;
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getComputedPropertyValue_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<JSObject> propObj,
    ComputedPropertyDescriptor desc) {
  assert(
      !selfHandle->flags_.proxyObject && !propObj->flags_.proxyObject &&
      "getComputedPropertyValue_RJS cannot be used with proxy objects");

  if (LLVM_LIKELY(!desc.flags.accessor))
    return createPseudoHandle(
        getComputedSlotValue(propObj.get(), runtime, desc));

  auto *accessor = vmcast<PropertyAccessor>(
      getComputedSlotValue(propObj.get(), runtime, desc));
  if (!accessor->getter)
    return createPseudoHandle(HermesValue::encodeUndefinedValue());

  // Execute the accessor on this object.
  return accessor->getter.get(runtime)->executeCall0(
      runtime->makeHandle(accessor->getter), runtime, selfHandle);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::hasNamedOrIndexed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name) {
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
    const auto strView =
        runtime->getIdentifierTable().getStringView(runtime, name);
    if (auto nameAsIndex = toArrayIndex(strView)) {
      if (haveOwnIndexed(selfHandle.get(), runtime, *nameAsIndex)) {
        return true;
      }
      if (selfHandle->flags_.fastIndexProperties) {
        return false;
      }
    }
    // Here we have indexed properties but the symbol was not stored in the
    // indexedStorage.
    // Fall through to getNamed().
  }
  return hasNamed(selfHandle, runtime, name);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::deleteComputed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() && "mustExist cannot be specified when deleting");

  // If nameValHandle is an object, we should convert it to string now,
  // because toString may have side-effect, and we want to do this only
  // once.
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }

  auto nameValPrimitiveHandle = *converted;

  // If the name is a valid integer array index, store it here.
  OptValue<uint32_t> arrayIndex;

  // If we have indexed storage, we must attempt to convert the name to array
  // index, even if the conversion is expensive.
  if (selfHandle->flags_.indexedStorage) {
    MutableHandle<StringPrimitive> strPrim{runtime};
    TO_ARRAY_INDEX(runtime, nameValPrimitiveHandle, strPrim, arrayIndex);
  }

  // Try the fast-path first: the "name" is a valid array index and we don't
  // have "index-like" named properties.
  if (arrayIndex && selfHandle->flags_.fastIndexProperties) {
    // Delete the indexed property.
    if (deleteOwnIndexed(selfHandle, runtime, *arrayIndex))
      return true;

    // Cannot delete property (for example this may be a typed array).
    if (opFlags.getThrowOnError()) {
      // TODO: better error message.
      return runtime->raiseTypeError("Cannot delete property");
    }
    return false;
  }

  // slow path, check if object is lazy before continuing.
  if (LLVM_UNLIKELY(selfHandle->flags_.lazyObject)) {
    // initialize and try again.
    initializeLazyObject(runtime, selfHandle);
    return deleteComputed(selfHandle, runtime, nameValHandle, opFlags);
  }

  // Convert the string to an SymbolID;
  SymbolID id;
  LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);

  // Find the property by name.
  NamedPropertyDescriptor desc;
  auto pos = findProperty(selfHandle, runtime, id, desc);

  // If the property exists, make sure it is configurable.
  if (pos) {
    // If the property isn't configurable, fail.
    if (LLVM_UNLIKELY(!desc.flags.configurable)) {
      if (opFlags.getThrowOnError()) {
        // TODO: a better message.
        return runtime->raiseTypeError("Property is not configurable");
      }
      return false;
    }
  }

  // At this point we know that the named property either doesn't exist, or
  // is configurable and so can be deleted, or the object is a Proxy.

  // If it is an "index-like" property, we must also delete the "shadow" indexed
  // property in order to keep Array.length correct.
  if (arrayIndex) {
    if (!deleteOwnIndexed(selfHandle, runtime, *arrayIndex)) {
      // Cannot delete property (for example this may be a typed array).
      if (opFlags.getThrowOnError()) {
        // TODO: better error message.
        return runtime->raiseTypeError("Cannot delete property");
      }
      return false;
    }
  }

  if (pos) {
    // delete the named property (if it exists).
    // Clear the deleted property value to prevent memory leaks.
    setNamedSlotValue(
        *selfHandle, runtime, desc, HermesValue::encodeEmptyValue());

    // Remove the property descriptor.
    auto newClazz = HiddenClass::deleteProperty(
        runtime->makeHandle(selfHandle->clazz_), runtime, *pos);
    selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());
  } else if (LLVM_UNLIKELY(selfHandle->flags_.proxyObject)) {
    CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
    if (key == ExecutionStatus::EXCEPTION)
      return ExecutionStatus::EXCEPTION;
    return proxyOpFlags(
        runtime,
        opFlags,
        "Proxy delete returned false",
        JSProxy::deleteComputed(selfHandle, runtime, *key));
  }

  return true;
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::isExtensible(
    PseudoHandle<JSObject> self,
    Runtime *runtime) {
  if (LLVM_UNLIKELY(self->isProxyObject())) {
    return JSProxy::isExtensible(runtime->makeHandle(std::move(self)), runtime);
  }
  return self->isExtensible();
}

Project: hermes
File: JSObject.cpp

void JSObject::initializeLazyObject(
    Runtime *runtime,
    Handle<JSObject> lazyObject) {
  assert(lazyObject->flags_.lazyObject && "object must be lazy");
  // object is now assumed to be a regular object.
  lazyObject->flags_.lazyObject = 0;

  // only functions can be lazy.
  assert(vmisa<Callable>(lazyObject.get()) && "unexpected lazy object");
  Callable::defineLazyProperties(Handle<Callable>::vmcast(lazyObject), runtime);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::putComputedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    Handle<> valueHandle,
    Handle<> receiver,
    PropOpFlags opFlags) {
  assert(
      !opFlags.getMustExist() &&
      "mustExist flag cannot be used with computed properties");

  // Try the fast-path first: has "index-like" properties, the "name"
  // already is a valid integer index, selfHandle and receiver are the
  // same, and it is present in storage.
  if (selfHandle->flags_.fastIndexProperties) {
    if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
      if (selfHandle.getHermesValue().getRaw() == receiver->getRaw()) {
        if (haveOwnIndexed(selfHandle.get(), runtime, *arrayIndex)) {
          auto result =
              setOwnIndexed(selfHandle, runtime, *arrayIndex, valueHandle);
          if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
            return ExecutionStatus::EXCEPTION;
          if (LLVM_LIKELY(*result))
            return true;
          if (opFlags.getThrowOnError()) {
            // TODO: better message.
            return runtime->raiseTypeError(
                "Cannot assign to read-only property");
          }
          return false;
        }
      }
    }
  }

  // If nameValHandle is an object, we should convert it to string now,
  // because toString may have side-effect, and we want to do this only
  // once.
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  auto nameValPrimitiveHandle = *converted;

  ComputedPropertyDescriptor desc;

  // Look for the property in this object or along the prototype chain.
  MutableHandle<JSObject> propObj{runtime};
  if (LLVM_UNLIKELY(
          getComputedPrimitiveDescriptor(
              selfHandle, runtime, nameValPrimitiveHandle, propObj, desc) ==
          ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }

  // If the property exists (or, we hit a proxy/hostobject on the way
  // up the chain)
  if (propObj) {
    // Get the simple case out of the way: If the property already
    // exists on selfHandle, is not an accessor, selfHandle and
    // receiver are the same, selfHandle is not a host
    // object/proxy/internal setter, and the property is writable,
    // just write into the same slot.

    if (LLVM_LIKELY(
            selfHandle == propObj &&
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
            !desc.flags.accessor && !desc.flags.internalSetter &&
            !desc.flags.hostObject && !desc.flags.proxyObject &&
            desc.flags.writable)) {
      if (LLVM_UNLIKELY(
              setComputedSlotValue(selfHandle, runtime, desc, valueHandle) ==
              ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    // Is it an accessor?
    if (LLVM_UNLIKELY(desc.flags.accessor)) {
      auto *accessor = vmcast<PropertyAccessor>(
          getComputedSlotValue(propObj.get(), runtime, desc));

      // If it is a read-only accessor, fail.
      if (!accessor->setter) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeErrorForValue(
              "Cannot assign to property ",
              nameValPrimitiveHandle,
              " which has only a getter");
        }
        return false;
      }

      // Execute the accessor on this object.
      if (accessor->setter.get(runtime)->executeCall1(
              runtime->makeHandle(accessor->setter),
              runtime,
              receiver,
              valueHandle.get()) == ExecutionStatus::EXCEPTION) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
      assert(
          !opFlags.getMustExist() &&
          "MustExist cannot be used with Proxy objects");
      CallResult<Handle<>> key = toPropertyKey(runtime, nameValPrimitiveHandle);
      if (key == ExecutionStatus::EXCEPTION)
        return ExecutionStatus::EXCEPTION;
      CallResult<bool> setRes =
          JSProxy::setComputed(propObj, runtime, *key, valueHandle, receiver);
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      if (!*setRes && opFlags.getThrowOnError()) {
        // TODO: better message.
        return runtime->raiseTypeError(
            TwineChar16("Proxy trap returned false for property"));
      }
      return setRes;
    }

    if (LLVM_UNLIKELY(!desc.flags.writable)) {
      if (desc.flags.staticBuiltin) {
        SymbolID id{};
        LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
        return raiseErrorForOverridingStaticBuiltin(
            selfHandle, runtime, runtime->makeHandle(id));
      }
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeErrorForValue(
            "Cannot assign to read-only property ", nameValPrimitiveHandle, "");
      }
      return false;
    }

    if (selfHandle == propObj && desc.flags.internalSetter) {
      SymbolID id{};
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
      return internalSetter(
          selfHandle,
          runtime,
          id,
          desc.castToNamedPropertyDescriptorRef(),
          valueHandle,
          opFlags);
    }
  }

  // The property does not exist as an conventional own property on
  // this object.

  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
    }
    if (!receiverHandle) {
      return false;
    }
    CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
        receiverHandle, runtime, nameValPrimitiveHandle, IgnoreProxy::No, desc);
    if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    DefinePropertyFlags dpf;
    if (*descDefinedRes) {
      if (LLVM_UNLIKELY(desc.flags.accessor || !desc.flags.writable)) {
        return false;
      }

      if (LLVM_LIKELY(
              !desc.flags.internalSetter && !receiverHandle->isHostObject() &&
              !receiverHandle->isProxyObject())) {
        if (LLVM_UNLIKELY(
                setComputedSlotValue(
                    receiverHandle, runtime, desc, valueHandle) ==
                ExecutionStatus::EXCEPTION)) {
          return ExecutionStatus::EXCEPTION;
        }
        return true;
      }
    }

    if (LLVM_UNLIKELY(
            desc.flags.internalSetter || receiverHandle->isHostObject() ||
            receiverHandle->isProxyObject())) {
      SymbolID id{};
      LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);
      if (desc.flags.internalSetter) {
        return internalSetter(
            receiverHandle,
            runtime,
            id,
            desc.castToNamedPropertyDescriptorRef(),
            valueHandle,
            opFlags);
      } else if (receiverHandle->isHostObject()) {
        return vmcast<HostObject>(receiverHandle.get())->set(id, *valueHandle);
      }
      assert(
          receiverHandle->isProxyObject() && "descriptor flags are impossible");
      if (*descDefinedRes) {
        dpf.setValue = 1;
      } else {
        dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
      }
      return JSProxy::defineOwnProperty(
          receiverHandle, runtime, id, dpf, valueHandle, opFlags);
    }
  }

  /// Can we add more properties?
  if (LLVM_UNLIKELY(!receiverHandle->isExtensible())) {
    if (opFlags.getThrowOnError()) {
      return runtime->raiseTypeError(
          "cannot add a new property"); // TODO: better message.
    }
    return false;
  }

  // If we have indexed storage we must check whether the property is an index,
  // and if it is, store it in indexed storage.
  if (receiverHandle->flags_.indexedStorage) {
    OptValue<uint32_t> arrayIndex;
    MutableHandle<StringPrimitive> strPrim{runtime};
    TO_ARRAY_INDEX(runtime, nameValPrimitiveHandle, strPrim, arrayIndex);
    if (arrayIndex) {
      // Check whether we need to update array's ".length" property.
      if (auto *array = dyn_vmcast<JSArray>(receiverHandle.get())) {
        if (LLVM_UNLIKELY(*arrayIndex >= JSArray::getLength(array))) {
          auto cr = putNamed_RJS(
              receiverHandle,
              runtime,
              Predefined::getSymbolID(Predefined::length),
              runtime->makeHandle(
                  HermesValue::encodeNumberValue(*arrayIndex + 1)),
              opFlags);
          if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION))
            return ExecutionStatus::EXCEPTION;
          if (LLVM_UNLIKELY(!*cr))
            return false;
        }
      }

      auto result =
          setOwnIndexed(receiverHandle, runtime, *arrayIndex, valueHandle);
      if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION))
        return ExecutionStatus::EXCEPTION;
      if (LLVM_LIKELY(*result))
        return true;

      if (opFlags.getThrowOnError()) {
        // TODO: better message.
        return runtime->raiseTypeError("Cannot assign to read-only property");
      }
      return false;
    }
  }

  SymbolID id{};
  LAZY_TO_IDENTIFIER(runtime, nameValPrimitiveHandle, id);

  // Add a new named property.
  return addOwnProperty(
      receiverHandle,
      runtime,
      id,
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
      valueHandle,
      opFlags);
}

Project: hermes
File: JSObject.cpp

bool JSObject::_haveOwnIndexedImpl(JSObject *self, Runtime *, uint32_t) {
  return false;
}

Project: hermes
File: testlib.cpp

  RD2(Runtime& plain) : RuntimeDecorator(plain) {}

Project: hermes
File: JSObject.cpp

uint32_t matchesProtoClasses(
    Runtime *runtime,
    Handle<JSObject> obj,
    Handle<BigStorage> arr) {
  MutableHandle<JSObject> head(runtime, obj->getParent(runtime));
  uint32_t i = 0;
  while (head.get()) {
    HermesValue protoCls = arr->at(i++);
    if (protoCls.isNull() || protoCls.getObject() != head->getClass(runtime) ||
        head->isProxyObject()) {
      return 0;
    }
    head = head->getParent(runtime);
  }
  // The chains must both end at the same point.
  if (head || !arr->at(i++).isNull()) {
    return 0;
  }
  assert(i > 0 && "success should be positive");
  return i;
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::getComputedPrimitiveDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    MutableHandle<JSObject> &propObj,
    ComputedPropertyDescriptor &desc) {
  assert(
      !nameValHandle->isObject() &&
      "nameValHandle passed to "
      "getComputedPrimitiveDescriptor cannot "
      "be an object");

  propObj = selfHandle.get();

  SymbolID id{};

  GCScopeMarkerRAII marker{runtime};
  do {
    // A proxy is ignored here so we can check the bit later and
    // return it back to the caller for additional processing.

    Handle<JSObject> loopHandle = propObj;

    CallResult<bool> res = getOwnComputedPrimitiveDescriptorImpl(
        loopHandle, runtime, nameValHandle, IgnoreProxy::Yes, id, desc);
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    if (*res) {
      return ExecutionStatus::RETURNED;
    }

    if (LLVM_UNLIKELY(propObj->flags_.hostObject)) {
      desc.flags.hostObject = true;
      desc.flags.writable = true;
      return ExecutionStatus::RETURNED;
    }
    if (LLVM_UNLIKELY(propObj->flags_.proxyObject)) {
      desc.flags.proxyObject = true;
      return ExecutionStatus::RETURNED;
    }
    // This isn't a proxy, so use the faster getParent() instead of
    // getPrototypeOf.
    propObj = propObj->getParent(runtime);
    // Flush at the end of the loop to allow first iteration to be as fast as
    // possible.
    marker.flush();
  } while (propObj);
  return ExecutionStatus::RETURNED;
}

Project: hermes
File: JSObject.cpp

std::string JSObject::_snapshotNameImpl(GCCell *cell, GC *gc) {
  auto *const self = vmcast<JSObject>(cell);
  return self->getHeuristicTypeName(gc);
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getNamedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    Handle<> receiver,
    PropOpFlags opFlags,
    PropertyCacheEntry *cacheEntry) {
  NamedPropertyDescriptor desc;
  // Locate the descriptor. propObj contains the object which may be anywhere
  // along the prototype chain.
  JSObject *propObj = getNamedDescriptor(selfHandle, runtime, name, desc);
  if (!propObj) {
    if (LLVM_UNLIKELY(opFlags.getMustExist())) {
      return runtime->raiseReferenceError(
          TwineChar16("Property '") +
          runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
          "' doesn't exist");
    }
    return createPseudoHandle(HermesValue::encodeUndefinedValue());
  }

  if (LLVM_LIKELY(
          !desc.flags.accessor && !desc.flags.hostObject &&
          !desc.flags.proxyObject)) {
    // Populate the cache if requested.
    if (cacheEntry && !propObj->getClass(runtime)->isDictionaryNoCache()) {
      cacheEntry->clazz = propObj->getClassGCPtr().getStorageType();
      cacheEntry->slot = desc.slot;
    }
    return createPseudoHandle(getNamedSlotValue(propObj, runtime, desc));
  }

  if (desc.flags.accessor) {
    auto *accessor =
        vmcast<PropertyAccessor>(getNamedSlotValue(propObj, runtime, desc));
    if (!accessor->getter)
      return createPseudoHandle(HermesValue::encodeUndefinedValue());

    // Execute the accessor on this object.
    return Callable::executeCall0(
        runtime->makeHandle(accessor->getter), runtime, receiver);
  } else if (desc.flags.hostObject) {
    auto res = vmcast<HostObject>(propObj)->get(name);
    if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    return createPseudoHandle(*res);
  } else {
    assert(desc.flags.proxyObject && "descriptor flags are impossible");
    return JSProxy::getNamed(
        runtime->makeHandle(propObj), runtime, name, receiver);
  }
}

Project: hermes
File: JSObject.cpp

bool JSObject::isFrozen(PseudoHandle<JSObject> self, Runtime *runtime) {
  if (self->flags_.frozen)
    return true;
  if (!self->flags_.noExtend)
    return false;

  auto selfHandle = runtime->makeHandle(std::move(self));

  if (!HiddenClass::areAllReadOnly(
          runtime->makeHandle(selfHandle->clazz_), runtime)) {
    return false;
  }

  if (!checkAllOwnIndexed(
          *selfHandle,
          runtime,
          ObjectVTable::CheckAllOwnIndexedMode::ReadOnly)) {
    return false;
  }

  // Now that we know we are sealed, set the flag.
  selfHandle->flags_.frozen = true;
  selfHandle->flags_.sealed = true;
  return true;
}

Project: hermes
File: JSObject.cpp

CallResult<bool> proxyOpFlags(
    Runtime *runtime,
    PropOpFlags opFlags,
    const char *msg,
    CallResult<bool> res) {
  if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  if (!*res && opFlags.getThrowOnError()) {
    return runtime->raiseTypeError(msg);
  }
  return res;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, FunctionTest) {
  // test move ctor
  Function fmove = function("function() { return 1 }");
  {
    Function g = function("function() { return 2 }");
    fmove = std::move(g);
  }
  EXPECT_EQ(fmove.call(rt).getNumber(), 2);

  // This tests all the function argument converters, and all the
  // non-lvalue overloads of call().
  Function f = function(
      "function(n, b, d, df, i, s1, s2, s3, s_sun, s_bad, o, a, f, v) { "
      "return "
      "n === null && "
      "b === true && "
      "d === 3.14 && "
      "Math.abs(df - 2.71) < 0.001 && "
      "i === 17 && "
      "s1 == 's1' && "
      "s2 == 's2' && "
      "s3 == 's3' && "
      "s_sun == 's\\u2600' && "
      "typeof s_bad == 'string' && "
      "typeof o == 'object' && "
      "Array.isArray(a) && "
      "typeof f == 'function' && "
      "v == 42 }");
  EXPECT_TRUE(f.call(
                   rt,
                   nullptr,
                   true,
                   3.14,
                   2.71f,
                   17,
                   "s1",
                   String::createFromAscii(rt, "s2"),
                   std::string{"s3"},
                   std::string{u8"s\u2600"},
                   // invalid UTF8 sequence due to unexpected continuation byte
                   std::string{"s\x80"},
                   Object(rt),
                   Array(rt, 1),
                   function("function(){}"),
                   Value(42))
                  .getBool());

  // lvalue overloads of call()
  Function flv = function(
      "function(s, o, a, f, v) { return "
      "s == 's' && "
      "typeof o == 'object' && "
      "Array.isArray(a) && "
      "typeof f == 'function' && "
      "v == 42 }");

  String s = String::createFromAscii(rt, "s");
  Object o = Object(rt);
  Array a = Array(rt, 1);
  Value v = 42;
  EXPECT_TRUE(flv.call(rt, s, o, a, f, v).getBool());

  Function f1 = function("function() { return 1; }");
  Function f2 = function("function() { return 2; }");
  f2 = std::move(f1);
  EXPECT_EQ(f2.call(rt).getNumber(), 1);
}

Project: hermes
File: testlib.cpp

  Object createObject(std::shared_ptr<HostObject> ho) {
    class DHO2 : public DecoratedHostObject {
     public:
      using DecoratedHostObject::DecoratedHostObject;

      Value get(Runtime& rt, const PropNameID& name) override {
        numGets++;
        return DecoratedHostObject::get(rt, name);
      }
    };
    return Object::createFromHostObject(
        plain(), std::make_shared<DHO2>(*this, ho));
  }

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::putNamedOrIndexed(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    Handle<> valueHandle,
    PropOpFlags opFlags) {
  if (LLVM_UNLIKELY(selfHandle->flags_.indexedStorage)) {
    // Note that getStringView can be satisfied without materializing the
    // Identifier.
    const auto strView =
        runtime->getIdentifierTable().getStringView(runtime, name);
    if (auto nameAsIndex = toArrayIndex(strView)) {
      return putComputed_RJS(
          selfHandle,
          runtime,
          runtime->makeHandle(HermesValue::encodeNumberValue(*nameAsIndex)),
          valueHandle,
          opFlags);
    }
    // Here we have indexed properties but the symbol was not index-like.
    // Fall through to putNamed().
  }
  return putNamed_RJS(selfHandle, runtime, name, valueHandle, opFlags);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, MultiDecoratorTest) {
  struct Inc {
    void before() {
      ++count;
    }

    // Test optional after method.

    int count = 0;
  };

  struct Nest {
    void before() {
      ++nest;
    }

    void after() {
      --nest;
    }

    int nest = 0;
  };

  class MultiRuntime final
      : public WithRuntimeDecorator<std::tuple<Inc, Nest>> {
   public:
    explicit MultiRuntime(std::unique_ptr<Runtime> rt)
        : WithRuntimeDecorator<std::tuple<Inc, Nest>>(*rt, tuple_),
          rt_(std::move(rt)) {}

    int count() {
      return std::get<0>(tuple_).count;
    }
    int nest() {
      return std::get<1>(tuple_).nest;
    }

   private:
    std::unique_ptr<Runtime> rt_;
    std::tuple<Inc, Nest> tuple_;
  };

  MultiRuntime mrt(factory());

  Function expectNestOne = Function::createFromHostFunction(
      mrt,
      PropNameID::forAscii(mrt, "expectNestOne"),
      0,
      [](Runtime& rt, const Value& thisVal, const Value* args, size_t count) {
        MultiRuntime* funcmrt = dynamic_cast<MultiRuntime*>(&rt);
        EXPECT_NE(funcmrt, nullptr);
        EXPECT_EQ(funcmrt->count(), 3);
        EXPECT_EQ(funcmrt->nest(), 1);
        return Value::undefined();
      });

  expectNestOne.call(mrt);

  EXPECT_EQ(mrt.count(), 3);
  EXPECT_EQ(mrt.nest(), 0);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ValueTest) {
  EXPECT_TRUE(checkValue(Value::undefined(), "undefined"));
  EXPECT_TRUE(checkValue(Value(), "undefined"));
  EXPECT_TRUE(checkValue(Value::null(), "null"));
  EXPECT_TRUE(checkValue(nullptr, "null"));

  EXPECT_TRUE(checkValue(Value(false), "false"));
  EXPECT_TRUE(checkValue(false, "false"));
  EXPECT_TRUE(checkValue(true, "true"));

  EXPECT_TRUE(checkValue(Value(1.5), "1.5"));
  EXPECT_TRUE(checkValue(2.5, "2.5"));

  EXPECT_TRUE(checkValue(Value(10), "10"));
  EXPECT_TRUE(checkValue(20, "20"));
  EXPECT_TRUE(checkValue(30, "30"));

  // rvalue implicit conversion
  EXPECT_TRUE(checkValue(String::createFromAscii(rt, "one"), "'one'"));
  // lvalue explicit copy
  String s = String::createFromAscii(rt, "two");
  EXPECT_TRUE(checkValue(Value(rt, s), "'two'"));

  {
    // rvalue assignment of trivial value
    Value v1 = 100;
    Value v2 = String::createFromAscii(rt, "hundred");
    v2 = std::move(v1);
    EXPECT_TRUE(v2.isNumber());
    EXPECT_EQ(v2.getNumber(), 100);
  }

  {
    // rvalue assignment of js heap value
    Value v1 = String::createFromAscii(rt, "hundred");
    Value v2 = 100;
    v2 = std::move(v1);
    EXPECT_TRUE(v2.isString());
    EXPECT_EQ(v2.getString(rt).utf8(rt), "hundred");
  }

  Object o = Object(rt);
  EXPECT_TRUE(function("function(value) { return typeof(value) == 'object'; }")
                  .call(rt, Value(rt, o))
                  .getBool());

  uint8_t utf8[] = "[null, 2, \"c\", \"emoji: \xf0\x9f\x86\x97\", {}]";

  EXPECT_TRUE(
      function("function (arr) { return "
               "Array.isArray(arr) && "
               "arr.length == 5 && "
               "arr[0] === null && "
               "arr[1] == 2 && "
               "arr[2] == 'c' && "
               "arr[3] == 'emoji: \\uD83C\\uDD97' && "
               "typeof arr[4] == 'object'; }")
          .call(rt, Value::createFromJsonUtf8(rt, utf8, sizeof(utf8) - 1))
          .getBool());

  EXPECT_TRUE(eval("undefined").isUndefined());
  EXPECT_TRUE(eval("null").isNull());
  EXPECT_TRUE(eval("true").isBool());
  EXPECT_TRUE(eval("false").isBool());
  EXPECT_TRUE(eval("123").isNumber());
  EXPECT_TRUE(eval("123.4").isNumber());
  EXPECT_TRUE(eval("'str'").isString());
  // "{}" returns undefined.  empty code block?
  EXPECT_TRUE(eval("({})").isObject());
  EXPECT_TRUE(eval("[]").isObject());
  EXPECT_TRUE(eval("(function(){})").isObject());

  EXPECT_EQ(eval("123").getNumber(), 123);
  EXPECT_EQ(eval("123.4").getNumber(), 123.4);
  EXPECT_EQ(eval("'str'").getString(rt).utf8(rt), "str");
  EXPECT_TRUE(eval("[]").getObject(rt).isArray(rt));

  EXPECT_EQ(eval("456").asNumber(), 456);
  EXPECT_THROW(eval("'word'").asNumber(), JSIException);
  EXPECT_EQ(
      eval("({1:2, 3:4})").asObject(rt).getProperty(rt, "1").getNumber(), 2);
  EXPECT_THROW(eval("'oops'").asObject(rt), JSIException);

  EXPECT_EQ(eval("['zero',1,2,3]").toString(rt).utf8(rt), "zero,1,2,3");
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, JSErrorsCanBeConstructedWithStack) {
  auto err = JSError(rt, "message", "stack");
  EXPECT_EQ(err.getMessage(), "message");
  EXPECT_EQ(err.getStack(), "stack");
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::seal(Handle<JSObject> selfHandle, Runtime *runtime) {
  CallResult<bool> statusRes = JSObject::preventExtensions(
      selfHandle, runtime, PropOpFlags().plusThrowOnError());
  if (LLVM_UNLIKELY(statusRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  assert(
      *statusRes && "seal preventExtensions with ThrowOnError returned false");

  // Already sealed?
  if (selfHandle->flags_.sealed)
    return ExecutionStatus::RETURNED;

  auto newClazz = HiddenClass::makeAllNonConfigurable(
      runtime->makeHandle(selfHandle->clazz_), runtime);
  selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());

  selfHandle->flags_.sealed = true;

  return ExecutionStatus::RETURNED;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, FunctionConstructorTest) {
  Function ctor = function(
      "function (a) {"
      "  if (typeof a !== 'undefined') {"
      "   this.pika = a;"
      "  }"
      "}");
  ctor.getProperty(rt, "prototype")
      .getObject(rt)
      .setProperty(rt, "pika", "chu");
  auto empty = ctor.callAsConstructor(rt);
  EXPECT_TRUE(empty.isObject());
  auto emptyObj = std::move(empty).getObject(rt);
  EXPECT_EQ(emptyObj.getProperty(rt, "pika").getString(rt).utf8(rt), "chu");
  auto who = ctor.callAsConstructor(rt, "who");
  EXPECT_TRUE(who.isObject());
  auto whoObj = std::move(who).getObject(rt);
  EXPECT_EQ(whoObj.getProperty(rt, "pika").getString(rt).utf8(rt), "who");

  auto instanceof = function("function (o, b) { return o instanceof b; }");
  EXPECT_TRUE(instanceof.call(rt, emptyObj, ctor).getBool());
  EXPECT_TRUE(instanceof.call(rt, whoObj, ctor).getBool());

  auto dateCtor = rt.global().getPropertyAsFunction(rt, "Date");
  auto date = dateCtor.callAsConstructor(rt);
  EXPECT_TRUE(date.isObject());
  EXPECT_TRUE(instanceof.call(rt, date, dateCtor).getBool());
  // Sleep for 50 milliseconds
  std::this_thread::sleep_for(std::chrono::milliseconds(50));
  EXPECT_GE(
      function("function (d) { return (new Date()).getTime() - d.getTime(); }")
          .call(rt, date)
          .getNumber(),
      50);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, ExceptionStackTraceTest) {
  static const char invokeUndefinedScript[] =
      "function hello() {"
      "  var a = {}; a.log(); }"
      "function world() { hello(); }"
      "world()";
  std::string stack;
  try {
    rt.evaluateJavaScript(
        std::make_unique<StringBuffer>(invokeUndefinedScript), "");
  } catch (JSError& e) {
    stack = e.getStack();
  }
  EXPECT_NE(stack.find("world"), std::string::npos);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::getOwnComputedDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    ComputedPropertyDescriptor &desc,
    MutableHandle<> &valueOrAccessor) {
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  // The proxy is ignored here so we can avoid calling
  // JSProxy::getOwnProperty twice on proxies, since
  // getOwnComputedPrimitiveDescriptor doesn't pass back the
  // valueOrAccessor.
  CallResult<bool> res = JSObject::getOwnComputedPrimitiveDescriptor(
      selfHandle, runtime, *converted, IgnoreProxy::Yes, desc);
  if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  if (*res) {
    valueOrAccessor = getComputedSlotValue(selfHandle.get(), runtime, desc);
    return true;
  }
  if (LLVM_UNLIKELY(selfHandle->isProxyObject())) {
    return JSProxy::getOwnProperty(
        selfHandle, runtime, nameValHandle, desc, &valueOrAccessor);
  }
  return false;
}

Project: hermes
File: JSObject.cpp

void JSObject::allocateNewSlotStorage(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SlotIndex newSlotIndex,
    Handle<> valueHandle) {
  // If it is a direct property, just store the value and we are done.
  if (LLVM_LIKELY(newSlotIndex < DIRECT_PROPERTY_SLOTS)) {
    selfHandle->directProps()[newSlotIndex].set(
        *valueHandle, &runtime->getHeap());
    return;
  }

  // Make the slot index relative to the indirect storage.
  newSlotIndex -= DIRECT_PROPERTY_SLOTS;

  // Allocate a new property storage if not already allocated.
  if (LLVM_UNLIKELY(!selfHandle->propStorage_)) {
    // Allocate new storage.
    assert(newSlotIndex == 0 && "allocated slot must be at end");
    auto arrRes = runtime->ignoreAllocationFailure(
        PropStorage::create(runtime, DEFAULT_PROPERTY_CAPACITY));
    selfHandle->propStorage_.set(
        runtime, vmcast<PropStorage>(arrRes), &runtime->getHeap());
  } else if (LLVM_UNLIKELY(
                 newSlotIndex >=
                 selfHandle->propStorage_.get(runtime)->capacity())) {
    // Reallocate the existing one.
    assert(
        newSlotIndex == selfHandle->propStorage_.get(runtime)->size() &&
        "allocated slot must be at end");
    auto hnd = runtime->makeMutableHandle(selfHandle->propStorage_);
    PropStorage::resize(hnd, runtime, newSlotIndex + 1);
    selfHandle->propStorage_.set(runtime, *hnd, &runtime->getHeap());
  }

  {
    NoAllocScope scope{runtime};
    auto *const propStorage = selfHandle->propStorage_.getNonNull(runtime);
    if (newSlotIndex >= propStorage->size()) {
      assert(
          newSlotIndex == propStorage->size() &&
          "allocated slot must be at end");
      PropStorage::resizeWithinCapacity(propStorage, runtime, newSlotIndex + 1);
    }
    // If we don't need to resize, just store it directly.
    propStorage->at(newSlotIndex).set(*valueHandle, &runtime->getHeap());
  }
}

Project: hermes
File: JSObject.cpp

CallResult<uint32_t> appendAllPropertyNames(
    Handle<JSObject> obj,
    Runtime *runtime,
    MutableHandle<BigStorage> &arr,
    uint32_t beginIndex) {
  uint32_t size = beginIndex;
  // We know that duplicate property names can only exist between objects in
  // the prototype chain. Hence there should not be duplicated properties
  // before we start to look at any prototype.
  bool needDedup = false;
  MutableHandle<> prop(runtime);
  MutableHandle<JSObject> head(runtime, obj.get());
  MutableHandle<StringPrimitive> tmpVal{runtime};
  while (head.get()) {
    GCScope gcScope(runtime);

    // enumerableProps will contain all enumerable own properties from obj.
    // Impl note: this is the only place where getOwnPropertyKeys will be
    // called without IncludeNonEnumerable on a Proxy.  Everywhere else,
    // trap ordering is specified but ES9 13.7.5.15 says "The mechanics and
    // order of enumerating the properties is not specified", which is
    // unusual.
    auto cr =
        JSObject::getOwnPropertyNames(head, runtime, true /* onlyEnumerable */);
    if (LLVM_UNLIKELY(cr == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    auto enumerableProps = *cr;
    auto marker = gcScope.createMarker();
    for (unsigned i = 0, e = enumerableProps->getEndIndex(); i < e; ++i) {
      gcScope.flushToMarker(marker);
      prop = enumerableProps->at(runtime, i);
      if (!needDedup) {
        // If no dedup is needed, add it directly.
        if (LLVM_UNLIKELY(
                BigStorage::push_back(arr, runtime, prop) ==
                ExecutionStatus::EXCEPTION)) {
          return ExecutionStatus::EXCEPTION;
        }
        ++size;
        continue;
      }
      // Otherwise loop through all existing properties and check if we
      // have seen it before.
      bool dupFound = false;
      if (prop->isNumber()) {
        for (uint32_t j = beginIndex; j < size && !dupFound; ++j) {
          HermesValue val = arr->at(j);
          if (val.isNumber()) {
            dupFound = val.getNumber() == prop->getNumber();
          } else {
            // val is string, prop is number.
            tmpVal = val.getString();
            auto valNum = toArrayIndex(
                StringPrimitive::createStringView(runtime, tmpVal));
            dupFound = valNum && valNum.getValue() == prop->getNumber();
          }
        }
      } else {
        for (uint32_t j = beginIndex; j < size && !dupFound; ++j) {
          HermesValue val = arr->at(j);
          if (val.isNumber()) {
            // val is number, prop is string.
            auto propNum = toArrayIndex(StringPrimitive::createStringView(
                runtime, Handle<StringPrimitive>::vmcast(prop)));
            dupFound = propNum && (propNum.getValue() == val.getNumber());
          } else {
            dupFound = val.getString()->equals(prop->getString());
          }
        }
      }
      if (LLVM_LIKELY(!dupFound)) {
        if (LLVM_UNLIKELY(
                BigStorage::push_back(arr, runtime, prop) ==
                ExecutionStatus::EXCEPTION)) {
          return ExecutionStatus::EXCEPTION;
        }
        ++size;
      }
    }
    // Continue to follow the prototype chain.
    CallResult<PseudoHandle<JSObject>> parentRes =
        JSObject::getPrototypeOf(head, runtime);
    if (LLVM_UNLIKELY(parentRes == ExecutionStatus::EXCEPTION)) {
      return ExecutionStatus::EXCEPTION;
    }
    head = parentRes->get();
    needDedup = true;
  }
  return size;
}

Project: hermes
File: JSObject.cpp

PseudoHandle<JSObject> JSObject::create(
    Runtime *runtime,
    unsigned propertyCount) {
  JSObjectAlloc<JSObject> mem{runtime};
  JSObject *objProto = runtime->objectPrototypeRawPtr;
  auto self = mem.initToPseudoHandle(new (mem) JSObject(
      runtime,
      &vt.base,
      objProto,
      runtime->getHiddenClassForPrototypeRaw(
          objProto, numOverlapSlots<JSObject>() + ANONYMOUS_PROPERTY_SLOTS),
      GCPointerBase::NoBarriers()));

  return runtime->ignoreAllocationFailure(
      JSObject::allocatePropStorage(std::move(self), runtime, propertyCount));
}

Project: hermes
File: testlib.cpp

  Object createObject(std::shared_ptr<HostObject> ho) {
    class DHO1 : public DecoratedHostObject {
     public:
      using DecoratedHostObject::DecoratedHostObject;

      Value get(Runtime& rt, const PropNameID& name) override {
        numGets++;
        return DecoratedHostObject::get(rt, name);
      }
    };
    return Object::createFromHostObject(
        plain(), std::make_shared<DHO1>(*this, ho));
  }

Project: hermes
File: JSObject.cpp

OptValue<PropertyFlags> JSObject::_getOwnIndexedPropertyFlagsImpl(
    JSObject *self,
    Runtime *runtime,
    uint32_t) {
  return llvh::None;
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::getOwnComputedDescriptor(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    ComputedPropertyDescriptor &desc) {
  auto converted = toPropertyKeyIfObject(runtime, nameValHandle);
  if (LLVM_UNLIKELY(converted == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  return JSObject::getOwnComputedPrimitiveDescriptor(
      selfHandle, runtime, *converted, IgnoreProxy::No, desc);
}

Project: hermes
File: JSObject.cpp

CallResult<PseudoHandle<>> JSObject::getComputedPropertyValue_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<JSObject> propObj,
    ComputedPropertyDescriptor desc,
    Handle<> nameValHandle) {
  if (!propObj) {
    return createPseudoHandle(HermesValue::encodeEmptyValue());
  }

  if (LLVM_LIKELY(!desc.flags.proxyObject)) {
    return JSObject::getComputedPropertyValue_RJS(
        selfHandle, runtime, propObj, desc);
  }

  CallResult<Handle<>> keyRes = toPropertyKey(runtime, nameValHandle);
  if (LLVM_UNLIKELY(keyRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  CallResult<bool> hasRes = JSProxy::hasComputed(propObj, runtime, *keyRes);
  if (LLVM_UNLIKELY(hasRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  if (!*hasRes) {
    return createPseudoHandle(HermesValue::encodeEmptyValue());
  }
  return JSProxy::getComputed(propObj, runtime, *keyRes, selfHandle);
}

Project: hermes
File: JSObject.cpp

JSObject::checkPropertyUpdate(
    Runtime *runtime,
    const PropertyFlags currentFlags,
    DefinePropertyFlags dpFlags,
    const HermesValue curValueOrAccessor,
    Handle<> valueOrAccessor,
    PropOpFlags opFlags) {
  // 8.12.9 [5] Return true, if every field in Desc is absent.
  if (dpFlags.isEmpty())
    return std::make_pair(PropertyUpdateStatus::done, currentFlags);

  assert(
      (!dpFlags.isAccessor() || (!dpFlags.setWritable && !dpFlags.writable)) &&
      "can't set both accessor and writable");
  assert(
      !dpFlags.enableInternalSetter &&
      "cannot change the value of internalSetter");

  // 8.12.9 [6] Return true, if every field in Desc also occurs in current and
  // the value of every field in Desc is the same value as the corresponding
  // field in current when compared using the SameValue algorithm (9.12).
  // TODO: this would probably be much more efficient with bitmasks.
  if ((!dpFlags.setEnumerable ||
       dpFlags.enumerable == currentFlags.enumerable) &&
      (!dpFlags.setConfigurable ||
       dpFlags.configurable == currentFlags.configurable)) {
    if (dpFlags.isAccessor()) {
      if (currentFlags.accessor) {
        auto *curAccessor = vmcast<PropertyAccessor>(curValueOrAccessor);
        auto *newAccessor = vmcast<PropertyAccessor>(valueOrAccessor.get());

        if ((!dpFlags.setGetter ||
             curAccessor->getter == newAccessor->getter) &&
            (!dpFlags.setSetter ||
             curAccessor->setter == newAccessor->setter)) {
          return std::make_pair(PropertyUpdateStatus::done, currentFlags);
        }
      }
    } else {
      if (!currentFlags.accessor &&
          (!dpFlags.setValue ||
           isSameValue(curValueOrAccessor, valueOrAccessor.get())) &&
          (!dpFlags.setWritable || dpFlags.writable == currentFlags.writable)) {
        return std::make_pair(PropertyUpdateStatus::done, currentFlags);
      }
    }
  }

  // 8.12.9 [7]
  // If the property is not configurable, some aspects are not changeable.
  if (!currentFlags.configurable) {
    // Trying to change non-configurable to configurable?
    if (dpFlags.configurable) {
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            "property is not configurable"); // TODO: better message.
      }
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
    }

    // Trying to change the enumerability of non-configurable property?
    if (dpFlags.setEnumerable &&
        dpFlags.enumerable != currentFlags.enumerable) {
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            "property is not configurable"); // TODO: better message.
      }
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
    }
  }

  PropertyFlags newFlags = currentFlags;

  // 8.12.9 [8] If IsGenericDescriptor(Desc) is true, then no further validation
  // is required.
  if (!(dpFlags.setValue || dpFlags.setWritable || dpFlags.setGetter ||
        dpFlags.setSetter)) {
    // Do nothing
  }
  // 8.12.9 [9]
  // Changing between accessor and data descriptor?
  else if (currentFlags.accessor != dpFlags.isAccessor()) {
    if (!currentFlags.configurable) {
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            "property is not configurable"); // TODO: better message.
      }
      return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
    }

    // If we change from accessor to data descriptor, Preserve the existing
    // values of the converted property’s [[Configurable]] and [[Enumerable]]
    // attributes and set the rest of the property’s attributes to their default
    // values.
    // If it's the other way around, since the accessor doesn't have the
    // [[Writable]] attribute, do nothing.
    newFlags.writable = 0;

    // If we are changing from accessor to non-accessor, we must set a new
    // value.
    if (!dpFlags.isAccessor())
      dpFlags.setValue = 1;
  }
  // 8.12.9 [10] if both are data descriptors.
  else if (!currentFlags.accessor) {
    if (!currentFlags.configurable) {
      if (!currentFlags.writable) {
        // If the current property is not writable, but the new one is.
        if (dpFlags.writable) {
          if (opFlags.getThrowOnError()) {
            return runtime->raiseTypeError(
                "property is not configurable"); // TODO: better message.
          }
          return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
        }

        // If we are setting a different value.
        if (dpFlags.setValue &&
            !isSameValue(curValueOrAccessor, valueOrAccessor.get())) {
          if (opFlags.getThrowOnError()) {
            return runtime->raiseTypeError(
                "property is not writable"); // TODO: better message.
          }
          return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
        }
      }
    }
  }
  // 8.12.9 [11] Both are accessors.
  else {
    auto *curAccessor = vmcast<PropertyAccessor>(curValueOrAccessor);
    auto *newAccessor = vmcast<PropertyAccessor>(valueOrAccessor.get());

    // If not configurable, make sure that nothing is changing.
    if (!currentFlags.configurable) {
      if ((dpFlags.setGetter && newAccessor->getter != curAccessor->getter) ||
          (dpFlags.setSetter && newAccessor->setter != curAccessor->setter)) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeError(
              "property is not configurable"); // TODO: better message.
        }
        return std::make_pair(PropertyUpdateStatus::failed, PropertyFlags{});
      }
    }

    // If not setting the getter or the setter, re-use the current one.
    if (!dpFlags.setGetter)
      newAccessor->getter.set(
          runtime, curAccessor->getter, &runtime->getHeap());
    if (!dpFlags.setSetter)
      newAccessor->setter.set(
          runtime, curAccessor->setter, &runtime->getHeap());
  }

  // 8.12.9 [12] For each attribute field of Desc that is present, set the
  // correspondingly named attribute of the property named P of object O to the
  // value of the field.
  if (dpFlags.setEnumerable)
    newFlags.enumerable = dpFlags.enumerable;
  if (dpFlags.setWritable)
    newFlags.writable = dpFlags.writable;
  if (dpFlags.setConfigurable)
    newFlags.configurable = dpFlags.configurable;

  if (dpFlags.setValue)
    newFlags.accessor = false;
  else if (dpFlags.isAccessor())
    newFlags.accessor = true;
  else
    return std::make_pair(PropertyUpdateStatus::done, newFlags);

  return std::make_pair(PropertyUpdateStatus::needSet, newFlags);
}

Project: hermes
File: JSObject.cpp

ExecutionStatus JSObject::freeze(
    Handle<JSObject> selfHandle,
    Runtime *runtime) {
  CallResult<bool> statusRes = JSObject::preventExtensions(
      selfHandle, runtime, PropOpFlags().plusThrowOnError());
  if (LLVM_UNLIKELY(statusRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  assert(
      *statusRes &&
      "freeze preventExtensions with ThrowOnError returned false");

  // Already frozen?
  if (selfHandle->flags_.frozen)
    return ExecutionStatus::RETURNED;

  auto newClazz = HiddenClass::makeAllReadOnly(
      runtime->makeHandle(selfHandle->clazz_), runtime);
  selfHandle->clazz_.set(runtime, *newClazz, &runtime->getHeap());

  selfHandle->flags_.frozen = true;
  selfHandle->flags_.sealed = true;

  return ExecutionStatus::RETURNED;
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, HostObjectWithValueMembers) {
  class Bag : public HostObject {
   public:
    Bag() = default;

    const Value& operator[](const std::string& name) const {
      auto iter = data_.find(name);
      if (iter == data_.end()) {
        return undef_;
      }
      return iter->second;
    }

   protected:
    Value get(Runtime& rt, const PropNameID& name) override {
      return Value(rt, (*this)[name.utf8(rt)]);
    }

    void set(Runtime& rt, const PropNameID& name, const Value& val) override {
      data_.emplace(name.utf8(rt), Value(rt, val));
    }

    Value undef_;
    std::map<std::string, Value> data_;
  };

  auto sharedBag = std::make_shared<Bag>();
  auto& bag = *sharedBag;
  Object jsbag = Object::createFromHostObject(rt, std::move(sharedBag));
  auto set = function(
      "function (o) {"
      "  o.foo = 'bar';"
      "  o.count = 37;"
      "  o.nul = null;"
      "  o.iscool = true;"
      "  o.obj = { 'foo': 'bar' };"
      "}");
  set.call(rt, jsbag);
  auto checkFoo = function("function (o) { return o.foo === 'bar'; }");
  auto checkCount = function("function (o) { return o.count === 37; }");
  auto checkNul = function("function (o) { return o.nul === null; }");
  auto checkIsCool = function("function (o) { return o.iscool === true; }");
  auto checkObj = function(
      "function (o) {"
      "  return (typeof o.obj) === 'object' && o.obj.foo === 'bar';"
      "}");
  // Check this looks good from js
  EXPECT_TRUE(checkFoo.call(rt, jsbag).getBool());
  EXPECT_TRUE(checkCount.call(rt, jsbag).getBool());
  EXPECT_TRUE(checkNul.call(rt, jsbag).getBool());
  EXPECT_TRUE(checkIsCool.call(rt, jsbag).getBool());
  EXPECT_TRUE(checkObj.call(rt, jsbag).getBool());

  // Check this looks good from c++
  EXPECT_EQ(bag["foo"].getString(rt).utf8(rt), "bar");
  EXPECT_EQ(bag["count"].getNumber(), 37);
  EXPECT_TRUE(bag["nul"].isNull());
  EXPECT_TRUE(bag["iscool"].getBool());
  EXPECT_EQ(
      bag["obj"].getObject(rt).getProperty(rt, "foo").getString(rt).utf8(rt),
      "bar");
}

Project: hermes
File: JSObject.cpp

std::string JSObject::getHeuristicTypeName(GC *gc) {
  PointerBase *const base = gc->getPointerBase();
  if (auto constructorVal = tryGetNamedNoAlloc(
          this, base, Predefined::getSymbolID(Predefined::constructor))) {
    if (auto *constructor = dyn_vmcast<JSObject>(*constructorVal)) {
      auto name = constructor->getNameIfExists(base);
      // If the constructor's name doesn't exist, or it is just the object
      // constructor, attempt to find a different name.
      if (!name.empty() && name != "Object")
        return name;
    }
  }

  std::string name = getVT()->base.snapshotMetaData.defaultNameForNode(this);
  // A constructor's name was not found, check if the object is in dictionary
  // mode.
  if (getClass(base)->isDictionary()) {
    return name + "(Dictionary)";
  }

  // If it's not an Object, the CellKind is most likely good enough on its own
  if (getKind() != CellKind::ObjectKind) {
    return name;
  }

  // If the object isn't a dictionary, and it has only a few property names,
  // make the name based on those property names.
  std::vector<std::string> propertyNames;
  HiddenClass::forEachPropertyNoAlloc(
      getClass(base),
      base,
      [gc, &propertyNames](SymbolID id, NamedPropertyDescriptor) {
        if (InternalProperty::isInternal(id)) {
          // Internal properties aren't user-visible, skip them.
          return;
        }
        propertyNames.emplace_back(gc->convertSymbolToUTF8(id));
      });
  // NOTE: One option is to sort the property names before truncation, to
  // reduce the number of groups; however, by not sorting them it makes it
  // easier to spot sets of objects with the same properties but in different
  // orders, and thus find HiddenClass optimizations to make.

  // For objects with a lot of properties but aren't in dictionary mode yet,
  // keep the number displayed small.
  constexpr int kMaxPropertiesForTypeName = 5;
  bool truncated = false;
  if (propertyNames.size() > kMaxPropertiesForTypeName) {
    propertyNames.erase(
        propertyNames.begin() + kMaxPropertiesForTypeName, propertyNames.end());
    truncated = true;
  }
  // The final name should look like Object(a, b, c).
  if (propertyNames.empty()) {
    // Don't add parentheses for objects with no properties.
    return name;
  }
  name += "(";
  bool first = true;
  for (const auto &prop : propertyNames) {
    if (!first) {
      name += ", ";
    }
    first = false;
    name += prop;
  }
  if (truncated) {
    // No need to check for comma edge case because this only happens for
    // greater than one property.
    static_assert(
        kMaxPropertiesForTypeName >= 1,
        "Property truncation should not happen for 0 properties");
    name += ", ...";
  }
  name += ")";
  return name;
}

Project: hermes
File: JSObject.cpp

  static NamedPropertyDescriptor &castToNamedPropertyDescriptorRef(
      ComputedPropertyDescriptor &desc) {
    return desc.castToNamedPropertyDescriptorRef();
  }

Project: hermes
File: JSObject.cpp

CallResult<bool> getOwnComputedPrimitiveDescriptorImpl(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    Handle<> nameValHandle,
    JSObject::IgnoreProxy ignoreProxy,
    SymbolID &id,
    ComputedPropertyDescriptor &desc) {
  assert(
      !nameValHandle->isObject() &&
      "nameValHandle passed to "
      "getOwnComputedPrimitiveDescriptor "
      "cannot be an object");

  // Try the fast paths first if we have "fast" index properties and the
  // property name is an obvious index.
  if (auto arrayIndex = toArrayIndexFastPath(*nameValHandle)) {
    if (JSObject::Helper::flags(*selfHandle).fastIndexProperties) {
      auto res = JSObject::Helper::getOwnIndexedPropertyFlags(
          selfHandle.get(), runtime, *arrayIndex);
      if (res) {
        // This a valid array index, residing in our indexed storage.
        desc.flags = *res;
        desc.flags.indexed = 1;
        desc.slot = *arrayIndex;
        return true;
      }

      // This a valid array index, but we don't have it in our indexed storage,
      // and we don't have index-like named properties.
      return false;
    }

    if (!selfHandle->getClass(runtime)->getHasIndexLikeProperties() &&
        !selfHandle->isHostObject() && !selfHandle->isLazy() &&
        !selfHandle->isProxyObject()) {
      // Early return to handle the case where an object definitely has no
      // index-like properties. This avoids allocating a new StringPrimitive and
      // uniquing it below.
      return false;
    }
  }

  // Convert the string to a SymbolID
  LAZY_TO_IDENTIFIER(runtime, nameValHandle, id);

  // Look for a named property with this name.
  if (JSObject::getOwnNamedDescriptor(
          selfHandle,
          runtime,
          id,
          JSObject::Helper::castToNamedPropertyDescriptorRef(desc))) {
    return true;
  }

  if (LLVM_LIKELY(
          !JSObject::Helper::flags(*selfHandle).indexedStorage &&
          !selfHandle->isLazy() && !selfHandle->isProxyObject())) {
    return false;
  }
  MutableHandle<StringPrimitive> strPrim{runtime};

  // If we have indexed storage, perform potentially expensive conversions
  // to array index and check it.
  if (JSObject::Helper::flags(*selfHandle).indexedStorage) {
    // If the name is a valid integer array index, store it here.
    OptValue<uint32_t> arrayIndex;

    // Try to convert the property name to an array index.
    TO_ARRAY_INDEX(runtime, nameValHandle, strPrim, arrayIndex);

    if (arrayIndex) {
      auto res = JSObject::Helper::getOwnIndexedPropertyFlags(
          selfHandle.get(), runtime, *arrayIndex);
      if (res) {
        desc.flags = *res;
        desc.flags.indexed = 1;
        desc.slot = *arrayIndex;
        return true;
      }
    }
    return false;
  }

  if (selfHandle->isLazy()) {
    JSObject::initializeLazyObject(runtime, selfHandle);
    return JSObject::getOwnComputedPrimitiveDescriptor(
        selfHandle, runtime, nameValHandle, ignoreProxy, desc);
  }

  assert(selfHandle->isProxyObject() && "descriptor flags are impossible");
  if (ignoreProxy == JSObject::IgnoreProxy::Yes) {
    return false;
  }
  return JSProxy::getOwnProperty(
      selfHandle, runtime, nameValHandle, desc, nullptr);
}

Project: hermes
File: JSObject.cpp

CallResult<bool> JSObject::putNamedWithReceiver_RJS(
    Handle<JSObject> selfHandle,
    Runtime *runtime,
    SymbolID name,
    Handle<> valueHandle,
    Handle<> receiver,
    PropOpFlags opFlags) {
  NamedPropertyDescriptor desc;

  // Look for the property in this object or along the prototype chain.
  JSObject *propObj = getNamedDescriptor(
      selfHandle,
      runtime,
      name,
      PropertyFlags::defaultNewNamedPropertyFlags(),
      desc);

  // If the property exists (or, we hit a proxy/hostobject on the way
  // up the chain)
  if (propObj) {
    // Get the simple case out of the way: If the property already
    // exists on selfHandle, is not an accessor, selfHandle and
    // receiver are the same, selfHandle is not a host
    // object/proxy/internal setter, and the property is writable,
    // just write into the same slot.

    if (LLVM_LIKELY(
            *selfHandle == propObj &&
            selfHandle.getHermesValue().getRaw() == receiver->getRaw() &&
            !desc.flags.accessor && !desc.flags.internalSetter &&
            !desc.flags.hostObject && !desc.flags.proxyObject &&
            desc.flags.writable)) {
      setNamedSlotValue(
          *selfHandle, runtime, desc, valueHandle.getHermesValue());
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.accessor)) {
      auto *accessor =
          vmcast<PropertyAccessor>(getNamedSlotValue(propObj, runtime, desc));

      // If it is a read-only accessor, fail.
      if (!accessor->setter) {
        if (opFlags.getThrowOnError()) {
          return runtime->raiseTypeError(
              TwineChar16("Cannot assign to property '") +
              runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
              "' which has only a getter");
        }
        return false;
      }

      // Execute the accessor on this object.
      if (accessor->setter.get(runtime)->executeCall1(
              runtime->makeHandle(accessor->setter),
              runtime,
              receiver,
              *valueHandle) == ExecutionStatus::EXCEPTION) {
        return ExecutionStatus::EXCEPTION;
      }
      return true;
    }

    if (LLVM_UNLIKELY(desc.flags.proxyObject)) {
      assert(
          !opFlags.getMustExist() &&
          "MustExist cannot be used with Proxy objects");
      CallResult<bool> setRes = JSProxy::setNamed(
          runtime->makeHandle(propObj), runtime, name, valueHandle, receiver);
      if (LLVM_UNLIKELY(setRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      if (!*setRes && opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            TwineChar16("Proxy set returned false for property '") +
            runtime->getIdentifierTable().getStringView(runtime, name) + "'");
      }
      return setRes;
    }

    if (LLVM_UNLIKELY(!desc.flags.writable)) {
      if (desc.flags.staticBuiltin) {
        return raiseErrorForOverridingStaticBuiltin(
            selfHandle, runtime, runtime->makeHandle(name));
      }
      if (opFlags.getThrowOnError()) {
        return runtime->raiseTypeError(
            TwineChar16("Cannot assign to read-only property '") +
            runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
            "'");
      }
      return false;
    }

    if (*selfHandle == propObj && desc.flags.internalSetter) {
      return internalSetter(
          selfHandle, runtime, name, desc, valueHandle, opFlags);
    }
  }

  // The property does not exist as an conventional own property on
  // this object.

  MutableHandle<JSObject> receiverHandle{runtime, *selfHandle};
  if (selfHandle.getHermesValue().getRaw() != receiver->getRaw() ||
      receiverHandle->isHostObject() || receiverHandle->isProxyObject()) {
    if (selfHandle.getHermesValue().getRaw() != receiver->getRaw()) {
      receiverHandle = dyn_vmcast<JSObject>(*receiver);
    }
    if (!receiverHandle) {
      return false;
    }

    if (getOwnNamedDescriptor(receiverHandle, runtime, name, desc)) {
      if (LLVM_UNLIKELY(desc.flags.accessor || !desc.flags.writable)) {
        return false;
      }

      assert(
          !receiverHandle->isHostObject() && !receiverHandle->isProxyObject() &&
          "getOwnNamedDescriptor never sets hostObject or proxyObject flags");

      setNamedSlotValue(
          *receiverHandle, runtime, desc, valueHandle.getHermesValue());
      return true;
    }

    // Now deal with host and proxy object cases.  We need to call
    // getOwnComputedPrimitiveDescriptor because it knows how to call
    // the [[getOwnProperty]] Proxy impl if needed.
    if (LLVM_UNLIKELY(
            receiverHandle->isHostObject() ||
            receiverHandle->isProxyObject())) {
      if (receiverHandle->isHostObject()) {
        return vmcast<HostObject>(receiverHandle.get())
            ->set(name, *valueHandle);
      }
      ComputedPropertyDescriptor desc;
      CallResult<bool> descDefinedRes = getOwnComputedPrimitiveDescriptor(
          receiverHandle,
          runtime,
          name.isUniqued() ? runtime->makeHandle(HermesValue::encodeStringValue(
                                 runtime->getStringPrimFromSymbolID(name)))
                           : runtime->makeHandle(name),
          IgnoreProxy::No,
          desc);
      if (LLVM_UNLIKELY(descDefinedRes == ExecutionStatus::EXCEPTION)) {
        return ExecutionStatus::EXCEPTION;
      }
      DefinePropertyFlags dpf;
      if (*descDefinedRes) {
        dpf.setValue = 1;
      } else {
        dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
      }
      return JSProxy::defineOwnProperty(
          receiverHandle, runtime, name, dpf, valueHandle, opFlags);
    }
  }

  // Does the caller require it to exist?
  if (LLVM_UNLIKELY(opFlags.getMustExist())) {
    return runtime->raiseReferenceError(
        TwineChar16("Property '") +
        runtime->getIdentifierTable().getStringViewForDev(runtime, name) +
        "' doesn't exist");
  }

  // Add a new property.

  return addOwnProperty(
      receiverHandle,
      runtime,
      name,
      DefinePropertyFlags::getDefaultNewPropertyFlags(),
      valueHandle,
      opFlags);
}

Project: hermes
File: testlib.cpp

TEST_P(JSITest, JSErrorStackOverflowHandling) {
  rt.global().setProperty(
      rt,
      "callSomething",
      Function::createFromHostFunction(
          rt,
          PropNameID::forAscii(rt, "callSomething"),
          0,
          [this](
              Runtime& rt2,
              const Value& thisVal,
              const Value* args,
              size_t count) {
            EXPECT_EQ(&rt, &rt2);
            return function("function() { return 0; }").call(rt);
          }));
  try {
    eval("(function f() { callSomething(); f.apply(); })()");
    FAIL();
  } catch (const JSError& ex) {
    EXPECT_NE(std::string(ex.what()).find("exceeded"), std::string::npos);
  }
}

Project: hermes
File: JSObject.cpp

CallResult<Handle<BigStorage>> getForInPropertyNames(
    Runtime *runtime,
    Handle<JSObject> obj,
    uint32_t &beginIndex,
    uint32_t &endIndex) {
  Handle<HiddenClass> clazz(runtime, obj->getClass(runtime));

  // Fast case: Check the cache.
  MutableHandle<BigStorage> arr(runtime, clazz->getForInCache(runtime));
  if (arr) {
    beginIndex = matchesProtoClasses(runtime, obj, arr);
    if (beginIndex) {
      // Cache is valid for this object, so use it.
      endIndex = arr->size();
      return arr;
    }
    // Invalid for this object. We choose to clear the cache since the
    // changes to the prototype chain probably affect other objects too.
    clazz->clearForInCache(runtime);
    // Clear arr to slightly reduce risk of OOM from allocation below.
    arr = nullptr;
  }

  // Slow case: Build the array of properties.
  auto ownPropEstimate = clazz->getNumProperties();
  auto arrRes = obj->shouldCacheForIn(runtime)
      ? BigStorage::createLongLived(runtime, ownPropEstimate)
      : BigStorage::create(runtime, ownPropEstimate);
  if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) {
    return ExecutionStatus::EXCEPTION;
  }
  arr = std::move(*arrRes);
  if (setProtoClasses(runtime, obj, arr) == ExecutionStatus::EXCEPTION) {
    return ExecutionStatus::EXCEPTION;
  }
  beginIndex = arr->size();
  // If obj or any of its prototypes are unsuitable for caching, then
  // beginIndex is 0 and we return an array with only the property names.
  bool canCache = beginIndex;
  auto end = appendAllPropertyNames(obj, runtime, arr, beginIndex);
  if (end == ExecutionStatus::EXCEPTION) {
    return ExecutionStatus::EXCEPTION;
  }
  endIndex = *end;
  // Avoid degenerate memory explosion: if > 75% of the array is properties
  // or classes from prototypes, then don't cache it.
  const bool tooMuchProto = *end / 4 > ownPropEstimate;
  if (canCache && !tooMuchProto) {
    assert(beginIndex > 0 && "cached array must start with proto classes");
#ifdef HERMES_SLOW_DEBUG
    assert(beginIndex == matchesProtoClasses(runtime, obj, arr) && "matches");
#endif
    clazz->setForInCache(*arr, runtime);
  }
  return arr;
}

Project: hermes
File: testlib.cpp

  RD1(Runtime& plain) : RuntimeDecorator(plain) {}