// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdlib.h>

#include <optional>

#include "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/compilation-cache.h"
#include "src/execution/execution.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "src/runtime/runtime.h"
#include "src/strings/unicode-inl.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/test-api.h"
#include "test/common/flag-utils.h"

using ::v8::Boolean;
using ::v8::Context;
using ::v8::Function;
using ::v8::FunctionTemplate;
using ::v8::Local;
using ::v8::Name;
using ::v8::Object;
using ::v8::ObjectTemplate;
using ::v8::Script;
using ::v8::String;
using ::v8::Symbol;
using ::v8::Value;

namespace {

constexpr v8::EmbedderDataTypeTag kApiInterceptorTag = 1;

void Returns42(const v8::FunctionCallbackInfo<v8::Value>& info) {
  info.GetReturnValue().Set(42);
}

void Return239Callback(Local<Name> name,
                       const v8::PropertyCallbackInfo<Value>& info) {
  ApiTestFuzzer::Fuzz();
  CheckReturnValue(info, FUNCTION_ADDR(Return239Callback));
  info.GetReturnValue().Set(v8_str("bad value"));
  info.GetReturnValue().Set(v8_num(239));
}

v8::Intercepted EmptyInterceptorGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted EmptyInterceptorSetter(
    Local<Name> name, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted EmptyInterceptorQuery(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted EmptyInterceptorDeleter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  return v8::Intercepted::kNo;
}

void EmptyInterceptorEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {}

v8::Intercepted EmptyInterceptorDefinerWithSideEffect(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Local<v8::Value> result = CompileRun("interceptor_definer_side_effect()");
  if (!result->IsNull()) {
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

void SimpleGetterImpl(Local<String> name_str,
                      const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<Object> self = info.This();
  info.GetReturnValue().Set(
      self->Get(
              info.GetIsolate()->GetCurrentContext(),
              String::Concat(info.GetIsolate(), v8_str("accessor_"), name_str))
          .ToLocalChecked());
}

void SimpleSetterImpl(Local<String> name_str,
                      const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<Object> self = info.This();
  Local<Value> value = info[0];
  self->Set(info.GetIsolate()->GetCurrentContext(),
            String::Concat(info.GetIsolate(), v8_str("accessor_"), name_str),
            value)
      .FromJust();
}

void SimpleGetterCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
  Local<String> name_str = args.Data().As<String>();
  SimpleGetterImpl(name_str, args);
}

void SimpleSetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<String> name_str = info.Data().As<String>();
  SimpleSetterImpl(name_str, info);
}

void SymbolGetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<Name> name = info.Data().As<Name>();
  CHECK(name->IsSymbol());
  v8::Isolate* isolate = info.GetIsolate();
  Local<Symbol> sym = name.As<Symbol>();
  if (sym->Description(isolate)->IsUndefined()) return;
  SimpleGetterImpl(sym->Description(isolate).As<String>(), info);
}

void SymbolSetterCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
  Local<Name> name = info.Data().As<Name>();
  CHECK(name->IsSymbol());
  v8::Isolate* isolate = info.GetIsolate();
  Local<Symbol> sym = name.As<Symbol>();
  if (sym->Description(isolate)->IsUndefined()) return;
  SimpleSetterImpl(sym->Description(isolate).As<String>(), info);
}

v8::Intercepted InterceptorGetter(
    Local<Name> generic_name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (generic_name->IsSymbol()) return v8::Intercepted::kNo;
  Local<String> name = generic_name.As<String>();
  String::Utf8Value utf8(info.GetIsolate(), name);
  char* name_str = *utf8;
  char prefix[] = "interceptor_";
  int i;
  for (i = 0; name_str[i] && prefix[i]; ++i) {
    if (name_str[i] != prefix[i]) return v8::Intercepted::kNo;
  }
  Local<Object> self = info.Holder().As<Object>();
  info.GetReturnValue().Set(
      self->GetPrivate(
              info.GetIsolate()->GetCurrentContext(),
              v8::Private::ForApi(info.GetIsolate(), v8_str(name_str + i)))
          .ToLocalChecked());
  return v8::Intercepted::kYes;
}

v8::Intercepted InterceptorSetter(
    Local<Name> generic_name, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  if (generic_name->IsSymbol()) return v8::Intercepted::kNo;
  Local<String> name = generic_name.As<String>();
  // Intercept accesses that set certain integer values, for which the name does
  // not start with 'accessor_'.
  String::Utf8Value utf8(info.GetIsolate(), name);
  char* name_str = *utf8;
  char prefix[] = "accessor_";
  int i;
  for (i = 0; name_str[i] && prefix[i]; ++i) {
    if (name_str[i] != prefix[i]) break;
  }
  if (!prefix[i]) return v8::Intercepted::kNo;

  Local<Context> context = info.GetIsolate()->GetCurrentContext();
  if (value->IsInt32() && value->Int32Value(context).FromJust() < 10000) {
    Local<Object> self = info.Holder().As<Object>();
    Local<v8::Private> symbol = v8::Private::ForApi(info.GetIsolate(), name);
    self->SetPrivate(context, symbol, value).FromJust();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted GenericInterceptorGetter(
    Local<Name> generic_name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  Local<String> str;
  if (generic_name->IsSymbol()) {
    Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
    if (name->IsUndefined()) return v8::Intercepted::kNo;
    str = String::Concat(isolate, v8_str("_sym_"), name.As<String>());
  } else {
    Local<String> name = generic_name.As<String>();
    String::Utf8Value utf8(isolate, name);
    char* name_str = *utf8;
    if (*name_str != '$') return v8::Intercepted::kNo;
    str = String::Concat(isolate, v8_str("_str_"), name);
  }

  Local<Object> self = info.Holder();
  info.GetReturnValue().Set(
      self->Get(isolate->GetCurrentContext(), str).ToLocalChecked());
  return v8::Intercepted::kYes;
}

v8::Intercepted GenericInterceptorQuery(
    Local<Name> generic_name,
    const v8::PropertyCallbackInfo<v8::Integer>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  Local<String> str;
  if (generic_name->IsSymbol()) {
    Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
    if (name->IsUndefined()) return v8::Intercepted::kNo;
    str = String::Concat(isolate, v8_str("_sym_"), name.As<String>());
  } else {
    Local<String> name = generic_name.As<String>();
    String::Utf8Value utf8(isolate, name);
    char* name_str = *utf8;
    if (*name_str != '$') return v8::Intercepted::kNo;
    str = String::Concat(isolate, v8_str("_str_"), name);
  }

  Local<Object> self = info.Holder();
  v8::PropertyAttribute attributes;
  bool exists;
  if (self->GetPropertyAttributes(isolate->GetCurrentContext(), str,
                                  &attributes)
          .To(&exists)) {
    if (!exists) return v8::Intercepted::kNo;
    info.GetReturnValue().Set(attributes);
  }
  return v8::Intercepted::kYes;
}

v8::Intercepted GenericInterceptorSetter(
    Local<Name> generic_name, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  Local<String> str;
  if (generic_name->IsSymbol()) {
    Local<Value> name = generic_name.As<Symbol>()->Description(isolate);
    if (name->IsUndefined()) return v8::Intercepted::kNo;
    str = String::Concat(info.GetIsolate(), v8_str("_sym_"), name.As<String>());
  } else {
    Local<String> name = generic_name.As<String>();
    String::Utf8Value utf8(info.GetIsolate(), name);
    char* name_str = *utf8;
    if (*name_str != '$') return v8::Intercepted::kNo;
    str = String::Concat(info.GetIsolate(), v8_str("_str_"), name);
  }

  Local<Object> self = info.Holder();
  self->Set(info.GetIsolate()->GetCurrentContext(), str, value).FromJust();
  return v8::Intercepted::kYes;
}

void AddAccessor(v8::Isolate* isolate, Local<FunctionTemplate> templ,
                 Local<Name> name, v8::FunctionCallback getter,
                 v8::FunctionCallback setter) {
  Local<FunctionTemplate> getter_templ =
      FunctionTemplate::New(isolate, getter, name);
  Local<FunctionTemplate> setter_templ =
      FunctionTemplate::New(isolate, setter, name);

  templ->PrototypeTemplate()->SetAccessorProperty(name, getter_templ,
                                                  setter_templ);
}

void AddStringOnlyInterceptor(Local<FunctionTemplate> templ,
                              v8::NamedPropertyGetterCallback getter,
                              v8::NamedPropertySetterCallbackV2 setter) {
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      getter, setter, nullptr, nullptr, nullptr, Local<v8::Value>(),
      v8::PropertyHandlerFlags::kOnlyInterceptStrings));
}

void AddInterceptor(Local<FunctionTemplate> templ,
                    v8::NamedPropertyGetterCallback getter,
                    v8::NamedPropertySetterCallbackV2 setter) {
  templ->InstanceTemplate()->SetHandler(
      v8::NamedPropertyHandlerConfiguration(getter, setter));
}

v8::Intercepted DummyIndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyGetter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertyGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyGetter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyIndexedPropertyDefiner(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyDefiner));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertyDefiner(
    Local<Name> property, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyDefiner));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyIndexedPropertySetter(
    uint32_t index, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertySetter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertySetter(
    Local<Name> property, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertySetter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyIndexedPropertyDescriptor(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyDescriptor));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertyDescriptor(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyDescriptor));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyIndexedPropertyQuery(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyQuery));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertyQuery(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyQuery));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyIndexedPropertyDeleter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyDeleter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted DummyNamedPropertyDeleter(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyDeleter));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

void DummyIndexedPropertyEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyIndexedPropertyEnumerator));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
}

void DummyNamedPropertyEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(DummyNamedPropertyEnumerator));
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
}

int echo_named_call_count;

v8::Intercepted EchoNamedProperty(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(v8_str("data")
            ->Equals(info.GetIsolate()->GetCurrentContext(), info.Data())
            .FromJust());
  echo_named_call_count++;
  info.GetReturnValue().Set(name);
  return v8::Intercepted::kYes;
}

v8::Intercepted InterceptorHasOwnPropertyGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptorHasOwnPropertyGetterGC(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  i::heap::InvokeMajorGC(CcTest::heap());
  return v8::Intercepted::kNo;
}

v8::Intercepted QueryCallback(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  int& query_counter_int = *GetData<int>(info);
  query_counter_int++;

  auto context = info.GetIsolate()->GetCurrentContext();

  if (v8_str("prop_rw")->Equals(context, property).FromJust()) {
    info.GetReturnValue().Set(v8::PropertyAttribute::None);
    return v8::Intercepted::kYes;
  }
  if (v8_str("prop_ro")->Equals(context, property).FromJust()) {
    info.GetReturnValue().Set(v8::PropertyAttribute::ReadOnly);
    return v8::Intercepted::kYes;
  }

  return v8::Intercepted::kNo;
}

// Examples that show when the query callback is triggered.
void TestQueryInterceptor(bool interceptor_is_proto, const bool masking) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  const bool interceptor_is_object = !interceptor_is_proto;

  int query_counter = 0;

  LocalContext env;

  v8::Local<v8::Object> interceptor;
  {
    v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
    v8::NamedPropertyHandlerConfiguration config{nullptr};
    config.query = QueryCallback;
    config.data = MakeData(isolate, &query_counter);
    if (!masking) {
      config.flags = v8::PropertyHandlerFlags::kNonMasking;
    }
    templ->InstanceTemplate()->SetHandler(config);
    interceptor = templ->GetFunction(env.local())
                      .ToLocalChecked()
                      ->NewInstance(env.local())
                      .ToLocalChecked();
  }
  env->Global()
      ->Set(env.local(), v8_str("interceptor"), interceptor)
      .FromJust();

  v8_run_bool(
      "Object.defineProperties(interceptor.__proto__, {"
      "  proto_prop_rw: {configurable:true, writable:true, enumerable:true},"
      "  proto_prop_ro: {configurable:true, writable:false, enumerable:true},"
      "});");

  if (interceptor_is_proto) {
    v8_run_bool("var obj = Object.create(interceptor);");
  } else {
    v8_run_bool("var obj = interceptor;");
  }

  //
  // Perform operations on properties not intercepted by the interceptor.
  //

  query_counter = 0;
  CHECK(
      v8_run_bool("Object.getOwnPropertyDescriptor(obj, 'x') === undefined;"));
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  v8_run_bool("Object.defineProperty(obj, 'not_enum', {value: 17});");
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  v8_run_bool(
      "Object.defineProperty(obj, 'enum', {value: 17, enumerable: true, "
      "writable: true});");
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(v8_run_bool("obj.propertyIsEnumerable('enum');"));
  if (interceptor_is_object) {
    if (masking) CHECK_EQ(1, query_counter);
    if (!masking) CHECK_EQ(0, query_counter);
  }
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(!v8_run_bool("obj.propertyIsEnumerable('not_enum');"));
  if (interceptor_is_object) {
    if (masking) CHECK_EQ(1, query_counter);
    if (!masking) CHECK_EQ(0, query_counter);
  }
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(v8_run_bool("obj.hasOwnProperty('enum');"));
  CHECK(v8_run_bool("obj.hasOwnProperty('not_enum');"));
  if (interceptor_is_object && masking) {
    CHECK_EQ(2, query_counter);
  } else {
    CHECK_EQ(0, query_counter);
  }

  query_counter = 0;
  CHECK(!v8_run_bool("obj.hasOwnProperty('x');"));
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(!v8_run_bool("obj.propertyIsEnumerable('undef');"));
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  v8_run_bool("Object.defineProperty(obj, 'enum', {value: 42});");
  if (interceptor_is_object) {
    if (masking) CHECK_EQ(1, query_counter);
    if (!masking) CHECK_EQ(0, query_counter);
  }
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(!v8_run_bool("'x' in obj;"));
  CHECK_EQ(1, query_counter);

  //
  // Perform operations on properties intercepted by the interceptor.
  //

  // Attempt to store to interceptor's RW property should trigger query
  // and since there are no setter, the property should be added to the
  // object itself.
  query_counter = 0;
  CHECK_EQ(interceptor_is_object,
           v8_run_bool("obj.hasOwnProperty('prop_rw');"));
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  v8_run_bool("obj.prop_rw = 44;");
  // First call happens during property lookup and second call happens
  // (as there are no setter callback) as a part of DefineOwnProperty on
  // the obj.
  if (interceptor_is_object) CHECK_EQ(2, query_counter);
  if (interceptor_is_proto) CHECK_EQ(1, query_counter);

  query_counter = 0;
  // Either the property already existed on the object or it was added
  // by the last store.
  CHECK(v8_run_bool("obj.hasOwnProperty('prop_rw');"));
  if (interceptor_is_object && masking) {
    CHECK_EQ(1, query_counter);
  } else {
    CHECK_EQ(0, query_counter);
  }
  // Attempt to store to interceptor's RO property should trigger query
  // and since it's RO the store shouldn't happen.

  query_counter = 0;
  v8_run_bool("obj.prop_ro = 44;");
  CHECK_EQ(1, query_counter);

  query_counter = 0;
  CHECK_EQ(interceptor_is_object,
           v8_run_bool("obj.hasOwnProperty('prop_ro');"));
  if (interceptor_is_object) CHECK_EQ(1, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  //
  // Perform operations on properties existing in the interceptor's prototype.
  //

  query_counter = 0;
  CHECK(!v8_run_bool("obj.hasOwnProperty('proto_prop_rw');"));
  if (interceptor_is_object && masking) CHECK_EQ(1, query_counter);
  if (interceptor_is_object && !masking) CHECK_EQ(0, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(!v8_run_bool("obj.hasOwnProperty('proto_prop_ro');"));
  if (interceptor_is_object && masking) CHECK_EQ(1, query_counter);
  if (interceptor_is_object && !masking) CHECK_EQ(0, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  // Attempt to store to interceptor's prototype's RW property should trigger
  // query and since it's RW the store should happen.
  query_counter = 0;
  v8_run_bool("obj.proto_prop_rw = 44;");
  // First call happens during property lookup and second call happens
  // (as there are no setter callback) as a part of DefineOwnProperty on
  // the obj.
  if (interceptor_is_object && masking) CHECK_EQ(2, query_counter);
  if (interceptor_is_proto && masking) CHECK_EQ(1, query_counter);
  // Property on the prototype chain hides it from non-masking interceptor.
  if (!masking) CHECK_EQ(0, query_counter);

  query_counter = 0;
  CHECK(v8_run_bool("obj.hasOwnProperty('proto_prop_rw');"));
  if (interceptor_is_object && masking) CHECK_EQ(1, query_counter);
  if (interceptor_is_object && !masking) CHECK_EQ(0, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);

  // Attempt to store to interceptor's prototype's RO property should not
  // trigger query because its existence "blocks" the store from happening.
  query_counter = 0;
  v8_run_bool("obj.proto_prop_ro = 44;");
  if (interceptor_is_object) CHECK_EQ(0, query_counter);
  if (interceptor_is_proto) {
    // On the way to the interceptor's prototype there's an interceptor which
    // might be queried.
    if (masking) CHECK_EQ(1, query_counter);
    if (!masking) CHECK_EQ(0, query_counter);
  }

  query_counter = 0;
  CHECK(!v8_run_bool("obj.hasOwnProperty('proto_prop_ro');"));
  if (interceptor_is_object && masking) CHECK_EQ(1, query_counter);
  if (interceptor_is_object && !masking) CHECK_EQ(0, query_counter);
  if (interceptor_is_proto) CHECK_EQ(0, query_counter);
}

}  // namespace

THREADED_TEST(QueryInterceptor) {
  TestQueryInterceptor(false,  // interceptor_is_prototype
                       true    // masking
  );
}

THREADED_TEST(QueryInterceptorNonMasking) {
  TestQueryInterceptor(false,  // interceptor_is_prototype
                       false   // masking
  );
}

THREADED_TEST(QueryInterceptorPrototype) {
  TestQueryInterceptor(true,  // interceptor_is_prototype
                       true   // masking
  );
}

THREADED_TEST(QueryInterceptorPrototypeNonMasking) {
  TestQueryInterceptor(true,  // interceptor_is_prototype
                       false  // masking
  );
}

namespace {

bool get_was_called = false;
bool set_was_called = false;

int set_was_called_counter = 0;

v8::Intercepted GetterCallback(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  get_was_called = true;
  return v8::Intercepted::kNo;
}

v8::Intercepted SetterCallback(Local<Name> property, Local<Value> value,
                               const v8::PropertyCallbackInfo<Boolean>& info) {
  set_was_called = true;
  set_was_called_counter++;
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptingSetterCallback(
    Local<Name> property, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kYes;
}

}  // namespace

// Check that get callback is called in defineProperty with accessor descriptor.
THREADED_TEST(DefinerCallbackAccessorInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(
      v8::NamedPropertyHandlerConfiguration(GetterCallback, SetterCallback));
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();

  get_was_called = false;
  set_was_called = false;

  v8_compile("Object.defineProperty(obj, 'x', {set: function() {return 17;}});")
      ->Run(env.local())
      .ToLocalChecked();
  CHECK(get_was_called);
  CHECK(!set_was_called);
}

// Check that set callback is called for function declarations.
THREADED_TEST(SetterCallbackFunctionDeclarationInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(
      v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  set_was_called_counter = 0;

  // Declare function.
  v8::Local<v8::String> code = v8_str("function x() {return 42;}; x();");
  CHECK_EQ(42, v8::Script::Compile(ctx, code)
                   .ToLocalChecked()
                   ->Run(ctx)
                   .ToLocalChecked()
                   ->Int32Value(ctx)
                   .FromJust());
  CHECK_EQ(1, set_was_called_counter);

  // Redeclare function.
  code = v8_str("function x() {return 43;}; x();");
  CHECK_EQ(43, v8::Script::Compile(ctx, code)
                   .ToLocalChecked()
                   ->Run(ctx)
                   .ToLocalChecked()
                   ->Int32Value(ctx)
                   .FromJust());
  CHECK_EQ(2, set_was_called_counter);

  // Redefine function.
  code = v8_str("x = function() {return 44;}; x();");
  CHECK_EQ(44, v8::Script::Compile(ctx, code)
                   .ToLocalChecked()
                   ->Run(ctx)
                   .ToLocalChecked()
                   ->Int32Value(ctx)
                   .FromJust());
  CHECK_EQ(3, set_was_called_counter);
}

namespace {
int descriptor_was_called;

v8::Intercepted PropertyDescriptorCallback(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // Intercept the callback and create some descriptor.
  descriptor_was_called++;
  const char* code =
      "var desc = {value: 5};"
      "desc;";
  Local<Value> descriptor = v8_compile(code)
                                ->Run(info.GetIsolate()->GetCurrentContext())
                                .ToLocalChecked();
  info.GetReturnValue().Set(descriptor);
  return v8::Intercepted::kYes;
}
}  // namespace

// Check that the descriptor callback is called on the global object.
THREADED_TEST(DescriptorCallbackOnGlobalObject) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, PropertyDescriptorCallback, nullptr, nullptr, nullptr));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  descriptor_was_called = 0;

  // Declare function.
  v8::Local<v8::String> code = v8_str(
      "var x = 42; var desc = Object.getOwnPropertyDescriptor(this, 'x'); "
      "desc.value;");
  CHECK_EQ(5, v8::Script::Compile(ctx, code)
                  .ToLocalChecked()
                  ->Run(ctx)
                  .ToLocalChecked()
                  ->Int32Value(ctx)
                  .FromJust());
  CHECK_EQ(1, descriptor_was_called);
}

namespace {
v8::Intercepted QueryCallbackSetDontDelete(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  info.GetReturnValue().Set(v8::DontDelete);
  return v8::Intercepted::kYes;
}

}  // namespace

// Regression for a Node.js test that fails in debug mode.
THREADED_TEST(InterceptorFunctionRedeclareWithQueryCallback) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, QueryCallbackSetDontDelete));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  // Declare and redeclare function.
  v8::Local<v8::String> code = v8_str(
      "function x() {return 42;};"
      "function x() {return 43;};");
  v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).ToLocalChecked();
}

// A lexical declaration must throw when the interceptor reports the property
// as non-configurable (DontDelete), per HasRestrictedGlobalProperty checks in
// https://tc39.es/ecma262/#sec-globaldeclarationinstantiation.
THREADED_TEST(LexicalDeclThrowsForRestrictedGlobalViaInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, QueryCallbackSetDontDelete, nullptr, nullptr,
      v8::Local<v8::Value>(),
      v8::PropertyHandlerFlags::kHasDontDeleteProperty));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  v8::TryCatch try_catch(CcTest::isolate());
  v8::Local<v8::String> code = v8_str("let x;");
  CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty());
  CHECK(try_catch.HasCaught());
}

// Without kHasDontDeleteProperty, the interceptor is not consulted for
// HasRestrictedGlobalProperty and lexical declarations succeed.
THREADED_TEST(LexicalDeclSucceedsWithoutRestrictedGlobalFlag) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, QueryCallbackSetDontDelete));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  v8::TryCatch try_catch(CcTest::isolate());
  v8::Local<v8::String> code = v8_str("let x;");
  CHECK(!v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty());
  CHECK(!try_catch.HasCaught());
}

// Regression test for chromium bug 656648.
// Do not crash on non-masking, intercepting setter callbacks.
THREADED_TEST(NonMaskingInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, InterceptingSetterCallback, nullptr, nullptr, nullptr,
      Local<Value>(), v8::PropertyHandlerFlags::kNonMasking));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  v8::Local<v8::String> code = v8_str("function x() {return 43;};");
  v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).ToLocalChecked();
}

// Check that function re-declarations throw if they are read-only.
THREADED_TEST(SetterCallbackFunctionDeclarationInterceptorThrow) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());

  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(
      v8::NamedPropertyHandlerConfiguration(nullptr, SetterCallback));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  set_was_called = false;

  v8::Local<v8::String> code = v8_str(
      "function x() {return 42;};"
      "Object.defineProperty(this, 'x', {"
      "configurable: false, "
      "writable: false});"
      "x();");
  CHECK_EQ(42, v8::Script::Compile(ctx, code)
                   .ToLocalChecked()
                   ->Run(ctx)
                   .ToLocalChecked()
                   ->Int32Value(ctx)
                   .FromJust());

  CHECK(set_was_called);

  v8::TryCatch try_catch(CcTest::isolate());
  set_was_called = false;

  // Redeclare function that is read-only.
  code = v8_str("function x() {return 43;};");
  CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty());
  CHECK(try_catch.HasCaught());

  CHECK(!set_was_called);
}


namespace {

bool get_was_called_in_order = false;
bool define_was_called_in_order = false;

v8::Intercepted GetterCallbackOrder(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  get_was_called_in_order = true;
  CHECK(!define_was_called_in_order);
  info.GetReturnValue().Set(property);
  return v8::Intercepted::kYes;
}

v8::Intercepted DefinerCallbackOrder(
    Local<Name> property, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  // Get called before DefineProperty because we query the descriptor first.
  CHECK(get_was_called_in_order);
  define_was_called_in_order = true;
  return v8::Intercepted::kNo;
}

}  // namespace

