// Copyright 2014 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 "src/objects/lookup.h"

#include <optional>

#include "src/common/globals.h"
#include "src/deoptimizer/deoptimizer.h"
#include "src/execution/isolate-inl.h"
#include "src/execution/isolate.h"
#include "src/execution/protectors-inl.h"
#include "src/init/bootstrapper.h"
#include "src/logging/counters.h"
#include "src/objects/arguments-inl.h"
#include "src/objects/dictionary-inl.h"
#include "src/objects/elements.h"
#include "src/objects/field-type.h"
#include "src/objects/hash-table-inl.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/js-shared-array-inl.h"
#include "src/objects/js-struct-inl.h"
#include "src/objects/map-updater.h"
#include "src/objects/ordered-hash-table.h"
#include "src/objects/property-details.h"
#include "src/objects/struct-inl.h"

namespace v8::internal {

template <bool is_element>
void LookupIterator::Start() {
  DisallowGarbageCollection no_gc;
  if (V8_LIKELY(IsJSReceiver(*lookup_start_object_))) {
    holder_ = Cast<JSReceiver>(lookup_start_object_);

  } else {
    // Strings are the only non-JSReceiver objects with properties (only
    // elements and 'length') directly on the wrapper. Inline the lookup
    // implementation here.
    if (IsString(*lookup_start_object_) &&
        ((!is_element && *name_ == ReadOnlyRoots(isolate_).length_string()) ||
         (is_element &&
          index_ < static_cast<size_t>(
                       Cast<String>(*lookup_start_object_)->length())))) {
      has_property_ = true;
      state_ = STRING_LOOKUP_START_OBJECT;
      return;
    }
    if (!check_prototype_chain()) {
      // This is an attempt to perform an own property lookup on a
      // non-JSReceiver that doesn't have any properties.
      DCHECK(!IsJSReceiver(*lookup_start_object_));
      has_property_ = false;
      state_ = NOT_FOUND;
      return;
    }
    holder_ = direct_handle(GetRootForNonJSReceiver(), isolate_);
  }

  // This is a regular JSReceiver lookup.
  has_property_ = false;
  state_ = NOT_FOUND;

  Tagged<JSReceiver> holder = *holder_;
  Tagged<Map> map = holder->map();

  state_ = LookupInHolder<is_element>(map, holder);
  if (IsFound()) return;

  NextInternal<is_element>(map, holder);
}

template void LookupIterator::Start<true>();
template void LookupIterator::Start<false>();

void LookupIterator::Next() {
  DCHECK_NE(JSPROXY, state_);
  DCHECK_NE(TRANSITION, state_);
  DCHECK_NE(NOT_FOUND, state_);
  DisallowGarbageCollection no_gc;
  has_property_ = false;

  Tagged<JSReceiver> holder = *holder_;
  Tagged<Map> map = holder->map();

  if (IsSpecialReceiverMap(map)) {
    state_ = IsElement() ? LookupInSpecialHolder<true>(map, holder)
                         : LookupInSpecialHolder<false>(map, holder);
    if (IsFound()) return;
  }

  IsElement() ? NextInternal<true>(map, holder)
              : NextInternal<false>(map, holder);
}

template <bool is_element>
void LookupIterator::NextInternal(Tagged<Map> orig_map,
                                  Tagged<JSReceiver> holder) {
  Tagged<Map> map = orig_map;
  do {
    Tagged<JSReceiver> maybe_holder = NextHolder(map);
    if (maybe_holder.is_null()) {
      switch (interceptor_state_) {
        case InterceptorState::kSkipNonMasking:
          // If we've found a non-masking interceptor, but we're not checking
          // the prototype chain, we need to do this now.
          if (!check_prototype_chain()) {
            interceptor_state_ = InterceptorState::kSkipNonMaskingOwnProperty;
            continue;
          }
          [[fallthrough]];
        // We're at the end of the chain, and haven't found anything, so
        // non-masking interceptors can be applied.
        case InterceptorState::kSkipNonMaskingOwnProperty:
          RestartLookupForNonMaskingInterceptors<is_element>();
          return;
        default:
          break;
      }
      state_ = NOT_FOUND;
      if (holder != *holder_) holder_ = direct_handle(holder, isolate_);
      return;
    }
    holder = maybe_holder;
    map = holder->map();
    state_ = LookupInHolder<is_element>(map, holder);
    if (interceptor_state_ == InterceptorState::kSkipNonMaskingOwnProperty &&
        IsFound()) {
      // In theory we also need to check JSPROXY but since the iterator doesn't
      // know which question to ask, we assume the lookup succeeds.
      switch (state_) {
        // https://webidl.spec.whatwg.org/#dfn-named-property-visibility says a
        // second interceptor on the prototype chain is invisible.
        case INTERCEPTOR:
          continue;
        case ACCESS_CHECK: {
          // If an access check fails, we assume the lookup succeeds.
          if (HasAccess()) {
            continue;
          }
          break;
        }
        default:
          break;
      }
      // We need fully reset state, since this is not a true hit.
      number_ = InternalIndex::NotFound();
      property_details_ = PropertyDetails::Empty();
      state_ = NOT_FOUND;
      if (holder != *holder_) holder_ = direct_handle(holder, isolate_);
      return;
    }
  } while (!IsFound());

  if (V8_UNLIKELY(is_element && IsJSArrayMap(orig_map))) {
    isolate_->CountUsage(v8::Isolate::kHoleyArrayReadthrough);
  }

  holder_ = direct_handle(holder, isolate_);
}

template <bool is_element>
void LookupIterator::RestartInternal(InterceptorState interceptor_state) {
  interceptor_state_ = interceptor_state;
  property_details_ = PropertyDetails::Empty();
  number_ = InternalIndex::NotFound();
  Start<is_element>();
}

template void LookupIterator::RestartInternal<true>(InterceptorState);
template void LookupIterator::RestartInternal<false>(InterceptorState);

void LookupIterator::RecheckTypedArrayBounds() {
  DCHECK(IsJSTypedArray(*holder_));
  DCHECK_EQ(state_, TYPED_ARRAY_INDEX_NOT_FOUND);

  if (!IsElement(*holder_)) {
    // This happens when the index is not an allowed index.
    return;
  }

  Tagged<JSObject> js_object = Cast<JSObject>(*holder_);
  ElementsAccessor* accessor = js_object->GetElementsAccessor();
  Tagged<FixedArrayBase> backing_store = js_object->elements();
  number_ =
      accessor->GetEntryForIndex(isolate_, js_object, backing_store, index_);

  if (number_.is_not_found()) {
    // The state is already TYPED_ARRAY_INDEX_NOT_FOUND.
    return;
  }
  property_details_ = accessor->GetDetails(js_object, number_);
#ifdef DEBUG
  Tagged<Map> map = holder_->map();
  DCHECK(!map->has_frozen_elements());
  DCHECK(!map->has_sealed_elements());
#endif  // DEBUG
  has_property_ = true;
  DCHECK_EQ(property_details_.kind(), v8::internal::PropertyKind::kData);
  state_ = DATA;
}

Tagged<JSReceiver> LookupIterator::GetRootForNonJSReceiver() const {
  // Own property lookup case is handled before calling this method.
  DCHECK(check_prototype_chain());

  Tagged<HeapObject> root =
      Object::GetPrototypeChainRootMap(*lookup_start_object_, isolate_)
          ->prototype();

  if (IsNull(root)) {
    isolate_->PushStackTraceAndDie(
        "null prototype chain root",
        reinterpret_cast<void*>((*lookup_start_object_).ptr()));
  }
  return Cast<JSReceiver>(root);
}

DirectHandle<Map> LookupIterator::GetReceiverMap() const {
  if (IsNumber(*receiver_)) return factory()->heap_number_map();
  return direct_handle(Cast<HeapObject>(receiver_)->map(), isolate_);
}

bool LookupIterator::HasAccess() const {
  // TRANSITION is true when being called from DefineNamedOwnIC.
  DCHECK(state_ == ACCESS_CHECK || state_ == TRANSITION);
  return isolate_->MayAccess(isolate_->native_context(), GetHolder<JSObject>());
}

template <bool is_element>
void LookupIterator::ReloadPropertyInformation() {
  state_ = BEFORE_PROPERTY;
  interceptor_state_ = InterceptorState::kUninitialized;
  state_ = LookupInHolder<is_element>(holder_->map(), *holder_);
  DCHECK(IsFound() || !holder_->HasFastProperties());
}

// static
void LookupIterator::InternalUpdateProtector(
    Isolate* isolate, DirectHandle<JSAny> receiver_generic,
    DirectHandle<Name> name, MaybeDirectHandle<Object> value,
    MaybeDirectHandle<Object> old_value) {
  if (isolate->bootstrapper()->IsActive()) return;
  if (!IsJSObject(*receiver_generic)) return;
  auto receiver = Cast<JSObject>(receiver_generic);

  ReadOnlyRoots roots(isolate);
  if (*name == roots.constructor_string()) {
    // Setting the constructor property could change an instance's @@species
    if (IsJSArray(*receiver)) {
      if (!Protectors::IsArraySpeciesLookupChainIntact(isolate)) return;
      isolate->CountUsage(
          v8::Isolate::UseCounterFeature::kArrayInstanceConstructorModified);
      Protectors::InvalidateArraySpeciesLookupChain(isolate);
      return;
    } else if (IsJSPromise(*receiver)) {
      if (!Protectors::IsPromiseSpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidatePromiseSpeciesLookupChain(isolate);
      return;
    } else if (IsJSRegExp(*receiver)) {
      if (!Protectors::IsRegExpSpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidateRegExpSpeciesLookupChain(isolate);
      return;
    } else if (IsJSTypedArray(*receiver)) {
      if (!Protectors::IsTypedArraySpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidateTypedArraySpeciesLookupChain(isolate);
      return;
    }
    if (receiver->map()->is_prototype_map()) {
      DisallowGarbageCollection no_gc;
      // Setting the constructor of any prototype with the @@species protector
      // (of any realm) also needs to invalidate the protector.
      if (isolate->IsInCreationContext(
              Cast<JSObject>(*receiver),
              Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
        if (!Protectors::IsArraySpeciesLookupChainIntact(isolate)) return;
        isolate->CountUsage(
            v8::Isolate::UseCounterFeature::kArrayPrototypeConstructorModified);
        Protectors::InvalidateArraySpeciesLookupChain(isolate);
      } else if (IsJSPromisePrototype(*receiver)) {
        if (!Protectors::IsPromiseSpeciesLookupChainIntact(isolate)) return;
        Protectors::InvalidatePromiseSpeciesLookupChain(isolate);
      } else if (IsJSRegExpPrototype(*receiver)) {
        if (!Protectors::IsRegExpSpeciesLookupChainIntact(isolate)) return;
        Protectors::InvalidateRegExpSpeciesLookupChain(isolate);
      } else if (IsJSTypedArrayPrototype(*receiver)) {
        if (!Protectors::IsTypedArraySpeciesLookupChainIntact(isolate)) return;
        Protectors::InvalidateTypedArraySpeciesLookupChain(isolate);
      }
    }
  } else if (*name == roots.next_string()) {
    if (IsJSArrayIterator(*receiver) || IsJSArrayIteratorPrototype(*receiver)) {
      // Setting the next property of %ArrayIteratorPrototype% also needs to
      // invalidate the array iterator protector.
      if (!Protectors::IsArrayIteratorLookupChainIntact(isolate)) return;
      Protectors::InvalidateArrayIteratorLookupChain(isolate);
    } else if (IsJSMapIterator(*receiver) ||
               IsJSMapIteratorPrototype(*receiver)) {
      if (!Protectors::IsMapIteratorLookupChainIntact(isolate)) return;
      Protectors::InvalidateMapIteratorLookupChain(isolate);
    } else if (IsJSSetIterator(*receiver) ||
               IsJSSetIteratorPrototype(*receiver)) {
      if (!Protectors::IsSetIteratorLookupChainIntact(isolate)) return;
      Protectors::InvalidateSetIteratorLookupChain(isolate);
    } else if (IsJSStringIterator(*receiver) ||
               IsJSStringIteratorPrototype(*receiver)) {
      // Setting the next property of %StringIteratorPrototype% invalidates the
      // string iterator protector.
      if (!Protectors::IsStringIteratorLookupChainIntact(isolate)) return;
      Protectors::InvalidateStringIteratorLookupChain(isolate);
    }
  } else if (*name == roots.species_symbol()) {
    // Setting the Symbol.species property of any Array, Promise or TypedArray
    // constructor invalidates the @@species protector
    if (IsJSArrayConstructor(*receiver)) {
      if (!Protectors::IsArraySpeciesLookupChainIntact(isolate)) return;
      isolate->CountUsage(
          v8::Isolate::UseCounterFeature::kArraySpeciesModified);
      Protectors::InvalidateArraySpeciesLookupChain(isolate);
    } else if (IsJSPromiseConstructor(*receiver)) {
      if (!Protectors::IsPromiseSpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidatePromiseSpeciesLookupChain(isolate);
    } else if (IsJSRegExpConstructor(*receiver)) {
      if (!Protectors::IsRegExpSpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidateRegExpSpeciesLookupChain(isolate);
    } else if (IsTypedArrayConstructor(*receiver)) {
      if (!Protectors::IsTypedArraySpeciesLookupChainIntact(isolate)) return;
      Protectors::InvalidateTypedArraySpeciesLookupChain(isolate);
    }
  } else if (*name == roots.is_concat_spreadable_symbol()) {
    if (!Protectors::IsIsConcatSpreadableLookupChainIntact(isolate)) return;
    Protectors::InvalidateIsConcatSpreadableLookupChain(isolate);
  } else if (*name == roots.iterator_symbol()) {
    if (IsJSArray(*receiver)) {
      if (!Protectors::IsArrayIteratorLookupChainIntact(isolate)) return;
      // When the value of ArrayIterator is canonical, its behavior remains
      // unchanged.
      // Attributes:
      // 1. `writable` and `configurable` are trivial.
      // 2. `enumerable`: The default JSArray iterator only iterates over array
      // elements in the range [0..length). Making ArrayIterator enumerable does
      // not affect this behavior.
      // See: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
      DirectHandle<Object> the_old_value, the_value;
      if (old_value.To(&the_old_value) && value.To(&the_value) &&
          the_old_value.is_identical_to(the_value)) {
        return;
      }

      Protectors::InvalidateArrayIteratorLookupChain(isolate);
    } else if (IsJSSet(*receiver) || IsJSSetIterator(*receiver) ||
               IsJSSetIteratorPrototype(*receiver) ||
               IsJSSetPrototype(*receiver)) {
      if (Protectors::IsSetIteratorLookupChainIntact(isolate)) {
        Protectors::InvalidateSetIteratorLookupChain(isolate);
      }
    } else if (IsJSMapIterator(*receiver) ||
               IsJSMapIteratorPrototype(*receiver)) {
      if (Protectors::IsMapIteratorLookupChainIntact(isolate)) {
        Protectors::InvalidateMapIteratorLookupChain(isolate);
      }
    } else if (IsJSIteratorPrototype(*receiver)) {
      if (Protectors::IsMapIteratorLookupChainIntact(isolate)) {
        Protectors::InvalidateMapIteratorLookupChain(isolate);
      }
      if (Protectors::IsSetIteratorLookupChainIntact(isolate)) {
        Protectors::InvalidateSetIteratorLookupChain(isolate);
      }
    } else if (isolate->IsInCreationContext(
                   *receiver, Context::INITIAL_STRING_PROTOTYPE_INDEX)) {
      // Setting the Symbol.iterator property of String.prototype invalidates
      // the string iterator protector. Symbol.iterator can also be set on a
      // String wrapper, but not on a primitive string. We only support
      // protector for primitive strings.
      if (!Protectors::IsStringIteratorLookupChainIntact(isolate)) return;
      Protectors::InvalidateStringIteratorLookupChain(isolate);
    }
  } else if (*name == roots.resolve_string()) {
    if (!Protectors::IsPromiseResolveLookupChainIntact(isolate)) return;
    // Setting the "resolve" property on any %Promise% intrinsic object
    // invalidates the Promise.resolve protector.
    if (IsJSPromiseConstructor(*receiver)) {
      Protectors::InvalidatePromiseResolveLookupChain(isolate);
    }
  } else if (*name == roots.then_string()) {
    if (!Protectors::IsPromiseThenLookupChainIntact(isolate)) return;
    // Setting the "then" property on any JSPromise instance or on the
    // initial %PromisePrototype% invalidates the Promise#then protector.
    // Also setting the "then" property on the initial %ObjectPrototype%
    // invalidates the Promise#then protector, since we use this protector
    // to guard the fast-path in AsyncGeneratorResolve, where we can skip
    // the ResolvePromise step and go directly to FulfillPromise if we
    // know that the Object.prototype doesn't contain a "then" method.
    if (IsJSPromise(*receiver) || IsJSObjectPrototype(*receiver) ||
        IsJSPromisePrototype(*receiver)) {
      Protectors::InvalidatePromiseThenLookupChain(isolate);
    }
  } else if (*name == roots.match_all_symbol() ||
             *name == roots.replace_symbol() || *name == roots.split_symbol()) {
    if (!Protectors::IsNumberStringNotRegexpLikeIntact(isolate)) return;
    // We need to protect the prototype chains of `Number.prototype` and
    // `String.prototype`: that `Symbol.{matchAll|replace|split}` is not added
    // as a property on any object on these prototype chains. We detect
    // `Number.prototype` and `String.prototype` by checking for a prototype
    // that is a JSPrimitiveWrapper. This is a safe approximation. Using
    // JSPrimitiveWrapper as prototype should be sufficiently rare.
    if (receiver->map()->is_prototype_map() &&
        (IsJSPrimitiveWrapper(*receiver) || IsJSObjectPrototype(*receiver))) {
      Protectors::InvalidateNumberStringNotRegexpLike(isolate);
    }
  } else if (*name == roots.to_primitive_symbol()) {
    if (!Protectors::IsStringWrapperToPrimitiveIntact(isolate)) return;
    if (isolate->IsInCreationContext(*receiver,
                                     Context::INITIAL_STRING_PROTOTYPE_INDEX) ||
        isolate->IsInCreationContext(*receiver,
                                     Context::INITIAL_OBJECT_PROTOTYPE_INDEX) ||
        IsStringWrapper(*receiver)) {
      Protectors::InvalidateStringWrapperToPrimitive(isolate);
    }
  } else if (*name == roots.valueOf_string()) {
    if (!Protectors::IsStringWrapperToPrimitiveIntact(isolate)) return;
    if (isolate->IsInCreationContext(*receiver,
                                     Context::INITIAL_STRING_PROTOTYPE_INDEX) ||
        IsStringWrapper(*receiver)) {
      Protectors::InvalidateStringWrapperToPrimitive(isolate);
    }
  }
}

void LookupIterator::PrepareForDataProperty(DirectHandle<Object> value) {
  DCHECK(state_ == DATA || state_ == ACCESSOR);
  DCHECK(HolderIsReceiverOrHiddenPrototype());
  DCHECK(!IsWasmObject(*receiver_));

  DirectHandle<JSReceiver> holder = GetHolder<JSReceiver>();
  // We are not interested in tracking constness of a JSProxy's direct
  // properties.
  DCHECK_IMPLIES(IsJSProxy(*holder), name()->IsAnyPrivate());
  if (IsJSProxy(*holder)) return;

  if (IsElement(*holder)) {
    DirectHandle<JSObject> holder_obj = Cast<JSObject>(holder);
    ElementsKind kind = holder_obj->GetElementsKind();
    ElementsKind to = Object::OptimalElementsKind(*value);
    if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to);
    to = GetMoreGeneralElementsKind(kind, to);

    if (kind != to) {
      JSObject::TransitionElementsKind(isolate(), holder_obj, to);
    }

    // Copy the backing store if it is copy-on-write.
    if (IsSmiOrObjectElementsKind(to) || IsSealedElementsKind(to) ||
        IsNonextensibleElementsKind(to)) {
      JSObject::EnsureWritableFastElements(isolate(), holder_obj);
    }
    return;
  }

  if (IsJSGlobalObject(*holder)) {
    DirectHandle<GlobalDictionary> dictionary(
        Cast<JSGlobalObject>(*holder)->global_dictionary(kAcquireLoad),
        isolate());
    DirectHandle<PropertyCell> cell(dictionary->CellAt(dictionary_entry()),
                                    isolate());
    property_details_ = cell->property_details();
    PropertyCell::PrepareForAndSetValue(
        isolate(), dictionary, dictionary_entry(), value, property_details_);
    return;
  }

  PropertyConstness new_constness = PropertyConstness::kConst;
  if (constness() == PropertyConstness::kConst) {
    DCHECK_EQ(PropertyKind::kData, property_details_.kind());
    // Check that current value matches new value otherwise we should make
    // the property mutable.
    if (holder->HasFastProperties()) {
      if (property_details_.representation().IsDouble() && IsNumber(*value)) {
        double v = Object::NumberValue(*value);
        if (std::isnan(v)) {
          value = isolate_->factory()->nan_value();
        }
      }
      if (!CanStayConst(*value)) new_constness = PropertyConstness::kMutable;
    } else if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL) {
      if (!DictCanStayConst(*value)) {
        property_details_ =
            property_details_.CopyWithConstness(PropertyConstness::kMutable);

        // We won't reach the map updating code after Map::Update below, because
        // that's only for the case that the existing map is a fast mode map.
        // Therefore, we need to perform the necessary updates to the property
        // details and the prototype validity cell directly.
        if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
          Tagged<SwissNameDictionary> dict =
              holder->property_dictionary_swiss();
          dict->DetailsAtPut(dictionary_entry(), property_details_);
        } else {
          Tagged<NameDictionary> dict = holder->property_dictionary();
          dict->DetailsAtPut(dictionary_entry(), property_details_);
        }

        Tagged<Map> old_map = holder->map();
        if (old_map->is_prototype_map()) {
          JSObject::InvalidatePrototypeChains(old_map);
        }
      }
      return;
    }
  }

  if (!holder->HasFastProperties()) return;

  auto holder_obj = Cast<JSObject>(holder);
  Handle<Map> old_map(holder->map(), isolate_);

  DirectHandle<Map> new_map = Map::Update(isolate_, old_map);
  if (!new_map->is_dictionary_map()) {  // fast -> fast
    new_map = Map::PrepareForDataProperty(
        isolate(), new_map, descriptor_number(), new_constness, value);

    if (old_map.is_identical_to(new_map)) {
      // Update the property details if the representation was None.
      if (constness() != new_constness || representation().IsNone()) {
        property_details_ =
            new_map->instance_descriptors()->GetDetails(descriptor_number());
      }
      return;
    }
  }
  // We should only get here if the new_map is different from the old map,
  // otherwise we would have fallen through to the is_identical_to check above.
  DCHECK_NE(*old_map, *new_map);

  JSObject::MigrateToMap(isolate_, holder_obj, new_map);
  ReloadPropertyInformation<false>();

  // If we transitioned from fast to slow and the property changed from kConst
  // to kMutable, then this change in the constness is indicated by neither the
  // old or the new map. We need to update the constness ourselves.
  DCHECK(!old_map->is_dictionary_map());
  if (V8_DICT_PROPERTY_CONST_TRACKING_BOOL && new_map->is_dictionary_map() &&
      new_constness == PropertyConstness::kMutable) {  // fast -> slow
    property_details_ =
        property_details_.CopyWithConstness(PropertyConstness::kMutable);

    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      Tagged<SwissNameDictionary> dict =
          holder_obj->property_dictionary_swiss();
      dict->DetailsAtPut(dictionary_entry(), property_details_);
    } else {
      Tagged<NameDictionary> dict = holder_obj->property_dictionary();
      dict->DetailsAtPut(dictionary_entry(), property_details_);
    }

    DCHECK_IMPLIES(new_map->is_prototype_map(),
                   !new_map->IsPrototypeValidityCellValid());
  }
}

void LookupIterator::ReconfigureDataProperty(DirectHandle<Object> value,
                                             PropertyAttributes attributes) {
  DCHECK(state_ == DATA || state_ == ACCESSOR);
  DCHECK(HolderIsReceiverOrHiddenPrototype());

  DirectHandle<JSReceiver> holder = GetHolder<JSReceiver>();
  if (V8_UNLIKELY(IsWasmObject(*holder))) UNREACHABLE();

  // Property details can never change for private properties.
  if (IsJSProxy(*holder)) {
    DCHECK(name()->IsAnyPrivate());
    return;
  }

  DirectHandle<JSObject> holder_obj = Cast<JSObject>(holder);
  if (IsElement(*holder)) {
    DCHECK(!holder_obj->HasTypedArrayOrRabGsabTypedArrayElements());
    DCHECK(attributes != NONE || !holder_obj->HasFastElements());
    DirectHandle<FixedArrayBase> elements(holder_obj->elements(), isolate());
    holder_obj->GetElementsAccessor()->Reconfigure(
        isolate_, holder_obj, elements, number_, value, attributes);
    ReloadPropertyInformation<true>();
  } else if (holder_obj->HasFastProperties()) {
    DirectHandle<Map> old_map(holder_obj->map(), isolate_);
    // Force mutable to avoid changing constant value by reconfiguring
    // kData -> kAccessor -> kData.
    DirectHandle<Map> new_map = MapUpdater::ReconfigureExistingProperty(
        isolate_, old_map, descriptor_number(), i::PropertyKind::kData,
        attributes, PropertyConstness::kMutable);
    if (!new_map->is_dictionary_map()) {
      // Make sure that the data property has a compatible representation.
      // TODO(leszeks): Do this as part of ReconfigureExistingProperty.
      new_map =
          Map::PrepareForDataProperty(isolate(), new_map, descriptor_number(),
                                      PropertyConstness::kMutable, value);
    }
    JSObject::MigrateToMap(isolate_, holder_obj, new_map);
    ReloadPropertyInformation<false>();
  }

  if (!IsElement(*holder) && !holder_obj->HasFastProperties()) {
    if (holder_obj->map()->is_prototype_map() &&
        (((property_details_.attributes() & READ_ONLY) == 0 &&
          (attributes & READ_ONLY) != 0) ||
         (property_details_.attributes() & DONT_ENUM) !=
             (attributes & DONT_ENUM))) {
      // Invalidate prototype validity cell when a property is reconfigured
      // from writable to read-only as this may invalidate transitioning store
      // IC handlers.
      // Invalidate prototype validity cell when a property changes
      // enumerability to clear the prototype chain enum cache.
      JSObject::InvalidatePrototypeChains(holder->map());
    }
    if (IsJSGlobalObject(*holder_obj)) {
      PropertyDetails details(PropertyKind::kData, attributes,
                              PropertyCellType::kMutable);
      DirectHandle<GlobalDictionary> dictionary(
          Cast<JSGlobalObject>(*holder_obj)->global_dictionary(kAcquireLoad),
          isolate());

      DirectHandle<PropertyCell> cell = PropertyCell::PrepareForAndSetValue(
          isolate(), dictionary, dictionary_entry(), value, details);
      property_details_ = cell->property_details();
      DCHECK_EQ(cell->value(), *value);
    } else {
      PropertyDetails details(PropertyKind::kData, attributes,
                              PropertyConstness::kMutable);
      if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
        DirectHandle<SwissNameDictionary> dictionary(
            holder_obj->property_dictionary_swiss(), isolate());
        dictionary->ValueAtPut(dictionary_entry(), *value);
        dictionary->DetailsAtPut(dictionary_entry(), details);
        DCHECK_EQ(details.AsSmi(),
                  dictionary->DetailsAt(dictionary_entry()).AsSmi());
        property_details_ = details;
      } else {
        DirectHandle<NameDictionary> dictionary(
            holder_obj->property_dictionary(), isolate());
        PropertyDetails original_details =
            dictionary->DetailsAt(dictionary_entry());
        int enumeration_index = original_details.dictionary_index();
        DCHECK_GT(enumeration_index, 0);
        details = details.set_index(enumeration_index);
        dictionary->SetEntry(dictionary_entry(), *name(), *value, details);
        property_details_ = details;
      }
    }
    state_ = DATA;
  }

  WriteDataValue(value, true);

#if VERIFY_HEAP
  if (v8_flags.verify_heap) {
    holder->HeapObjectVerify(isolate());
  }
#endif
}

// Can only be called when the receiver is a JSObject, or when the name is a
// private field, otherwise JSProxy has to be handled via a trap.
// Adding properties to primitive values is not observable.
void LookupIterator::PrepareTransitionToDataProperty(
    DirectHandle<JSTransitionableReceiver> receiver, DirectHandle<Object> value,
    PropertyAttributes attributes, StoreOrigin store_origin) {
  DCHECK_IMPLIES(IsJSProxy(*receiver), name()->IsAnyPrivate());
  DCHECK_IMPLIES(!receiver.is_identical_to(GetStoreTarget<JSReceiver>()),
                 name()->IsAnyPrivateName());
  DCHECK(!IsAlwaysSharedSpaceJSObject(*receiver));
  if (state_ == TRANSITION) return;

  if (!IsElement() && name()->IsAnyPrivate()) {
    attributes = static_cast<PropertyAttributes>(attributes | DONT_ENUM);
  }

  DCHECK(state_ != LookupIterator::ACCESSOR || IsAccessorInfo(*GetAccessors()));
  DCHECK_NE(TYPED_ARRAY_INDEX_NOT_FOUND, state_);
  DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype());

  DirectHandle<Map> map(receiver->map(), isolate_);

  // Migrate to the newest map before storing the property.
  map = Map::Update(isolate(), map);

  // Dictionary maps can always have additional data properties.
  if (map->is_dictionary_map()) {
    state_ = TRANSITION;
    if (IsJSGlobalObjectMap(*map)) {
      DCHECK(!IsTheHole(*value));
      // Don't set enumeration index (it will be set during value store).
      property_details_ =
          PropertyDetails(PropertyKind::kData, attributes,
                          PropertyCell::InitialType(isolate_, *value));
      transition_ = isolate_->factory()->NewPropertyCell(
          name(), property_details_, value);
      has_property_ = true;
    } else {
      // Don't set enumeration index (it will be set during value store).
      property_details_ =
          PropertyDetails(PropertyKind::kData, attributes,
                          PropertyDetails::kConstIfDictConstnessTracking);
      transition_ = map;
    }
    return;
  }

  DirectHandle<Map> transition =
      Map::TransitionToDataProperty(isolate_, map, name_, value, attributes,
                                    PropertyConstness::kConst, store_origin);
  state_ = TRANSITION;
  transition_ = transition;

  if (transition->is_dictionary_map()) {
    DCHECK(!IsJSGlobalObjectMap(*transition));
    // Don't set enumeration index (it will be set during value store).
    property_details_ =
        PropertyDetails(PropertyKind::kData, attributes,
                        PropertyDetails::kConstIfDictConstnessTracking);
  } else {
    property_details_ = transition->GetLastDescriptorDetails();
    has_property_ = true;
  }
}

Maybe<bool> LookupIterator::ApplyTransitionToDataProperty(
    DirectHandle<JSTransitionableReceiver> receiver,
    Maybe<ShouldThrow> should_throw) {
  DCHECK_EQ(TRANSITION, state_);

  DCHECK_IMPLIES(!receiver.is_identical_to(GetStoreTarget<JSReceiver>()),
                 name()->IsAnyPrivateName());
  holder_ = receiver;
  if (IsJSGlobalObject(*receiver)) {
    JSObject::InvalidatePrototypeChains(receiver->map());

    // Install a property cell.
    auto global = Cast<JSGlobalObject>(receiver);
    DCHECK(!global->HasFastProperties());
    DirectHandle<GlobalDictionary> dictionary(
        global->global_dictionary(kAcquireLoad), isolate_);

    ASSIGN_RETURN_ON_EXCEPTION_VALUE(
        isolate_, dictionary,
        GlobalDictionary::Add(isolate_, dictionary, name(), transition_cell(),
                              property_details_, &number_),
        Nothing<bool>());
    global->set_global_dictionary(*dictionary, kReleaseStore);

    // Reload details containing proper enumeration index value.
    property_details_ = transition_cell()->property_details();
    has_property_ = true;
    state_ = DATA;
    return Just(true);
  }
  DirectHandle<Map> transition = transition_map();
  bool simple_transition = transition->GetBackPointer() == receiver->map();

  if (configuration_ == DEFAULT && !transition->is_dictionary_map() &&
      !transition->is_prototype_map() &&
      !transition->IsPrototypeValidityCellValid()) {
    // Only LookupIterator instances with DEFAULT (full prototype chain)
    // configuration can produce valid transition handler maps.
    // Note that it doesn't make sense to prepare a fast property transition
    // handler for prototype maps because they are not going to be reused
    // anyway.
    DirectHandle<UnionOf<Smi, Cell>> validity_cell =
        Map::GetOrCreatePrototypeChainValidityCell(transition, isolate());
    transition->set_prototype_validity_cell(*validity_cell, kRelaxedStore);
  }

  if (!IsJSProxy(*receiver)) {
    static_assert(std::is_same_v<JSTransitionableReceiver::Without<JSProxy>,
                                 Union<JSObject>>);
    JSObject::MigrateToMap(isolate_, Cast<JSObject>(receiver), transition);
  }

  if (simple_transition) {
    number_ = transition->LastAdded();
    property_details_ = transition->GetLastDescriptorDetails();
    state_ = DATA;
  } else if (receiver->map()->is_dictionary_map()) {
    if (receiver->map()->is_prototype_map() && IsJSObject(*receiver)) {
      JSObject::InvalidatePrototypeChains(receiver->map());
    }
    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      DirectHandle<SwissNameDictionary> dictionary(
          Cast<JSReceiver>(receiver)->property_dictionary_swiss(), isolate_);

      dictionary =
          SwissNameDictionary::Add(isolate(), dictionary, name(),
                                   isolate_->factory()->uninitialized_value(),
                                   property_details_, &number_);
      Cast<JSReceiver>(receiver)->SetProperties(*dictionary);
    } else {
      DirectHandle<NameDictionary> dictionary(
          Cast<JSReceiver>(receiver)->property_dictionary(), isolate_);

      ASSIGN_RETURN_ON_EXCEPTION_VALUE(
          isolate_, dictionary,
          NameDictionary::Add(isolate(), dictionary, name(),
                              isolate_->factory()->uninitialized_value(),
                              property_details_, &number_),
          Nothing<bool>());
      Cast<JSReceiver>(receiver)->SetProperties(*dictionary);
      // TODO(pthier): Add flags to swiss dictionaries.
      if (name()->IsInteresting(isolate())) {
        dictionary->set_may_have_interesting_properties(true);
      }
      // Reload details containing proper enumeration index value.
      property_details_ = dictionary->DetailsAt(number_);
    }
    has_property_ = true;
    state_ = DATA;

  } else {
    ReloadPropertyInformation<false>();
  }
  return Just(true);
}

