sc_oberthur_parse_tokeninfo (struct sc_pkcs15_card *p15card,
		unsigned char *buff, size_t len, int postpone_allowed)
{
	struct sc_context *ctx = p15card->card->ctx;
	char label[0x21];
	unsigned flags;
	int ii;

	LOG_FUNC_CALLED(ctx);
	if (!buff || len < 0x24)
		LOG_TEST_RET(ctx, SC_ERROR_INVALID_ARGUMENTS, "Cannot parse token info");

	memset(label, 0, sizeof(label));

	memcpy(label, buff, 0x20);
	ii = 0x20;
	while (*(label + --ii)==' ' && ii)
		;
	*(label + ii + 1) = '\0';

	flags = *(buff + 0x22) * 0x100 + *(buff + 0x23);

	set_string(&p15card->tokeninfo->label, label);
	set_string(&p15card->tokeninfo->manufacturer_id, "Oberthur/OpenSC");

	if (flags & 0x01)
		p15card->tokeninfo->flags |= SC_PKCS15_TOKEN_PRN_GENERATION;

	sc_log(ctx, "label %s", p15card->tokeninfo->label);
	sc_log(ctx, "manufacturer_id %s", p15card->tokeninfo->manufacturer_id);

	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}


sc_pkcs15emu_oberthur_init(struct sc_pkcs15_card * p15card)
{
	struct sc_context *ctx = p15card->card->ctx;
	struct sc_pkcs15_auth_info auth_info;
	struct sc_pkcs15_object   obj;
	struct sc_card *card = p15card->card;
	struct sc_path path;
	int rv, ii, tries_left;
	char serial[0x10];
	unsigned char sopin_reference = 0x04;

	SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE);
	sc_bin_to_hex(card->serialnr.value, card->serialnr.len, serial, sizeof(serial), 0);
	set_string(&p15card->tokeninfo->serial_number, serial);

	p15card->ops.parse_df = sc_awp_parse_df;
	p15card->ops.clear = sc_awp_clear;

	sc_log(ctx, "Oberthur init: serial %s", p15card->tokeninfo->serial_number);

	sc_format_path(AWP_PIN_DF, &path);
	rv = sc_select_file(card, &path, NULL);
	LOG_TEST_RET(ctx, rv, "Oberthur init failed: cannot select PIN dir");

	tries_left = -1;
	rv = sc_verify(card, SC_AC_CHV, sopin_reference, (unsigned char *)"", 0, &tries_left);
	if (rv && rv != SC_ERROR_PIN_CODE_INCORRECT)   {
		sopin_reference = 0x84;
		rv = sc_verify(card, SC_AC_CHV, sopin_reference, (unsigned char *)"", 0, &tries_left);
	}
	if (rv && rv != SC_ERROR_PIN_CODE_INCORRECT)
		LOG_TEST_RET(ctx, rv, "Invalid state of SO-PIN");

	/* add PIN */
	memset(&auth_info, 0, sizeof(auth_info));
	memset(&obj,  0, sizeof(obj));

	auth_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN;
	auth_info.auth_method	= SC_AC_CHV;
	auth_info.auth_id.len = 1;
	auth_info.auth_id.value[0] = 0xFF;
	auth_info.attrs.pin.min_length		= 4;
	auth_info.attrs.pin.max_length		= 64;
	auth_info.attrs.pin.stored_length	= 64;
	auth_info.attrs.pin.type		= SC_PKCS15_PIN_TYPE_ASCII_NUMERIC;
	auth_info.attrs.pin.reference		= sopin_reference;
	auth_info.attrs.pin.pad_char		= 0xFF;
	auth_info.attrs.pin.flags		= SC_PKCS15_PIN_FLAG_CASE_SENSITIVE
				| SC_PKCS15_PIN_FLAG_INITIALIZED
				| SC_PKCS15_PIN_FLAG_NEEDS_PADDING
				| SC_PKCS15_PIN_FLAG_SO_PIN;
	auth_info.tries_left		= tries_left;
	auth_info.logged_in = SC_PIN_STATE_UNKNOWN;

	strncpy(obj.label, "SO PIN", SC_PKCS15_MAX_LABEL_SIZE-1);
	obj.flags = SC_PKCS15_CO_FLAG_MODIFIABLE | SC_PKCS15_CO_FLAG_PRIVATE;

	sc_log(ctx, "Add PIN(%s,auth_id:%s,reference:%i)", obj.label,
			sc_pkcs15_print_id(&auth_info.auth_id), auth_info.attrs.pin.reference);
	rv = sc_pkcs15emu_add_pin_obj(p15card, &obj, &auth_info);
	LOG_TEST_RET(ctx, rv, "Oberthur init failed: cannot add PIN object");

	tries_left = -1;
	rv = sc_verify(card, SC_AC_CHV, 0x81, (unsigned char *)"", 0, &tries_left);
	if (rv == SC_ERROR_PIN_CODE_INCORRECT)   {
		/* add PIN */
		memset(&auth_info, 0, sizeof(auth_info));
		memset(&obj,  0, sizeof(obj));

		auth_info.auth_id.len = sizeof(PinDomainID) > sizeof(auth_info.auth_id.value)
				? sizeof(auth_info.auth_id.value) : sizeof(PinDomainID);
		memcpy(auth_info.auth_id.value, PinDomainID, auth_info.auth_id.len);
		auth_info.auth_method	= SC_AC_CHV;

		auth_info.attrs.pin.min_length		= 4;
		auth_info.attrs.pin.max_length		= 64;
		auth_info.attrs.pin.stored_length	= 64;
		auth_info.attrs.pin.type		= SC_PKCS15_PIN_TYPE_ASCII_NUMERIC;
		auth_info.attrs.pin.reference		= 0x81;
		auth_info.attrs.pin.pad_char		= 0xFF;
		auth_info.attrs.pin.flags		= SC_PKCS15_PIN_FLAG_CASE_SENSITIVE
					| SC_PKCS15_PIN_FLAG_INITIALIZED
					| SC_PKCS15_PIN_FLAG_NEEDS_PADDING
					| SC_PKCS15_PIN_FLAG_LOCAL;
		auth_info.tries_left		= tries_left;

		strncpy(obj.label, PIN_DOMAIN_LABEL, SC_PKCS15_MAX_LABEL_SIZE-1);
		obj.flags = SC_PKCS15_CO_FLAG_MODIFIABLE | SC_PKCS15_CO_FLAG_PRIVATE;
		if (sopin_reference == 0x84) {
			/*
			 * auth_pin_reset_oberthur_style() in card-oberthur.c
			 * always uses PUK with reference 0x84 for
			 * unblocking of User PIN
			 */
			obj.auth_id.len = 1;
			obj.auth_id.value[0] = 0xFF;
		}

		sc_format_path(AWP_PIN_DF, &auth_info.path);
		auth_info.path.type = SC_PATH_TYPE_PATH;

		sc_log(ctx, "Add PIN(%s,auth_id:%s,reference:%i)", obj.label,
				sc_pkcs15_print_id(&auth_info.auth_id), auth_info.attrs.pin.reference);
		rv = sc_pkcs15emu_add_pin_obj(p15card, &obj, &auth_info);
		LOG_TEST_RET(ctx, rv, "Oberthur init failed: cannot add PIN object");
	}
	else if (rv != SC_ERROR_DATA_OBJECT_NOT_FOUND)    {
		LOG_TEST_RET(ctx, rv, "Oberthur init failed: cannot verify PIN");
	}

	for (ii=0; oberthur_infos[ii].name; ii++)   {
		sc_log(ctx, "Oberthur init: read %s file", oberthur_infos[ii].name);
		free(oberthur_infos[ii].content);
		rv = sc_oberthur_read_file(p15card, oberthur_infos[ii].path,
				&oberthur_infos[ii].content, &oberthur_infos[ii].len, 1);
		LOG_TEST_RET(ctx, rv, "Oberthur init failed: read oberthur file error");

		sc_log(ctx,
		       "Oberthur init: parse %s file, content length %"SC_FORMAT_LEN_SIZE_T"u",
		       oberthur_infos[ii].name, oberthur_infos[ii].len);
		rv = oberthur_infos[ii].parser(p15card, oberthur_infos[ii].content, oberthur_infos[ii].len,
				oberthur_infos[ii].postpone_allowed);
		LOG_TEST_RET(ctx, rv, "Oberthur init failed: parse error");
	}

	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}