// Check that getter callback is called before definer callback.
THREADED_TEST(DefinerCallbackGetAndDefine) {
  v8::HandleScope scope(CcTest::isolate());
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      GetterCallbackOrder, SetterCallback, nullptr, nullptr, nullptr,
      DefinerCallbackOrder));
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();

  CHECK(!get_was_called_in_order);
  CHECK(!define_was_called_in_order);

  v8_compile("Object.defineProperty(obj, 'x', {set: function() {return 17;}});")
      ->Run(env.local())
      .ToLocalChecked();
  CHECK(get_was_called_in_order);
  CHECK(define_was_called_in_order);
}

namespace {  //  namespace for InObjectLiteralDefinitionWithInterceptor

// Workaround for no-snapshot builds: only intercept once Context::New() is
// done, otherwise we'll intercept
// bootstrapping like defining array on the global object.
bool context_is_done = false;
bool getter_callback_was_called = false;

v8::Intercepted ReturnUndefinedGetterCallback(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (context_is_done) {
    getter_callback_was_called = true;
    info.GetReturnValue().SetUndefined();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

}  // namespace

// Check that an interceptor is not invoked during ES6 style definitions inside
// an object literal.
THREADED_TEST(InObjectLiteralDefinitionWithInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;

  // Set up a context in which all global object definitions are intercepted.
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  v8::Local<ObjectTemplate> object_template = templ->InstanceTemplate();
  object_template->SetHandler(
      v8::NamedPropertyHandlerConfiguration(ReturnUndefinedGetterCallback));
  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  context_is_done = true;

  // The interceptor returns undefined for any global object,
  // so setting a property on an object should throw.
  v8::Local<v8::String> code = v8_str("var o = {}; o.x = 5");
  {
    getter_callback_was_called = false;
    v8::TryCatch try_catch(CcTest::isolate());
    CHECK(v8::Script::Compile(ctx, code).ToLocalChecked()->Run(ctx).IsEmpty());
    CHECK(try_catch.HasCaught());
    CHECK(getter_callback_was_called);
  }

  // Defining a property in the object literal should not throw
  // because the interceptor is not invoked.
  {
    getter_callback_was_called = false;
    v8::TryCatch try_catch(CcTest::isolate());
    code = v8_str("var l = {x: 5};");
    CHECK(v8::Script::Compile(ctx, code)
              .ToLocalChecked()
              ->Run(ctx)
              .ToLocalChecked()
              ->IsUndefined());
    CHECK(!try_catch.HasCaught());
    CHECK(!getter_callback_was_called);
  }
}

THREADED_TEST(InterceptorHasOwnProperty) {
  LocalContext context;
  v8::Isolate* isolate = context.isolate();
  v8::HandleScope scope(isolate);
  Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate);
  Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
  instance_templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetter));
  Local<Function> function =
      fun_templ->GetFunction(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("constructor"), function)
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "var o = new constructor();"
      "o.hasOwnProperty('ostehaps');");
  CHECK(!value->BooleanValue(isolate));
  value = CompileRun(
      "o.ostehaps = 42;"
      "o.hasOwnProperty('ostehaps');");
  CHECK(value->BooleanValue(isolate));
  value = CompileRun(
      "var p = new constructor();"
      "p.hasOwnProperty('ostehaps');");
  CHECK(!value->BooleanValue(isolate));
}


THREADED_TEST(InterceptorHasOwnPropertyCausingGC) {
  LocalContext context;
  v8::Isolate* isolate = context.isolate();
  v8::HandleScope scope(isolate);
  Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate);
  Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
  instance_templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorHasOwnPropertyGetterGC));
  Local<Function> function =
      fun_templ->GetFunction(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("constructor"), function)
      .FromJust();
  // Let's first make some stuff so we can be sure to get a good GC.
  CompileRun(
      "function makestr(size) {"
      "  switch (size) {"
      "    case 1: return 'f';"
      "    case 2: return 'fo';"
      "    case 3: return 'foo';"
      "  }"
      "  return makestr(size >> 1) + makestr((size + 1) >> 1);"
      "}"
      "var x = makestr(12345);"
      "x = makestr(31415);"
      "x = makestr(23456);");
  v8::Local<Value> value = CompileRun(
      "var o = new constructor();"
      "o.__proto__ = new String(x);"
      "o.hasOwnProperty('ostehaps');");
  CHECK(!value->BooleanValue(isolate));
}

namespace {

void CheckInterceptorIC(v8::NamedPropertyGetterCallback getter,
                        v8::NamedPropertySetterCallbackV2 setter,
                        v8::NamedPropertyQueryCallback query,
                        v8::NamedPropertyDefinerCallbackV2 definer,
                        v8::PropertyHandlerFlags flags, const char* source,
                        std::optional<int> expected) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
      getter, setter, query, nullptr /* deleter */, nullptr /* enumerator */,
      definer, nullptr /* descriptor */, v8_str("data"), flags));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(source);
  if (expected) {
    CHECK_EQ(*expected, value->Int32Value(context.local()).FromJust());
  } else {
    CHECK(value.IsEmpty());
  }
}

void CheckInterceptorIC(v8::NamedPropertyGetterCallback getter,
                        v8::NamedPropertyQueryCallback query,
                        const char* source, std::optional<int> expected) {
  CheckInterceptorIC(getter, nullptr, query, nullptr,
                     v8::PropertyHandlerFlags::kNone, source, expected);
}

void CheckInterceptorLoadIC(v8::NamedPropertyGetterCallback getter,
                            const char* source, int expected) {
  CheckInterceptorIC(getter, nullptr, nullptr, nullptr,
                     v8::PropertyHandlerFlags::kNone, source, expected);
}

v8::Intercepted InterceptorLoadICGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Isolate* isolate = CcTest::isolate();
  CHECK_EQ(isolate, info.GetIsolate());
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  CHECK(v8_str("data")->Equals(context, info.Data()).FromJust());
  CHECK(v8_str("x")->Equals(context, name).FromJust());
  info.GetReturnValue().Set(v8::Integer::New(isolate, 42));
  return v8::Intercepted::kYes;
}

}  // namespace

// This test should hit the load IC for the interceptor case.
THREADED_TEST(InterceptorLoadIC) {
  CheckInterceptorLoadIC(InterceptorLoadICGetter,
                         "var result = 0;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = o.x;"
                         "}",
                         42);
}


// Below go several tests which verify that JITing for various
// configurations of interceptor and explicit fields works fine
// (those cases are special cased to get better performance).

namespace {

v8::Intercepted InterceptorLoadXICGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  if (v8_str("x")->Equals(isolate->GetCurrentContext(), name).FromJust()) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(42);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptorLoadXICGetterWithSideEffects(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // TODO(ishell): figure out what is the test supposed to do regarding
  // producing side effects but claiming that the interceptor hasn't
  // intercepted the operation. Is it about restarting the lookup iterator?
  ApiTestFuzzer::Fuzz();
  CompileRun("interceptor_getter_side_effect()");
  v8::Isolate* isolate = info.GetIsolate();
  if (v8_str("x")->Equals(isolate->GetCurrentContext(), name).FromJust()) {
    info.GetReturnValue().Set(42);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

}  // namespace

THREADED_TEST(InterceptorLoadICWithFieldOnHolder) {
  CheckInterceptorLoadIC(InterceptorLoadXICGetter,
                         "var result = 0;"
                         "o.y = 239;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = o.y;"
                         "}",
                         239);
}


THREADED_TEST(InterceptorLoadICWithSubstitutedProto) {
  CheckInterceptorLoadIC(InterceptorLoadXICGetter,
                         "var result = 0;"
                         "o.__proto__ = { 'y': 239 };"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = o.y + o.x;"
                         "}",
                         239 + 42);
}


THREADED_TEST(InterceptorLoadICWithPropertyOnProto) {
  CheckInterceptorLoadIC(InterceptorLoadXICGetter,
                         "var result = 0;"
                         "o.__proto__.y = 239;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = o.y + o.x;"
                         "}",
                         239 + 42);
}


THREADED_TEST(InterceptorLoadICUndefined) {
  CheckInterceptorLoadIC(InterceptorLoadXICGetter,
                         "var result = 0;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = (o.y == undefined) ? 239 : 42;"
                         "}",
                         239);
}


THREADED_TEST(InterceptorLoadICWithOverride) {
  CheckInterceptorLoadIC(InterceptorLoadXICGetter,
                         "fst = new Object();  fst.__proto__ = o;"
                         "snd = new Object();  snd.__proto__ = fst;"
                         "var result1 = 0;"
                         "for (var i = 0; i < 1000;  i++) {"
                         "  result1 = snd.x;"
                         "}"
                         "fst.x = 239;"
                         "var result = 0;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result = snd.x;"
                         "}"
                         "result + result1",
                         239 + 42);
}


// Test the case when we stored field into
// a stub, but interceptor produced value on its own.
THREADED_TEST(InterceptorLoadICFieldNotNeeded) {
  CheckInterceptorLoadIC(
      InterceptorLoadXICGetter,
      "proto = new Object();"
      "o.__proto__ = proto;"
      "proto.x = 239;"
      "for (var i = 0; i < 1000; i++) {"
      "  o.x;"
      // Now it should be ICed and keep a reference to x defined on proto
      "}"
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result += o.x;"
      "}"
      "result;",
      42 * 1000);
}


// Test the case when we stored field into
// a stub, but it got invalidated later on.
THREADED_TEST(InterceptorLoadICInvalidatedField) {
  CheckInterceptorLoadIC(
      InterceptorLoadXICGetter,
      "proto1 = new Object();"
      "proto2 = new Object();"
      "o.__proto__ = proto1;"
      "proto1.__proto__ = proto2;"
      "proto2.y = 239;"
      "for (var i = 0; i < 1000; i++) {"
      "  o.y;"
      // Now it should be ICed and keep a reference to y defined on proto2
      "}"
      "proto1.y = 42;"
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result += o.y;"
      "}"
      "result;",
      42 * 1000);
}

namespace {

int interceptor_load_not_handled_calls = 0;
v8::Intercepted InterceptorLoadNotHandled(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ++interceptor_load_not_handled_calls;
  return v8::Intercepted::kNo;
}
}  // namespace

// Test how post-interceptor lookups are done in the non-cacheable
// case: the interceptor should not be invoked during this lookup.
THREADED_TEST(InterceptorLoadICPostInterceptor) {
  interceptor_load_not_handled_calls = 0;
  CheckInterceptorLoadIC(InterceptorLoadNotHandled,
                         "receiver = new Object();"
                         "receiver.__proto__ = o;"
                         "proto = new Object();"
                         "/* Make proto a slow-case object. */"
                         "for (var i = 0; i < 1000; i++) {"
                         "  proto[\"xxxxxxxx\" + i] = [];"
                         "}"
                         "proto.x = 17;"
                         "o.__proto__ = proto;"
                         "var result = 0;"
                         "for (var i = 0; i < 1000; i++) {"
                         "  result += receiver.x;"
                         "}"
                         "result;",
                         17 * 1000);
  CHECK_EQ(1000, interceptor_load_not_handled_calls);
}


// Test the case when we stored field into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and fields' holders.
THREADED_TEST(InterceptorLoadICInvalidatedFieldViaGlobal) {
  CheckInterceptorLoadIC(
      InterceptorLoadXICGetter,
      "o.__proto__ = this;"  // set a global to be a proto of o.
      "this.__proto__.y = 239;"
      "for (var i = 0; i < 10; i++) {"
      "  if (o.y != 239) throw 'oops: ' + o.y;"
      // Now it should be ICed and keep a reference to y defined on
      // field_holder.
      "}"
      "this.y = 42;"  // Assign on a global.
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  result += o.y;"
      "}"
      "result;",
      42 * 10);
}

namespace {
void SetOnHolder(Local<Name> name, Local<Value> value,
                 const v8::PropertyCallbackInfo<Boolean>& info) {
  info.Holder()
      ->CreateDataProperty(info.GetIsolate()->GetCurrentContext(), name, value)
      .FromJust();
}
}  // namespace

THREADED_TEST(InterceptorLoadICWithCallbackOnHolder) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  templ->SetNativeDataProperty(v8_str("y"), Return239Callback);
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  // Check the case when receiver and interceptor's holder
  // are the same objects.
  v8::Local<Value> value = CompileRun(
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = o.y;"
      "}");
  CHECK_EQ(239, value->Int32Value(context.local()).FromJust());

  // Check the case when interceptor's holder is in proto chain
  // of receiver.
  value = CompileRun(
      "r = { __proto__: o };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = r.y;"
      "}");
  CHECK_EQ(239, value->Int32Value(context.local()).FromJust());
}


THREADED_TEST(InterceptorLoadICWithCallbackOnProto) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  v8::Local<v8::ObjectTemplate> templ_p = ObjectTemplate::New(isolate);
  templ_p->SetNativeDataProperty(v8_str("y"), Return239Callback);

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  context->Global()
      ->Set(context.local(), v8_str("p"),
            templ_p->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  // Check the case when receiver and interceptor's holder
  // are the same objects.
  v8::Local<Value> value = CompileRun(
      "o.__proto__ = p;"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = o.x + o.y;"
      "}");
  CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust());

  // Check the case when interceptor's holder is in proto chain
  // of receiver.
  value = CompileRun(
      "r = { __proto__: o };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = r.x + r.y;"
      "}");
  CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust());
}


THREADED_TEST(InterceptorLoadICForCallbackWithOverride) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  templ->SetNativeDataProperty(v8_str("y"), Return239Callback);

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<Value> value = CompileRun(
      "fst = new Object();  fst.__proto__ = o;"
      "snd = new Object();  snd.__proto__ = fst;"
      "var result1 = 0;"
      "for (var i = 0; i < 7;  i++) {"
      "  result1 = snd.x;"
      "}"
      "fst.x = 239;"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = snd.x;"
      "}"
      "result + result1");
  CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust());
}


// Test the case when we stored callback into
// a stub, but interceptor produced value on its own.
THREADED_TEST(InterceptorLoadICCallbackNotNeeded) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  v8::Local<v8::ObjectTemplate> templ_p = ObjectTemplate::New(isolate);
  templ_p->SetNativeDataProperty(v8_str("y"), Return239Callback);

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  context->Global()
      ->Set(context.local(), v8_str("p"),
            templ_p->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<Value> value = CompileRun(
      "o.__proto__ = p;"
      "for (var i = 0; i < 7; i++) {"
      "  o.x;"
      // Now it should be ICed and keep a reference to x defined on p
      "}"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result += o.x;"
      "}"
      "result");
  CHECK_EQ(42 * 7, value->Int32Value(context.local()).FromJust());
}


// Test the case when we stored callback into
// a stub, but it got invalidated later on.
THREADED_TEST(InterceptorLoadICInvalidatedCallback) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  v8::Local<v8::ObjectTemplate> templ_p = ObjectTemplate::New(isolate);
  templ_p->SetNativeDataProperty(v8_str("y"), Return239Callback, SetOnHolder);

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  context->Global()
      ->Set(context.local(), v8_str("p"),
            templ_p->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<Value> value = CompileRun(
      "inbetween = new Object();"
      "o.__proto__ = inbetween;"
      "inbetween.__proto__ = p;"
      "for (var i = 0; i < 10; i++) {"
      "  o.y;"
      // Now it should be ICed and keep a reference to y defined on p
      "}"
      "inbetween.y = 42;"
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  result += o.y;"
      "}"
      "result");
  CHECK_EQ(42 * 10, value->Int32Value(context.local()).FromJust());
}


// Test the case when we stored callback into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and callbacks' holders.
THREADED_TEST(InterceptorLoadICInvalidatedCallbackViaGlobal) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  v8::Local<v8::ObjectTemplate> templ_p = ObjectTemplate::New(isolate);
  templ_p->SetNativeDataProperty(v8_str("y"), Return239Callback, SetOnHolder);

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  context->Global()
      ->Set(context.local(), v8_str("p"),
            templ_p->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<Value> value = CompileRun(
      "o.__proto__ = this;"
      "this.__proto__ = p;"
      "for (var i = 0; i < 10; i++) {"
      "  if (o.y != 239) throw 'oops: ' + o.y;"
      // Now it should be ICed and keep a reference to y defined on p
      "}"
      "this.y = 42;"
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  result += o.y;"
      "}"
      "result");
  CHECK_EQ(42 * 10, value->Int32Value(context.local()).FromJust());
}

// Test load of a non-existing global when a global object has an interceptor.
void InterceptorLoadGlobalICGlobalWithInterceptor(bool masking) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration config(EmptyInterceptorGetter,
                                               EmptyInterceptorSetter);
  if (!masking) {
    config.flags = v8::PropertyHandlerFlags::kNonMasking;
  }
  templ_global->SetHandler(config);

  LocalContext context(nullptr, templ_global);
  i::DirectHandle<i::JSReceiver> global_proxy =
      v8::Utils::OpenDirectHandle<Object, i::JSReceiver>(context->Global());
  CHECK(IsJSGlobalProxy(*global_proxy));
  i::DirectHandle<i::JSGlobalObject> global(
      i::Cast<i::JSGlobalObject>(global_proxy->map()->prototype()),
      CcTest::i_isolate());
  CHECK(global->map()->has_named_interceptor());

  v8::Local<Value> value = CompileRun(
      "var f = function() { "
      "  try {"
      "    x1;"
      "  } catch(e) {"
      "  }"
      "  return typeof x1 === 'undefined';"
      "};"
      "for (var i = 0; i < 10; i++) {"
      "  f();"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));

  value = CompileRun(
      "var f = function() { "
      "  try {"
      "    x2;"
      "    return false;"
      "  } catch(e) {"
      "    return true;"
      "  }"
      "};"
      "for (var i = 0; i < 10; i++) {"
      "  f();"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));

  value = CompileRun(
      "var f = function() { "
      "  try {"
      "    typeof(x3);"
      "    return true;"
      "  } catch(e) {"
      "    return false;"
      "  }"
      "};"
      "for (var i = 0; i < 10; i++) {"
      "  f();"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));
}

THREADED_TEST(InterceptorLoadGlobalICGlobalWithNonMaskingInterceptor) {
  const bool masking = false;
  InterceptorLoadGlobalICGlobalWithInterceptor(masking);
}

THREADED_TEST(InterceptorLoadGlobalICGlobalWithMaskingInterceptor) {
  const bool masking = true;
  InterceptorLoadGlobalICGlobalWithInterceptor(masking);
}

// Test load of a non-existing global through prototype chain when a global
// object has an interceptor.
void InterceptorLoadICGlobalWithInterceptor(bool masking) {
  i::v8_flags.allow_natives_syntax = true;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration config(GenericInterceptorGetter,
                                               GenericInterceptorSetter);
  if (!masking) {
    config.flags = v8::PropertyHandlerFlags::kNonMasking;
  }
  templ_global->SetHandler(config);

  LocalContext context(nullptr, templ_global);
  i::DirectHandle<i::JSReceiver> global_proxy =
      v8::Utils::OpenDirectHandle<Object, i::JSReceiver>(context->Global());
  CHECK(IsJSGlobalProxy(*global_proxy));
  i::DirectHandle<i::JSGlobalObject> global(
      i::Cast<i::JSGlobalObject>(global_proxy->map()->prototype()),
      CcTest::i_isolate());
  CHECK(global->map()->has_named_interceptor());

  ExpectInt32(
      "(function() {"
      "  var f = function(obj) { "
      "    return obj.$foo;"
      "  };"
      "  this._str_$foo = 42;"
      "  var obj = { __proto__: this };"
      "  for (var i = 0; i < 1500; i++) obj['p' + i] = 0;"
      "  /* Ensure that |obj| is in dictionary mode. */"
      "  if (%HasFastProperties(obj)) return -1;"
      "  for (var i = 0; i < 3; i++) {"
      "    f(obj);"
      "  };"
      "  return f(obj);"
      "})();",
      42);
}

THREADED_TEST(InterceptorLoadICGlobalWithNonMaskingInterceptor) {
  const bool masking = false;
  InterceptorLoadICGlobalWithInterceptor(masking);
}

THREADED_TEST(InterceptorLoadICGlobalWithMaskingInterceptor) {
  const bool masking = true;
  InterceptorLoadICGlobalWithInterceptor(masking);
}

namespace {
v8::Intercepted InterceptorLoadICGetter0(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(v8_str("x")
            ->Equals(info.GetIsolate()->GetCurrentContext(), name)
            .FromJust());
  info.GetReturnValue().Set(0);
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(InterceptorReturningZero) {
  CheckInterceptorLoadIC(InterceptorLoadICGetter0, "o.x == undefined ? 1 : 0",
                         0);
}

namespace {

template <typename TKey, v8::internal::PropertyAttributes attribute>
v8::Intercepted HasICQuery(TKey name,
                           const v8::PropertyCallbackInfo<v8::Integer>& info) {
  v8::Isolate* isolate = CcTest::isolate();
  CHECK_EQ(isolate, info.GetIsolate());
  if (attribute != v8::internal::ABSENT) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(attribute);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

template <typename TKey>
v8::Intercepted HasICQueryToggle(
    TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  static bool is_absent = false;
  is_absent = !is_absent;
  v8::Isolate* isolate = CcTest::isolate();
  CHECK_EQ(isolate, info.GetIsolate());
  if (!is_absent) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(v8::None);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

template <typename TKey, v8::internal::PropertyAttributes attribute>
v8::Intercepted HasICQuerySideEffect(
    TKey name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  if (attribute != v8::internal::ABSENT) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
  }
  v8::Isolate* isolate = CcTest::isolate();
  CHECK_EQ(isolate, info.GetIsolate());
  CompileRun("interceptor_query_side_effect()");
  if (attribute != v8::internal::ABSENT) {
    info.GetReturnValue().Set(attribute);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

int named_query_counter = 0;
v8::Intercepted NamedQueryCallback(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  named_query_counter++;
  return v8::Intercepted::kNo;
}

}  // namespace

THREADED_TEST(InterceptorHasIC) {
  named_query_counter = 0;
  CheckInterceptorIC(nullptr, NamedQueryCallback,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  'x' in o;"
                     "}",
                     0);
  CHECK_EQ(1000, named_query_counter);
}

THREADED_TEST(InterceptorHasICQueryAbsent) {
  CheckInterceptorIC(nullptr, HasICQuery<Local<Name>, v8::internal::ABSENT>,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  if ('x' in o) ++result;"
                     "}",
                     0);
}

THREADED_TEST(InterceptorHasICQueryNone) {
  CheckInterceptorIC(nullptr, HasICQuery<Local<Name>, v8::internal::NONE>,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  if ('x' in o) ++result;"
                     "}",
                     1000);
}

THREADED_TEST(InterceptorHasICGetter) {
  CheckInterceptorIC(InterceptorLoadICGetter, nullptr,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  if ('x' in o) ++result;"
                     "}",
                     1000);
}

THREADED_TEST(InterceptorHasICQueryGetter) {
  CheckInterceptorIC(InterceptorLoadICGetter,
                     HasICQuery<Local<Name>, v8::internal::ABSENT>,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  if ('x' in o) ++result;"
                     "}",
                     0);
}

THREADED_TEST(InterceptorHasICQueryToggle) {
  CheckInterceptorIC(InterceptorLoadICGetter, HasICQueryToggle<Local<Name>>,
                     "var result = 0;"
                     "for (var i = 0; i < 1000; i++) {"
                     "  if ('x' in o) ++result;"
                     "}",
                     500);
}

THREADED_TEST(InterceptorStoreICWithSideEffectfulCallbacks1) {
  CheckInterceptorIC(EmptyInterceptorGetter,
                     HasICQuerySideEffect<Local<Name>, v8::internal::NONE>,
                     "let r;"
                     "let inside_side_effect = false;"
                     "let interceptor_query_side_effect = function() {"
                     "  if (!inside_side_effect) {"
                     "    inside_side_effect = true;"
                     "    r.x = 153;"
                     "    inside_side_effect = false;"
                     "  }"
                     "};"
                     "for (var i = 0; i < 20; i++) {"
                     "  r = { __proto__: o };"
                     "  r.x = i;"
                     "}",
                     19);
}

TEST(Crash_InterceptorStoreICWithSideEffectfulCallbacks1) {
  CheckInterceptorIC(EmptyInterceptorGetter,
                     HasICQuerySideEffect<Local<Name>, v8::internal::ABSENT>,
                     "let r;"
                     "let inside_side_effect = false;"
                     "let interceptor_query_side_effect = function() {"
                     "  if (!inside_side_effect) {"
                     "    inside_side_effect = true;"
                     "    r.x = 153;"
                     "    inside_side_effect = false;"
                     "  }"
                     "};"
                     "for (var i = 0; i < 20; i++) {"
                     "  r = { __proto__: o };"
                     "  r.x = i;"
                     "}",
                     19);
}

TEST(Crash_InterceptorStoreICWithSideEffectfulCallbacks2) {
  CheckInterceptorIC(InterceptorLoadXICGetterWithSideEffects,
                     nullptr,  // query callback is not provided
                     "let r;"
                     "let inside_side_effect = false;"
                     "let interceptor_getter_side_effect = function() {"
                     "  if (!inside_side_effect) {"
                     "    inside_side_effect = true;"
                     "    r.y = 153;"
                     "    inside_side_effect = false;"
                     "  }"
                     "};"
                     "for (var i = 0; i < 20; i++) {"
                     "  r = { __proto__: o };"
                     "  r.y = i;"
                     "}",
                     19);
}

THREADED_TEST(InterceptorDefineICWithSideEffectfulCallbacks) {
  CheckInterceptorIC(EmptyInterceptorGetter, EmptyInterceptorSetter,
                     EmptyInterceptorQuery,
                     EmptyInterceptorDefinerWithSideEffect,
                     v8::PropertyHandlerFlags::kNonMasking,
                     "let inside_side_effect = false;"
                     "let interceptor_definer_side_effect = function() {"
                     "  if (!inside_side_effect) {"
                     "    inside_side_effect = true;"
                     "    o.y = 153;"
                     "    inside_side_effect = false;"
                     "  }"
                     "  return true;"  // Accept the request.
                     "};"
                     "class Base {"
                     "  constructor(arg) {"
                     "    return arg;"
                     "  }"
                     "}"
                     "class ClassWithField extends Base {"
                     "  y = (() => {"
                     "    return 42;"
                     "  })();"
                     "  constructor(arg) {"
                     "    super(arg);"
                     "  }"
                     "}"
                     "new ClassWithField(o);"
                     "o.y",
                     153);
}

TEST(Crash_InterceptorDefineICWithSideEffectfulCallbacks) {
  CheckInterceptorIC(EmptyInterceptorGetter, EmptyInterceptorSetter,
                     EmptyInterceptorQuery,
                     EmptyInterceptorDefinerWithSideEffect,
                     v8::PropertyHandlerFlags::kNonMasking,
                     "let inside_side_effect = false;"
                     "let interceptor_definer_side_effect = function() {"
                     "  if (!inside_side_effect) {"
                     "    inside_side_effect = true;"
                     "    o.y = 153;"
                     "    inside_side_effect = false;"
                     "  }"
                     "  return null;"  // Decline the request.
                     "};"
                     "class Base {"
                     "  constructor(arg) {"
                     "    return arg;"
                     "  }"
                     "}"
                     "class ClassWithField extends Base {"
                     "  y = (() => {"
                     "    return 42;"
                     "  })();"
                     "  constructor(arg) {"
                     "    super(arg);"
                     "  }"
                     "}"
                     "new ClassWithField(o);"
                     "o.y",
                     42);
}

namespace {
v8::Intercepted InterceptorStoreICSetter(
    Local<Name> key, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  CHECK(v8_str("x")->Equals(context, key).FromJust());
  CHECK_EQ(42, value->Int32Value(context).FromJust());
  return v8::Intercepted::kYes;
}
}  // namespace

// This test should hit the store IC for the interceptor case.
THREADED_TEST(InterceptorStoreIC) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
      InterceptorLoadICGetter, InterceptorStoreICSetter, nullptr, nullptr,
      nullptr, v8_str("data")));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "for (var i = 0; i < 1000; i++) {"
      "  o.x = 42;"
      "}");
}


THREADED_TEST(InterceptorStoreICWithNoSetter) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "for (var i = 0; i < 1000; i++) {"
      "  o.y = 239;"
      "}"
      "42 + o.y");
  CHECK_EQ(239 + 42, value->Int32Value(context.local()).FromJust());
}