void LookupIterator::Delete() {
  DirectHandle<JSReceiver> holder = Cast<JSReceiver>(holder_);
  if (IsElement(*holder)) {
    DirectHandle<JSObject> object = Cast<JSObject>(holder);
    ElementsAccessor* accessor = object->GetElementsAccessor();
    accessor->Delete(isolate_, object, number_);
  } else {
    DCHECK(!name()->IsAnyPrivateName());
    bool is_prototype_map = holder->map()->is_prototype_map();
    RCS_SCOPE(isolate_,
              is_prototype_map
                  ? RuntimeCallCounterId::kPrototypeObject_DeleteProperty
                  : RuntimeCallCounterId::kObject_DeleteProperty);

    PropertyNormalizationMode mode =
        is_prototype_map ? KEEP_INOBJECT_PROPERTIES : CLEAR_INOBJECT_PROPERTIES;

    if (holder->HasFastProperties()) {
      JSObject::NormalizeProperties(isolate_, Cast<JSObject>(holder), mode, 0,
                                    "DeletingProperty");
      ReloadPropertyInformation<false>();
    }
    JSReceiver::DeleteNormalizedProperty(holder, dictionary_entry());
    if (IsJSObject(*holder)) {
      JSObject::ReoptimizeIfPrototype(Cast<JSObject>(holder));
    }
  }
  state_ = NOT_FOUND;
}