oberthur: Correctly check for return values

Thanks oss-fuzz

https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28843

#define PIN_DOMAIN_LABEL	"SCM"

const unsigned char PinDomainID[3] = {0x53, 0x43, 0x4D};

#define AWP_PIN_DF		"3F005011"

static int sc_pkcs15emu_oberthur_add_prvkey(struct sc_pkcs15_card *, unsigned, unsigned);

static int sc_pkcs15emu_oberthur_add_pubkey(struct sc_pkcs15_card *, unsigned, unsigned);

static int sc_pkcs15emu_oberthur_add_cert(struct sc_pkcs15_card *, unsigned);

static int sc_pkcs15emu_oberthur_add_data(struct sc_pkcs15_card *, unsigned, unsigned, int);

static int sc_oberthur_parse_tokeninfo (struct sc_pkcs15_card *, unsigned char *, size_t, int);

static int sc_oberthur_parse_containers (struct sc_pkcs15_card *, unsigned char *, size_t, int);

static int sc_oberthur_parse_publicinfo (struct sc_pkcs15_card *, unsigned char *, size_t, int);

static int sc_oberthur_parse_privateinfo (struct sc_pkcs15_card *, unsigned char *, size_t, int);

static int sc_awp_parse_df(struct sc_pkcs15_card *, struct sc_pkcs15_df *);