THREADED_TEST(EmptyInterceptorDoesNotShadowReadOnlyProperty) {
  // Interceptor should not shadow readonly property 'x' on the prototype, and
  // attempt to store to 'x' must throw.
  CheckInterceptorIC(EmptyInterceptorGetter,
                     HasICQuery<Local<Name>, v8::internal::ABSENT>,
                     "'use strict';"
                     "let p = {};"
                     "Object.defineProperty(p, 'x', "
                     "                      {value: 153, writable: false});"
                     "o.__proto__ = p;"
                     "let result = 0;"
                     "let r;"
                     "for (var i = 0; i < 20; i++) {"
                     "  r = { __proto__: o };"
                     "  try {"
                     "    r.x = i;"
                     "  } catch (e) {"
                     "    result++;"
                     "  }"
                     "}"
                     "result",
                     20);
}

THREADED_TEST(InterceptorShadowsReadOnlyProperty) {
  // Interceptor claims that it has a writable property 'x', so the existence
  // of the readonly property 'x' on the prototype should not cause exceptions.
  CheckInterceptorIC(InterceptorLoadXICGetter,
                     nullptr,  // query callback
                     "'use strict';"
                     "let p = {};"
                     "Object.defineProperty(p, 'x', "
                     "                      {value: 153, writable: false});"
                     "o.__proto__ = p;"
                     "let result = 0;"
                     "let r;"
                     "for (var i = 0; i < 20; i++) {"
                     "  r = { __proto__: o };"
                     "  try {"
                     "    r.x = i;"
                     "    result++;"
                     "  } catch (e) {}"
                     "}"
                     "result",
                     20);
}

THREADED_TEST(EmptyInterceptorDoesNotShadowAccessors) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddAccessor(isolate, parent, v8_str("age"), SimpleGetterCallback,
              SimpleSetterCallback);
  AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "child.age = 10;");
  ExpectBoolean("child.hasOwnProperty('age')", false);
  ExpectInt32("child.age", 10);
  ExpectInt32("child.accessor_age", 10);
}

THREADED_TEST(EmptyInterceptorVsStoreGlobalICs) {
  // In sloppy mode storing to global must succeed.
  CheckInterceptorIC(EmptyInterceptorGetter,
                     HasICQuery<Local<Name>, v8::internal::ABSENT>,
                     "globalThis.__proto__ = o;"
                     "let result = 0;"
                     "for (var i = 0; i < 20; i++) {"
                     "  try {"
                     "    x = i;"
                     "    result++;"
                     "  } catch (e) {}"
                     "}"
                     "result + x",
                     20 + 19);

  // In strict mode storing to global must throw.
  CheckInterceptorIC(EmptyInterceptorGetter,
                     HasICQuery<Local<Name>, v8::internal::ABSENT>,
                     "'use strict';"
                     "globalThis.__proto__ = o;"
                     "let result = 0;"
                     "for (var i = 0; i < 20; i++) {"
                     "  try {"
                     "    x = i;"
                     "  } catch (e) {"
                     "    result++;"
                     "  }"
                     "}"
                     "result + (typeof(x) === 'undefined' ? 100 : 0)",
                     120);
}

namespace {

void CheckGlobalInterceptorIC(v8::NamedPropertyGetterCallback getter,
                              v8::NamedPropertySetterCallbackV2 setter,
                              v8::NamedPropertyQueryCallback query,
                              v8::PropertyHandlerFlags flags,
                              const char* source, std::optional<int> expected) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_global = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration config(
      getter, setter, query, nullptr /* deleter */, nullptr /* enumerator */,
      nullptr /* definer */, nullptr /* descriptor */, {}, flags);
  templ_global->SetHandler(config);

  LocalContext context(nullptr, templ_global);
  i::DirectHandle<i::JSReceiver> global_proxy =
      v8::Utils::OpenDirectHandle<Object, i::JSReceiver>(context->Global());
  CHECK(IsJSGlobalProxy(*global_proxy));
  i::DirectHandle<i::JSGlobalObject> global(
      i::Cast<i::JSGlobalObject>(global_proxy->map()->prototype()),
      CcTest::i_isolate());
  CHECK(global->map()->has_named_interceptor());

  v8::Local<Value> value = CompileRun(source);
  if (expected) {
    CHECK_EQ(*expected, value->Int32Value(context.local()).FromJust());
  } else {
    CHECK(value.IsEmpty());
  }
}

void StoreGlobalICWithGlobalInterceptor(bool masking) {
  v8::PropertyHandlerFlags flags = v8::PropertyHandlerFlags::kNone;
  if (!masking) {
    flags = v8::PropertyHandlerFlags::kNonMasking;
  }

  // In sloppy mode storing to global must succeed.
  CheckGlobalInterceptorIC(      //
      GenericInterceptorGetter,  //
      GenericInterceptorSetter,  //
      GenericInterceptorQuery,   //
      flags,                     //
      R"(
        // Make interceptor behave like it has a read-only property "$y".
        Object.defineProperty(globalThis, '_str_$y',
                              {value: 153, writable: false});

        let result = 0;

        result += (typeof($x) === 'undefined' ? 0 : 10000);
        result += ($y === 153                 ? 0 : 10000);

        for (var i = 0; i < 20; i++) {
          try {
            $x = i;  // not intercepted, absent variable
            result++;
          } catch (e) {
          }
        }
        for (var i = 0; i < 20; i++) {
          try {
            $y = i;  // intercepted, read only property
            result++;
          } catch (e) {
          }
        }

        // Check contextual stores succeeded.
        result += ($x === 19  ? 0 : 100);
        result += ($y === 153 ? 0 : 1000);

        result
      )",
      40);

  // In strict mode storing to global must throw.
  CheckGlobalInterceptorIC(      //
      GenericInterceptorGetter,  //
      GenericInterceptorSetter,  //
      GenericInterceptorQuery,   //
      flags,
      R"(
        'use strict';

        // Make interceptor behave like it has a read-only property "$y".
        Object.defineProperty(globalThis, '_str_$y',
                              {value: 153, writable: false});

        let result = 0;

        result += (typeof($x) === 'undefined' ? 0 : 10000);
        result += ($y === 153                 ? 0 : 10000);

        for (var i = 0; i < 20; i++) {
          try {
            $x = i;  // not intercepted, absent variable
          } catch (e) {
            result++;
          }
        }
        for (var i = 0; i < 20; i++) {
          try {
            $y = i;  // intercepted, read only property
          } catch (e) {
            result++;
          }
        }

        // Check contextual stores did not happen.
        result += (typeof($x) === 'undefined' ? 0 : 100);
        result += ($y === 153                 ? 0 : 1000);

        result
      )",
      40);
}

}  // namespace

// Checks correctness of global objects with interceptor vs contextual stores.
THREADED_TEST(StoreGlobalICWithGlobalNonMaskingInterceptor) {
  const bool masking = false;
  StoreGlobalICWithGlobalInterceptor(masking);
}

THREADED_TEST(StoreGlobalICWithGlobalMaskingInterceptor) {
  const bool masking = true;
  StoreGlobalICWithGlobalInterceptor(masking);
}

THREADED_TEST(LegacyInterceptorDoesNotSeeSymbols) {
  LocalContext env;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  v8::Local<v8::Symbol> age = v8::Symbol::New(isolate, v8_str("age"));

  child->Inherit(parent);
  AddAccessor(isolate, parent, age, SymbolGetterCallback, SymbolSetterCallback);
  AddStringOnlyInterceptor(child, InterceptorGetter, InterceptorSetter);

  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  env->Global()->Set(env.local(), v8_str("age"), age).FromJust();
  CompileRun(
      "var child = new Child;"
      "child[age] = 10;");
  ExpectInt32("child[age]", 10);
  ExpectBoolean("child.hasOwnProperty('age')", false);
  ExpectBoolean("child.hasOwnProperty('accessor_age')", true);
}


THREADED_TEST(GenericInterceptorDoesSeeSymbols) {
  LocalContext env;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  v8::Local<v8::Symbol> age = v8::Symbol::New(isolate, v8_str("age"));
  v8::Local<v8::Symbol> anon = v8::Symbol::New(isolate);

  child->Inherit(parent);
  AddAccessor(isolate, parent, age, SymbolGetterCallback, SymbolSetterCallback);
  AddInterceptor(child, GenericInterceptorGetter, GenericInterceptorSetter);

  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  env->Global()->Set(env.local(), v8_str("age"), age).FromJust();
  env->Global()->Set(env.local(), v8_str("anon"), anon).FromJust();
  CompileRun(
      "var child = new Child;"
      "child[age] = 10;");
  ExpectInt32("child[age]", 10);
  ExpectInt32("child._sym_age", 10);

  // Check that it also sees strings.
  CompileRun("child.$foo = 47");
  ExpectInt32("child.$foo", 47);
  ExpectInt32("child._str_$foo", 47);

  // Check that the interceptor can punt (in this case, on anonymous symbols).
  CompileRun("child[anon] = 31337");
  ExpectInt32("child[anon]", 31337);
}


THREADED_TEST(NamedPropertyHandlerGetter) {
  echo_named_call_count = 0;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      EchoNamedProperty, nullptr, nullptr, nullptr, nullptr, v8_str("data")));
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  CHECK_EQ(0, echo_named_call_count);
  v8_compile("obj.x")->Run(env.local()).ToLocalChecked();
  CHECK_EQ(1, echo_named_call_count);
  const char* code = "var str = 'oddle'; obj[str] + obj.poddle;";
  v8::Local<Value> str = CompileRun(code);
  String::Utf8Value value(isolate, str);
  CHECK_EQ(0, strcmp(*value, "oddlepoddle"));
  // Check default behavior
  CHECK_EQ(10, v8_compile("obj.flob = 10;")
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
  CHECK(v8_compile("'myProperty' in obj")
            ->Run(env.local())
            .ToLocalChecked()
            ->BooleanValue(isolate));
  CHECK(v8_compile("delete obj.myProperty")
            ->Run(env.local())
            .ToLocalChecked()
            ->BooleanValue(isolate));
}

namespace {
v8::Intercepted NotInterceptingPropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptingPropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kYes;
}

v8::Intercepted CheckDescriptorInDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(!desc.has_writable());
  CHECK(!desc.has_value());
  CHECK(!desc.has_enumerable());
  CHECK(desc.has_configurable());
  CHECK(!desc.configurable());
  CHECK(desc.has_get());
  CHECK(desc.get()->IsFunction());
  CHECK(desc.has_set());
  CHECK(desc.set()->IsUndefined());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallback) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;

  {  // Intercept defineProperty()
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
        nullptr, nullptr, nullptr, nullptr, nullptr,
        NotInterceptingPropertyDefineCallback));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj.x = 17; "
        "Object.defineProperty(obj, 'x', {value: 42});"
        "obj.x;";
    CHECK_EQ(42, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }

  {  // Intercept defineProperty() for correct accessor descriptor
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
        nullptr, nullptr, nullptr, nullptr, nullptr,
        CheckDescriptorInDefineCallback));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj.x = 17; "
        "Object.defineProperty(obj, 'x', {"
        "get: function(){ return 42; }, "
        "set: undefined,"
        "configurable: 0"
        "});"
        "obj.x;";
    CHECK_EQ(17, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }

  {  // Do not intercept defineProperty()
    v8::Local<v8::FunctionTemplate> templ2 =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ2->InstanceTemplate()->SetHandler(
        v8::NamedPropertyHandlerConfiguration(
            nullptr, nullptr, nullptr, nullptr, nullptr,
            InterceptingPropertyDefineCallback));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ2->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();

    const char* code =
        "obj.x = 17; "
        "Object.defineProperty(obj, 'x', {value: 42});"
        "obj.x;";
    CHECK_EQ(17, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }
}

namespace {
v8::Intercepted NotInterceptingPropertyDefineCallbackIndexed(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptingPropertyDefineCallbackIndexed(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  return v8::Intercepted::kYes;
}

v8::Intercepted CheckDescriptorInDefineCallbackIndexed(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(!desc.has_writable());
  CHECK(!desc.has_value());
  CHECK(desc.has_enumerable());
  CHECK(desc.enumerable());
  CHECK(!desc.has_configurable());
  CHECK(desc.has_get());
  CHECK(desc.get()->IsFunction());
  CHECK(desc.has_set());
  CHECK(desc.set()->IsUndefined());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackIndexed) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;

  {  // Intercept defineProperty()
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(
        v8::IndexedPropertyHandlerConfiguration(
            nullptr, nullptr, nullptr, nullptr, nullptr,
            NotInterceptingPropertyDefineCallbackIndexed));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj[2] = 17; "
        "Object.defineProperty(obj, 2, {value: 42});"
        "obj[2];";
    CHECK_EQ(42, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }

  {  // Intercept defineProperty() for correct accessor descriptor
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(
        v8::IndexedPropertyHandlerConfiguration(
            nullptr, nullptr, nullptr, nullptr, nullptr,
            CheckDescriptorInDefineCallbackIndexed));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj[2] = 17; "
        "Object.defineProperty(obj, 2, {"
        "get: function(){ return 42; }, "
        "set: undefined,"
        "enumerable: true"
        "});"
        "obj[2];";
    CHECK_EQ(17, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }

  {  // Do not intercept defineProperty()
    v8::Local<v8::FunctionTemplate> templ2 =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ2->InstanceTemplate()->SetHandler(
        v8::IndexedPropertyHandlerConfiguration(
            nullptr, nullptr, nullptr, nullptr, nullptr,
            InterceptingPropertyDefineCallbackIndexed));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ2->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();

    const char* code =
        "obj[2] = 17; "
        "Object.defineProperty(obj, 2, {value: 42});"
        "obj[2];";
    CHECK_EQ(17, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }
}

// Test that freeze() is intercepted.
THREADED_TEST(PropertyDefinerCallbackForFreeze) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      InterceptingPropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "obj.x = 17; "
      "Object.freeze(obj.x); "
      "Object.isFrozen(obj.x);";

  CHECK(v8_compile(code)
            ->Run(env.local())
            .ToLocalChecked()
            ->BooleanValue(isolate));
}

// Check that the descriptor passed to the callback is enumerable.
namespace {
v8::Intercepted CheckEnumerablePropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(desc.has_value());
  CHECK_EQ(42, desc.value()
                   ->Int32Value(info.GetIsolate()->GetCurrentContext())
                   .FromJust());
  CHECK(desc.has_enumerable());
  CHECK(desc.enumerable());
  CHECK(!desc.has_writable());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackEnumerable) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckEnumerablePropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "obj.x = 17; "
      "Object.defineProperty(obj, 'x', {value: 42, enumerable: true});"
      "obj.x;";
  CHECK_EQ(17, v8_compile(code)
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
}

// Check that the descriptor passed to the callback is configurable.
namespace {
v8::Intercepted CheckConfigurablePropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(desc.has_value());
  CHECK_EQ(42, desc.value()
                   ->Int32Value(info.GetIsolate()->GetCurrentContext())
                   .FromJust());
  CHECK(desc.has_configurable());
  CHECK(desc.configurable());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackConfigurable) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckConfigurablePropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "obj.x = 17; "
      "Object.defineProperty(obj, 'x', {value: 42, configurable: true});"
      "obj.x;";
  CHECK_EQ(17, v8_compile(code)
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
}

// Check that the descriptor passed to the callback is writable.
namespace {
v8::Intercepted CheckWritablePropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(desc.has_writable());
  CHECK(desc.writable());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackWritable) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckWritablePropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "obj.x = 17; "
      "Object.defineProperty(obj, 'x', {value: 42, writable: true});"
      "obj.x;";
  CHECK_EQ(17, v8_compile(code)
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
}

// Check that the descriptor passed to the callback has a getter.
namespace {
v8::Intercepted CheckGetterPropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(desc.has_get());
  CHECK(!desc.has_set());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackWithGetter) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckGetterPropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "obj.x = 17;"
      "Object.defineProperty(obj, 'x', {get: function() {return 42;}});"
      "obj.x;";
  CHECK_EQ(17, v8_compile(code)
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
}

// Check that the descriptor passed to the callback has a setter.
namespace {
v8::Intercepted CheckSetterPropertyDefineCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CHECK(desc.has_set());
  CHECK(!desc.has_get());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackWithSetter) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckSetterPropertyDefineCallback));
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  const char* code =
      "Object.defineProperty(obj, 'x', {set: function() {return 42;}});"
      "obj.x = 17;";
  CHECK_EQ(17, v8_compile(code)
                   ->Run(env.local())
                   .ToLocalChecked()
                   ->Int32Value(env.local())
                   .FromJust());
}

namespace {
std::vector<std::string> definer_calls;
v8::Intercepted LogDefinerCallsAndContinueCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  String::Utf8Value utf8(info.GetIsolate(), name);
  definer_calls.push_back(*utf8);
  return v8::Intercepted::kNo;
}
v8::Intercepted LogDefinerCallsAndStopCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  String::Utf8Value utf8(info.GetIsolate(), name);
  definer_calls.push_back(*utf8);
  return v8::Intercepted::kYes;
}

struct DefineNamedOwnICInterceptorConfig {
  std::string code;
  std::vector<std::string> intercepted_defines;
};

std::vector<DefineNamedOwnICInterceptorConfig> configs{
    {
        R"(
          class ClassWithNormalField extends Base {
            field = (() => {
              Object.defineProperty(
                this,
                'normalField',
                { writable: true, configurable: true, value: 'initial'}
              );
              return 1;
            })();
            normalField = 'written';
            constructor(arg) {
              super(arg);
            }
          }
          new ClassWithNormalField(obj);
          stop ? (obj.field === undefined && obj.normalField === undefined)
            : (obj.field === 1 && obj.normalField === 'written'))",
        {"normalField", "field", "normalField"},  // intercepted defines
    },
    {
        R"(
            let setterCalled = false;
            class ClassWithSetterField extends Base {
              field = (() => {
                Object.defineProperty(
                  this,
                  'setterField',
                  { configurable: true, set(val) { setterCalled = true; } }
                );
                return 1;
              })();
              setterField = 'written';
              constructor(arg) {
                super(arg);
              }
            }
            new ClassWithSetterField(obj);
            !setterCalled &&
              (stop ? (obj.field === undefined && obj.setterField === undefined)
                : (obj.field === 1 && obj.setterField === 'written')))",
        {"setterField", "field", "setterField"},  // intercepted defines
    },
    {
        R"(
          class ClassWithReadOnlyField extends Base {
            field = (() => {
              Object.defineProperty(
                this,
                'readOnlyField',
                { writable: false, configurable: true, value: 'initial'}
              );
              return 1;
            })();
            readOnlyField = 'written';
            constructor(arg) {
              super(arg);
            }
          }
          new ClassWithReadOnlyField(obj);
          stop ? (obj.field === undefined && obj.readOnlyField === undefined)
            : (obj.field === 1 && obj.readOnlyField === 'written'))",
        {"readOnlyField", "field", "readOnlyField"},  // intercepted defines
    },
    {
        R"(
          class ClassWithNonConfigurableField extends Base {
            field = (() => {
              Object.defineProperty(
                this,
                'nonConfigurableField',
                { writable: false, configurable: false, value: 'initial'}
              );
              return 1;
            })();
            nonConfigurableField = 'configured';
            constructor(arg) {
              super(arg);
            }
          }
          let nonConfigurableThrown = false;
          try { new ClassWithNonConfigurableField(obj); }
          catch { nonConfigurableThrown = true; }
          stop ? (!nonConfigurableThrown && obj.field === undefined
                  && obj.nonConfigurableField === undefined)
              : (nonConfigurableThrown && obj.field === 1
                && obj.nonConfigurableField === 'initial'))",
        // intercepted defines
        {"nonConfigurableField", "field", "nonConfigurableField"}}
    // We don't test non-extensible objects here because objects with
    // interceptors cannot prevent extensions.
};
}  // namespace

void CheckPropertyDefinerCallbackInDefineNamedOwnIC(Local<Context> context,
                                                    bool stop) {
  v8_compile(R"(
    class Base {
      constructor(arg) {
        return arg;
      }
    })")
      ->Run(context)
      .ToLocalChecked();

  v8_compile(stop ? "var stop = true;" : "var stop = false;")
      ->Run(context)
      .ToLocalChecked();

  for (auto& config : configs) {
    printf("stop = %s, running...\n%s\n", stop ? "true" : "false",
           config.code.c_str());

    definer_calls.clear();

    // Create the object with interceptors.
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
        nullptr, nullptr, nullptr, nullptr, nullptr,
        stop ? LogDefinerCallsAndStopCallback
             : LogDefinerCallsAndContinueCallback,
        nullptr));
    Local<Object> obj = templ->GetFunction(context)
                            .ToLocalChecked()
                            ->NewInstance(context)
                            .ToLocalChecked();
    context->Global()->Set(context, v8_str("obj"), obj).FromJust();

    CHECK(v8_compile(config.code.c_str())
              ->Run(context)
              .ToLocalChecked()
              ->IsTrue());
    for (size_t i = 0; i < definer_calls.size(); ++i) {
      printf("define %s\n", definer_calls[i].c_str());
    }

    CHECK_EQ(config.intercepted_defines.size(), definer_calls.size());
    for (size_t i = 0; i < config.intercepted_defines.size(); ++i) {
      CHECK_EQ(config.intercepted_defines[i], definer_calls[i]);
    }
  }
}