Maybe<bool> LookupIterator::TransitionToAccessorProperty(
    DirectHandle<Object> getter, DirectHandle<Object> setter,
    PropertyAttributes attributes) {
  DCHECK(!IsNull(*getter) || !IsNull(*setter));
  // Can only be called when the receiver is a JSObject. JSProxy has to be
  // handled via a trap. Adding properties to primitive values is not
  // observable.
  DirectHandle<JSObject> receiver = GetStoreTarget<JSObject>();
  if (!IsElement() && name()->IsAnyPrivate()) {
    attributes = static_cast<PropertyAttributes>(attributes | DONT_ENUM);
  }

  if (!IsElement(*receiver) && !receiver->map()->is_dictionary_map()) {
    DirectHandle<Map> old_map(receiver->map(), isolate_);

    if (!holder_.is_identical_to(receiver)) {
      holder_ = receiver;
      state_ = NOT_FOUND;
    } else if (state_ == INTERCEPTOR) {
      LookupInRegularHolder<false>(*old_map, *holder_);
    }
    // The case of IsFound() && number_.is_not_found() can occur for
    // interceptors.
    DCHECK_IMPLIES(!IsFound(), number_.is_not_found());

    DirectHandle<Map> new_map = Map::TransitionToAccessorProperty(
        isolate_, old_map, name_, number_, getter, setter, attributes);
    bool simple_transition = new_map->GetBackPointer() == receiver->map();
    JSObject::MigrateToMap(isolate_, receiver, new_map);

    if (simple_transition) {
      number_ = new_map->LastAdded();
      property_details_ = new_map->GetLastDescriptorDetails();
      state_ = ACCESSOR;
      return Just(true);
    }

    ReloadPropertyInformation<false>();
    if (!new_map->is_dictionary_map()) return Just(true);
  }

  DirectHandle<AccessorPair> pair;
  if (state() == ACCESSOR && IsAccessorPair(*GetAccessors())) {
    pair = Cast<AccessorPair>(GetAccessors());
    // If the component and attributes are identical, nothing has to be done.
    if (pair->Equals(*getter, *setter)) {
      if (property_details().attributes() == attributes) {
        if (!IsElement(*receiver)) JSObject::ReoptimizeIfPrototype(receiver);
        return Just(true);
      }
    } else {
      pair = AccessorPair::Copy(isolate(), pair);
      pair->SetComponents(*getter, *setter);
    }
  } else {
    pair = factory()->NewAccessorPair();
    pair->SetComponents(*getter, *setter);
  }

  Maybe<bool> result = TransitionToAccessorPair(pair, attributes);

#if VERIFY_HEAP
  if (v8_flags.verify_heap || result.IsNothing()) {
    receiver->JSObjectVerify(isolate());
  }
#endif
  return result;
}