static void sc_awp_clear(struct sc_pkcs15_card *);

struct crypto_container {
	unsigned  id_pub;
	unsigned  id_prv;
	unsigned  id_cert;
}

struct container {
	char uuid[37];
	struct crypto_container exchange;
	struct crypto_container sign;

	struct container *next;
	struct container *prev;
}

struct container *Containers = NULL;

static struct {
	const char *name;
	const char *path;
	unsigned char *content;
	size_t len;
	int (*parser)(struct sc_pkcs15_card *, unsigned char *, size_t, int);
	int postpone_allowed;
} oberthur_infos[] = {
	/* Never change the following order */
	{ "Token info",			AWP_TOKEN_INFO, 	NULL, 0, sc_oberthur_parse_tokeninfo, 	0},
	{ "Containers MS",		AWP_CONTAINERS_MS, 	NULL, 0, sc_oberthur_parse_containers, 	0},
	{ "Public objects list",	AWP_OBJECTS_LIST_PUB, 	NULL, 0, sc_oberthur_parse_publicinfo, 	0},
	{ "Private objects list",	AWP_OBJECTS_LIST_PRV,	NULL, 0, sc_oberthur_parse_privateinfo, 1},
	{ NULL, NULL, NULL, 0, NULL, 0}
};


  void Compute(OpKernelContext* ctx) override {
    const Tensor& input = ctx->input(0);
    const Tensor& input_min_tensor = ctx->input(1);
    const Tensor& input_max_tensor = ctx->input(2);

    int num_slices = 1;
    if (axis_ > -1) {
      num_slices = input.dim_size(axis_);
    }
    OP_REQUIRES(ctx, input_min_tensor.NumElements() == num_slices,
                errors::InvalidArgument(
                    "input_min_tensor must have as many elements as input on "
                    "the dequantization axis (",
                    axis_, "), got ", input_min_tensor.NumElements(),
                    ", expected ", num_slices));
    OP_REQUIRES(ctx, input_max_tensor.NumElements() == num_slices,
                errors::InvalidArgument(
                    "input_max_tensor must have as many elements as input on "
                    "the dequantization axis (",
                    axis_, "), got ", input_max_tensor.NumElements(),
                    ", expected ", num_slices));

    Tensor* output = nullptr;
    OP_REQUIRES_OK(ctx, ctx->allocate_output(0, input.shape(), &output));
    Tensor float_output =
        need_cast_ ? tensorflow::Tensor(DT_FLOAT, input.shape()) : *output;
    if (num_slices == 1) {
      const float min_range = input_min_tensor.flat<float>()(0);
      const float max_range = input_max_tensor.flat<float>()(0);
      DequantizeTensor(ctx, input, min_range, max_range, &float_output);
    } else {
      OP_REQUIRES(ctx, mode_ != QUANTIZE_MODE_MIN_FIRST,
                  errors::Unimplemented("MIN_FIRST mode is not implemented for "
                                        "Dequantize with axis != -1."));

      int64 pre_dim = 1, post_dim = 1;
      for (int i = 0; i < axis_; ++i) {
        pre_dim *= float_output.dim_size(i);
      }
      for (int i = axis_ + 1; i < float_output.dims(); ++i) {
        post_dim *= float_output.dim_size(i);
      }
      auto input_tensor = input.template bit_casted_shaped<T, 3>(
          {pre_dim, num_slices, post_dim});
      auto output_tensor =
          float_output.flat_inner_outer_dims<float, 3>(axis_ - 1);
      auto min_ranges = input_min_tensor.vec<float>();
      auto max_ranges = input_max_tensor.vec<float>();
      for (int i = 0; i < num_slices; ++i) {
        DequantizeSlice(ctx->eigen_device<Device>(), ctx,
                        input_tensor.template chip<1>(i), min_ranges(i),
                        max_ranges(i), output_tensor.template chip<1>(i));
      }
    }
    if (need_cast_) {
      S* out_ptr = output->flat<S>().data();
      float* in_ptr = float_output.flat<float>().data();
      for (int64 i = 0; i < float_output.NumElements(); ++i) {
        out_ptr[i] = static_cast<S>(in_ptr[i]);
      }
    }
  }