THREADED_TEST(PropertyDefinerCallbackInDefineNamedOwnIC) {
  {
    LocalContext env;
    v8::HandleScope scope(env.isolate());
    CheckPropertyDefinerCallbackInDefineNamedOwnIC(env.local(), true);
  }

  {
    LocalContext env;
    v8::HandleScope scope(env.isolate());
    CheckPropertyDefinerCallbackInDefineNamedOwnIC(env.local(), false);
  }

  {
    i::v8_flags.lazy_feedback_allocation = false;
    i::FlagList::EnforceFlagImplications();
    LocalContext env;
    v8::HandleScope scope(env.isolate());
    CheckPropertyDefinerCallbackInDefineNamedOwnIC(env.local(), true);
  }

  {
    i::v8_flags.lazy_feedback_allocation = false;
    i::FlagList::EnforceFlagImplications();
    LocalContext env;
    v8::HandleScope scope(env.isolate());
    CheckPropertyDefinerCallbackInDefineNamedOwnIC(env.local(), false);
  }
}

namespace {
int descriptor_callback_count;
v8::Intercepted CheckDescriptorInDefineNamedOwnICCallback(
    Local<Name> name, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  ++descriptor_callback_count;
  CHECK(desc.has_value());
  CHECK_EQ(42, desc.value()
                   ->Int32Value(info.GetIsolate()->GetCurrentContext())
                   .FromJust());
  CHECK(desc.has_writable());
  CHECK(desc.writable());
  CHECK(desc.has_enumerable());
  CHECK(desc.enumerable());
  CHECK(desc.has_configurable());
  CHECK(desc.configurable());
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDefinerCallbackDescriptorInDefineNamedOwnIC) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;
  v8::Local<v8::FunctionTemplate> templ =
      v8::FunctionTemplate::New(CcTest::isolate());
  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, nullptr,
      CheckDescriptorInDefineNamedOwnICCallback));
  Local<Object> obj = templ->GetFunction(env.local())
                          .ToLocalChecked()
                          ->NewInstance(env.local())
                          .ToLocalChecked();
  env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust();

  descriptor_callback_count = 0;
  CompileRun(R"(
    class Base {
      constructor(arg) {
        return arg;
      }
    }
    class Derived extends Base {
      field = 42;
    }
    new Derived(obj);
  )");
  CHECK_EQ(1, descriptor_callback_count);
}

namespace {
v8::Intercepted EmptyPropertyDescriptorCallback(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  return v8::Intercepted::kNo;
}

v8::Intercepted InterceptingPropertyDescriptorCallback(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // Intercept the operation and return some descriptor.
  const char* code =
      "var desc = {value: 42};"
      "desc;";
  Local<Value> descriptor = v8_compile(code)
                                ->Run(info.GetIsolate()->GetCurrentContext())
                                .ToLocalChecked();
  info.GetReturnValue().Set(descriptor);
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(PropertyDescriptorCallback) {
  v8::HandleScope scope(CcTest::isolate());
  LocalContext env;

  {  // Normal behavior of getOwnPropertyDescriptor() with empty callback.
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
        nullptr, nullptr, EmptyPropertyDescriptorCallback, nullptr, nullptr,
        nullptr));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj.x = 17; "
        "var desc = Object.getOwnPropertyDescriptor(obj, 'x');"
        "desc.value;";
    CHECK_EQ(17, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }

  {  // Intercept getOwnPropertyDescriptor().
    v8::Local<v8::FunctionTemplate> templ =
        v8::FunctionTemplate::New(CcTest::isolate());
    templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
        nullptr, nullptr, InterceptingPropertyDescriptorCallback, nullptr,
        nullptr, nullptr));
    env->Global()
        ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                              .ToLocalChecked()
                                              ->NewInstance(env.local())
                                              .ToLocalChecked())
        .FromJust();
    const char* code =
        "obj.x = 17; "
        "var desc = Object.getOwnPropertyDescriptor(obj, 'x');"
        "desc.value;";
    CHECK_EQ(42, v8_compile(code)
                     ->Run(env.local())
                     .ToLocalChecked()
                     ->Int32Value(env.local())
                     .FromJust());
  }
}

namespace {
int echo_indexed_call_count = 0;

v8::Intercepted EchoIndexedProperty(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(v8_num(637)
            ->Equals(info.GetIsolate()->GetCurrentContext(), info.Data())
            .FromJust());
  echo_indexed_call_count++;
  info.GetReturnValue().Set(v8_num(index));
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(IndexedPropertyHandlerGetter) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      EchoIndexedProperty, nullptr, nullptr, nullptr, nullptr, v8_num(637)));
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  Local<Script> script = v8_compile("obj[900]");
  CHECK_EQ(900, script->Run(env.local())
                    .ToLocalChecked()
                    ->Int32Value(env.local())
                    .FromJust());
}


THREADED_TEST(PropertyHandlerInPrototype) {
  LocalContext env;
  v8::Isolate* isolate = env.isolate();
  v8::HandleScope scope(isolate);

  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  // Originally, the interceptor callbacks used to check receiver value passed
  // to the callbacks, but this functionality was removed, so now the test just
  // triggers interceptor callbacks which don't handle the requests.
  templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      DummyIndexedPropertyGetter, DummyIndexedPropertySetter,
      DummyIndexedPropertyQuery, DummyIndexedPropertyDeleter,
      DummyIndexedPropertyEnumerator));

  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      DummyNamedPropertyGetter, DummyNamedPropertySetter,
      DummyNamedPropertyQuery, DummyNamedPropertyDeleter,
      DummyNamedPropertyEnumerator));

  Local<v8::Object> bottom = templ->GetFunction(env.local())
                                 .ToLocalChecked()
                                 ->NewInstance(env.local())
                                 .ToLocalChecked();
  Local<v8::Object> top = templ->GetFunction(env.local())
                              .ToLocalChecked()
                              ->NewInstance(env.local())
                              .ToLocalChecked();
  Local<v8::Object> middle = templ->GetFunction(env.local())
                                 .ToLocalChecked()
                                 ->NewInstance(env.local())
                                 .ToLocalChecked();

  bottom->SetPrototype(env.local(), middle).FromJust();
  middle->SetPrototype(env.local(), top).FromJust();
  env->Global()->Set(env.local(), v8_str("obj"), bottom).FromJust();

  // Indexed and named get.
  CompileRun("obj[0]");
  CompileRun("obj.x");

  // Indexed and named set.
  CompileRun("obj[1] = 42");
  CompileRun("obj.y = 42");

  // Indexed and named query.
  CompileRun("0 in obj");
  CompileRun("'x' in obj");

  // Indexed and named deleter.
  CompileRun("delete obj[0]");
  CompileRun("delete obj.x");

  // Enumerators.
  CompileRun("for (var p in obj) ;");
}

TEST(PropertyHandlerInPrototypeWithDefine) {
  LocalContext env;
  v8::Isolate* isolate = env.isolate();
  v8::HandleScope scope(isolate);

  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  // Originally, the interceptor callbacks used to check receiver value passed
  // to the callbacks, but this functionality was removed, so now the test just
  // triggers interceptor callbacks which don't handle the requests.
  templ->InstanceTemplate()->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      DummyIndexedPropertyGetter, DummyIndexedPropertySetter,
      DummyIndexedPropertyDescriptor, DummyIndexedPropertyDeleter,
      DummyIndexedPropertyEnumerator, DummyIndexedPropertyDefiner));

  templ->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      DummyNamedPropertyGetter, DummyNamedPropertySetter,
      DummyNamedPropertyDescriptor, DummyNamedPropertyDeleter,
      DummyNamedPropertyEnumerator, DummyNamedPropertyDefiner));

  Local<v8::Object> bottom = templ->GetFunction(env.local())
                                 .ToLocalChecked()
                                 ->NewInstance(env.local())
                                 .ToLocalChecked();
  Local<v8::Object> top = templ->GetFunction(env.local())
                              .ToLocalChecked()
                              ->NewInstance(env.local())
                              .ToLocalChecked();
  Local<v8::Object> middle = templ->GetFunction(env.local())
                                 .ToLocalChecked()
                                 ->NewInstance(env.local())
                                 .ToLocalChecked();

  bottom->SetPrototype(env.local(), middle).FromJust();
  middle->SetPrototype(env.local(), top).FromJust();
  env->Global()->Set(env.local(), v8_str("obj"), bottom).FromJust();

  // Indexed and named get.
  CompileRun("obj[0]");
  CompileRun("obj.x");

  // Indexed and named set.
  CompileRun("obj[1] = 42");
  CompileRun("obj.y = 42");

  // Indexed and named deleter.
  CompileRun("delete obj[0]");
  CompileRun("delete obj.x");

  // Enumerators.
  CompileRun("for (var p in obj) ;");

  // Indexed and named definer.
  CompileRun("Object.defineProperty(obj, 2, {});");
  CompileRun("Object.defineProperty(obj, 'z', {});");

  // Indexed and named propertyDescriptor.
  CompileRun("Object.getOwnPropertyDescriptor(obj, 2);");
  CompileRun("Object.getOwnPropertyDescriptor(obj, 'z');");
}

namespace {
bool is_bootstrapping = false;
v8::Intercepted PrePropertyHandlerGet(
    Local<Name> key, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (!is_bootstrapping &&
      v8_str("pre")
          ->Equals(info.GetIsolate()->GetCurrentContext(), key)
          .FromJust()) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(v8_str("PrePropertyHandler: pre"));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted PrePropertyHandlerQuery(
    Local<Name> key, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  if (!is_bootstrapping &&
      v8_str("pre")
          ->Equals(info.GetIsolate()->GetCurrentContext(), key)
          .FromJust()) {
    info.GetReturnValue().Set(v8::None);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

THREADED_TEST(PrePropertyHandler) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(isolate);
  desc->InstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration(
      PrePropertyHandlerGet, nullptr, PrePropertyHandlerQuery));
  is_bootstrapping = true;
  LocalContext env(nullptr, desc->InstanceTemplate());
  is_bootstrapping = false;
  CompileRun("var pre = 'Object: pre'; var on = 'Object: on';");
  v8::Local<Value> result_pre = CompileRun("pre");
  CHECK(v8_str("PrePropertyHandler: pre")
            ->Equals(env.local(), result_pre)
            .FromJust());
  v8::Local<Value> result_on = CompileRun("on");
  CHECK(v8_str("Object: on")->Equals(env.local(), result_on).FromJust());
  v8::Local<Value> result_post = CompileRun("post");
  CHECK(result_post.IsEmpty());
}


THREADED_TEST(EmptyInterceptorBreakTransitions) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Constructor"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var o1 = new Constructor;"
      "o1.a = 1;"  // Ensure a and x share the descriptor array.
      "Object.defineProperty(o1, 'x', {value: 10});");
  CompileRun(
      "var o2 = new Constructor;"
      "o2.a = 1;"
      "Object.defineProperty(o2, 'x', {value: 10});");
}


THREADED_TEST(EmptyInterceptorDoesNotShadowJSAccessors) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "var parent = child.__proto__;"
      "Object.defineProperty(parent, 'age', "
      "  {get: function(){ return this.accessor_age; }, "
      "   set: function(v){ this.accessor_age = v; }, "
      "   enumerable: true, configurable: true});"
      "child.age = 10;");
  ExpectBoolean("child.hasOwnProperty('age')", false);
  ExpectInt32("child.age", 10);
  ExpectInt32("child.accessor_age", 10);
}


THREADED_TEST(EmptyInterceptorDoesNotShadowApiAccessors) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  auto returns_42 = FunctionTemplate::New(isolate, Returns42);
  parent->PrototypeTemplate()->SetAccessorProperty(v8_str("age"), returns_42);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "var parent = child.__proto__;");
  ExpectBoolean("child.hasOwnProperty('age')", false);
  ExpectInt32("child.age", 42);
  // Check interceptor followup.
  ExpectInt32(
      "var result;"
      "for (var i = 0; i < 4; ++i) {"
      "  result = child.age;"
      "}"
      "result",
      42);
}


THREADED_TEST(EmptyInterceptorDoesNotAffectJSProperties) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddInterceptor(child, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "var parent = child.__proto__;"
      "parent.name = 'Alice';");
  ExpectBoolean("child.hasOwnProperty('name')", false);
  ExpectString("child.name", "Alice");
  CompileRun("child.name = 'Bob';");
  ExpectString("child.name", "Bob");
  ExpectBoolean("child.hasOwnProperty('name')", true);
  ExpectString("parent.name", "Alice");
}


THREADED_TEST(SwitchFromInterceptorToAccessor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> templ = FunctionTemplate::New(isolate);
  AddAccessor(isolate, templ, v8_str("age"), SimpleGetterCallback,
              SimpleSetterCallback);
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "function setAge(i){ obj.age = i; };"
      "for(var i = 0; i <= 10000; i++) setAge(i);");
  // All i < 10000 go to the interceptor.
  ExpectInt32("obj.interceptor_age", 9999);
  // The last i goes to the accessor.
  ExpectInt32("obj.accessor_age", 10000);
}


THREADED_TEST(SwitchFromAccessorToInterceptor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> templ = FunctionTemplate::New(isolate);
  AddAccessor(isolate, templ, v8_str("age"), SimpleGetterCallback,
              SimpleSetterCallback);
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "function setAge(i){ obj.age = i; };"
      "for(var i = 20000; i >= 9999; i--) setAge(i);");
  // All i >= 10000 go to the accessor.
  ExpectInt32("obj.accessor_age", 10000);
  // The last i goes to the interceptor.
  ExpectInt32("obj.interceptor_age", 9999);
}


THREADED_TEST(SwitchFromInterceptorToAccessorWithInheritance) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddAccessor(isolate, parent, v8_str("age"), SimpleGetterCallback,
              SimpleSetterCallback);
  AddInterceptor(child, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "function setAge(i){ child.age = i; };"
      "for(var i = 0; i <= 10000; i++) setAge(i);");
  // All i < 10000 go to the interceptor.
  ExpectInt32("child.interceptor_age", 9999);
  // The last i goes to the accessor.
  ExpectInt32("child.accessor_age", 10000);
}


THREADED_TEST(SwitchFromAccessorToInterceptorWithInheritance) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<FunctionTemplate> parent = FunctionTemplate::New(isolate);
  Local<FunctionTemplate> child = FunctionTemplate::New(isolate);
  child->Inherit(parent);
  AddAccessor(isolate, parent, v8_str("age"), SimpleGetterCallback,
              SimpleSetterCallback);
  AddInterceptor(child, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "function setAge(i){ child.age = i; };"
      "for(var i = 20000; i >= 9999; i--) setAge(i);");
  // All i >= 10000 go to the accessor.
  ExpectInt32("child.accessor_age", 10000);
  // The last i goes to the interceptor.
  ExpectInt32("child.interceptor_age", 9999);
}


THREADED_TEST(SwitchFromInterceptorToJSAccessor) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "function setter(i) { this.accessor_age = i; };"
      "function getter() { return this.accessor_age; };"
      "function setAge(i) { obj.age = i; };"
      "Object.defineProperty(obj, 'age', { get:getter, set:setter });"
      "for(var i = 0; i <= 10000; i++) setAge(i);");
  // All i < 10000 go to the interceptor.
  ExpectInt32("obj.interceptor_age", 9999);
  // The last i goes to the JavaScript accessor.
  ExpectInt32("obj.accessor_age", 10000);
  // The installed JavaScript getter is still intact.
  // This last part is a regression test for issue 1651 and relies on the fact
  // that both interceptor and accessor are being installed on the same object.
  ExpectInt32("obj.age", 10000);
  ExpectBoolean("obj.hasOwnProperty('age')", true);
  ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value");
}


THREADED_TEST(SwitchFromJSAccessorToInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "function setter(i) { this.accessor_age = i; };"
      "function getter() { return this.accessor_age; };"
      "function setAge(i) { obj.age = i; };"
      "Object.defineProperty(obj, 'age', { get:getter, set:setter });"
      "for(var i = 20000; i >= 9999; i--) setAge(i);");
  // All i >= 10000 go to the accessor.
  ExpectInt32("obj.accessor_age", 10000);
  // The last i goes to the interceptor.
  ExpectInt32("obj.interceptor_age", 9999);
  // The installed JavaScript getter is still intact.
  // This last part is a regression test for issue 1651 and relies on the fact
  // that both interceptor and accessor are being installed on the same object.
  ExpectInt32("obj.age", 10000);
  ExpectBoolean("obj.hasOwnProperty('age')", true);
  ExpectUndefined("Object.getOwnPropertyDescriptor(obj, 'age').value");
}


THREADED_TEST(SwitchFromInterceptorToProperty) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate());
  Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate());
  child->Inherit(parent);
  AddInterceptor(child, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "function setAge(i){ child.age = i; };"
      "for(var i = 0; i <= 10000; i++) setAge(i);");
  // All i < 10000 go to the interceptor.
  ExpectInt32("child.interceptor_age", 9999);
  // The last i goes to child's own property.
  ExpectInt32("child.age", 10000);
}


THREADED_TEST(SwitchFromPropertyToInterceptor) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> parent = FunctionTemplate::New(CcTest::isolate());
  Local<FunctionTemplate> child = FunctionTemplate::New(CcTest::isolate());
  child->Inherit(parent);
  AddInterceptor(child, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Child"),
            child->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var child = new Child;"
      "function setAge(i){ child.age = i; };"
      "for(var i = 20000; i >= 9999; i--) setAge(i);");
  // All i >= 10000 go to child's own property.
  ExpectInt32("child.age", 10000);
  // The last i goes to the interceptor.
  ExpectInt32("child.interceptor_age", 9999);
}

namespace {
bool interceptor_for_hidden_properties_called;
v8::Intercepted InterceptorForHiddenProperties(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  interceptor_for_hidden_properties_called = true;
  return v8::Intercepted::kNo;
}
}  // namespace

THREADED_TEST(NoSideEffectPropertyHandler) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  LocalContext context;

  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
      EmptyInterceptorGetter, EmptyInterceptorSetter, EmptyInterceptorQuery,
      EmptyInterceptorDeleter, EmptyInterceptorEnumerator));
  v8::Local<v8::Object> object =
      templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), object).FromJust();

  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("obj.x"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("obj.x = 1"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("'x' in obj"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("delete obj.x"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  // Wrap the variable declaration since declaring globals is a side effect.
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("(function() { for (var p in obj) ; })()"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());

  // Side-effect-free version.
  Local<ObjectTemplate> templ2 = ObjectTemplate::New(isolate);
  templ2->SetHandler(v8::NamedPropertyHandlerConfiguration(
      EmptyInterceptorGetter, EmptyInterceptorSetter, EmptyInterceptorQuery,
      EmptyInterceptorDeleter, EmptyInterceptorEnumerator,
      v8::Local<v8::Value>(), v8::PropertyHandlerFlags::kHasNoSideEffect));
  v8::Local<v8::Object> object2 =
      templ2->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj2"), object2).FromJust();

  v8::debug::EvaluateGlobal(
      isolate, v8_str("obj2.x"),
      v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
      .ToLocalChecked();
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("obj2.x = 1"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  v8::debug::EvaluateGlobal(
      isolate, v8_str("'x' in obj2"),
      v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
      .ToLocalChecked();
  CHECK(v8::debug::EvaluateGlobal(
            isolate, v8_str("delete obj2.x"),
            v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
            .IsEmpty());
  v8::debug::EvaluateGlobal(
      isolate, v8_str("(function() { for (var p in obj2) ; })()"),
      v8::debug::EvaluateGlobalMode::kDisableBreaksAndThrowOnSideEffect)
      .ToLocalChecked();
}

THREADED_TEST(HiddenPropertiesWithInterceptors) {
  LocalContext context;
  v8::Isolate* isolate = context.isolate();
  v8::HandleScope scope(isolate);

  interceptor_for_hidden_properties_called = false;

  v8::Local<v8::Private> key =
      v8::Private::New(isolate, v8_str("api-test::hidden-key"));

  // Associate an interceptor with an object and start setting hidden values.
  Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(isolate);
  Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate();
  instance_templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorForHiddenProperties));
  Local<v8::Function> function =
      fun_templ->GetFunction(context.local()).ToLocalChecked();
  Local<v8::Object> obj =
      function->NewInstance(context.local()).ToLocalChecked();
  CHECK(obj->SetPrivate(context.local(), key, v8::Integer::New(isolate, 2302))
            .FromJust());
  CHECK_EQ(2302, obj->GetPrivate(context.local(), key)
                     .ToLocalChecked()
                     ->Int32Value(context.local())
                     .FromJust());
  CHECK(!interceptor_for_hidden_properties_called);
}

namespace {
v8::Intercepted XPropertyGetter(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(info.Data()->IsUndefined());
  info.GetReturnValue().Set(property);
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(NamedInterceptorPropertyRead) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  Local<Script> script = v8_compile("obj.x");
  for (int i = 0; i < 10; i++) {
    Local<Value> result = script->Run(context.local()).ToLocalChecked();
    CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
  }
}


THREADED_TEST(NamedInterceptorDictionaryIC) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter));
  LocalContext context;
  // Create an object with a named interceptor.
  context->Global()
      ->Set(context.local(), v8_str("interceptor_obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  Local<Script> script = v8_compile("interceptor_obj.x");
  for (int i = 0; i < 10; i++) {
    Local<Value> result = script->Run(context.local()).ToLocalChecked();
    CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
  }
  // Create a slow case object and a function accessing a property in
  // that slow case object (with dictionary probing in generated
  // code). Then force object with a named interceptor into slow-case,
  // pass it to the function, and check that the interceptor is called
  // instead of accessing the local property.
  Local<Value> result = CompileRun(
      "function get_x(o) { return o.x; };"
      "var obj = { x : 42, y : 0 };"
      "delete obj.y;"
      "for (var i = 0; i < 10; i++) get_x(obj);"
      "interceptor_obj.x = 42;"
      "interceptor_obj.y = 10;"
      "delete interceptor_obj.y;"
      "for (var i = 0; i < 10; i++) get_x(interceptor_obj);");
  CHECK(result->Equals(context.local(), v8_str("x")).FromJust());
}


THREADED_TEST(NamedInterceptorDictionaryICMultipleContext) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<Context> context1 = Context::New(isolate);

  context1->Enter();
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(XPropertyGetter));
  // Create an object with a named interceptor.
  v8::Local<v8::Object> object = templ->NewInstance(context1).ToLocalChecked();
  context1->Global()
      ->Set(context1, v8_str("interceptor_obj"), object)
      .FromJust();

  // Force the object into the slow case.
  CompileRun(
      "interceptor_obj.y = 0;"
      "delete interceptor_obj.y;");
  context1->Exit();

  {
    // Introduce the object into a different context.
    // Repeat named loads to exercise ICs.
    LocalContext context2;
    context2->Global()
        ->Set(context2.local(), v8_str("interceptor_obj"), object)
        .FromJust();
    Local<Value> result = CompileRun(
        "function get_x(o) { return o.x; }"
        "interceptor_obj.x = 42;"
        "for (var i=0; i != 10; i++) {"
        "  get_x(interceptor_obj);"
        "}"
        "get_x(interceptor_obj)");
    // Check that the interceptor was actually invoked.
    CHECK(result->Equals(context2.local(), v8_str("x")).FromJust());
  }

  // Return to the original context and force some object to the slow case
  // to cause the NormalizedMapCache to verify.
  context1->Enter();
  CompileRun("var obj = { x : 0 }; delete obj.x;");
  context1->Exit();
}

namespace {
v8::Intercepted SetXOnPrototypeGetter(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
  // Set x on the prototype object and do not handle the get request.
  v8::Local<v8::Value> proto = info.Holder()->GetPrototype();
  proto.As<v8::Object>()
      ->Set(info.GetIsolate()->GetCurrentContext(), v8_str("x"),
            v8::Integer::New(info.GetIsolate(), 23))
      .FromJust();
  return v8::Intercepted::kNo;
}
}  // namespace

// This is a regression test for http://crbug.com/20104. Map
// transitions should not interfere with post interceptor lookup.
THREADED_TEST(NamedInterceptorMapTransitionRead) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<v8::FunctionTemplate> function_template =
      v8::FunctionTemplate::New(isolate);
  Local<v8::ObjectTemplate> instance_template =
      function_template->InstanceTemplate();
  instance_template->SetHandler(
      v8::NamedPropertyHandlerConfiguration(SetXOnPrototypeGetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("F"),
            function_template->GetFunction(context.local()).ToLocalChecked())
      .FromJust();
  // Create an instance of F and introduce a map transition for x.
  CompileRun("var o = new F(); o.x = 23;");
  // Create an instance of F and invoke the getter. The result should be 23.
  Local<Value> result = CompileRun("o = new F(); o.x");
  CHECK_EQ(23, result->Int32Value(context.local()).FromJust());
}

namespace {
v8::Intercepted IndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (index == 37) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(v8_num(625));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted IndexedPropertySetter(
    uint32_t index, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  if (index == 39) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

THREADED_TEST(IndexedInterceptorWithIndexedAccessor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      IndexedPropertyGetter, IndexedPropertySetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  Local<Script> getter_script =
      v8_compile("obj.__defineGetter__(\"3\", function(){return 5;});obj[3];");
  Local<Script> setter_script = v8_compile(
      "obj.__defineSetter__(\"17\", function(val){this.foo = val;});"
      "obj[17] = 23;"
      "obj.foo;");
  Local<Script> interceptor_setter_script = v8_compile(
      "obj.__defineSetter__(\"39\", function(val){this.foo = \"hit\";});"
      "obj[39] = 47;"
      "obj.foo;");  // This setter should not run, due to the interceptor.
  Local<Script> interceptor_getter_script = v8_compile("obj[37];");
  Local<Value> result = getter_script->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(5)->Equals(context.local(), result).FromJust());
  result = setter_script->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(23)->Equals(context.local(), result).FromJust());
  result = interceptor_setter_script->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(23)->Equals(context.local(), result).FromJust());
  result = interceptor_getter_script->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(625)->Equals(context.local(), result).FromJust());
}

namespace {
v8::Intercepted UnboxedDoubleIndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (index < 25) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(v8_num(index));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted UnboxedDoubleIndexedPropertySetter(
    uint32_t index, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  if (index < 25) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

void UnboxedDoubleIndexedPropertyEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  // Force the list of returned keys to be stored in a FastDoubleArray.
  Local<Script> indexed_property_names_script = v8_compile(
      "keys = new Array(); keys[125000] = 1;"
      "for(i = 0; i < 80000; i++) { keys[i] = i; };"
      "keys.length = 25; keys;");
  Local<Value> result =
      indexed_property_names_script->Run(info.GetIsolate()->GetCurrentContext())
          .ToLocalChecked();
  info.GetReturnValue().Set(result.As<v8::Array>());
}
}  // namespace

// Make sure that the interceptor code in the runtime properly handles
// merging property name lists for double-array-backed arrays.
THREADED_TEST(IndexedInterceptorUnboxedDoubleWithIndexedAccessor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      UnboxedDoubleIndexedPropertyGetter, UnboxedDoubleIndexedPropertySetter,
      nullptr, nullptr, UnboxedDoubleIndexedPropertyEnumerator));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  // When obj is created, force it to be Stored in a FastDoubleArray.
  Local<Script> create_unboxed_double_script = v8_compile(
      "obj[125000] = 1; for(i = 0; i < 80000; i+=2) { obj[i] = i; } "
      "key_count = 0; "
      "for (x in obj) {key_count++;};"
      "obj;");
  Local<Value> result =
      create_unboxed_double_script->Run(context.local()).ToLocalChecked();
  CHECK(result->ToObject(context.local())
            .ToLocalChecked()
            ->HasRealIndexedProperty(context.local(), 2000)
            .FromJust());
  Local<Script> key_count_check = v8_compile("key_count;");
  result = key_count_check->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(40013)->Equals(context.local(), result).FromJust());
}