Maybe<bool> LookupIterator::TransitionToAccessorPair(
    DirectHandle<Object> pair, PropertyAttributes attributes) {
  DirectHandle<JSObject> receiver = GetStoreTarget<JSObject>();
  holder_ = receiver;

  PropertyDetails details(PropertyKind::kAccessor, attributes,
                          PropertyCellType::kMutable);

  if (IsElement(*receiver)) {
    // TODO(verwaest): Move code into the element accessor.
    isolate_->CountUsage(v8::Isolate::kIndexAccessor);
    DirectHandle<NumberDictionary> dictionary =
        JSObject::NormalizeElements(isolate_, receiver);

    dictionary = NumberDictionary::Set(isolate_, dictionary, array_index(),
                                       pair, receiver, details);
    receiver->RequireSlowElements(*dictionary);

    if (receiver->HasSlowArgumentsElements()) {
      Tagged<SloppyArgumentsElements> parameter_map =
          Cast<SloppyArgumentsElements>(receiver->elements());
      const uint32_t length = parameter_map->ulength().value();
      if (number_.is_found() && number_.as_uint32() < length) {
        parameter_map->set_mapped_entries(
            number_.as_int(), ReadOnlyRoots(isolate_).the_hole_value());
      }
      parameter_map->set_arguments(*dictionary);
    } else {
      receiver->set_elements(*dictionary);
    }

    ReloadPropertyInformation<true>();
  } else {
    PropertyNormalizationMode mode = CLEAR_INOBJECT_PROPERTIES;
    if (receiver->map()->is_prototype_map()) {
      JSObject::InvalidatePrototypeChains(receiver->map());
      mode = KEEP_INOBJECT_PROPERTIES;
    }

    // Normalize object to make this operation simple.
    JSObject::NormalizeProperties(isolate_, receiver, mode, 0,
                                  "TransitionToAccessorPair");

    MAYBE_RETURN(
        JSObject::SetNormalizedProperty(receiver, name_, pair, details),
        Nothing<bool>());
    JSObject::ReoptimizeIfPrototype(receiver);

    ReloadPropertyInformation<false>();
  }
  return Just(true);
}