Fix heap OOB read in dequantize op.

Also fixes SEGV in same op

PiperOrigin-RevId: 372437896
Change-Id: I135e94d360c2a1ce374c10f7e0fed1af603dbc02


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

[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

class JSITest : public JSITestBase {}

class RD1 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD1(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class RD2 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD2(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class HO : public HostObject {
 public:
  explicit HO(Runtime* expectedRT) : expectedRT_(expectedRT) {}

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

 private:
  // The runtime we expect to be called with.
  Runtime* expectedRT_;
}


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


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

/* No explicit global definitions found. */


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

class RD1 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD1(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class RD2 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD2(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class HO : public HostObject {
 public:
  explicit HO(Runtime* expectedRT) : expectedRT_(expectedRT) {}

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

 private:
  // The runtime we expect to be called with.
  Runtime* expectedRT_;
}

unsigned RD1::numGets = 0;

unsigned RD2::numGets = 0;


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


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


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


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'")));
}


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()");
}


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


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


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


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


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


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


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


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


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


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


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


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


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()) + "'");
}


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


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


class RD1 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD1(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class RD2 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD2(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class HO : public HostObject {
 public:
  explicit HO(Runtime* expectedRT) : expectedRT_(expectedRT) {}

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

 private:
  // The runtime we expect to be called with.
  Runtime* expectedRT_;
}


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

class JSITest : public JSITestBase {}

class RD1 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD1(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}

class RD2 : public RuntimeDecorator<Runtime, Runtime> {
 public:
  RD2(Runtime& plain) : RuntimeDecorator(plain) {}

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

  static unsigned numGets;
}


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


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