namespace {
void SloppyArgsIndexedPropertyEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  // Force the list of returned keys to be stored in a Arguments object.
  Local<Script> indexed_property_names_script = v8_compile(
      "function f(w,x) {"
      " return arguments;"
      "}"
      "keys = f(0, 1, 2, 3);"
      "keys;");
  Local<Object> result =
      indexed_property_names_script->Run(info.GetIsolate()->GetCurrentContext())
          .ToLocalChecked()
          .As<Object>();
  // Have to populate the handle manually, as it's not Cast-able.
  i::DirectHandle<i::JSReceiver> o =
      v8::Utils::OpenDirectHandle<Object, i::JSReceiver>(result);
  i::DirectHandle<i::JSArray> array(
      i::UncheckedCast<i::JSArray>(*o),
      reinterpret_cast<i::Isolate*>(info.GetIsolate()));
  info.GetReturnValue().Set(v8::Utils::ToLocal(array));
}

v8::Intercepted SloppyIndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  if (index < 4) {
    info.GetReturnValue().Set(v8_num(index));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// Make sure that the interceptor code in the runtime properly handles
// merging property name lists for non-string arguments arrays.
THREADED_TEST(IndexedInterceptorSloppyArgsWithIndexedAccessor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      SloppyIndexedPropertyGetter, nullptr, nullptr, nullptr,
      SloppyArgsIndexedPropertyEnumerator));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  Local<Script> create_args_script = v8_compile(
      "var key_count = 0;"
      "for (x in obj) {key_count++;} key_count;");
  Local<Value> result =
      create_args_script->Run(context.local()).ToLocalChecked();
  CHECK(v8_num(4)->Equals(context.local(), result).FromJust());
}

namespace {
v8::Intercepted IdentityIndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  info.GetReturnValue().Set(index);
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(IndexedInterceptorWithGetOwnPropertyDescriptor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  // Check fast object case.
  const char* fast_case_code =
      "Object.getOwnPropertyDescriptor(obj, 0).value.toString()";
  ExpectString(fast_case_code, "0");

  // Check slow case.
  const char* slow_case_code =
      "obj.x = 1; delete obj.x;"
      "Object.getOwnPropertyDescriptor(obj, 1).value.toString()";
  ExpectString(slow_case_code, "1");
}


THREADED_TEST(IndexedInterceptorWithNoSetter) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  const char* code =
      "try {"
      "  obj[0] = 239;"
      "  for (var i = 0; i < 100; i++) {"
      "    var v = obj[0];"
      "    if (v != 0) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}

static bool AccessAlwaysBlocked(Local<v8::Context> accessing_context,
                                Local<v8::Object> accessed_object,
                                Local<v8::Value> data) {
  return false;
}


THREADED_TEST(IndexedInterceptorWithAccessorCheck) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  templ->SetAccessCheckCallback(AccessAlwaysBlocked);

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "var result = 'PASSED';"
      "for (var i = 0; i < 100; i++) {"
      "  try {"
      "    var v = obj[0];"
      "    result = 'Wrong value ' + v + ' at iteration ' + i;"
      "    break;"
      "  } catch (e) {"
      "    /* pass */"
      "  }"
      "}"
      "result";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorWithDifferentIndices) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var v = obj[i];"
      "    if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorWithNegativeIndices) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var expected = i;"
      "    var key = i;"
      "    if (i == 25) {"
      "       key = -1;"
      "       expected = undefined;"
      "    }"
      "    if (i == 50) {"
      "       /* probe minimal Smi number on 32-bit platforms */"
      "       key = -(1 << 30);"
      "       expected = undefined;"
      "    }"
      "    if (i == 75) {"
      "       /* probe minimal Smi number on 64-bit platforms */"
      "       key = 1 << 31;"
      "       expected = undefined;"
      "    }"
      "    var v = obj[key];"
      "    if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorWithNotSmiLookup) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var expected = i;"
      "    var key = i;"
      "    if (i == 50) {"
      "       key = 'foobar';"
      "       expected = undefined;"
      "    }"
      "    var v = obj[key];"
      "    if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorGoingMegamorphic) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "var original = obj;"
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var expected = i;"
      "    if (i == 50) {"
      "       obj = {50: 'foobar'};"
      "       expected = 'foobar';"
      "    }"
      "    var v = obj[i];"
      "    if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "    if (i == 50) obj = original;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorReceiverTurningSmi) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "var original = obj;"
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var expected = i;"
      "    if (i == 5) {"
      "       obj = 239;"
      "       expected = undefined;"
      "    }"
      "    var v = obj[i];"
      "    if (v != expected) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "    if (i == 5) obj = original;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}


THREADED_TEST(IndexedInterceptorOnProto) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::IndexedPropertyHandlerConfiguration(IdentityIndexedPropertyGetter));

  LocalContext context;
  Local<v8::Object> obj = templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  const char* code =
      "var o = {__proto__: obj};"
      "try {"
      "  for (var i = 0; i < 100; i++) {"
      "    var v = o[i];"
      "    if (v != i) throw 'Wrong value ' + v + ' at iteration ' + i;"
      "  }"
      "  'PASSED'"
      "} catch(e) {"
      "  e"
      "}";
  ExpectString(code, "PASSED");
}

namespace {

void CheckIndexedInterceptorHasIC(v8::IndexedPropertyGetterCallback getter,
                                  v8::IndexedPropertyQueryCallback query,
                                  const char* source, int expected) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      getter, nullptr, query, nullptr, nullptr, v8_str("data")));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(source);
  CHECK_EQ(expected, value->Int32Value(context.local()).FromJust());
}

int indexed_query_counter = 0;
v8::Intercepted IndexedQueryCallback(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  indexed_query_counter++;
  return v8::Intercepted::kNo;
}

v8::Intercepted IndexHasICQueryAbsent(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  // The request is not intercepted so don't call ApiTestFuzzer::Fuzz() here.
  v8::Isolate* isolate = CcTest::isolate();
  CHECK_EQ(isolate, info.GetIsolate());
  // TODO(ishell): the PropertyAttributes::ABSENT is not exposed in the Api,
  // so it can't be officially returned. We should fix the tests instead.
  info.GetReturnValue().Set(v8::internal::ABSENT);
  return v8::Intercepted::kNo;
}

}  // namespace

THREADED_TEST(IndexedInterceptorHasIC) {
  indexed_query_counter = 0;
  CheckIndexedInterceptorHasIC(nullptr, IndexedQueryCallback,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  i in o;"
                               "}",
                               0);
  CHECK_EQ(1000, indexed_query_counter);
}

THREADED_TEST(IndexedInterceptorHasICQueryAbsent) {
  CheckIndexedInterceptorHasIC(nullptr,
                               // HasICQuery<uint32_t, v8::internal::ABSENT>,
                               IndexHasICQueryAbsent,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  if (i in o) ++result;"
                               "}",
                               0);
}

THREADED_TEST(IndexedInterceptorHasICQueryNone) {
  CheckIndexedInterceptorHasIC(nullptr,
                               HasICQuery<uint32_t, v8::internal::NONE>,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  if (i in o) ++result;"
                               "}",
                               1000);
}

THREADED_TEST(IndexedInterceptorHasICGetter) {
  CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, nullptr,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  if (i in o) ++result;"
                               "}",
                               1000);
}

THREADED_TEST(IndexedInterceptorHasICQueryGetter) {
  CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
                               HasICQuery<uint32_t, v8::internal::ABSENT>,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  if (i in o) ++result;"
                               "}",
                               0);
}

THREADED_TEST(IndexedInterceptorHasICQueryToggle) {
  CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter,
                               HasICQueryToggle<uint32_t>,
                               "var result = 0;"
                               "for (var i = 0; i < 1000; i++) {"
                               "  if (i in o) ++result;"
                               "}",
                               500);
}

namespace {
v8::Intercepted NoBlockGetterX(Local<Name> name,
                               const v8::PropertyCallbackInfo<v8::Value>&) {
  return v8::Intercepted::kNo;
}

v8::Intercepted NoBlockGetterI(uint32_t index,
                               const v8::PropertyCallbackInfo<v8::Value>&) {
  return v8::Intercepted::kNo;
}

v8::Intercepted PDeleter(Local<Name> name,
                         const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  if (!name->Equals(info.GetIsolate()->GetCurrentContext(), v8_str("foo"))
           .FromJust()) {
    return v8::Intercepted::kNo;
  }

  // Intercepted, but property deletion failed.
  info.GetReturnValue().Set(false);
  return v8::Intercepted::kYes;
}

v8::Intercepted IDeleter(uint32_t index,
                         const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  if (index != 2) {
    return v8::Intercepted::kNo;
  }

  // Intercepted, but property deletion failed.
  info.GetReturnValue().Set(false);
  return v8::Intercepted::kYes;
}
}  // namespace

THREADED_TEST(Deleter) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
      NoBlockGetterX, nullptr, nullptr, PDeleter, nullptr));
  obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      NoBlockGetterI, nullptr, nullptr, IDeleter, nullptr));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("k"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "k.foo = 'foo';"
      "k.bar = 'bar';"
      "k[2] = 2;"
      "k[4] = 4;");
  CHECK(v8_compile("delete k.foo")
            ->Run(context.local())
            .ToLocalChecked()
            ->IsFalse());
  CHECK(v8_compile("delete k.bar")
            ->Run(context.local())
            .ToLocalChecked()
            ->IsTrue());

  CHECK(v8_compile("k.foo")
            ->Run(context.local())
            .ToLocalChecked()
            ->Equals(context.local(), v8_str("foo"))
            .FromJust());
  CHECK(v8_compile("k.bar")
            ->Run(context.local())
            .ToLocalChecked()
            ->IsUndefined());

  CHECK(v8_compile("delete k[2]")
            ->Run(context.local())
            .ToLocalChecked()
            ->IsFalse());
  CHECK(v8_compile("delete k[4]")
            ->Run(context.local())
            .ToLocalChecked()
            ->IsTrue());

  CHECK(v8_compile("k[2]")
            ->Run(context.local())
            .ToLocalChecked()
            ->Equals(context.local(), v8_num(2))
            .FromJust());
  CHECK(
      v8_compile("k[4]")->Run(context.local()).ToLocalChecked()->IsUndefined());
}

namespace {
v8::Intercepted GetK(Local<Name> name,
                     const v8::PropertyCallbackInfo<v8::Value>& info) {
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  if (name->Equals(context, v8_str("foo")).FromJust() ||
      name->Equals(context, v8_str("bar")).FromJust() ||
      name->Equals(context, v8_str("baz")).FromJust()) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().SetUndefined();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

v8::Intercepted IndexedGetK(uint32_t index,
                            const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (index == 0 || index == 1) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().SetUndefined();
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

static void NamedEnum(const v8::PropertyCallbackInfo<v8::Array>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 3);
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("foo"))
          .FromJust());
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("bar"))
          .FromJust());
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_str("baz"))
          .FromJust());
  info.GetReturnValue().Set(result);
}


static void IndexedEnum(const v8::PropertyCallbackInfo<v8::Array>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 2);
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  CHECK(
      result->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("0"))
          .FromJust());
  CHECK(
      result->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("1"))
          .FromJust());
  info.GetReturnValue().Set(result);
}


THREADED_TEST(Enumerators) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::NamedPropertyHandlerConfiguration(GetK, nullptr, nullptr,
                                                        nullptr, NamedEnum));
  obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      IndexedGetK, nullptr, nullptr, nullptr, IndexedEnum));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("k"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<v8::Array> result = CompileRun(
                                    "k[10] = 0;"
                                    "k.a = 0;"
                                    "k[5] = 0;"
                                    "k.b = 0;"
                                    "k[4294967294] = 0;"
                                    "k.c = 0;"
                                    "k[4294967295] = 0;"
                                    "k.d = 0;"
                                    "k[140000] = 0;"
                                    "k.e = 0;"
                                    "k[30000000000] = 0;"
                                    "k.f = 0;"
                                    "var result = [];"
                                    "for (var prop in k) {"
                                    "  result.push(prop);"
                                    "}"
                                    "result")
                                    .As<v8::Array>();
  // Check that we get all the property names returned including the
  // ones from the enumerators in the right order: indexed properties
  // in numerical order, indexed interceptor properties, named
  // properties in insertion order, named interceptor properties.
  // This order is not mandated by the spec, so this test is just
  // documenting our behavior.
  CHECK_EQ(17u, result->Length());
  // Indexed properties.
  CHECK(v8_str("5")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 0))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("10")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 1))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("140000")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 2))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("4294967294")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 3))
                         .ToLocalChecked())
            .FromJust());
  // Indexed Interceptor properties
  CHECK(v8_str("0")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 4))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("1")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 5))
                         .ToLocalChecked())
            .FromJust());
  // Named properties in insertion order.
  CHECK(v8_str("a")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 6))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("b")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 7))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("c")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 8))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("4294967295")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 9))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("d")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 10))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("e")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 11))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("30000000000")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 12))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("f")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 13))
                         .ToLocalChecked())
            .FromJust());
  // Named interceptor properties.
  CHECK(v8_str("foo")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 14))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("bar")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 15))
                         .ToLocalChecked())
            .FromJust());
  CHECK(v8_str("baz")
            ->Equals(context.local(),
                     result->Get(context.local(), v8::Integer::New(isolate, 16))
                         .ToLocalChecked())
            .FromJust());
}

namespace {
v8::Global<Value> call_ic_function_global;
v8::Global<Value> call_ic_function2_global;
v8::Global<Value> call_ic_function3_global;

v8::Intercepted InterceptorCallICGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(v8_str("x")
            ->Equals(info.GetIsolate()->GetCurrentContext(), name)
            .FromJust());
  info.GetReturnValue().Set(call_ic_function_global);
  return v8::Intercepted::kYes;
}
}  // namespace

// This test should hit the call IC for the interceptor case.
THREADED_TEST(InterceptorCallIC) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  Local<Value> call_ic_function =
      v8_compile("function f(x) { return x + 1; }; f")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function_global.Reset(isolate, call_ic_function);
  v8::Local<Value> value = CompileRun(
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result = o.x(41);"
      "}");
  CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
  call_ic_function_global.Reset();
}


// This test checks that if interceptor doesn't provide
// a value, we can fetch regular value.
THREADED_TEST(InterceptorCallICSeesOthers) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "o.x = function f(x) { return x + 1; };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result = o.x(41);"
      "}");
  CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
}

namespace {
v8::Global<Value> call_ic_function4_global;
v8::Intercepted InterceptorCallICGetter4(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  CHECK(v8_str("x")
            ->Equals(info.GetIsolate()->GetCurrentContext(), name)
            .FromJust());
  info.GetReturnValue().Set(call_ic_function4_global);
  return v8::Intercepted::kYes;
}
}  // namespace

// This test checks that if interceptor provides a function,
// even if we cached shadowed variant, interceptor's function
// is invoked
THREADED_TEST(InterceptorCallICCacheableNotNeeded) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter4));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> call_ic_function4 =
      v8_compile("function f(x) { return x - 1; }; f")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function4_global.Reset(isolate, call_ic_function4);
  v8::Local<Value> value = CompileRun(
      "Object.getPrototypeOf(o).x = function(x) { return x + 1; };"
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result = o.x(42);"
      "}");
  CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
  call_ic_function4_global.Reset();
}


// Test the case when we stored cacheable lookup into
// a stub, but it got invalidated later on
THREADED_TEST(InterceptorCallICInvalidatedCacheable) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "proto1 = new Object();"
      "proto2 = new Object();"
      "o.__proto__ = proto1;"
      "proto1.__proto__ = proto2;"
      "proto2.y = function(x) { return x + 1; };"
      // Invoke it many times to compile a stub
      "for (var i = 0; i < 7; i++) {"
      "  o.y(42);"
      "}"
      "proto1.y = function(x) { return x - 1; };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result += o.y(42);"
      "}");
  CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
}


// This test checks that if interceptor doesn't provide a function,
// cached constant function is used
THREADED_TEST(InterceptorCallICConstantFunctionUsed) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "o.x = inc;"
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result = o.x(42);"
      "}");
  CHECK_EQ(43, value->Int32Value(context.local()).FromJust());
}

namespace {
v8::Global<Value> call_ic_function5_global;
v8::Intercepted InterceptorCallICGetter5(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Isolate* isolate = info.GetIsolate();
  if (v8_str("x")->Equals(isolate->GetCurrentContext(), name).FromJust()) {
    info.GetReturnValue().Set(call_ic_function5_global);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// This test checks that if interceptor provides a function,
// even if we cached constant function, interceptor's function
// is invoked
THREADED_TEST(InterceptorCallICConstantFunctionNotNeeded) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter5));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> call_ic_function5 =
      v8_compile("function f(x) { return x - 1; }; f")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function5_global.Reset(isolate, call_ic_function5);
  v8::Local<Value> value = CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "o.x = inc;"
      "var result = 0;"
      "for (var i = 0; i < 1000; i++) {"
      "  result = o.x(42);"
      "}");
  CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
  call_ic_function5_global.Reset();
}

namespace {
v8::Global<Value> call_ic_function6_global;
v8::Intercepted InterceptorCallICGetter6(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Isolate* isolate = info.GetIsolate();
  if (v8_str("x")->Equals(isolate->GetCurrentContext(), name).FromJust()) {
    info.GetReturnValue().Set(call_ic_function6_global);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// Same test as above, except the code is wrapped in a function
// to test the optimized compiler.
THREADED_TEST(InterceptorCallICConstantFunctionNotNeededWrapped) {
  i::v8_flags.allow_natives_syntax = true;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorCallICGetter6));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> call_ic_function6 =
      v8_compile("function f(x) { return x - 1; }; f")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function6_global.Reset(isolate, call_ic_function6);
  v8::Local<Value> value = CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "o.x = inc;"
      "function test() {"
      "  var result = 0;"
      "  for (var i = 0; i < 1000; i++) {"
      "    result = o.x(42);"
      "  }"
      "  return result;"
      "};"
      "%PrepareFunctionForOptimization(test);"
      "test();"
      "test();"
      "test();"
      "%OptimizeFunctionOnNextCall(test);"
      "test()");
  CHECK_EQ(41, value->Int32Value(context.local()).FromJust());
  call_ic_function6_global.Reset();
}


// Test the case when we stored constant function into
// a stub, but it got invalidated later on
THREADED_TEST(InterceptorCallICInvalidatedConstantFunction) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "proto1 = new Object();"
      "proto2 = new Object();"
      "o.__proto__ = proto1;"
      "proto1.__proto__ = proto2;"
      "proto2.y = inc;"
      // Invoke it many times to compile a stub
      "for (var i = 0; i < 7; i++) {"
      "  o.y(42);"
      "}"
      "proto1.y = function(x) { return x - 1; };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result += o.y(42);"
      "}");
  CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
}


// Test the case when we stored constant function into
// a stub, but it got invalidated later on due to override on
// global object which is between interceptor and constant function' holders.
THREADED_TEST(InterceptorCallICInvalidatedConstantFunctionViaGlobal) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<Value> value = CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "o.__proto__ = this;"
      "this.__proto__.y = inc;"
      // Invoke it many times to compile a stub
      "for (var i = 0; i < 7; i++) {"
      "  if (o.y(42) != 43) throw 'oops: ' + o.y(42);"
      "}"
      "this.y = function(x) { return x - 1; };"
      "var result = 0;"
      "for (var i = 0; i < 7; i++) {"
      "  result += o.y(42);"
      "}");
  CHECK_EQ(41 * 7, value->Int32Value(context.local()).FromJust());
}


// Test the case when actual function to call sits on global object.
THREADED_TEST(InterceptorCallICCachedFromGlobal) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));

  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<Value> value = CompileRun(
      "try {"
      "  o.__proto__ = this;"
      "  for (var i = 0; i < 10; i++) {"
      "    var v = o.parseFloat('239');"
      "    if (v != 239) throw v;"
      // Now it should be ICed and keep a reference to parseFloat.
      "  }"
      "  var result = 0;"
      "  for (var i = 0; i < 10; i++) {"
      "    result += o.parseFloat('239');"
      "  }"
      "  result"
      "} catch(e) {"
      "  e"
      "};");
  CHECK_EQ(239 * 10, value->Int32Value(context.local()).FromJust());
}

namespace {
v8::Global<Value> keyed_call_ic_function_global;

v8::Intercepted InterceptorKeyedCallICGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  ApiTestFuzzer::Fuzz();
  if (v8_str("x")
          ->Equals(info.GetIsolate()->GetCurrentContext(), name)
          .FromJust()) {
    info.GetReturnValue().Set(keyed_call_ic_function_global);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// Test the case when we stored cacheable lookup into
// a stub, but the function name changed (to another cacheable function).
THREADED_TEST(InterceptorKeyedCallICKeyChange1) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "proto = new Object();"
      "proto.y = function(x) { return x + 1; };"
      "proto.z = function(x) { return x - 1; };"
      "o.__proto__ = proto;"
      "var result = 0;"
      "var method = 'y';"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) { method = 'z'; };"
      "  result += o[method](41);"
      "}");
  CHECK_EQ(42 * 5 + 40 * 5, context->Global()
                                ->Get(context.local(), v8_str("result"))
                                .ToLocalChecked()
                                ->Int32Value(context.local())
                                .FromJust());
}


// Test the case when we stored cacheable lookup into
// a stub, but the function name changed (and the new function is present
// both before and after the interceptor in the prototype chain).
THREADED_TEST(InterceptorKeyedCallICKeyChange2) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorKeyedCallICGetter));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("proto1"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<v8::Value> keyed_call_ic_function =
      v8_compile("function f(x) { return x - 1; }; f")
          ->Run(context.local())
          .ToLocalChecked();
  keyed_call_ic_function_global.Reset(isolate, keyed_call_ic_function);
  CompileRun(
      "o = new Object();"
      "proto2 = new Object();"
      "o.y = function(x) { return x + 1; };"
      "proto2.y = function(x) { return x + 2; };"
      "o.__proto__ = proto1;"
      "proto1.__proto__ = proto2;"
      "var result = 0;"
      "var method = 'x';"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) { method = 'y'; };"
      "  result += o[method](41);"
      "}");
  CHECK_EQ(42 * 5 + 40 * 5, context->Global()
                                ->Get(context.local(), v8_str("result"))
                                .ToLocalChecked()
                                ->Int32Value(context.local())
                                .FromJust());
  keyed_call_ic_function_global.Reset();
}