Tagged<JSObject> LookupIterator::GetHolderForApi() const {
  DCHECK(state_ == INTERCEPTOR || state_ == ACCESSOR || state_ == ACCESS_CHECK);
  return i::GetHolderForApi(Cast<JSObject>(*holder_));
}

bool LookupIterator::HolderIsReceiver() const {
  DCHECK_NE(state_, STRING_LOOKUP_START_OBJECT);
  DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY ||
         state_ == TYPED_ARRAY_INDEX_NOT_FOUND || state_ == MODULE_NAMESPACE);
  // Optimization that only works if configuration_ is not mutable.
  if (!check_prototype_chain()) return true;
  return *receiver_ == *holder_;
}

bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const {
  DCHECK_NE(state_, STRING_LOOKUP_START_OBJECT);
  DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY ||
         state_ == MODULE_NAMESPACE);
  // Optimization that only works if configuration_ is not mutable.
  if (!check_prototype_chain()) return true;
  if (*receiver_ == *holder_) return true;
  if (!IsJSGlobalProxy(*receiver_)) return false;
  return Cast<JSGlobalProxy>(receiver_)->map()->prototype() == *holder_;
}

DirectHandle<Object> LookupIterator::FetchValue(
    AllowAllocation allow_allocation) const {
  Tagged<Object> result;
  DCHECK_NE(state_, STRING_LOOKUP_START_OBJECT);
  DCHECK(!IsWasmObject(*holder_));
  if (IsElement(*holder_)) {
    DirectHandle<JSObject> holder = GetHolder<JSObject>();
    ElementsAccessor* accessor = holder->GetElementsAccessor();
    return accessor->Get(isolate_, holder, number_);
  } else if (IsJSGlobalObject(*holder_)) {
    DirectHandle<JSGlobalObject> holder = GetHolder<JSGlobalObject>();
    result =
        holder->global_dictionary(kAcquireLoad)->ValueAt(dictionary_entry());
  } else if (!holder_->HasFastProperties()) {
    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      result =
          holder_->property_dictionary_swiss()->ValueAt(dictionary_entry());
    } else {
      result = holder_->property_dictionary()->ValueAt(dictionary_entry());
    }
  } else if (property_details_.location() == PropertyLocation::kField) {
    DCHECK_EQ(PropertyKind::kData, property_details_.kind());
    DirectHandle<JSObject> holder = GetHolder<JSObject>();
    FieldIndex field_index =
        FieldIndex::ForDetails(holder->map(), property_details_);
    if (allow_allocation == AllowAllocation::kNo && field_index.is_double()) {
      return isolate_->factory()->undefined_value();
    }
    return JSObject::FastPropertyAt(
        isolate_, holder, property_details_.representation(), field_index);
  } else {
    result = holder_->map()->instance_descriptors()->GetStrongValue(
        descriptor_number());
  }
  return direct_handle(result, isolate_);
}

bool LookupIterator::CanStayConst(Tagged<Object> value) const {
  DCHECK(!holder_.is_null());
  DCHECK(!IsElement(*holder_));
  DCHECK(holder_->HasFastProperties());
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyConstness::kConst, property_details_.constness());
  if (IsUninitializedHole(value)) {
    // Storing uninitialized value means that we are preparing for a computed
    // property value in an object literal. The initializing store will follow
    // and it will properly update constness based on the actual value.
    return true;
  }
  DirectHandle<JSObject> holder = GetHolder<JSObject>();
  FieldIndex field_index =
      FieldIndex::ForDetails(holder->map(), property_details_);
  if (property_details_.representation().IsDouble()) {
    if (!IsNumber(value)) return false;
    // Attempt to store HeapNumber with the hole NaN pattern should have
    // already generalized field constness to kMutable.
    DCHECK_IMPLIES(!IsSmi(value), !Cast<HeapNumber>(value)->is_the_hole());
    Tagged<Object> current_value = holder->RawFastPropertyAt(field_index);
    DCHECK(IsHeapNumber(current_value));
    // Only allow initializing stores to double to stay constant.
    return Cast<HeapNumber>(current_value)->is_the_hole();
  }

  Tagged<Object> current_value = holder->RawFastPropertyAt(field_index);
  return IsUninitializedHole(current_value);
}

bool LookupIterator::DictCanStayConst(Tagged<Object> value) const {
  DCHECK(!holder_.is_null());
  DCHECK(!IsElement(*holder_));
  DCHECK(!holder_->HasFastProperties());
  DCHECK(!IsJSGlobalObject(*holder_));
  DCHECK(!IsJSProxy(*holder_));
  DCHECK_EQ(PropertyConstness::kConst, property_details_.constness());

  DisallowHeapAllocation no_gc;

  if (IsUninitializedHole(value)) {
    // Storing uninitialized value means that we are preparing for a computed
    // property value in an object literal. The initializing store will follow
    // and it will properly update constness based on the actual value.
    return true;
  }
  DirectHandle<JSReceiver> holder = GetHolder<JSReceiver>();
  Tagged<Object> current_value;
  if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
    Tagged<SwissNameDictionary> dict = holder->property_dictionary_swiss();
    current_value = dict->ValueAt(dictionary_entry());
  } else {
    Tagged<NameDictionary> dict = holder->property_dictionary();
    current_value = dict->ValueAt(dictionary_entry());
  }

  return IsUninitializedHole(current_value);
}