// Same as InterceptorKeyedCallICKeyChange1 only the cacheable function sit
// on the global object.
THREADED_TEST(InterceptorKeyedCallICKeyChangeOnGlobal) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "function inc(x) { return x + 1; };"
      "inc(1);"
      "function dec(x) { return x - 1; };"
      "dec(1);"
      "o.__proto__ = this;"
      "this.__proto__.x = inc;"
      "this.__proto__.y = dec;"
      "var result = 0;"
      "var method = 'x';"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) { method = 'y'; };"
      "  result += o[method](41);"
      "}");
  CHECK_EQ(42 * 5 + 40 * 5, context->Global()
                                ->Get(context.local(), v8_str("result"))
                                .ToLocalChecked()
                                ->Int32Value(context.local())
                                .FromJust());
}


// Test the case when actual function to call sits on global object.
THREADED_TEST(InterceptorKeyedCallICFromGlobal) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  CompileRun(
      "function len(x) { return x.length; };"
      "o.__proto__ = this;"
      "var m = 'parseFloat';"
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) {"
      "    m = 'len';"
      "    saved_result = result;"
      "  };"
      "  result = o[m]('239');"
      "}");
  CHECK_EQ(3, context->Global()
                  ->Get(context.local(), v8_str("result"))
                  .ToLocalChecked()
                  ->Int32Value(context.local())
                  .FromJust());
  CHECK_EQ(239, context->Global()
                    ->Get(context.local(), v8_str("saved_result"))
                    .ToLocalChecked()
                    ->Int32Value(context.local())
                    .FromJust());
}


// Test the map transition before the interceptor.
THREADED_TEST(InterceptorKeyedCallICMapChangeBefore) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("proto"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  CompileRun(
      "var o = new Object();"
      "o.__proto__ = proto;"
      "o.method = function(x) { return x + 1; };"
      "var m = 'method';"
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) { o.method = function(x) { return x - 1; }; };"
      "  result += o[m](41);"
      "}");
  CHECK_EQ(42 * 5 + 40 * 5, context->Global()
                                ->Get(context.local(), v8_str("result"))
                                .ToLocalChecked()
                                ->Int32Value(context.local())
                                .FromJust());
}


// Test the map transition after the interceptor.
THREADED_TEST(InterceptorKeyedCallICMapChangeAfter) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ_o = ObjectTemplate::New(isolate);
  templ_o->SetHandler(v8::NamedPropertyHandlerConfiguration(NoBlockGetterX));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("o"),
            templ_o->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  CompileRun(
      "var proto = new Object();"
      "o.__proto__ = proto;"
      "proto.method = function(x) { return x + 1; };"
      "var m = 'method';"
      "var result = 0;"
      "for (var i = 0; i < 10; i++) {"
      "  if (i == 5) { proto.method = function(x) { return x - 1; }; };"
      "  result += o[m](41);"
      "}");
  CHECK_EQ(42 * 5 + 40 * 5, context->Global()
                                ->Get(context.local(), v8_str("result"))
                                .ToLocalChecked()
                                ->Int32Value(context.local())
                                .FromJust());
}

namespace {
int interceptor_call_count = 0;

v8::Intercepted InterceptorICRefErrorGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (!is_bootstrapping &&
      v8_str("x")
          ->Equals(info.GetIsolate()->GetCurrentContext(), name)
          .FromJust() &&
      interceptor_call_count++ < 20) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(call_ic_function2_global);
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// This test should hit load and call ICs for the interceptor case.
// Once in a while, the interceptor will reply that a property was not
// found in which case we should get a reference error.
THREADED_TEST(InterceptorICReferenceErrors) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorICRefErrorGetter));
  is_bootstrapping = true;
  LocalContext context(nullptr, templ, v8::Local<Value>());
  is_bootstrapping = false;
  v8::Local<Value> call_ic_function2 =
      v8_compile("function h(x) { return x; }; h")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function2_global.Reset(isolate, call_ic_function2);
  v8::Local<Value> value = CompileRun(
      "function f() {"
      "  for (var i = 0; i < 1000; i++) {"
      "    try { x; } catch(e) { return true; }"
      "  }"
      "  return false;"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));
  interceptor_call_count = 0;
  value = CompileRun(
      "function g() {"
      "  for (var i = 0; i < 1000; i++) {"
      "    try { x(42); } catch(e) { return true; }"
      "  }"
      "  return false;"
      "};"
      "g();");
  CHECK(value->BooleanValue(isolate));
  call_ic_function2_global.Reset();
}

namespace {
int interceptor_ic_exception_get_count = 0;

v8::Intercepted InterceptorICExceptionGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  if (is_bootstrapping) return v8::Intercepted::kNo;
  if (v8_str("x")
          ->Equals(info.GetIsolate()->GetCurrentContext(), name)
          .FromJust() &&
      ++interceptor_ic_exception_get_count < 20) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetReturnValue().Set(call_ic_function3_global);
  }
  if (interceptor_ic_exception_get_count == 20) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetIsolate()->ThrowException(v8_num(42));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// Test interceptor load/call IC where the interceptor throws an
// exception once in a while.
THREADED_TEST(InterceptorICGetterExceptions) {
  interceptor_ic_exception_get_count = 0;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorICExceptionGetter));
  is_bootstrapping = true;
  LocalContext context(nullptr, templ, v8::Local<Value>());
  is_bootstrapping = false;
  v8::Local<Value> call_ic_function3 =
      v8_compile("function h(x) { return x; }; h")
          ->Run(context.local())
          .ToLocalChecked();
  call_ic_function3_global.Reset(isolate, call_ic_function3);
  v8::Local<Value> value = CompileRun(
      "function f() {"
      "  for (var i = 0; i < 100; i++) {"
      "    try { x; } catch(e) { return true; }"
      "  }"
      "  return false;"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));
  interceptor_ic_exception_get_count = 0;
  value = CompileRun(
      "function f() {"
      "  for (var i = 0; i < 100; i++) {"
      "    try { x(42); } catch(e) { return true; }"
      "  }"
      "  return false;"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));
  call_ic_function3_global.Reset();
}

namespace {
int interceptor_ic_exception_set_count = 0;

v8::Intercepted InterceptorICExceptionSetter(
    Local<Name> key, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  if (++interceptor_ic_exception_set_count > 20) {
    // Side effects are allowed only when the property is present or throws.
    ApiTestFuzzer::Fuzz();
    info.GetIsolate()->ThrowException(v8_num(42));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}
}  // namespace

// Test interceptor store IC where the interceptor throws an exception
// once in a while.
THREADED_TEST(InterceptorICSetterExceptions) {
  interceptor_ic_exception_set_count = 0;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, InterceptorICExceptionSetter));
  LocalContext context(nullptr, templ, v8::Local<Value>());
  v8::Local<Value> value = CompileRun(
      "function f() {"
      "  for (var i = 0; i < 100; i++) {"
      "    try { x = 42; } catch(e) { return true; }"
      "  }"
      "  return false;"
      "};"
      "f();");
  CHECK(value->BooleanValue(isolate));
}


// Test that we ignore null interceptors.
THREADED_TEST(NullNamedInterceptor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::NamedPropertyHandlerConfiguration(nullptr));
  LocalContext context;
  templ->Set(CcTest::isolate(), "x", v8_num(42));
  v8::Local<v8::Object> obj =
      templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
  v8::Local<Value> value = CompileRun("obj.x");
  CHECK(value->IsInt32());
  CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
}


// Test that we ignore null interceptors.
THREADED_TEST(NullIndexedInterceptor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> templ = ObjectTemplate::New(isolate);
  templ->SetHandler(v8::IndexedPropertyHandlerConfiguration(nullptr));
  LocalContext context;
  templ->Set(CcTest::isolate(), "42", v8_num(42));
  v8::Local<v8::Object> obj =
      templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();
  v8::Local<Value> value = CompileRun("obj[42]");
  CHECK(value->IsInt32());
  CHECK_EQ(42, value->Int32Value(context.local()).FromJust());
}


THREADED_TEST(NamedPropertyHandlerGetterAttributes) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
  templ->InstanceTemplate()->SetHandler(
      v8::NamedPropertyHandlerConfiguration(InterceptorLoadXICGetter));
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("obj"), templ->GetFunction(env.local())
                                            .ToLocalChecked()
                                            ->NewInstance(env.local())
                                            .ToLocalChecked())
      .FromJust();
  ExpectTrue("obj.x === 42");
  ExpectTrue("!obj.propertyIsEnumerable('x')");
}


THREADED_TEST(Regress256330) {
  if (!i::v8_flags.turbofan) return;
  i::v8_flags.allow_natives_syntax = true;
  LocalContext context;
  v8::HandleScope scope(context.isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(context.isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  context->Global()
      ->Set(context.local(), v8_str("Bug"),
            templ->GetFunction(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "\"use strict\"; var o = new Bug;"
      "function f(o) { o.x = 10; };"
      "%PrepareFunctionForOptimization(f);"
      "f(o); f(o); f(o);"
      "%OptimizeFunctionOnNextCall(f);"
      "f(o);");
  int status = v8_run_int32value(v8_compile("%GetOptimizationStatus(f)"));
  int mask = static_cast<int>(i::OptimizationStatus::kIsFunction) |
             static_cast<int>(i::OptimizationStatus::kOptimized);
  CHECK_EQ(mask, status & mask);
}

THREADED_TEST(OptimizedInterceptorSetter) {
  i::v8_flags.allow_natives_syntax = true;
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      // Initialize fields to avoid transitions later.
      "obj.age = 0;"
      "obj.accessor_age = 42;"
      "function setter(i) { this.accessor_age = i; };"
      "function getter() { return this.accessor_age; };"
      "function setAge(i) { obj.age = i; };"
      "Object.defineProperty(obj, 'age', { get:getter, set:setter });"
      "%PrepareFunctionForOptimization(setAge);"
      "setAge(1);"
      "setAge(2);"
      "setAge(3);"
      "%OptimizeFunctionOnNextCall(setAge);"
      "setAge(4);");
  // All stores went through the interceptor.
  ExpectInt32("obj.interceptor_age", 4);
  ExpectInt32("obj.accessor_age", 42);
}

THREADED_TEST(OptimizedInterceptorGetter) {
  i::v8_flags.allow_natives_syntax = true;
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      // Initialize fields to avoid transitions later.
      "obj.age = 1;"
      "obj.accessor_age = 42;"
      "function getter() { return this.accessor_age; };"
      "function getAge() { return obj.interceptor_age; };"
      "Object.defineProperty(obj, 'interceptor_age', { get:getter });"
      "%PrepareFunctionForOptimization(getAge);"
      "getAge();"
      "getAge();"
      "getAge();"
      "%OptimizeFunctionOnNextCall(getAge);");
  // Access through interceptor.
  ExpectInt32("getAge()", 1);
}

THREADED_TEST(OptimizedInterceptorFieldRead) {
  i::v8_flags.allow_natives_syntax = true;
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "obj.__proto__.interceptor_age = 42;"
      "obj.age = 100;"
      "function getAge() { return obj.interceptor_age; };"
      "%PrepareFunctionForOptimization(getAge);");
  ExpectInt32("getAge();", 100);
  ExpectInt32("getAge();", 100);
  ExpectInt32("getAge();", 100);
  CompileRun("%OptimizeFunctionOnNextCall(getAge);");
  // Access through interceptor.
  ExpectInt32("getAge();", 100);
}

THREADED_TEST(OptimizedInterceptorFieldWrite) {
  i::v8_flags.allow_natives_syntax = true;
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(templ, InterceptorGetter, InterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Obj"),
            templ->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var obj = new Obj;"
      "obj.age = 100000;"
      "function setAge(i) { obj.age = i };"
      "%PrepareFunctionForOptimization(setAge);"
      "setAge(100);"
      "setAge(101);"
      "setAge(102);"
      "%OptimizeFunctionOnNextCall(setAge);"
      "setAge(103);");
  ExpectInt32("obj.age", 100000);
  ExpectInt32("obj.interceptor_age", 103);
}


THREADED_TEST(Regress149912) {
  LocalContext context;
  v8::HandleScope scope(context.isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(context.isolate());
  AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
  context->Global()
      ->Set(context.local(), v8_str("Bug"),
            templ->GetFunction(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun("Number.prototype.__proto__ = new Bug; var x = 0; x.foo();");
}

THREADED_TEST(Regress625155) {
  LocalContext context;
  v8::HandleScope scope(context.isolate());
  Local<FunctionTemplate> templ = FunctionTemplate::New(context.isolate());
  AddInterceptor(templ, EmptyInterceptorGetter, EmptyInterceptorSetter);
  context->Global()
      ->Set(context.local(), v8_str("Bug"),
            templ->GetFunction(context.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "Number.prototype.__proto__ = new Bug;"
      "var x;"
      "x = 0xDEAD;"
      "x.boom = 0;"
      "x = 's';"
      "x.boom = 0;"
      "x = 1.5;"
      "x.boom = 0;");
}

THREADED_TEST(Regress125988) {
  v8::HandleScope scope(CcTest::isolate());
  Local<FunctionTemplate> intercept = FunctionTemplate::New(CcTest::isolate());
  AddInterceptor(intercept, EmptyInterceptorGetter, EmptyInterceptorSetter);
  LocalContext env;
  env->Global()
      ->Set(env.local(), v8_str("Intercept"),
            intercept->GetFunction(env.local()).ToLocalChecked())
      .FromJust();
  CompileRun(
      "var a = new Object();"
      "var b = new Intercept();"
      "var c = new Object();"
      "c.__proto__ = b;"
      "b.__proto__ = a;"
      "a.x = 23;"
      "for (var i = 0; i < 3; i++) c.x;");
  ExpectBoolean("c.hasOwnProperty('x')", false);
  ExpectInt32("c.x", 23);
  CompileRun(
      "a.y = 42;"
      "for (var i = 0; i < 3; i++) c.x;");
  ExpectBoolean("c.hasOwnProperty('x')", false);
  ExpectInt32("c.x", 23);
  ExpectBoolean("c.hasOwnProperty('y')", false);
  ExpectInt32("c.y", 42);
}

namespace {
void IndexedPropertyEnumerator(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 1);
  result->Set(info.GetIsolate()->GetCurrentContext(), 0,
              v8::Integer::New(info.GetIsolate(), 7))
      .FromJust();
  info.GetReturnValue().Set(result);
}

void NamedPropertyEnumerator(const v8::PropertyCallbackInfo<v8::Array>& info) {
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 2);
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  result->Set(context, 0, v8_str("x")).FromJust();
  result->Set(context, 1, v8::Symbol::GetIterator(info.GetIsolate()))
      .FromJust();
  info.GetReturnValue().Set(result);
}
}  // namespace

THREADED_TEST(GetOwnPropertyNamesWithInterceptor) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);

  obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
  obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
  obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, IndexedPropertyEnumerator));
  obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, NamedPropertyEnumerator));

  LocalContext context;
  v8::Local<v8::Object> global = context->Global();
  global->Set(context.local(), v8_str("object"),
              obj_template->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<v8::Value> result =
      CompileRun("Object.getOwnPropertyNames(object)");
  CHECK(result->IsArray());
  v8::Local<v8::Array> result_array = result.As<v8::Array>();
  CHECK_EQ(2u, result_array->Length());
  CHECK(result_array->Get(context.local(), 0).ToLocalChecked()->IsString());
  CHECK(result_array->Get(context.local(), 1).ToLocalChecked()->IsString());
  CHECK(v8_str("7")
            ->Equals(context.local(),
                     result_array->Get(context.local(), 0).ToLocalChecked())
            .FromJust());
  CHECK(v8_str("x")
            ->Equals(context.local(),
                     result_array->Get(context.local(), 1).ToLocalChecked())
            .FromJust());

  result = CompileRun("var ret = []; for (var k in object) ret.push(k); ret");
  CHECK(result->IsArray());
  result_array = result.As<v8::Array>();
  CHECK_EQ(2u, result_array->Length());
  CHECK(result_array->Get(context.local(), 0).ToLocalChecked()->IsString());
  CHECK(result_array->Get(context.local(), 1).ToLocalChecked()->IsString());
  CHECK(v8_str("7")
            ->Equals(context.local(),
                     result_array->Get(context.local(), 0).ToLocalChecked())
            .FromJust());
  CHECK(v8_str("x")
            ->Equals(context.local(),
                     result_array->Get(context.local(), 1).ToLocalChecked())
            .FromJust());

  result = CompileRun("Object.getOwnPropertySymbols(object)");
  CHECK(result->IsArray());
  result_array = result.As<v8::Array>();
  CHECK_EQ(1u, result_array->Length());
  CHECK(result_array->Get(context.local(), 0)
            .ToLocalChecked()
            ->Equals(context.local(), v8::Symbol::GetIterator(isolate))
            .FromJust());
}

namespace {
void IndexedPropertyEnumeratorException(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  info.GetIsolate()->ThrowException(v8_num(42));
}
}  // namespace

THREADED_TEST(GetOwnPropertyNamesWithIndexedInterceptorExceptions_regress4026) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);

  obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
  obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
  // First just try a failing indexed interceptor.
  obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      nullptr, nullptr, nullptr, nullptr, IndexedPropertyEnumeratorException));

  LocalContext context;
  v8::Local<v8::Object> global = context->Global();
  global->Set(context.local(), v8_str("object"),
              obj_template->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  v8::Local<v8::Value> result = CompileRun(
      "var result  = []; "
      "try { "
      "  for (var k in object) result .push(k);"
      "} catch (e) {"
      "  result  = e"
      "}"
      "result ");
  CHECK(!result->IsArray());
  CHECK(v8_num(42)->Equals(context.local(), result).FromJust());

  result = CompileRun(
      "var result = [];"
      "try { "
      "  result = Object.keys(object);"
      "} catch (e) {"
      "  result = e;"
      "}"
      "result");
  CHECK(!result->IsArray());
  CHECK(v8_num(42)->Equals(context.local(), result).FromJust());
}

namespace {
void NamedPropertyEnumeratorException(
    const v8::PropertyCallbackInfo<v8::Array>& info) {
  info.GetIsolate()->ThrowException(v8_num(43));
}
}  // namespace

THREADED_TEST(GetOwnPropertyNamesWithNamedInterceptorExceptions_regress4026) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::ObjectTemplate> obj_template = v8::ObjectTemplate::New(isolate);

  obj_template->Set(isolate, "7", v8::Integer::New(isolate, 7));
  obj_template->Set(isolate, "x", v8::Integer::New(isolate, 42));
  // First just try a failing indexed interceptor.
  obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      static_cast<v8::NamedPropertyGetterCallback>(nullptr), nullptr, nullptr,
      nullptr, NamedPropertyEnumeratorException));

  LocalContext context;
  v8::Local<v8::Object> global = context->Global();
  global->Set(context.local(), v8_str("object"),
              obj_template->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  v8::Local<v8::Value> result = CompileRun(
      "var result = []; "
      "try { "
      "  for (var k in object) result.push(k);"
      "} catch (e) {"
      "  result = e"
      "}"
      "result");
  CHECK(!result->IsArray());
  CHECK(v8_num(43)->Equals(context.local(), result).FromJust());

  result = CompileRun(
      "var result = [];"
      "try { "
      "  result = Object.keys(object);"
      "} catch (e) {"
      "  result = e;"
      "}"
      "result");
  CHECK(!result->IsArray());
  CHECK(v8_num(43)->Equals(context.local(), result).FromJust());
}

namespace {

struct PreprocessExceptionTestConfig {
  bool query_should_throw : 1 = false;
  bool getter_should_throw : 1 = false;
  bool descriptor_should_throw : 1 = false;
};

template <typename T>
PreprocessExceptionTestConfig* GetPETConfig(
    const v8::PropertyCallbackInfo<T>& info) {
  return GetData<PreprocessExceptionTestConfig>(info);
}

const char* ToString(v8::ExceptionContext kind) {
  switch (kind) {
    case v8::ExceptionContext::kUnknown:
      return "Unknown";
    case v8::ExceptionContext::kConstructor:
      return "Constructor";
    case v8::ExceptionContext::kOperation:
      return "Operation";
    case v8::ExceptionContext::kAttributeGet:
      return "AttributeGet";
    case v8::ExceptionContext::kAttributeSet:
      return "AttributeSet";
    case v8::ExceptionContext::kIndexedQuery:
      return "IndexedQuery";
    case v8::ExceptionContext::kIndexedGetter:
      return "IndexedGetter";
    case v8::ExceptionContext::kIndexedDescriptor:
      return "IndexedDescriptor";
    case v8::ExceptionContext::kIndexedSetter:
      return "IndexedSetter";
    case v8::ExceptionContext::kIndexedDefiner:
      return "IndexedDefiner";
    case v8::ExceptionContext::kIndexedDeleter:
      return "IndexedDeleter";
    case v8::ExceptionContext::kNamedQuery:
      return "NamedQuery";
    case v8::ExceptionContext::kNamedGetter:
      return "NamedGetter";
    case v8::ExceptionContext::kNamedDescriptor:
      return "NamedDescriptor";
    case v8::ExceptionContext::kNamedSetter:
      return "NamedSetter";
    case v8::ExceptionContext::kNamedDefiner:
      return "NamedDefiner";
    case v8::ExceptionContext::kNamedDeleter:
      return "NamedDeleter";
    case v8::ExceptionContext::kNamedEnumerator:
      return "NamedEnumerator";
  }
  UNREACHABLE();
}

void PreprocessExceptionTestCallback(v8::ExceptionPropagationMessage info) {
  v8::Isolate* isolate = info.GetIsolate();
  v8::Local<v8::Object> exception = info.GetException();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();
  v8::Local<v8::String> message_key = v8_str("message");

  v8::Local<v8::String> message_value = exception->Get(context, message_key)
                                            .ToLocalChecked()
                                            ->ToString(context)
                                            .ToLocalChecked();
  String::Utf8Value interface_name(isolate, info.GetInterfaceName());
  String::Utf8Value property_name(isolate, info.GetPropertyName());
  String::Utf8Value message(isolate, message_value);

  auto buf = v8::base::OwnedVector<char>::NewForOverwrite(256);
  v8::base::SNPrintF(buf.as_vector(), "%s:%s:%s: %s", *interface_name,
                     *property_name, ToString(info.GetExceptionContext()),
                     *message);

  std::ignore =
      exception->CreateDataProperty(context, message_key, v8_str(buf.data()));
}

void CheckMessage(v8::TryCatch& try_catch, const char* expected_message) {
  CHECK(try_catch.HasCaught());
  v8::Local<v8::String> message_key = v8_str("message");
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::Local<v8::Context> context = isolate->GetCurrentContext();

  Local<v8::Value> result = try_catch.Exception()
                                .As<v8::Object>()
                                ->Get(context, message_key)
                                .ToLocalChecked();
  CHECK(result->IsString());
  String::Utf8Value message(isolate, result.As<String>());

  // Compare as std::string in order to see a readable message on failure.
  CHECK_EQ(std::string(*message), std::string(expected_message));
  try_catch.Reset();
}

// Named interceptor callbacks.

v8::Intercepted PETNamedQuery(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  if (GetPETConfig(info)->query_should_throw) {
    info.GetIsolate()->ThrowError("Named query failed.");
  } else {
    info.GetReturnValue().Set(v8::None);
  }
  return v8::Intercepted::kYes;
}
v8::Intercepted PETNamedGetter(Local<Name> name,
                               const v8::PropertyCallbackInfo<Value>& info) {
  if (GetPETConfig(info)->getter_should_throw) {
    info.GetIsolate()->ThrowError("Named getter failed.");
  } else {
    info.GetReturnValue().Set(153);
  }
  return v8::Intercepted::kYes;
}
v8::Intercepted PETNamedSetter(Local<Name> name, Local<Value> value,
                               const v8::PropertyCallbackInfo<Boolean>& info) {
  info.GetIsolate()->ThrowError("Named setter failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETNamedDeleter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  info.GetIsolate()->ThrowError("Named deleter failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETNamedDefiner(Local<Name> name,
                                const v8::PropertyDescriptor& desc,
                                const v8::PropertyCallbackInfo<Boolean>& info) {
  info.GetIsolate()->ThrowError("Named definer failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETNamedDescriptor(
    Local<Name> property, const v8::PropertyCallbackInfo<Value>& info) {
  if (GetPETConfig(info)->descriptor_should_throw) {
    info.GetIsolate()->ThrowError("Named descriptor failed.");
  } else {
    v8::Isolate* isolate = info.GetIsolate();
    v8::Local<v8::Object> descriptor = v8::Object::New(isolate);
    v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
    v8::Local<v8::Boolean> true_value = v8::Boolean::New(isolate, true);

    std::ignore = descriptor->Set(ctx, v8_str("value"), property);
    std::ignore = descriptor->Set(ctx, v8_str("writable"), true_value);
    std::ignore = descriptor->Set(ctx, v8_str("enumerable"), true_value);
    std::ignore = descriptor->Set(ctx, v8_str("configurable"), true_value);

    info.GetReturnValue().Set(descriptor);
  }
  return v8::Intercepted::kYes;
}

// Indexed interceptor callbacks.

v8::Intercepted PETIndexedQuery(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  if (GetPETConfig(info)->query_should_throw) {
    info.GetIsolate()->ThrowError("Indexed query failed.");
  } else {
    info.GetReturnValue().Set(v8::None);
  }
  return v8::Intercepted::kYes;
}
v8::Intercepted PETIndexedGetter(uint32_t index,
                                 const v8::PropertyCallbackInfo<Value>& info) {
  if (GetPETConfig(info)->getter_should_throw) {
    info.GetIsolate()->ThrowError("Indexed getter failed.");
  } else {
    info.GetReturnValue().Set(153);
  }
  return v8::Intercepted::kYes;
}
v8::Intercepted PETIndexedSetter(
    uint32_t index, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  info.GetIsolate()->ThrowError("Indexed setter failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETIndexedDeleter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& info) {
  info.GetIsolate()->ThrowError("Indexed deleter failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETIndexedDefiner(
    uint32_t index, const v8::PropertyDescriptor& desc,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  info.GetIsolate()->ThrowError("Indexed definer failed.");
  return v8::Intercepted::kYes;
}
v8::Intercepted PETIndexedDescriptor(
    uint32_t index, const v8::PropertyCallbackInfo<Value>& info) {
  if (GetPETConfig(info)->descriptor_should_throw) {
    info.GetIsolate()->ThrowError("Indexed descriptor failed.");
  } else {
    v8::Isolate* isolate = info.GetIsolate();
    v8::Local<v8::Object> descriptor = v8::Object::New(isolate);
    v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
    v8::Local<v8::Boolean> true_value = v8::Boolean::New(isolate, true);

    std::ignore = descriptor->Set(ctx, v8_str("value"), v8_uint(index));
    std::ignore = descriptor->Set(ctx, v8_str("writable"), true_value);
    std::ignore = descriptor->Set(ctx, v8_str("enumerable"), true_value);
    std::ignore = descriptor->Set(ctx, v8_str("configurable"), true_value);

    info.GetReturnValue().Set(descriptor);
  }
  return v8::Intercepted::kYes;
}

}  // namespace

void TestPreprocessExceptionFromInterceptors(
    v8::Isolate* isolate, PreprocessExceptionTestConfig& config,
    Local<Context> ctx, v8::Local<v8::Object> obj,
    bool is_descriptor_callback_available) {
  v8::TryCatch try_catch(isolate);

  config.query_should_throw = true;
  config.getter_should_throw = true;
  config.descriptor_should_throw = true;

  const char* expected;
  //
  // Check query callbacks.
  //
  {
    expected = "MyClass:foo:NamedQuery: Named query failed.";
    std::ignore = obj->GetPropertyAttributes(ctx, v8_str("foo"));
    CheckMessage(try_catch, expected);
    std::ignore = obj->HasOwnProperty(ctx, v8_str("foo"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.hasOwn(obj, 'foo');");
    CheckMessage(try_catch, expected);

    expected = "MyClass:1:IndexedQuery: Indexed query failed.";
    std::ignore = obj->GetPropertyAttributes(ctx, v8_uint(1));
    CheckMessage(try_catch, expected);
    std::ignore = obj->HasOwnProperty(ctx, v8_str("1"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.hasOwn(obj, 1);");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967294:IndexedQuery: Indexed query failed.";
    std::ignore = obj->GetPropertyAttributes(ctx, v8_uint(0xfffffffe));
    CheckMessage(try_catch, expected);
    std::ignore = obj->HasOwnProperty(ctx, v8_str("4294967294"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.hasOwn(obj, 0xfffffffe);");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967295:NamedQuery: Named query failed.";
    std::ignore = obj->GetPropertyAttributes(ctx, v8_uint(0xffffffff));
    CheckMessage(try_catch, expected);
    std::ignore = obj->HasOwnProperty(ctx, v8_str("4294967295"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.hasOwn(obj, 0xffffffff);");
    CheckMessage(try_catch, expected);
  }

  //
  // Check getter callbacks.
  //
  {
    expected = "MyClass:foo:NamedGetter: Named getter failed.";
    std::ignore = obj->Get(ctx, v8_str("foo"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj.foo");
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj['foo']");
    CheckMessage(try_catch, expected);

    expected = "MyClass:1:IndexedGetter: Indexed getter failed.";
    std::ignore = obj->Get(ctx, v8_uint(1));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[1]");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967294:IndexedGetter: Indexed getter failed.";
    std::ignore = obj->Get(ctx, v8_uint(0xfffffffe));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[0xfffffffe]");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967295:NamedGetter: Named getter failed.";
    std::ignore = obj->Get(ctx, v8_uint(0xffffffff));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[0xffffffff]");
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[4294967295]");
    CheckMessage(try_catch, expected);
  }

  //
  // Check setter callbacks.
  //
  {
    v8::Local<v8::Value> value = v8_str("value");

    expected = "MyClass:foo:NamedSetter: Named setter failed.";
    std::ignore = obj->Set(ctx, v8_str("foo"), value);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj.foo = 42;");
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj['foo'] = 42;");
    CheckMessage(try_catch, expected);

    expected = "MyClass:1:IndexedSetter: Indexed setter failed.";
    std::ignore = obj->Set(ctx, v8_uint(1), value);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[1] = 42;");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967294:IndexedSetter: Indexed setter failed.";
    std::ignore = obj->Set(ctx, v8_uint(0xfffffffe), value);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[0xfffffffe] = 42;");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967295:NamedSetter: Named setter failed.";
    std::ignore = obj->Set(ctx, v8_uint(0xffffffff), value);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("obj[0xffffffff] = 42;");
    CheckMessage(try_catch, expected);
  }

  //
  // Check deleter callbacks.
  //
  {
    expected = "MyClass:foo:NamedDeleter: Named deleter failed.";
    std::ignore = obj->Delete(ctx, v8_str("foo"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("delete obj.foo;");
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("delete obj['foo'];");
    CheckMessage(try_catch, expected);

    expected = "MyClass:1:IndexedDeleter: Indexed deleter failed.";
    std::ignore = obj->Delete(ctx, v8_str("1"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("delete obj[1];");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967294:IndexedDeleter: Indexed deleter failed.";
    std::ignore = obj->Delete(ctx, v8_str("4294967294"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("delete obj[0xfffffffe];");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967295:NamedDeleter: Named deleter failed.";
    std::ignore = obj->Delete(ctx, v8_str("4294967295"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("delete obj[0xffffffff];");
    CheckMessage(try_catch, expected);
  }

  //
  // Check descriptor callbacks.
  //
  {
    expected = is_descriptor_callback_available
                   ? "MyClass:foo:NamedDescriptor: Named descriptor failed."
                   : "MyClass:foo:NamedQuery: Named query failed.";
    std::ignore = obj->GetOwnPropertyDescriptor(ctx, v8_str("foo"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.getOwnPropertyDescriptor(obj, 'foo');");
    CheckMessage(try_catch, expected);

    expected = is_descriptor_callback_available
                   ? "MyClass:1:IndexedDescriptor: Indexed descriptor failed."
                   : "MyClass:1:IndexedQuery: Indexed query failed.";
    std::ignore = obj->GetOwnPropertyDescriptor(ctx, v8_str("1"));
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.getOwnPropertyDescriptor(obj, 1);");
    CheckMessage(try_catch, expected);

    expected =
        is_descriptor_callback_available
            ? "MyClass:4294967294:IndexedDescriptor: Indexed descriptor failed."
            : "MyClass:4294967294:IndexedQuery: Indexed query failed.";
    std::ignore = obj->GetOwnPropertyDescriptor(ctx, v8_str("4294967294"));
    CheckMessage(try_catch, expected);
    std::ignore =
        CompileRun("Object.getOwnPropertyDescriptor(obj, 0xfffffffe);");
    CheckMessage(try_catch, expected);

    expected =
        is_descriptor_callback_available
            ? "MyClass:4294967295:NamedDescriptor: Named descriptor failed."
            : "MyClass:4294967295:NamedQuery: Named query failed.";
    std::ignore = obj->GetOwnPropertyDescriptor(ctx, v8_str("4294967295"));
    CheckMessage(try_catch, expected);
    std::ignore =
        CompileRun("Object.getOwnPropertyDescriptor(obj, 0xffffffff);");
    CheckMessage(try_catch, expected);
  }

  //
  // Check definer callbacks.
  //
  config.query_should_throw = false;
  config.getter_should_throw = false;
  config.descriptor_should_throw = false;
  {
    v8::Local<v8::Value> value = v8_str("value");
    v8::PropertyDescriptor descriptor(value);

    expected = "MyClass:foo:NamedDefiner: Named definer failed.";
    std::ignore = obj->DefineOwnProperty(ctx, v8_str("foo"), value);
    CheckMessage(try_catch, expected);
    std::ignore = obj->DefineProperty(ctx, v8_str("foo"), descriptor);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.defineProperty(obj, 'foo', {});");
    CheckMessage(try_catch, expected);

    expected = "MyClass:1:IndexedDefiner: Indexed definer failed.";
    std::ignore = obj->DefineOwnProperty(ctx, v8_str("1"), value);
    CheckMessage(try_catch, expected);
    std::ignore = obj->DefineProperty(ctx, v8_str("1"), descriptor);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.defineProperty(obj, 1, {});");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967294:IndexedDefiner: Indexed definer failed.";
    std::ignore = obj->DefineOwnProperty(ctx, v8_str("4294967294"), value);
    CheckMessage(try_catch, expected);
    std::ignore = obj->DefineProperty(ctx, v8_str("4294967294"), descriptor);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.defineProperty(obj, 0xfffffffe, {});");
    CheckMessage(try_catch, expected);

    expected = "MyClass:4294967295:NamedDefiner: Named definer failed.";
    std::ignore = obj->DefineOwnProperty(ctx, v8_str("4294967295"), value);
    CheckMessage(try_catch, expected);
    std::ignore = obj->DefineProperty(ctx, v8_str("4294967295"), descriptor);
    CheckMessage(try_catch, expected);
    std::ignore = CompileRun("Object.defineProperty(obj, 0xffffffff, {});");
    CheckMessage(try_catch, expected);
  }
}

// Can't use THREADED_TEST because this test requires setting an exception
// preprocessing callback which might be observable in other tests.
TEST(PreprocessExceptionFromInterceptorsWithoutDescriptorCallback) {
  i::v8_flags.experimental_report_exceptions_from_callbacks = true;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::FunctionTemplate> ctor = v8::FunctionTemplate::New(isolate);
  ctor->SetClassName(v8_str("MyClass"));
  v8::Local<v8::ObjectTemplate> obj_template = ctor->InstanceTemplate();

  isolate->SetExceptionPropagationCallback(PreprocessExceptionTestCallback);

  PreprocessExceptionTestConfig config;

  obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      PETNamedGetter, PETNamedSetter, PETNamedQuery, PETNamedDeleter,
      nullptr,  // enumerator
      PETNamedDefiner,
      nullptr,  // descriptor
      MakeData(isolate, &config)));
  obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      PETIndexedGetter, PETIndexedSetter, PETIndexedQuery, PETIndexedDeleter,
      nullptr,  // enumerator
      PETIndexedDefiner,
      nullptr,  // descriptor
      MakeData(isolate, &config)));

  LocalContext env;
  Local<Context> ctx = env.local();
  v8::Local<v8::Object> obj = obj_template->NewInstance(ctx).ToLocalChecked();

  v8::Local<v8::Object> global = ctx->Global();
  global->Set(ctx, v8_str("obj"), obj).FromJust();

  constexpr bool is_descriptor_callback_available = false;
  TestPreprocessExceptionFromInterceptors(isolate, config, ctx, obj,
                                          is_descriptor_callback_available);
}

// Can't use THREADED_TEST because this test requires setting an exception
// preprocessing callback which might be observable in other tests.
TEST(PreprocessExceptionFromInterceptorsWithDescriptorCallback) {
  i::v8_flags.experimental_report_exceptions_from_callbacks = true;
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);

  v8::Local<v8::FunctionTemplate> ctor = v8::FunctionTemplate::New(isolate);
  ctor->SetClassName(v8_str("MyClass"));
  v8::Local<v8::ObjectTemplate> obj_template = ctor->InstanceTemplate();

  isolate->SetExceptionPropagationCallback(PreprocessExceptionTestCallback);

  PreprocessExceptionTestConfig config;

  obj_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      PETNamedGetter, PETNamedSetter, PETNamedQuery, PETNamedDeleter,
      nullptr,  // enumerator
      PETNamedDefiner, PETNamedDescriptor, MakeData(isolate, &config)));
  obj_template->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      PETIndexedGetter, PETIndexedSetter, PETIndexedQuery, PETIndexedDeleter,
      nullptr,  // enumerator
      PETIndexedDefiner, PETIndexedDescriptor, MakeData(isolate, &config)));

  LocalContext env;
  Local<Context> ctx = env.local();
  v8::Local<v8::Object> obj = obj_template->NewInstance(ctx).ToLocalChecked();

  v8::Local<v8::Object> global = ctx->Global();
  global->Set(ctx, v8_str("obj"), obj).FromJust();

  constexpr bool is_descriptor_callback_available = true;
  TestPreprocessExceptionFromInterceptors(isolate, config, ctx, obj,
                                          is_descriptor_callback_available);
}

namespace {

template <typename T>
Local<Object> BuildWrappedObject(v8::Isolate* isolate, T* data) {
  auto templ = v8::ObjectTemplate::New(isolate);
  templ->SetInternalFieldCount(1);
  auto instance =
      templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
  instance->SetAlignedPointerInInternalField(0, data, kApiInterceptorTag);
  return instance;
}

template <typename T>
T* GetWrappedObject(Local<Value> data) {
  return reinterpret_cast<T*>(
      Object::Cast(*data)->GetAlignedPointerFromInternalField(
          0, kApiInterceptorTag));
}

struct AccessCheckData {
  int count;
  bool result;
};

struct ShouldInterceptData {
  int value;
  bool should_intercept;
};

v8::Intercepted ShouldNamedGetterInterceptor(
    Local<Name> name, const v8::PropertyCallbackInfo<Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(ShouldNamedGetterInterceptor));
  auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
  if (!data->should_intercept) return v8::Intercepted::kNo;
  // Side effects are allowed only when the property is present or throws.
  ApiTestFuzzer::Fuzz();
  info.GetReturnValue().Set(v8_num(data->value));
  return v8::Intercepted::kYes;
}

v8::Intercepted ShouldIndexedGetterInterceptor(
    uint32_t index, const v8::PropertyCallbackInfo<Value>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(ShouldIndexedGetterInterceptor));
  auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
  if (!data->should_intercept) return v8::Intercepted::kNo;
  // Side effects are allowed only when the property is present or throws.
  ApiTestFuzzer::Fuzz();
  info.GetReturnValue().Set(v8_num(data->value));
  return v8::Intercepted::kYes;
}

v8::Intercepted ShouldNamedSetterInterceptor(
    Local<Name> name, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  CheckReturnValue(info, FUNCTION_ADDR(ShouldNamedSetterInterceptor));
  auto data = GetWrappedObject<ShouldInterceptData>(info.Data());
  if (!data->should_intercept) return v8::Intercepted::kNo;
  // Side effects are allowed only when the property is present or throws.
  ApiTestFuzzer::Fuzz();
  Local<Context> context = info.GetIsolate()->GetCurrentContext();
  if (value->IsInt32()) {
    USE(value->Int32Value(context).To(&data->value));
  }
  return v8::Intercepted::kYes;
}

void TestNonMaskingInterceptorOwnProperty(bool with_setter) {
  auto isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  LocalContext context;

  ShouldInterceptData intercept_data;
  intercept_data.value = 239;
  intercept_data.should_intercept = true;

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration conf(ShouldNamedGetterInterceptor);
  if (with_setter) conf.setter = ShouldNamedSetterInterceptor;
  conf.flags = v8::PropertyHandlerFlags::kNonMasking;
  conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
  interceptor_templ->SetHandler(conf);

  auto interceptor =
      interceptor_templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("obj"), interceptor)
      .FromJust();
  auto i_interceptor = v8::Utils::OpenHandle(*interceptor);

  CompileRun(
      "function get() { return obj.whatever; } "
      "function set(v) { obj.whatever = v; } "
      "function trainICs() { "
      "  for (var i = 0; i < 20; i++) {"
      "    var v = get();"
      "    set(v);"
      "  }"
      "}"
      "trainICs();");
  CHECK_EQ(intercept_data.value, 239);

  ExpectInt32("obj.whatever", 239);
  ExpectInt32("get()", 239);

  CompileRun("obj.whatever = 4;");
  if (with_setter) {
    CHECK_EQ(i_interceptor->map()->NumberOfOwnDescriptors(), 0);
    CHECK_EQ(intercept_data.value, 4);
    ExpectInt32("obj.whatever", 4);

    // Store through IC should also trigger setter callback.
    CompileRun("set(44);");
    CHECK_EQ(intercept_data.value, 44);
    ExpectInt32("obj.whatever", 44);

  } else {
    // No interceptor setter -> store adds property to the receiver.
    CHECK_EQ(i_interceptor->map()->NumberOfOwnDescriptors(), 1);
    CHECK_EQ(intercept_data.value, 239);

    // obj.whatever exists, thus it is not affected by the non-masking
    // interceptor.
    ExpectInt32("obj.whatever", 4);

    // Stores through ICs should also store to the object.
    CompileRun("trainICs(); set(55);");
    CHECK_EQ(intercept_data.value, 239);

    ExpectInt32("obj.whatever", 55);
  }
  intercept_data.value = 1239;

  CompileRun("delete obj.whatever;");
  ExpectInt32("obj.whatever", 1239);
  ExpectInt32("get()", 1239);

  if (with_setter) {
    // If the store is not intercepted then the property should be added to
    // receiver.
    intercept_data.should_intercept = false;
    CHECK_EQ(i_interceptor->map()->NumberOfOwnDescriptors(), 0);
    // CompileRun("obj.whatever = 257;");
    CompileRun("set(257);");
    CHECK_EQ(i_interceptor->map()->NumberOfOwnDescriptors(), 1);

    intercept_data.should_intercept = true;
    ExpectInt32("obj.whatever", 257);
    CHECK_EQ(intercept_data.value, 1239);
  }
}

}  // namespace

THREADED_TEST(NonMaskingInterceptorWithSetterOwnProperty) {
  TestNonMaskingInterceptorOwnProperty(true);
}

THREADED_TEST(NonMaskingInterceptorWithoutSetterOwnProperty) {
  TestNonMaskingInterceptorOwnProperty(false);
}

void TestNonMaskingInterceptorPrototypeProperty(bool with_setter) {
  auto isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  LocalContext context;

  ShouldInterceptData intercept_data;
  intercept_data.value = 239;
  intercept_data.should_intercept = true;

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration conf(ShouldNamedGetterInterceptor);
  if (with_setter) conf.setter = ShouldNamedSetterInterceptor;
  conf.flags = v8::PropertyHandlerFlags::kNonMasking;
  conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
  interceptor_templ->SetHandler(conf);

  auto interceptor =
      interceptor_templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("obj"), interceptor)
      .FromJust();

  // [[Get]]
  ExpectInt32("obj.whatever", 239);

  CompileRun("obj.__proto__ = {'whatever': 4};");
  ExpectInt32("obj.whatever", 4);

  CompileRun("delete obj.__proto__.whatever;");
  ExpectInt32("obj.whatever", 239);

  // [[Set]]
  CompileRun("obj.__proto__ = {'whatever': 77};");
  CompileRun("obj.whatever = 5;");
  CHECK_EQ(intercept_data.value, 239);

  CompileRun("delete obj.__proto__.whatever;");
  CompileRun("obj.whatever = 7;");
  ExpectInt32("obj.whatever", 7);

  // Regardless of the setter existence, the property was already added to
  // the receiver.
  CHECK_EQ(intercept_data.value, 239);

  if (with_setter) {
    // Delete property on receiver to make stores trigger the setter again.
    CompileRun("delete obj.whatever;");
    CompileRun("obj.whatever = 157;");

    CHECK_EQ(intercept_data.value, 157);
    ExpectInt32("obj.whatever", 157);

    intercept_data.should_intercept = false;
    CompileRun("obj.whatever = 257;");
  }
}

THREADED_TEST(NonMaskingInterceptorWithSetterPrototypeProperty) {
  TestNonMaskingInterceptorPrototypeProperty(true);
}

THREADED_TEST(NonMaskingInterceptorWithoutSetterPrototypeProperty) {
  TestNonMaskingInterceptorPrototypeProperty(false);
}

THREADED_TEST(NonMaskingInterceptorPrototypePropertyIC) {
  auto isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  LocalContext context;

  ShouldInterceptData intercept_data;
  intercept_data.value = 239;
  intercept_data.should_intercept = true;

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration conf(ShouldNamedGetterInterceptor);
  conf.flags = v8::PropertyHandlerFlags::kNonMasking;
  conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
  interceptor_templ->SetHandler(conf);

  auto interceptor =
      interceptor_templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("obj"), interceptor)
      .FromJust();

  CompileRun(
      "outer = {};"
      "outer.__proto__ = obj;"
      "function f(obj) {"
      "  var x;"
      "  for (var i = 0; i < 4; i++) {"
      "    x = obj.whatever;"
      "  }"
      "  return x;"
      "}");

  // Receiver == holder.
  CompileRun("obj.__proto__ = null;");
  ExpectInt32("f(obj)", 239);
  ExpectInt32("f(outer)", 239);

  // Receiver != holder.
  CompileRun("Object.setPrototypeOf(obj, {});");
  ExpectInt32("f(obj)", 239);
  ExpectInt32("f(outer)", 239);

  // Masked value on prototype.
  CompileRun("obj.__proto__.whatever = 4;");
  CompileRun("obj.__proto__.__proto__ = { 'whatever' : 5 };");
  ExpectInt32("f(obj)", 4);
  ExpectInt32("f(outer)", 4);

  // Masked value on prototype prototype.
  CompileRun("delete obj.__proto__.whatever;");
  ExpectInt32("f(obj)", 5);
  ExpectInt32("f(outer)", 5);

  // Reset.
  CompileRun("delete obj.__proto__.__proto__.whatever;");
  ExpectInt32("f(obj)", 239);
  ExpectInt32("f(outer)", 239);

  // Masked value on self.
  CompileRun("obj.whatever = 4;");
  ExpectInt32("f(obj)", 4);
  ExpectInt32("f(outer)", 4);

  // Reset.
  CompileRun("delete obj.whatever;");
  ExpectInt32("f(obj)", 239);
  ExpectInt32("f(outer)", 239);

  CompileRun("outer.whatever = 4;");
  ExpectInt32("f(obj)", 239);
  ExpectInt32("f(outer)", 4);
}

THREADED_TEST(NonMaskingInterceptorOwnPropertyTransition) {
  LocalContext context;
  auto isolate = context.isolate();
  v8::HandleScope handle_scope(isolate);

  ShouldInterceptData intercept_data;
  intercept_data.value = 239;
  intercept_data.should_intercept = true;

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration conf(ShouldNamedGetterInterceptor);
  conf.flags = v8::PropertyHandlerFlags::kNonMasking;
  conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &intercept_data);
  interceptor_templ->SetHandler(conf);

  auto interceptor =
      interceptor_templ->NewInstance(context.local()).ToLocalChecked();
  context->Global()
      ->Set(context.local(), v8_str("obj"), interceptor)
      .FromJust();

  // Need prototype property to force lookup failure during transition below.
  CompileRun("Object.getPrototypeOf(obj).prop = 42;");

  CompileRun(R"(
    Object.defineProperty(obj, "prop", {
      get: function () {
        return 139;
      }
    });
  )");
  ExpectInt32("obj.prop", 139);
}

THREADED_TEST(NonMaskingIndexedInterceptorOwnProperty) {
  LocalContext context;
  auto isolate = context.isolate();
  v8::HandleScope handle_scope(isolate);

  ShouldInterceptData obj_data;
  obj_data.value = 239;
  obj_data.should_intercept = true;

  ShouldInterceptData proto_data;
  proto_data.value = 153;
  // The prototype's interceptor doesn't claim the property, so a regular get
  // still falls through to the receiver's non-masking interceptor. Its mere
  // presence is what must make the own-property lookup stop, though.
  proto_data.should_intercept = false;

  // Prototype object with a (masking) indexed interceptor.
  auto proto_templ = v8::ObjectTemplate::New(isolate);
  {
    v8::IndexedPropertyHandlerConfiguration conf(
        ShouldIndexedGetterInterceptor);
    conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &proto_data);
    proto_templ->SetHandler(conf);
  }
  auto proto = proto_templ->NewInstance(context.local()).ToLocalChecked();

  // Receiver object with a non-masking indexed interceptor.
  auto obj_templ = v8::ObjectTemplate::New(isolate);
  {
    v8::IndexedPropertyHandlerConfiguration conf(
        ShouldIndexedGetterInterceptor);
    conf.flags = v8::PropertyHandlerFlags::kNonMasking;
    conf.data = BuildWrappedObject<ShouldInterceptData>(isolate, &obj_data);
    obj_templ->SetHandler(conf);
  }
  auto obj = obj_templ->NewInstance(context.local()).ToLocalChecked();
  obj->SetPrototype(context.local(), proto).FromJust();

  context->Global()->Set(context.local(), v8_str("obj"), obj).FromJust();

  // A second indexed interceptor on the prototype chain is *not* invisible (in
  // contrast to named interceptors). An own-property lookup that skips the
  // receiver's non-masking interceptor must not report the index as an own
  // property of the receiver.
  CHECK(!v8_run_bool("obj.hasOwnProperty(0);"));

  // The non-masking interceptor still serves the value via a regular lookup.
  ExpectInt32("obj[0]", 239);
}

namespace {

v8::Intercepted ConcatNamedPropertyGetter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  info.GetReturnValue().Set(
      // Return the property name concatenated with itself.
      String::Concat(info.GetIsolate(), name.As<String>(), name.As<String>()));
  return v8::Intercepted::kYes;
}

v8::Intercepted ConcatIndexedPropertyGetter(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
  info.GetReturnValue().Set(
      // Return the double value of the index.
      v8_num(index + index));
  return v8::Intercepted::kYes;
}

void EnumCallbackWithNames(const v8::PropertyCallbackInfo<v8::Array>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 4);
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_str("foo"))
          .FromJust());
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_str("bar"))
          .FromJust());
  CHECK(
      result
          ->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_str("baz"))
          .FromJust());
  CHECK(
      result->Set(context, v8::Integer::New(info.GetIsolate(), 3), v8_str("10"))
          .FromJust());

  //  Create a holey array.
  CHECK(result->Delete(context, v8::Integer::New(info.GetIsolate(), 1))
            .FromJust());
  info.GetReturnValue().Set(result);
}

void EnumCallbackWithIndices(const v8::PropertyCallbackInfo<v8::Array>& info) {
  ApiTestFuzzer::Fuzz();
  v8::Local<v8::Array> result = v8::Array::New(info.GetIsolate(), 4);
  v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();

  CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 0), v8_num(10))
            .FromJust());
  CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 1), v8_num(11))
            .FromJust());
  CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 2), v8_num(12))
            .FromJust());
  CHECK(result->Set(context, v8::Integer::New(info.GetIsolate(), 3), v8_num(14))
            .FromJust());

  //  Create a holey array.
  CHECK(result->Delete(context, v8::Integer::New(info.GetIsolate(), 1))
            .FromJust());
  info.GetReturnValue().Set(result);
}