InternalIndex LookupIterator::GetFieldDescriptorIndex() const {
  DCHECK(has_property_);
  DCHECK(holder_->HasFastProperties());
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyKind::kData, property_details_.kind());
  return descriptor_number();
}

InternalIndex LookupIterator::GetAccessorIndex() const {
  DCHECK(has_property_);
  DCHECK(holder_->HasFastProperties());
  DCHECK_EQ(PropertyLocation::kDescriptor, property_details_.location());
  DCHECK_EQ(PropertyKind::kAccessor, property_details_.kind());
  return descriptor_number();
}

FieldIndex LookupIterator::GetFieldIndex() const {
  DCHECK(has_property_);
  DCHECK(!holder_.is_null());
  DCHECK(holder_->HasFastProperties());
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK(!IsElement(*holder_));
  return FieldIndex::ForDetails(holder_->map(), property_details_);
}

DirectHandle<PropertyCell> LookupIterator::GetPropertyCell() const {
  DCHECK(!holder_.is_null());
  DCHECK(!IsElement(*holder_));
  DirectHandle<JSGlobalObject> holder = GetHolder<JSGlobalObject>();
  return direct_handle(
      holder->global_dictionary(kAcquireLoad)->CellAt(dictionary_entry()),
      isolate_);
}

DirectHandle<Object> LookupIterator::GetAccessors() const {
  DCHECK_EQ(ACCESSOR, state_);
  return FetchValue();
}

Handle<Object> LookupIterator::GetStringPropertyValue(
    AllowAllocation allow_allocation) const {
  DCHECK_EQ(state_, STRING_LOOKUP_START_OBJECT);
  if (IsElement()) {
    DirectHandle<String> string = Cast<String>(lookup_start_object_);
    // The state guarantees that it's an access within bounds.
    DCHECK_LT(index_, string->length());
    return isolate_->factory()->LookupSingleCharacterStringFromCode(
        String::Flatten(isolate_, string)->Get(static_cast<uint32_t>(index_)));
  }
  // The state guarantees that it's a lookup of "length" property.
  DCHECK_EQ(*name_, ReadOnlyRoots(isolate_).length_string());
  uint32_t length = Cast<String>(*lookup_start_object_)->length();
  static_assert(String::kMaxLength < Smi::kMaxValue);
  return handle(Smi::FromInt(length), isolate_);
}

Handle<Object> LookupIterator::GetDataValue(
    AllowAllocation allow_allocation) const {
  DCHECK_EQ(DATA, state_);
  return indirect_handle(FetchValue(allow_allocation), isolate_);
}

DirectHandle<Object> LookupIterator::GetDataValue(SeqCstAccessTag tag) const {
  DCHECK_EQ(DATA, state_);
  // Currently only shared structs and arrays support sequentially consistent
  // access.
  DCHECK(IsJSSharedStruct(*holder_) || IsJSSharedArray(*holder_));
  DirectHandle<JSObject> holder = GetHolder<JSObject>();
  if (IsElement(*holder)) {
    ElementsAccessor* accessor = holder->GetElementsAccessor();
    return accessor->GetAtomic(isolate_, holder, number_, kSeqCstAccess);
  }
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyKind::kData, property_details_.kind());
  FieldIndex field_index =
      FieldIndex::ForDetails(holder->map(), property_details_);
  return JSObject::FastPropertyAt(
      isolate_, holder, property_details_.representation(), field_index, tag);
}

void LookupIterator::WriteDataValue(DirectHandle<Object> value,
                                    bool initializing_store) {
  DCHECK_EQ(DATA, state_);
  // WriteDataValueToWasmObject() must be used instead for writing to
  // WasmObjects.
  DCHECK(!IsWasmObject(*holder_));
  DCHECK_IMPLIES(IsJSSharedStruct(*holder_), IsShared(*value));

  DirectHandle<JSReceiver> holder = GetHolder<JSReceiver>();
  if (IsElement(*holder)) {
    DirectHandle<JSObject> object = Cast<JSObject>(holder);
    ElementsAccessor* accessor = object->GetElementsAccessor();
    accessor->Set(object, number_, *value);
  } else if (holder->HasFastProperties()) {
    DCHECK(IsJSObject(*holder));
    if (property_details_.location() == PropertyLocation::kField) {
      if (property_details_.representation().IsDouble() && IsNumber(*value)) {
        double v = Object::NumberValue(*value);
        if (std::isnan(v)) {
          value = isolate_->factory()->nan_value();
        }
      }
      // Check that in case of VariableMode::kConst field the existing value is
      // equal to |value|.
      DCHECK_IMPLIES(!initializing_store && property_details_.constness() ==
                                                 PropertyConstness::kConst,
                     CanStayConst(*value));
      Cast<JSObject>(*holder)->WriteToField(descriptor_number(),
                                            property_details_, *value);
    } else {
      DCHECK_EQ(PropertyLocation::kDescriptor, property_details_.location());
      DCHECK_EQ(PropertyConstness::kConst, property_details_.constness());
    }
  } else if (IsJSGlobalObject(*holder)) {
    // PropertyCell::PrepareForAndSetValue already wrote the value into the
    // cell.
#ifdef DEBUG
    Tagged<GlobalDictionary> dictionary =
        Cast<JSGlobalObject>(*holder)->global_dictionary(kAcquireLoad);
    Tagged<PropertyCell> cell = dictionary->CellAt(dictionary_entry());
    DCHECK(cell->value() == *value ||
           (IsString(cell->value()) && IsString(*value) &&
            Cast<String>(cell->value())->Equals(Cast<String>(*value))));
#endif  // DEBUG
  } else {
    DCHECK_IMPLIES(IsJSProxy(*holder), name()->IsAnyPrivate());
    // Check similar to fast mode case above.
    DCHECK_IMPLIES(
        V8_DICT_PROPERTY_CONST_TRACKING_BOOL && !initializing_store &&
            property_details_.constness() == PropertyConstness::kConst,
        IsJSProxy(*holder) || DictCanStayConst(*value));

    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      Tagged<SwissNameDictionary> dictionary =
          holder->property_dictionary_swiss();
      dictionary->ValueAtPut(dictionary_entry(), *value);
    } else {
      Tagged<NameDictionary> dictionary = holder->property_dictionary();
      dictionary->ValueAtPut(dictionary_entry(), *value);
    }
  }
}

void LookupIterator::WriteDataValue(DirectHandle<Object> value,
                                    SeqCstAccessTag tag) {
  DCHECK_EQ(DATA, state_);
  // Currently only shared structs and arrays support sequentially consistent
  // access.
  DCHECK(IsJSSharedStruct(*holder_) || IsJSSharedArray(*holder_));
  DirectHandle<JSObject> holder = GetHolder<JSObject>();
  if (IsElement(*holder)) {
    ElementsAccessor* accessor = holder->GetElementsAccessor();
    accessor->SetAtomic(holder, number_, *value, kSeqCstAccess);
    return;
  }
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyKind::kData, property_details_.kind());
  DisallowGarbageCollection no_gc;
  FieldIndex field_index =
      FieldIndex::ForDescriptor(holder->map(), descriptor_number());
  holder->FastPropertyAtPut(field_index, *value, tag);
}

DirectHandle<Object> LookupIterator::SwapDataValue(DirectHandle<Object> value,
                                                   SeqCstAccessTag tag) {
  DCHECK_EQ(DATA, state_);
  // Currently only shared structs and arrays support sequentially consistent
  // access.
  DCHECK(IsJSSharedStruct(*holder_) || IsJSSharedArray(*holder_));
  DirectHandle<JSObject> holder = GetHolder<JSObject>();
  if (IsElement(*holder)) {
    ElementsAccessor* accessor = holder->GetElementsAccessor();
    return accessor->SwapAtomic(isolate_, holder, number_, *value,
                                kSeqCstAccess);
  }
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyKind::kData, property_details_.kind());
  DisallowGarbageCollection no_gc;
  FieldIndex field_index =
      FieldIndex::ForDescriptor(holder->map(), descriptor_number());
  return direct_handle(holder->RawFastPropertyAtSwap(field_index, *value, tag),
                       isolate_);
}

DirectHandle<Object> LookupIterator::CompareAndSwapDataValue(
    DirectHandle<Object> expected, DirectHandle<Object> value,
    SeqCstAccessTag tag) {
  DCHECK_EQ(DATA, state_);
  // Currently only shared structs and arrays support sequentially consistent
  // access.
  DCHECK(IsJSSharedStruct(*holder_) || IsJSSharedArray(*holder_));
  DisallowGarbageCollection no_gc;
  DirectHandle<JSObject> holder = GetHolder<JSObject>();
  if (IsElement(*holder)) {
    ElementsAccessor* accessor = holder->GetElementsAccessor();
    return accessor->CompareAndSwapAtomic(isolate_, holder, number_, *expected,
                                          *value, kSeqCstAccess);
  }
  DCHECK_EQ(PropertyLocation::kField, property_details_.location());
  DCHECK_EQ(PropertyKind::kData, property_details_.kind());
  FieldIndex field_index =
      FieldIndex::ForDescriptor(holder->map(), descriptor_number());
  return direct_handle(holder->RawFastPropertyAtCompareAndSwap(
                           field_index, *expected, *value, tag),
                       isolate_);
}

template <bool is_element>
bool LookupIterator::SkipInterceptor(Tagged<JSObject> holder) {
  Tagged<InterceptorInfo> info = GetInterceptor<is_element>(holder);
  if (!is_element && IsSymbol(*name_) && !info->can_intercept_symbols()) {
    return true;
  }
  if (info->non_masking()) {
    switch (interceptor_state_) {
      case InterceptorState::kUninitialized:
        interceptor_state_ = InterceptorState::kSkipNonMasking;
        [[fallthrough]];
      case InterceptorState::kSkipNonMaskingOwnProperty:
        [[fallthrough]];
      case InterceptorState::kSkipNonMasking:
        return true;
      case InterceptorState::kProcessNonMasking:
        return false;
    }
  }
  return interceptor_state_ == InterceptorState::kProcessNonMasking;
}

Tagged<JSReceiver> LookupIterator::NextHolder(Tagged<Map> map) {
  DisallowGarbageCollection no_gc;
  if (map->prototype() == ReadOnlyRoots(isolate_).null_value()) {
    return {};
  }
  bool check_prototype_chain =
      this->check_prototype_chain() ||
      interceptor_state_ == InterceptorState::kSkipNonMaskingOwnProperty;
  if (!check_prototype_chain && !IsJSGlobalProxyMap(map)) {
    return {};
  }
  return Cast<JSReceiver>(map->prototype());
}

LookupIterator::State LookupIterator::NotFound(
    Tagged<JSReceiver> const holder) const {
  if (!IsJSTypedArray(holder)) return NOT_FOUND;
  if (IsElement()) return TYPED_ARRAY_INDEX_NOT_FOUND;
  if (!IsString(*name_)) return NOT_FOUND;
  return IsSpecialIndex(Cast<String>(*name_)) ? TYPED_ARRAY_INDEX_NOT_FOUND
                                              : NOT_FOUND;
}

namespace {

template <bool is_element>
bool HasInterceptor(Tagged<Map> map, size_t index) {
  if (is_element) {
    if (index > JSObject::kMaxElementIndex) {
      // There is currently no way to install interceptors on an object with
      // typed array elements.
      DCHECK(!map->has_typed_array_or_rab_gsab_typed_array_elements());
      return map->has_named_interceptor();
    }
    return map->has_indexed_interceptor();
  } else {
    return map->has_named_interceptor();
  }
}

}  // namespace

template <bool is_element>
LookupIterator::State LookupIterator::LookupInSpecialHolder(
    Tagged<Map> const map, Tagged<JSReceiver> const holder) {
  static_assert(INTERCEPTOR == BEFORE_PROPERTY);
  switch (state_) {
    case NOT_FOUND:
      if (IsJSProxyMap(map)) {
        if (is_element || !name_->IsAnyPrivate()) return JSPROXY;
      }
      if (IsJSModuleNamespaceMap(map)) {
        if (is_element || !name_->IsAnyPrivate()) return MODULE_NAMESPACE;
      }
#if V8_ENABLE_WEBASSEMBLY
      if (IsWasmObjectMap(map)) return WASM_OBJECT;
#endif  // V8_ENABLE_WEBASSEMBLY
      if (map->is_access_check_needed()) {
        if (is_element || !name_->IsPrivateInternal()) return ACCESS_CHECK;
      }
      [[fallthrough]];
    case ACCESS_CHECK:
      if (check_interceptor() && HasInterceptor<is_element>(map, index_) &&
          !SkipInterceptor<is_element>(Cast<JSObject>(holder))) {
        if (is_element || !name_->IsAnyPrivate()) return INTERCEPTOR;
      }
      [[fallthrough]];
    case INTERCEPTOR:
      if (IsJSGlobalObjectMap(map) && !is_js_array_element(is_element)) {
        Tagged<GlobalDictionary> dict =
            Cast<JSGlobalObject>(holder)->global_dictionary(kAcquireLoad);
        number_ = dict->FindEntry(isolate(), name_);
        if (number_.is_not_found()) return NOT_FOUND;
        Tagged<PropertyCell> cell = dict->CellAt(number_);
        if (IsPropertyCellHole(cell->value())) {
          return NOT_FOUND;
        }
        property_details_ = cell->property_details();
        has_property_ = true;
        switch (property_details_.kind()) {
          case v8::internal::PropertyKind::kData:
            return DATA;
          case v8::internal::PropertyKind::kAccessor:
            return ACCESSOR;
        }
        UNREACHABLE();
      }
      [[fallthrough]];
    case MODULE_NAMESPACE:
      return LookupInRegularHolder<is_element>(map, holder);
    case ACCESSOR:
    case DATA:
    case WASM_OBJECT:
      return NOT_FOUND;
    case TYPED_ARRAY_INDEX_NOT_FOUND:
    case JSPROXY:
    case TRANSITION:
    case STRING_LOOKUP_START_OBJECT:
      UNREACHABLE();
  }
  UNREACHABLE();
}

template <bool is_element>
LookupIterator::State LookupIterator::LookupInRegularHolder(
    Tagged<Map> const map, Tagged<JSReceiver> const holder) {
  DisallowGarbageCollection no_gc;
  if (interceptor_state_ == InterceptorState::kProcessNonMasking) {
    return NOT_FOUND;
  }
  DCHECK(!IsWasmObject(holder));
  if (is_element && IsElement(holder)) {
    Tagged<JSObject> js_object = Cast<JSObject>(holder);
    ElementsAccessor* accessor = js_object->GetElementsAccessor();
    Tagged<FixedArrayBase> backing_store = js_object->elements();
    number_ =
        accessor->GetEntryForIndex(isolate_, js_object, backing_store, index_);
    if (number_.is_not_found()) {
      return IsJSTypedArray(holder) ? TYPED_ARRAY_INDEX_NOT_FOUND : NOT_FOUND;
    }
    property_details_ = accessor->GetDetails(js_object, number_);
    if (map->has_frozen_elements()) {
      property_details_ = property_details_.CopyAddAttributes(FROZEN);
    } else if (map->has_sealed_elements()) {
      property_details_ = property_details_.CopyAddAttributes(SEALED);
    }
  } else if (!map->is_dictionary_map()) {
    Tagged<DescriptorArray> descriptors = map->instance_descriptors();
    number_ = descriptors->SearchWithCache(isolate_, *name_, map);
    if (number_.is_not_found()) return NotFound(holder);
    property_details_ = descriptors->GetDetails(number_);
  } else {
    DCHECK_IMPLIES(IsJSProxy(holder), name()->IsAnyPrivate());
    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      Tagged<SwissNameDictionary> dict = holder->property_dictionary_swiss();
      number_ = dict->FindEntry(isolate(), *name_);
      if (number_.is_not_found()) return NotFound(holder);
      property_details_ = dict->DetailsAt(number_);
    } else {
      Tagged<NameDictionary> dict = holder->property_dictionary();
      number_ = dict->FindEntry(isolate(), name_);
      if (number_.is_not_found()) return NotFound(holder);
      property_details_ = dict->DetailsAt(number_);
    }
  }
  has_property_ = true;
  switch (property_details_.kind()) {
    case v8::internal::PropertyKind::kData:
      return DATA;
    case v8::internal::PropertyKind::kAccessor:
      return ACCESSOR;
  }

  UNREACHABLE();
}

// This is a specialization of function LookupInRegularHolder above
// which is tailored to test whether an object has an internal marker
// property.
// static
bool LookupIterator::HasInternalMarkerProperty(
    Isolate* isolate, Tagged<JSReceiver> const holder,
    DirectHandle<Symbol> const marker) {
  DisallowGarbageCollection no_gc;
  Tagged<Map> map = holder->map();
  if (map->is_dictionary_map()) {
    if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
      Tagged<SwissNameDictionary> dict = holder->property_dictionary_swiss();
      InternalIndex entry = dict->FindEntry(isolate, marker);
      return entry.is_found();
    } else {
      Tagged<NameDictionary> dict = holder->property_dictionary();
      InternalIndex entry = dict->FindEntry(isolate, marker);
      return entry.is_found();
    }
  } else {
    Tagged<DescriptorArray> descriptors = map->instance_descriptors();
    InternalIndex entry = descriptors->SearchWithCache(isolate, *marker, map);
    return entry.is_found();
  }
}

DirectHandle<InterceptorInfo>
LookupIterator::GetInterceptorForFailedAccessCheck() const {
  DCHECK_EQ(ACCESS_CHECK, state_);
  // Skip the interceptors for private
  if (IsAnyPrivateName()) {
    return DirectHandle<InterceptorInfo>();
  }

  DisallowGarbageCollection no_gc;
  Tagged<AccessCheckInfo> access_check_info =
      AccessCheckInfo::Get(isolate_, Cast<JSObject>(holder_));
  if (!access_check_info.is_null()) {
    // There is currently no way to create objects with typed array elements
    // and access checks.
    DCHECK(!holder_->map()->has_typed_array_or_rab_gsab_typed_array_elements());
    Tagged<Object> interceptor = is_js_array_element(IsElement())
                                     ? access_check_info->indexed_interceptor()
                                     : access_check_info->named_interceptor();
    if (interceptor != Tagged<Object>()) {
      return direct_handle(Cast<InterceptorInfo>(interceptor), isolate_);
    }
  }
  return DirectHandle<InterceptorInfo>();
}

bool LookupIterator::TryLookupCachedProperty(
    DirectHandle<AccessorPair> accessor) {
  DCHECK_EQ(state(), LookupIterator::ACCESSOR);
  return LookupCachedProperty(accessor);
}