v8::Intercepted RestrictiveNamedQuery(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  // Only "foo" is enumerable.
  if (v8_str("foo")
          ->Equals(info.GetIsolate()->GetCurrentContext(), property)
          .FromJust()) {
    info.GetReturnValue().Set(v8::None);
    return v8::Intercepted::kYes;
  }
  info.GetReturnValue().Set(v8::DontEnum);
  return v8::Intercepted::kYes;
}

v8::Intercepted RestrictiveIndexedQuery(
    uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  // Only index 2 and 12 are enumerable.
  if (index == 2 || index == 12) {
    info.GetReturnValue().Set(v8::None);
    return v8::Intercepted::kYes;
  }
  info.GetReturnValue().Set(v8::DontEnum);
  return v8::Intercepted::kYes;
}
}  // namespace

// Regression test for V8 bug 6627.
// Object.keys() must return enumerable keys only.
THREADED_TEST(EnumeratorsAndUnenumerableNamedProperties) {
  // The enumerator interceptor returns a list
  // of items which are filtered according to the
  // properties defined in the query interceptor.
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
      ConcatNamedPropertyGetter, nullptr, RestrictiveNamedQuery, nullptr,
      EnumCallbackWithNames));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
  ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");
  ExpectString("Object.getOwnPropertyNames(obj)[1]", "baz");
  ExpectString("Object.getOwnPropertyNames(obj)[2]", "10");

  ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");
  ExpectFalse("Object.getOwnPropertyDescriptor(obj, 'baz').enumerable");

  ExpectInt32("Object.entries(obj).length", 1);
  ExpectString("Object.entries(obj)[0][0]", "foo");
  ExpectString("Object.entries(obj)[0][1]", "foofoo");

  ExpectInt32("Object.keys(obj).length", 1);
  ExpectString("Object.keys(obj)[0]", "foo");

  ExpectInt32("Object.values(obj).length", 1);
  ExpectString("Object.values(obj)[0]", "foofoo");
}

namespace {
v8::Intercepted QueryInterceptorForFoo(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  // Don't intercept anything except "foo."
  if (!v8_str("foo")
           ->Equals(info.GetIsolate()->GetCurrentContext(), property)
           .FromJust()) {
    return v8::Intercepted::kNo;
  }
  // "foo" is enumerable.
  info.GetReturnValue().Set(v8::PropertyAttribute::None);
  return v8::Intercepted::kYes;
}
}  // namespace

// Test that calls to the query interceptor are independent of each
// other.
THREADED_TEST(EnumeratorsAndUnenumerableNamedPropertiesWithoutSet) {
  // The enumerator interceptor returns a list
  // of items which are filtered according to the
  // properties defined in the query interceptor.
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
      ConcatNamedPropertyGetter, nullptr, QueryInterceptorForFoo, nullptr,
      EnumCallbackWithNames));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
  ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");
  ExpectString("Object.getOwnPropertyNames(obj)[1]", "baz");
  ExpectString("Object.getOwnPropertyNames(obj)[2]", "10");

  ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");
  ExpectInt32("Object.keys(obj).length", 1);
}

THREADED_TEST(EnumeratorsAndUnenumerableIndexedPropertiesArgumentsElements) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      ConcatIndexedPropertyGetter, nullptr, RestrictiveIndexedQuery, nullptr,
      SloppyArgsIndexedPropertyEnumerator));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("Object.getOwnPropertyNames(obj).length", 4);
  ExpectString("Object.getOwnPropertyNames(obj)[0]", "0");
  ExpectString("Object.getOwnPropertyNames(obj)[1]", "1");
  ExpectString("Object.getOwnPropertyNames(obj)[2]", "2");
  ExpectString("Object.getOwnPropertyNames(obj)[3]", "3");

  ExpectTrue("Object.getOwnPropertyDescriptor(obj, '2').enumerable");

  ExpectInt32("Object.entries(obj).length", 1);
  ExpectString("Object.entries(obj)[0][0]", "2");
  ExpectInt32("Object.entries(obj)[0][1]", 4);

  ExpectInt32("Object.keys(obj).length", 1);
  ExpectString("Object.keys(obj)[0]", "2");

  ExpectInt32("Object.values(obj).length", 1);
  ExpectInt32("Object.values(obj)[0]", 4);
}

THREADED_TEST(EnumeratorsAndUnenumerableIndexedProperties) {
  // The enumerator interceptor returns a list
  // of items which are filtered according to the
  // properties defined in the query interceptor.
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::IndexedPropertyHandlerConfiguration(
      ConcatIndexedPropertyGetter, nullptr, RestrictiveIndexedQuery, nullptr,
      EnumCallbackWithIndices));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
  ExpectString("Object.getOwnPropertyNames(obj)[0]", "10");
  ExpectString("Object.getOwnPropertyNames(obj)[1]", "12");
  ExpectString("Object.getOwnPropertyNames(obj)[2]", "14");

  ExpectFalse("Object.getOwnPropertyDescriptor(obj, '10').enumerable");
  ExpectTrue("Object.getOwnPropertyDescriptor(obj, '12').enumerable");

  ExpectInt32("Object.entries(obj).length", 1);
  ExpectString("Object.entries(obj)[0][0]", "12");
  ExpectInt32("Object.entries(obj)[0][1]", 24);

  ExpectInt32("Object.keys(obj).length", 1);
  ExpectString("Object.keys(obj)[0]", "12");

  ExpectInt32("Object.values(obj).length", 1);
  ExpectInt32("Object.values(obj)[0]", 24);
}

THREADED_TEST(EnumeratorsAndForIn) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);
  v8::Local<v8::ObjectTemplate> obj = ObjectTemplate::New(isolate);
  obj->SetHandler(v8::NamedPropertyHandlerConfiguration(
      ConcatNamedPropertyGetter, nullptr, RestrictiveNamedQuery, nullptr,
      NamedEnum));
  LocalContext context;
  context->Global()
      ->Set(context.local(), v8_str("obj"),
            obj->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("Object.getOwnPropertyNames(obj).length", 3);
  ExpectString("Object.getOwnPropertyNames(obj)[0]", "foo");

  ExpectTrue("Object.getOwnPropertyDescriptor(obj, 'foo').enumerable");

  CompileRun(
      "let concat = '';"
      "for(var prop in obj) {"
      "  concat += `key:${prop}:value:${obj[prop]}`;"
      "}");

  // Check that for...in only iterates over enumerable properties.
  ExpectString("concat", "key:foo:value:foofoo");
}

namespace {

v8::Intercepted DatabaseGetter(Local<Name> name,
                               const v8::PropertyCallbackInfo<Value>& info) {
  auto context = info.GetIsolate()->GetCurrentContext();
  v8::MaybeLocal<Value> maybe_db =
      info.Holder()->GetRealNamedProperty(context, v8_str("db"));
  if (maybe_db.IsEmpty()) return v8::Intercepted::kNo;
  Local<v8::Object> db = maybe_db.ToLocalChecked().As<v8::Object>();
  if (!db->Has(context, name).FromJust()) return v8::Intercepted::kNo;

  // Side effects are allowed only when the property is present or throws.
  ApiTestFuzzer::Fuzz();
  info.GetReturnValue().Set(db->Get(context, name).ToLocalChecked());
  return v8::Intercepted::kYes;
}

v8::Intercepted DatabaseSetter(Local<Name> name, Local<Value> value,
                               const v8::PropertyCallbackInfo<Boolean>& info) {
  auto context = info.GetIsolate()->GetCurrentContext();
  if (name->Equals(context, v8_str("db")).FromJust()) {
    return v8::Intercepted::kNo;
  }

  // Side effects are allowed only when the property is present or throws.
  ApiTestFuzzer::Fuzz();
  Local<v8::Object> db = info.Holder()
                             ->GetRealNamedProperty(context, v8_str("db"))
                             .ToLocalChecked()
                             .As<v8::Object>();
  db->Set(context, name, value).FromJust();
  return v8::Intercepted::kYes;
}

}  // namespace


THREADED_TEST(NonMaskingInterceptorGlobalEvalRegression) {
  auto isolate = CcTest::isolate();
  v8::HandleScope handle_scope(isolate);
  LocalContext context;

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::NamedPropertyHandlerConfiguration conf(DatabaseGetter, DatabaseSetter);
  conf.flags = v8::PropertyHandlerFlags::kNonMasking;
  interceptor_templ->SetHandler(conf);

  context->Global()
      ->Set(context.local(), v8_str("intercepted_1"),
            interceptor_templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();
  context->Global()
      ->Set(context.local(), v8_str("intercepted_2"),
            interceptor_templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  // Init dbs.
  CompileRun(
      "intercepted_1.db = {};"
      "intercepted_2.db = {};");

  ExpectInt32(
      "var obj = intercepted_1;"
      "obj.x = 4;"
      "eval('obj.x');"
      "eval('obj.x');"
      "eval('obj.x');"
      "obj = intercepted_2;"
      "obj.x = 9;"
      "eval('obj.x');",
      9);
}

class ArrayLike {
 public:
  static v8::Intercepted Getter(
      uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& info) {
    auto& data = GetData<ArrayLike>(info)->data_;
    if (index < data.size()) {
      info.GetReturnValue().Set(data[index]);
      return v8::Intercepted::kYes;
    }
    return v8::Intercepted::kNo;
  }
  static v8::Intercepted Query(
      uint32_t index, const v8::PropertyCallbackInfo<v8::Integer>& info) {
    auto& data = GetData<ArrayLike>(info)->data_;
    if (index < data.size()) {
      return v8::Intercepted::kYes;
    }
    return v8::Intercepted::kNo;
  }

  static uint32_t IndexOf(Local<Value> value, uint32_t start_index,
                          uint32_t end_index, uint32_t* in_out_length,
                          const v8::PropertyCallbackInfo<void>& info) {
    auto& self = *GetData<ArrayLike>(info);
    self.index_of_count_++;

    Local<Context> context = info.GetIsolate()->GetCurrentContext();
    auto& data = self.data_;
    uint32_t length = static_cast<uint32_t>(data.size());
    *in_out_length = length;

    // This array contains only int32_t values.
    if (!value->IsInt32()) return i::kMaxUInt32;
    int32_t needle = value->ToInt32(context).ToLocalChecked()->Value();

    // Cap the indices, in case the builtin used arbitrary "length" value.
    start_index = std::min(start_index, length);
    end_index = std::min(end_index, length);

    auto begin = data.begin() + start_index;
    auto end = data.begin() + end_index;

    auto iter = std::find(begin, end, needle);
    if (iter == data.end()) return i::kMaxUInt32;
    return static_cast<uint32_t>(std::distance(data.begin(), iter));
  }

  static void LengthGetter(Local<Name> property,
                           const v8::PropertyCallbackInfo<Value>& info) {
    ArrayLike* self = GetData<ArrayLike>(info);

    info.GetReturnValue().Set(static_cast<uint32_t>(self->data_.size()));
  }

  std::vector<int32_t> data_;
  uint32_t index_of_count_ = 0;
};

THREADED_TEST(ArrayLikeInterceptor) {
  auto isolate = CcTest::isolate();
  i::v8_flags.fast_api_indexof = true;
  v8::HandleScope handle_scope(isolate);
  LocalContext context;

  const size_t N = 512;
  ArrayLike array_like;
  array_like.data_.reserve(N);
  for (size_t i = 0; i < N; i++) {
    array_like.data_.push_back(static_cast<int32_t>(i + 15));
  }

  auto interceptor_templ = v8::ObjectTemplate::New(isolate);
  v8::IndexedPropertyHandlerConfiguration conf(ArrayLike::Getter, nullptr,
                                               ArrayLike::Query);
  conf.index_of = ArrayLike::IndexOf;
  conf.data = MakeData(isolate, &array_like);
  interceptor_templ->SetHandler(conf);
  interceptor_templ->SetNativeDataProperty(
      v8_str("length"), ArrayLike::LengthGetter, nullptr, conf.data,
      v8::PropertyAttribute::ReadOnly, v8::SideEffectType::kHasNoSideEffect);

  context->Global()
      ->Set(context.local(), v8_str("interceptor"),
            interceptor_templ->NewInstance(context.local()).ToLocalChecked())
      .FromJust();

  ExpectInt32("interceptor.length", N);
  ExpectInt32("interceptor[153-15]", 153);
  ExpectInt32("Array.prototype.indexOf.call(interceptor, 153);", 153 - 15);

  CHECK_EQ(array_like.index_of_count_, 1);
}

namespace {

constexpr v8::ExternalPointerTypeTag kCallsTag = 15;

v8::Intercepted Regress42204611_Getter(
    Local<Name> name, const v8::PropertyCallbackInfo<v8::Value>& info) {
  std::vector<std::string>* calls = reinterpret_cast<std::vector<std::string>*>(
      info.Data().As<v8::External>()->Value(kCallsTag));

  calls->push_back("getter");
  return v8::Intercepted::kNo;
}
v8::Intercepted Regress42204611_Setter(
    Local<Name> name, Local<Value> value,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  std::vector<std::string>* calls = reinterpret_cast<std::vector<std::string>*>(
      info.Data().As<v8::External>()->Value(kCallsTag));

  calls->push_back("setter");
  return v8::Intercepted::kNo;
}
v8::Intercepted Regress42204611_Definer(
    Local<Name> name, const v8::PropertyDescriptor& descriptor,
    const v8::PropertyCallbackInfo<Boolean>& info) {
  std::vector<std::string>* calls = reinterpret_cast<std::vector<std::string>*>(
      info.Data().As<v8::External>()->Value(kCallsTag));

  calls->push_back("definer");
  return v8::Intercepted::kNo;
}

}  // namespace

// Regression test for crbug.com/42204611
THREADED_TEST(Regress42204611) {
  LocalContext env;
  v8::Isolate* isolate = env.isolate();
  v8::HandleScope handle_scope(isolate);

  std::vector<std::string> calls;
  Local<v8::External> calls_ext =
      v8::External::New(CcTest::isolate(), &calls, kCallsTag);

  v8::Local<v8::ObjectTemplate> object_template =
      v8::ObjectTemplate::New(isolate);
  object_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      Regress42204611_Getter, Regress42204611_Setter, nullptr, nullptr, nullptr,
      Regress42204611_Definer, calls_ext,
      static_cast<v8::PropertyHandlerFlags>(
          static_cast<int>(v8::PropertyHandlerFlags::kNonMasking) |
          static_cast<int>(v8::PropertyHandlerFlags::kHasNoSideEffect))));

  v8::Local<v8::Context> ctx =
      v8::Context::New(CcTest::isolate(), nullptr, object_template);

  {
    v8::Context::Scope scope(ctx);
    CompileRun(
        "Object.defineProperty(globalThis, 'key', {"
        "  value: 9, enumerable: true, configurable: true, writable: true"
        "})");
  }

  // We should intercept:
  //   1. The getter when getting the current property attributes,
  //   2. The definer when trying to intercept the define itself,
  //   3. The setter when applying the property descriptor.
  CHECK_EQ(calls, std::vector<std::string>({"getter", "definer", "setter"}));
}

namespace {

v8::Intercepted ThrowingQueryCallback(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  ApiTestFuzzer::Fuzz();
  auto throw_on_query = *GetData<bool>(info);
  if (!throw_on_query) return v8::Intercepted::kNo;
  auto isolate = info.GetIsolate();
  auto context = isolate->GetCurrentContext();
  if (v8_str("foo")->Equals(context, property).FromJust()) {
    isolate->ThrowException(v8_str("interceptor exception"));
    return v8::Intercepted::kYes;
  }
  return v8::Intercepted::kNo;
}

}  // namespace

THREADED_TEST(HasOwnPropertyWithInterceptorThrow) {
  LocalContext env;
  auto isolate = env.isolate();
  v8::HandleScope scope(isolate);
  auto fun_templ = v8::FunctionTemplate::New(isolate);
  auto instance_templ = fun_templ->InstanceTemplate();
  bool throw_on_query;
  auto data = MakeData(isolate, &throw_on_query);
  instance_templ->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, ThrowingQueryCallback, nullptr, nullptr, data,
      v8::PropertyHandlerFlags::kNone));
  auto function = fun_templ->GetFunction(env.local()).ToLocalChecked();
  auto obj = function->NewInstance(env.local()).ToLocalChecked();
  env->Global()->Set(env.local(), v8_str("obj"), obj).FromJust();

  // Turn off throw while installing property behind masking interceptor.
  throw_on_query = false;
  CHECK(obj->Set(env.local(), v8_str("foo"), v8_num(42)).FromJust());
  throw_on_query = true;

  // Check hasOwnProperty via api.
  {
    v8::TryCatch try_catch(isolate);
    v8::Maybe<bool> result = obj->HasOwnProperty(env.local(), v8_str("foo"));
    CHECK(result.IsNothing());
    CHECK(try_catch.HasCaught());
    CHECK(try_catch.Exception()->StrictEquals(v8_str("interceptor exception")));
  }
  // Check hasOwnProperty via js.
  {
    v8::TryCatch try_catch(isolate);
    v8::Local<v8::Value> result = CompileRun(R"(
      var r;
      try { r = obj.hasOwnProperty('foo'); } catch(e) { r = e; }
      r;
    )");
    CHECK(result->StrictEquals(v8_str("interceptor exception")));
  }
}

namespace {

// Query interceptor that, on the outer script's first lexical name, re-enters
// V8 to compile an inner script declaring c_0..c_9. That inner script grows the
// (full) ScriptContextTable into a fresh, larger table.
v8::Intercepted ReenterOnLexicalQuery(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  String::Utf8Value name(isolate, property);
  if (*name == nullptr || strncmp(*name, "v_", 2) != 0) {
    return v8::Intercepted::kNo;
  }
  std::string inner;
  for (int i = 0; i < 9; i++) inner += "let c_" + std::to_string(i) + " = 0;\n";
  inner += "let c_9 = 1234;\n";
  CompileRun(inner.c_str());
  return v8::Intercepted::kNo;
}

// Adds script contexts until the native context's ScriptContextTable is full,
// so that the next NewScriptContext grows (and reallocates) it.
void FillScriptContextTableToCapacity(Local<Context> context) {
  int i = 0;
  while (true) {
    i::DirectHandle<i::Context> i_context =
        v8::Utils::OpenDirectHandle(*context);
    i::Tagged<i::ScriptContextTable> table =
        i_context->native_context()->script_context_table();
    int length = table->length(v8::kAcquireLoad).value();
    int capacity = table->capacity().value();
    if (length > 0 && length == capacity) break;
    CompileRun(("let d_" + std::to_string(i++) + " = 0;").c_str());
  }
}

}  // namespace

// Regression test for a re-entrancy bug in NewScriptContext: a global-object
// property interceptor invoked from the name-clash check can grow and replace
// the ScriptContextTable. Extending the stale table captured before the loop
// resurrected the pre-interceptor table, dropping the re-entrant additions, so
// two contexts ended up at the same index and cached global ICs read/wrote out
// of bounds.
TEST(ScriptContextTableReentrancy) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  global_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, ReenterOnLexicalQuery, nullptr, nullptr, Local<Value>(),
      v8::PropertyHandlerFlags::kHasDontDeleteProperty));

  Local<Context> context = Context::New(isolate, nullptr, global_template);
  v8::Context::Scope context_scope(context);

  FillScriptContextTableToCapacity(context);

  // Compiling the outer script grabs a handle to the now-full table and, via
  // the clash check for `v_0`, re-enters V8 through the interceptor, which
  // declares an inner script (c_0..c_9) that grows and replaces the table.
  CompileRun("let v_0 = 0;");

  // Both the inner script's lexicals and the outer script's must survive: with
  // the stale-handle bug the outer Add dropped the inner script's context, so
  // c_9 was lost entirely.
  CHECK_EQ(1234, v8_run_int32("c_9"));
  CHECK_EQ(0, v8_run_int32("v_0"));
}

namespace {

// Query interceptor that, the first time it sees the outer script's second
// lexical name `b`, re-enters V8 to declare `a` -- a name the outer clash
// check has already passed by then.
v8::Intercepted ReenterAndDeclareEarlierName(
    Local<Name> property, const v8::PropertyCallbackInfo<v8::Integer>& info) {
  v8::Isolate* isolate = info.GetIsolate();
  String::Utf8Value name(isolate, property);
  if (*name == nullptr || strcmp(*name, "b") != 0) {
    return v8::Intercepted::kNo;
  }
  bool& declared_a = *GetData<bool>(info);
  if (!declared_a) {
    declared_a = true;
    CompileRun("let a = 1;");
  }
  return v8::Intercepted::kNo;
}

}  // namespace

// Regression test: the name-clash check in NewScriptContext runs before each
// name's interceptor, so a re-entrant declaration colliding with an
// already-checked name used to slip past clash detection and end up as a
// silent duplicate binding added by ScriptContextTable::Add. Compiling
// `let a; let b;` re-enters on `b` to declare `a`, which must turn the outer
// `a` into a redeclaration SyntaxError rather than a second binding.
TEST(ScriptContextTableReentrantClash) {
  v8::Isolate* isolate = CcTest::isolate();
  v8::HandleScope scope(isolate);

  bool declared_a = false;
  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  global_template->SetHandler(v8::NamedPropertyHandlerConfiguration(
      nullptr, nullptr, ReenterAndDeclareEarlierName, nullptr, nullptr,
      MakeData(isolate, &declared_a),
      v8::PropertyHandlerFlags::kHasDontDeleteProperty));

  Local<Context> context = Context::New(isolate, nullptr, global_template);
  v8::Context::Scope context_scope(context);

  v8::TryCatch try_catch(isolate);
  CompileRun("let a; let b;");
  CHECK(try_catch.HasCaught());
  CHECK(try_catch.Exception()->IsNativeError());
  String::Utf8Value message(isolate, try_catch.Message()->Get());
  CHECK_NOT_NULL(strstr(*message, "has already been declared"));

  // The re-entrant declaration committed before the outer script was rejected.
  CHECK_EQ(1, v8_run_int32("a"));
}