bool LookupIterator::TryLookupCachedProperty() {
  if (state() != LookupIterator::ACCESSOR) return false;

  DirectHandle<Object> accessor_pair = GetAccessors();
  return IsAccessorPair(*accessor_pair) &&
         LookupCachedProperty(Cast<AccessorPair>(accessor_pair));
}

bool LookupIterator::LookupCachedProperty(
    DirectHandle<AccessorPair> accessor_pair) {
  if (!HolderIsReceiverOrHiddenPrototype()) return false;
  if (!lookup_start_object_.is_identical_to(receiver_) &&
      !lookup_start_object_.is_identical_to(holder_)) {
    return false;
  }

  DCHECK_EQ(state(), LookupIterator::ACCESSOR);
  DCHECK(IsAccessorPair(*GetAccessors()));

  Tagged<Object> getter = accessor_pair->getter();
  std::optional<Tagged<Name>> maybe_name =
      FunctionTemplateInfo::TryGetCachedPropertyName(isolate(), getter);
  if (!maybe_name.has_value()) return false;

  if (IsJSFunction(getter)) {
    // If the getter was a JSFunction there's no guarantee that the holder
    // actually has a property with the cached name. In that case look it up to
    // make sure.
    LookupIterator it(isolate_, holder_,
                      direct_handle(maybe_name.value(), isolate_));
    if (it.state() != DATA) return false;
    name_ = it.name();
  } else {
    name_ = direct_handle(maybe_name.value(), isolate_);
  }

  // We have found a cached property! Modify the iterator accordingly.
  Restart();
  CHECK_EQ(state(), LookupIterator::DATA);
  return true;
}

// static
std::optional<Tagged<Object>> ConcurrentLookupIterator::TryGetOwnCowElement(
    Isolate* isolate, Tagged<FixedArray> array_elements,
    ElementsKind elements_kind, int array_length, size_t index) {
  DisallowGarbageCollection no_gc;

  CHECK_EQ(array_elements->map(), ReadOnlyRoots(isolate).fixed_cow_array_map());
  DCHECK(IsFastElementsKind(elements_kind) &&
         IsSmiOrObjectElementsKind(elements_kind));
  USE(elements_kind);
  DCHECK_GE(array_length, 0);

  //  ________________________________________
  // ( Check against both JSArray::length and )
  // ( FixedArray::length.                    )
  //  ----------------------------------------
  //         o   ^__^
  //          o  (oo)\_______
  //             (__)\       )\/\
  //                 ||----w |
  //                 ||     ||
  // The former is the source of truth, but due to concurrent reads it may not
  // match the given `array_elements`.
  if (index >= static_cast<size_t>(array_length)) return {};
  if (index >= static_cast<size_t>(array_elements->ulength().value())) {
    return {};
  }

  Tagged<Object> result = array_elements->get(static_cast<int>(index));

  //  ______________________________________
  // ( Filter out holes irrespective of the )
  // ( elements kind.                       )
  //  --------------------------------------
  //         o   ^__^
  //          o  (..)\_______
  //             (__)\       )\/\
  //                 ||----w |
  //                 ||     ||
  // The elements kind may not be consistent with the given elements backing
  // store.
  if (result == ReadOnlyRoots(isolate).the_hole_value()) return {};

  return result;
}

// static
ConcurrentLookupIterator::Result
ConcurrentLookupIterator::TryGetOwnConstantElement(
    Tagged<Object>* result_out, Isolate* isolate, LocalIsolate* local_isolate,
    Tagged<JSObject> holder, Tagged<FixedArrayBase> elements,
    ElementsKind elements_kind, size_t index) {
  DisallowGarbageCollection no_gc;

  DCHECK_LE(index, JSObject::kMaxElementIndex);

  // Own 'constant' elements (PropertyAttributes READ_ONLY|DONT_DELETE) occur in
  // three main cases:
  //
  // 1. Frozen elements: guaranteed constant.
  // 2. Dictionary elements: may be constant.
  // 3. String wrapper elements: guaranteed constant.

  // Interesting field reads below:
  //
  // - elements.length (immutable on FixedArrays).
  // - elements[i] (immutable if constant; be careful around dictionaries).
  // - holder.AsJSPrimitiveWrapper.value.AsString.length (immutable).
  // - holder.AsJSPrimitiveWrapper.value.AsString[i] (immutable).
  // - roots.single_character_string(charcode).

  if (IsFrozenElementsKind(elements_kind)) {
    if (!IsFixedArray(elements)) return kGaveUp;
    Tagged<FixedArray> elements_fixed_array = Cast<FixedArray>(elements);
    if (index >= elements_fixed_array->ulength().value()) {
      return kGaveUp;
    }
    Tagged<Object> result =
        elements_fixed_array->get(static_cast<uint32_t>(index));
    if (IsHoleyElementsKindForRead(elements_kind) &&
        result == ReadOnlyRoots(isolate).the_hole_value()) {
      return kNotPresent;
    }
    *result_out = result;
    return kPresent;
  } else if (IsDictionaryElementsKind(elements_kind)) {
    if (!IsNumberDictionary(elements)) return kGaveUp;
    // TODO(jgruber, v8:7790): Add support. Dictionary elements require racy
    // NumberDictionary lookups. This should be okay in general (slot iteration
    // depends only on the dict's capacity), but 1. we'd need to update
    // NumberDictionary methods to do atomic reads, and 2. the dictionary
    // elements case isn't very important for callers of this function.
    return kGaveUp;
  } else if (IsStringWrapperElementsKind(elements_kind)) {
    // In this case we don't care about the actual `elements`. All in-bounds
    // reads are redirected to the wrapped String.

    Tagged<JSPrimitiveWrapper> js_value = Cast<JSPrimitiveWrapper>(holder);
    Tagged<String> wrapped_string = Cast<String>(js_value->value());
    return ConcurrentLookupIterator::TryGetOwnChar(
        reinterpret_cast<Tagged<String>*>(result_out), isolate, local_isolate,
        wrapped_string, index);
  } else {
    DCHECK(!IsFrozenElementsKind(elements_kind));
    DCHECK(!IsDictionaryElementsKind(elements_kind));
    DCHECK(!IsStringWrapperElementsKind(elements_kind));
    return kGaveUp;
  }

  UNREACHABLE();
}

// static
ConcurrentLookupIterator::Result ConcurrentLookupIterator::TryGetOwnChar(
    Tagged<String>* result_out, Isolate* isolate, LocalIsolate* local_isolate,
    Tagged<String> string, size_t index) {
  DisallowGarbageCollection no_gc;
  // The access guard below protects string accesses related to internalized
  // strings.
  // TODO(jgruber): Support other string kinds.
  Tagged<Map> string_map = string->map(kAcquireLoad);
  InstanceType type = string_map->instance_type();
  if (!(InstanceTypeChecker::IsInternalizedString(type) ||
        InstanceTypeChecker::IsThinString(type))) {
    return kGaveUp;
  }

  const uint32_t length = static_cast<uint32_t>(string->length());
  if (index >= length) return kGaveUp;

  uint16_t charcode;
  {
    SharedStringAccessGuardIfNeeded access_guard(local_isolate);
    charcode = string->Get(static_cast<int>(index), access_guard);
  }

  if (charcode > unibrow::Latin1::kMaxChar) return kGaveUp;

  ReadOnlyRoots roots(isolate);
  Tagged<String> value = roots.single_character_string(charcode);

  *result_out = value;
  return kPresent;
}

// static
std::optional<Tagged<PropertyCell>>
ConcurrentLookupIterator::TryGetPropertyCell(
    Isolate* isolate, LocalIsolate* local_isolate,
    DirectHandle<JSGlobalObject> holder, DirectHandle<Name> name) {
  DisallowGarbageCollection no_gc;

  Tagged<Map> holder_map = holder->map();
  if (holder_map->is_access_check_needed()) return {};
  if (holder_map->has_named_interceptor()) return {};

  Tagged<GlobalDictionary> dict = holder->global_dictionary(kAcquireLoad);
  std::optional<Tagged<PropertyCell>> maybe_cell =
      dict->TryFindPropertyCellForConcurrentLookupIterator(isolate, name,
                                                           kRelaxedLoad);
  if (!maybe_cell.has_value()) return {};
  Tagged<PropertyCell> cell = maybe_cell.value();

  if (cell->property_details(kAcquireLoad).kind() == PropertyKind::kAccessor) {
    Tagged<Object> maybe_accessor_pair = cell->value(kAcquireLoad);
    if (!IsAccessorPair(maybe_accessor_pair)) return {};

    std::optional<Tagged<Name>> maybe_cached_property_name =
        FunctionTemplateInfo::TryGetCachedPropertyName(
            isolate,
            Cast<AccessorPair>(maybe_accessor_pair)->getter(kAcquireLoad));
    if (!maybe_cached_property_name.has_value()) return {};

    maybe_cell = dict->TryFindPropertyCellForConcurrentLookupIterator(
        isolate, direct_handle(*maybe_cached_property_name, local_isolate),
        kRelaxedLoad);
    if (!maybe_cell.has_value()) return {};
    cell = maybe_cell.value();
    if (cell->property_details(kAcquireLoad).kind() != PropertyKind::kData) {
      return {};
    }
  }

  DCHECK(maybe_cell.has_value());
  DCHECK_EQ(cell->property_details(kAcquireLoad).kind(), PropertyKind::kData);
  return cell;
}

}  // namespace v8::internal
