current commit
261015.17 · deps: update V8 to 15.6.75.13
// Copyright 2026 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.

#ifndef V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_
#define V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_

#include "src/objects/js-data-object-builder.h"
// Include the non-inl header before the rest of the headers.

#include "include/v8config.h"
#include "src/common/assert-scope.h"
#include "src/heap/factory.h"
#include "src/objects/descriptor-array-inl.h"
#include "src/objects/field-index.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/heap-object-set-map-inl.h"
#include "src/objects/js-objects-inl.h"
#include "src/objects/map-updater.h"
#include "src/objects/object-conversions-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/transitions-inl.h"

namespace v8 {
namespace internal {

class FoldedMutableHeapNumberAllocation {
 public:
  // TODO(leszeks): If allocation alignment is ever enabled, we'll need to add
  // padding fillers between heap numbers.
  static_assert(!USE_ALLOCATION_ALIGNMENT_HEAP_NUMBER_BOOL);

  FoldedMutableHeapNumberAllocation(Isolate* isolate, uint32_t count) {
    if (count == 0) return;
    uint32_t size = count * sizeof(HeapNumber);
    raw_bytes_ = isolate->factory()->NewByteArray(size);
  }

  Handle<ByteArray> raw_bytes() const { return raw_bytes_; }

 private:
  Handle<ByteArray> raw_bytes_ = {};
};

class FoldedMutableHeapNumberAllocator {
 public:
  FoldedMutableHeapNumberAllocator(
      Isolate* isolate, FoldedMutableHeapNumberAllocation* allocation,
      DisallowGarbageCollection& no_gc V8_LIFETIME_BOUND)
      : isolate_(isolate), roots_(isolate) {
    if (allocation->raw_bytes().is_null()) return;

    raw_bytes_ = allocation->raw_bytes();
    mutable_double_address_ =
        reinterpret_cast<Address>(allocation->raw_bytes()->begin());
  }

  ~FoldedMutableHeapNumberAllocator() {
    // Make all mutable HeapNumbers alive.
    if (mutable_double_address_ == 0) {
      DCHECK(raw_bytes_.is_null());
      return;
    }

    // We should have calculated the exact correct number of mutable heap
    // numbers for the double fields we've seen, however nested value
    // allocations could have transitioned those double field to tagged.
    Address raw_bytes_end = reinterpret_cast<Address>(raw_bytes_->end());
    if (mutable_double_address_ < raw_bytes_end) {
      isolate_->heap()->CreateFillerObjectAt(
          mutable_double_address_,
          base::checked_cast<int>(raw_bytes_end - mutable_double_address_));
    } else {
      DCHECK_EQ(mutable_double_address_, raw_bytes_end);
    }

    // Before setting the length of mutable_double_buffer back to zero, we
    // must ensure that the sweeper is not running or has already swept the
    // object's page. Otherwise the GC can add the contents of
    // mutable_double_buffer to the free list.
    isolate_->heap()->EnsureSweepingCompletedForObject(*raw_bytes_);
    raw_bytes_->set_length(0);
  }

  Tagged<HeapNumber> AllocateNext(ReadOnlyRoots roots, Float64 value) {
    DCHECK_GE(mutable_double_address_,
              reinterpret_cast<Address>(raw_bytes_->begin()));
    Tagged<HeapObject> hn = HeapObject::FromAddress(mutable_double_address_);
    hn->set_map_after_allocation(isolate_, roots.heap_number_map());
    Cast<HeapNumber>(hn)->set_value_as_bits(value.get_bits());
    mutable_double_address_ +=
        ALIGN_TO_ALLOCATION_ALIGNMENT(sizeof(HeapNumber));
    DCHECK_LE(mutable_double_address_,
              reinterpret_cast<Address>(raw_bytes_->end()));
    return Cast<HeapNumber>(hn);
  }

 private:
  Isolate* isolate_;
  ReadOnlyRoots roots_;
  Handle<ByteArray> raw_bytes_ = {};
  Address mutable_double_address_ = 0;
};

template <JSDataPropertyIteratorWithRawKeys PropertyIterator>
Handle<JSObject> JSDataObjectBuilder::BuildFromIterator(
    PropertyIterator&& it, MaybeDirectHandle<FixedArrayBase> maybe_elements) {
  may_have_duplicate_keys_ =
      std::remove_reference_t<PropertyIterator>::kMayHaveDuplicateKeys;

  DirectHandle<InternalizedString> failed_property_add_key;
  for (; !it.Done(); it.Advance()) {
    DirectHandle<InternalizedString> property_key;
    if (!TryAddFastPropertyForValue(
            it.GetKeyChars(),
            [&](Handle<InternalizedString> expected_key) {
              return it.GetKey(expected_key);
            },
            [&]() { return it.GetValue(true); }, &property_key)) {
      failed_property_add_key = property_key;
      break;
    }
  }

  DirectHandle<FixedArrayBase> elements;
  if (!maybe_elements.ToHandle(&elements)) {
    elements = isolate_->factory()->empty_fixed_array();
  }
  {
    auto value_it = it.RevisitValues();
    CreateAndInitialiseObject(value_it, elements);

    if (!it.Done()) {
      // If we're not yet done, we must have failed to add a property.
      // This property's value will be in the value iterator, so get it from
      // there rather than it.GetValue().
      DirectHandle<InternalizedString> key =
          std::exchange(failed_property_add_key, {});
      DCHECK(!key.is_null());
      Handle<Object> value(value_it.GetNext(), isolate_);
      AddSlowProperty(key, value);
      it.Advance();
    }
  }

  // Slow path: define remaining named properties.
  for (; !it.Done(); it.Advance()) {
    DCHECK(failed_property_add_key.is_null());
    DirectHandle<InternalizedString> key = it.GetKey({});
#ifdef DEBUG
    uint32_t index;
    DCHECK(!key->AsArrayIndex(&index));
#endif
    Handle<Object> value = it.GetValue(false);
    AddSlowProperty(key, value);
  }

  DCHECK(!object_.is_null());
  return object_;
}

template <JSDataPropertyIteratorWithEagerKeys PropertyIterator>
Handle<JSObject> JSDataObjectBuilder::BuildFromIterator(
    PropertyIterator&& it, MaybeDirectHandle<FixedArrayBase> maybe_elements) {
  may_have_duplicate_keys_ =
      std::remove_reference_t<PropertyIterator>::kMayHaveDuplicateKeys;

  for (; !it.Done(); it.Advance()) {
    if (!TryAddFastPropertyForValue(it.GetKey(), it.GetValue(true))) {
      break;
    }
  }

  DirectHandle<FixedArrayBase> elements;
  if (!maybe_elements.ToHandle(&elements)) {
    elements = isolate_->factory()->empty_fixed_array();
  }
  {
    auto value_it = it.RevisitValues();
    CreateAndInitialiseObject(value_it, elements);

    if (!it.Done()) {
      // If we're not yet done, we must have failed to add a property.
      // This property's value will be in the value iterator, so get it from
      // there rather than it.GetValue().
      DirectHandle<InternalizedString> key = it.GetKey();
      Handle<Object> value(value_it.GetNext(), isolate_);
      AddSlowProperty(key, value);
      it.Advance();
    }
  }

  // Slow path: define remaining named properties.
  for (; !it.Done(); it.Advance()) {
    DirectHandle<InternalizedString> key = it.GetKey();
#ifdef DEBUG
    uint32_t index;
    DCHECK(!key->AsArrayIndex(&index));
#endif
    Handle<Object> value = it.GetValue(false);
    AddSlowProperty(key, value);
  }

  DCHECK(!object_.is_null());
  return object_;
}

template <typename Char, typename GetKeyFunction, typename GetValueFunction>
bool JSDataObjectBuilder::TryAddFastPropertyForValue(
    base::Vector<const Char> key_chars, GetKeyFunction&& get_key,
    GetValueFunction&& get_value, DirectHandle<InternalizedString>* out_key) {
  // The fast path is only valid as long as we haven't allocated an object
  // yet.
  DCHECK(object_.is_null());

  DirectHandle<Map> previous_map = map_;
  bool existing_map_found =
      TryFastTransitionToPropertyKey(key_chars, get_key, out_key);
  // Unconditionally get the value after getting the transition result.
  DirectHandle<Object> value = get_value();
  if (existing_map_found) {
    // We found a map with a field for our value -- now make sure that field
    // is compatible with our value.
    if (map_->is_deprecated() || !TryGeneralizeFieldToValue(value)) {
      // TODO(leszeks): Try to stay on the fast path if we just deprecate
      // here.
      map_ = previous_map;
      return false;
    }
    AdvanceToNextProperty();
    return true;
  }
  return TryAddFastPropertyTransitionForValue(*out_key, value);
}

bool JSDataObjectBuilder::TryAddFastPropertyForValue(
    DirectHandle<InternalizedString> key, DirectHandle<Object> value) {
  if (map_->is_deprecated()) {
    // Normally previous TryAddFastPropertyForValue wouldn't allow a deprecated
    // map to reach here. However, we could get a deprecated map here if the
    // property iterator's GetKey/GetValue calls deprecated the map (e.g.
    // through recursive object literal building).
    DirectHandle<Map> new_map = Map::Update(isolate_, map_);

    // The migration target could be a dictionary map -- we can't rewind to a
    // previous map here like we do in the GetValueFunction version of this
    // function, or like in TryAddFastPropertyTransitionForValue, so we just
    // have to bail out and force CreateAndInitialiseObject deal with it.
    if (new_map->is_dictionary_map()) return false;

    map_ = new_map;
    RecalculateExtraHeapNumbersNeeded();
  }

  DCHECK(object_.is_null());
  DirectHandle<Map> previous_map = map_;
  bool existing_map_found = TryFastTransitionToPropertyKey(key);
  if (existing_map_found) {
    // We found a map with a field for our value -- now make sure that field
    // is compatible with our value.
    if (map_->is_deprecated() || !TryGeneralizeFieldToValue(value)) {
      // TODO(leszeks): Try to stay on the fast path if we just deprecate
      // here.
      map_ = previous_map;
      return false;
    }
    AdvanceToNextProperty();
    return true;
  }
  return TryAddFastPropertyTransitionForValue(key, value);
}

template <typename ValueIterator>
inline void JSDataObjectBuilder::CreateAndInitialiseObject(
    ValueIterator&& value_it, DirectHandle<FixedArrayBase> elements) {
  bool needs_migration = false;
  if (map_->is_deprecated()) {
    // We could get a deprecated map here if a GetKey/GetValue deprecated the
    // existing map_. We ideally don't want to create deprecated objects and
    // then migrate them, so update the map first.
    DirectHandle<Map> new_map = Map::Update(isolate_, map_);
    if (current_property_index_ > 0 && new_map->is_dictionary_map()) {
      // In edge cases, the migration target might be a dictionary map. We can't
      // switch to a dictionary map when current_property_index_ > 0, because
      // we'll have already iterated over some keys and we don't persist them
      // anywhere but the map's descriptor array, so we have to use the
      // deprecated map after all and migrate the object once we're done
      // initializing it.
      //
      // TODO(leszeks): We _could_ immediately allocate a slow JSObject and
      // populate it by walking the deprecated map's descriptor array to recover
      // the keys -- this is probably overkill for this edge case though.
      needs_migration = true;
    } else {
      map_ = new_map;
      RecalculateExtraHeapNumbersNeeded();
    }
  }
  // We've created a map for the first `i` property stack values (which might
  // be all of them). We need to write these properties to a newly allocated
  // object.
  DCHECK(object_.is_null());

  if (current_property_index_ < property_count_in_expected_final_map_) {
    // If we were on the expected map fast path all the way, but never reached
    // the expected final map itself, then finalize the map by rewinding to
    // the one whose property is the actual current property index.
    //
    // TODO(leszeks): Do we actually want to use the final map fast path when
    // we know that the current map _can't_ reach the final map? Will we even
    // hit this case given that we check for matching instance size?
    RewindExpectedFinalMapFastPathToBeforeCurrent();
  }

  if (map_->is_dictionary_map()) {
    // It's only safe to emit a dictionary map when we've not set up any
    // properties, as the caller assumes it can set up the first N properties
    // as fast data properties.
    DCHECK_EQ(current_property_index_, 0);

    Handle<JSObject> object = isolate_->factory()->NewSlowJSObjectFromMap(
        map_, expected_property_count_);
    object->set_elements(*elements);
    object_ = object;
    return;
  }

  // The map should have as many own descriptors as the number of properties
  // we've created so far...
  DCHECK_EQ(current_property_index_, map_->NumberOfOwnDescriptors());

  // ... and all of those properties should be in-object data properties.
  DCHECK_EQ(current_property_index_,
            map_->GetInObjectProperties() - map_->UnusedInObjectProperties());

  // Create a folded mutable HeapNumber allocation area before allocating the
  // object -- this ensures that there is no allocation between the object
  // allocation and its initial fields being initialised, where the verifier
  // would see invalid double field state.
  FoldedMutableHeapNumberAllocation hn_allocation(isolate_,
                                                  extra_heap_numbers_needed_);

  // Allocate the object then immediately start a no_gc scope -- again, this
  // is so the verifier doesn't see invalid double field state.
  Handle<JSObject> object = isolate_->factory()->NewJSObjectFromMap(
      map_, AllocationType::kYoung, DirectHandle<AllocationSite>::null(),
      NewJSObjectType::kNoEmbedderFieldsAndNoApiWrapper);
  {
    DisallowGarbageCollection no_gc;
    Tagged<JSObject> raw_object = *object;

    raw_object->set_elements(*elements);
    Tagged<DescriptorArray> descriptors =
        raw_object->map()->instance_descriptors();

    FoldedMutableHeapNumberAllocator hn_allocator(isolate_, &hn_allocation,
                                                  no_gc);

    ReadOnlyRoots roots(isolate_);

    // Initialize the in-object properties up to the last added property.
    int current_property_offset = raw_object->GetInObjectPropertyOffset(0);
    for (int i = 0; i < current_property_index_; ++i) {
      InternalIndex descriptor_index(i);
      Tagged<Object> value = value_it.GetNext();

      // See comment in RegisterFieldNeedsFreshHeapNumber, we need to allocate
      // HeapNumbers for double representation fields when we can't make
      // existing HeapNumbers mutable, or when we only have a Smi value.
      if (heap_number_mode_ != kHeapNumbersGuaranteedUniquelyOwned ||
          IsSmi(value)) {
        PropertyDetails details = descriptors->GetDetails(descriptor_index);
        if (details.representation().IsDouble()) {
          Float64 d = Float64::hole_nan();
          if (IsNumber(value)) {
            d = Float64::FromMaybeNaN(Object::NumberValue(value));
          }
          value = hn_allocator.AllocateNext(roots, d);
        }
      }

      DCHECK(FieldIndex::ForPropertyIndex(object->map(), i).is_inobject());
      DCHECK_EQ(current_property_offset,
                FieldIndex::ForPropertyIndex(object->map(), i).offset());
      DCHECK_EQ(current_property_offset,
                object->map()->GetInObjectPropertyOffset(i));
      FieldIndex index = FieldIndex::ForInObjectOffset(current_property_offset,
                                                       FieldIndex::kTagged);
      // Object is the most recent young allocation, so no write barrier
      // required.
      raw_object->RawFastInobjectPropertyAtPut(index, value,
                                               SKIP_WRITE_BARRIER);
      current_property_offset += kTaggedSize;
    }
    DCHECK_EQ(current_property_offset, object->map()->GetInObjectPropertyOffset(
                                           current_property_index_));
  }

  if (needs_migration) {
    JSObject::MigrateToMap(isolate_, object, Map::Update(isolate_, map_));
  }

  object_ = object;
}

template <typename Char, typename GetKeyFunction>
bool JSDataObjectBuilder::TryFastTransitionToPropertyKey(
    base::Vector<const Char> key_chars, GetKeyFunction&& get_key,
    DirectHandle<InternalizedString>* out_key) {
  Handle<InternalizedString> expected_key;
  DirectHandle<Map> target_map;

  InternalIndex descriptor_index(current_property_index_);
  if (IsOnExpectedFinalMapFastPath()) {
    expected_key =
        handle(Cast<InternalizedString>(
                   expected_final_map_->instance_descriptors()->GetKey(
                       descriptor_index)),
               isolate_);
    target_map = expected_final_map_;
  } else if (key_chars.data() != nullptr) {
    // The byte-compare transition fast path is only valid when `key_chars`
    // faithfully represent the decoded key. Callers that cannot provide valid
    // chars (the JSON parser passes an empty vector for escaped keys, whose raw
    // source bytes differ from the decoded key) fall through to the
    // decode-then-FindTransitionToField path below.
    TransitionsAccessor transitions(isolate_, *map_);
    auto expected_transition = transitions.ExpectedTransition(key_chars);
    if (!expected_transition.first.is_null()) {
      // Directly read out the target while reading out the key, otherwise it
      // might die if `get_key` can allocate.
      target_map = expected_transition.second;
      *out_key = Cast<InternalizedString>(expected_transition.first);

      // We were successful and we are done.
      DCHECK_EQ(target_map->instance_descriptors()
                    ->GetDetails(descriptor_index)
                    .location(),
                PropertyLocation::kField);
      map_ = target_map;
      return true;
    }
  }

  DirectHandle<String> key = *out_key = get_key(expected_key);
  if (key.is_identical_to(expected_key)) {
    // We were successful and we are done.
    DCHECK_EQ(target_map->instance_descriptors()
                  ->GetDetails(descriptor_index)
                  .location(),
              PropertyLocation::kField);
    map_ = target_map;
    return true;
  }

  if (IsOnExpectedFinalMapFastPath()) {
    // We were on the expected map fast path, but this missed that fast
    // path, so rewind the optimistic setting of the current map and disable
    // this fast path.
    RewindExpectedFinalMapFastPathToBeforeCurrent();
    property_count_in_expected_final_map_ = 0;
  }

  MaybeHandle<Map> maybe_target =
      TransitionsAccessor(isolate_, *map_).FindTransitionToField(key);
  if (!maybe_target.ToHandle(&target_map)) return false;

  map_ = target_map;
  return true;
}

bool JSDataObjectBuilder::TryFastTransitionToPropertyKey(
    DirectHandle<InternalizedString> key) {
  Tagged<InternalizedString> expected_key;
  DirectHandle<Map> target_map;

  InternalIndex descriptor_index(current_property_index_);
  if (IsOnExpectedFinalMapFastPath()) {
    expected_key = Cast<InternalizedString>(
        expected_final_map_->instance_descriptors()->GetKey(descriptor_index));
    target_map = expected_final_map_;
  }

  if (!expected_key.is_null() && key->Equals(expected_key)) {
    DCHECK_EQ(target_map->instance_descriptors()
                  ->GetDetails(descriptor_index)
                  .location(),
              PropertyLocation::kField);
    map_ = target_map;
    return true;
  }

  if (IsOnExpectedFinalMapFastPath()) {
    RewindExpectedFinalMapFastPathToBeforeCurrent();
    property_count_in_expected_final_map_ = 0;
  }

  MaybeHandle<Map> maybe_target =
      TransitionsAccessor(isolate_, *map_).FindTransitionToField(key);
  if (!maybe_target.ToHandle(&target_map)) return false;

  map_ = target_map;
  return true;
}

bool JSDataObjectBuilder::TryAddFastPropertyTransitionForValue(
    DirectHandle<InternalizedString> key, DirectHandle<Object> value) {
  if (may_have_duplicate_keys_) {
    Tagged<DescriptorArray> descriptors = map_->instance_descriptors();
    InternalIndex descriptor_number =
        descriptors->SearchWithCache(isolate_, *key, *map_);
    if (descriptor_number.is_found()) {
      return false;
    }
  } else {
    DCHECK(map_->instance_descriptors()
               ->SearchWithCache(isolate_, *key, *map_)
               .is_not_found());
  }

  if (!TransitionsAccessor::CanHaveMoreTransitions(isolate_, map_)) {
    return false;
  }

  auto [representation, constness] =
      Object::OptimalRepresentation(*value, PropertyConstness::kConst);
  DirectHandle<FieldType> type =
      Object::OptimalType(*value, isolate_, representation);
  MaybeHandle<Map> maybe_map =
      Map::CopyWithField(isolate_, map_, key, type, NONE, constness,
                         representation, INSERT_TRANSITION);
  DirectHandle<Map> next_map;
  if (!maybe_map.ToHandle(&next_map)) return false;
  if (next_map->is_dictionary_map()) return false;

  if (next_map->is_deprecated()) {
    next_map = Map::Update(isolate_, next_map);
    // If the migration target is a dictionary map, bail out so that we still
    // have a fast map (otherwise we'd lose its keys, since we don't persist
    // them anywhere else).
    if (next_map->is_dictionary_map()) return false;
    map_ = next_map;
    RecalculateExtraHeapNumbersNeeded();
  } else {
    map_ = next_map;
  }
  if (representation.IsDouble()) {
    RegisterFieldNeedsFreshHeapNumber(value);
  }
  AdvanceToNextProperty();
  return true;
}

bool JSDataObjectBuilder::TryGeneralizeFieldToValue(
    DirectHandle<Object> value) {
  DCHECK_LT(current_property_index_, map_->NumberOfOwnDescriptors());

  InternalIndex descriptor_index(current_property_index_);
  PropertyDetails current_details =
      map_->instance_descriptors()->GetDetails(descriptor_index);
  Representation expected_representation = current_details.representation();

  DCHECK_EQ(current_details.kind(), PropertyKind::kData);
  DCHECK_EQ(current_details.location(), PropertyLocation::kField);

  if (!Object::FitsRepresentation(*value, expected_representation)) {
    if (IsUninitializedHole(*value)) {
      if (expected_representation.IsDouble()) {
        // Value is uninitialized, so we'll unconditionally need a new box for
        // double fields.
        RegisterFieldNeedsFreshHeapNumber();
      }
      return true;
    }

    auto [representation, constness] =
        Object::OptimalRepresentation(*value, current_details.constness());
    representation = representation.generalize(expected_representation);
    if (!expected_representation.CanBeInPlaceChangedTo(representation)) {
      // Reconfigure the map for the value, deprecating if necessary. This
      // will only happen for double representation fields.
      if (IsOnExpectedFinalMapFastPath()) {
        // If we're on the fast path, we will have advanced the current map
        // all the way to the final expected map. Make sure to rewind to the
        // "real" current map if this happened.
        //
        // An alternative would be to deprecate the expected final map,
        // migrate it to the new representation, and stay on the fast path.
        // However, this would mean allocating all-new maps (with the new
        // representation) all the way between the current map and the new
        // expected final map; if we later fall off the fast path anyway, then
        // all those newly allocated maps will end up unused.
        RewindExpectedFinalMapFastPathToIncludeCurrent();
        property_count_in_expected_final_map_ = 0;
      }
      MapUpdater mu(isolate_, map_);
      Handle<Map> new_map = mu.ReconfigureToDataField(
          descriptor_index, current_details.attributes(), constness,
          representation, FieldType::Any(isolate_));

      // We only want to stay on the fast path if we got a fast map.
      if (new_map->is_dictionary_map()) return false;
      map_ = new_map;
      if (representation.IsDouble()) {
        RegisterFieldNeedsFreshHeapNumber(value);
      }
    } else {
      // Do the in-place reconfiguration.
      DCHECK(!representation.IsDouble() || expected_representation.IsDouble());
      DirectHandle<FieldType> value_type =
          Object::OptimalType(*value, isolate_, representation);
      MapUpdater::GeneralizeField(isolate_, map_, descriptor_index, constness,
                                  representation, value_type);
    }
  } else if (expected_representation.IsHeapObject() &&
             !FieldType::NowContains(
                 map_->instance_descriptors()->GetFieldType(descriptor_index),
                 value)) {
    DirectHandle<FieldType> value_type =
        Object::OptimalType(*value, isolate_, expected_representation);
    MapUpdater::GeneralizeField(isolate_, map_, descriptor_index,
                                current_details.constness(),
                                expected_representation, value_type);
  } else if (expected_representation.IsDouble()) {
    RegisterFieldNeedsFreshHeapNumber(value);
  }

  DCHECK(FieldType::NowContains(
      map_->instance_descriptors()->GetFieldType(descriptor_index), value));
  return true;
}

bool JSDataObjectBuilder::IsOnExpectedFinalMapFastPath() const {
  DCHECK_IMPLIES(property_count_in_expected_final_map_ > 0,
                 !expected_final_map_.is_null());
  return current_property_index_ < property_count_in_expected_final_map_;
}

void JSDataObjectBuilder::RegisterFieldNeedsFreshHeapNumber() {
  extra_heap_numbers_needed_++;
}

void JSDataObjectBuilder::RegisterFieldNeedsFreshHeapNumber(
    DirectHandle<Object> value) {
  // We need to allocate a new HeapNumber for double representation fields if
  // the HeapNumber values is not guaranteed to be uniquely owned by this
  // object (and therefore can't be made mutable), or if the value is a Smi
  // and there is no HeapNumber box for this value yet at all.
  if (heap_number_mode_ == kHeapNumbersGuaranteedUniquelyOwned &&
      !IsSmi(*value)) {
    DCHECK(IsHeapNumber(*value));
    return;
  }
  extra_heap_numbers_needed_++;
}

void JSDataObjectBuilder::RecalculateExtraHeapNumbersNeeded() {
  extra_heap_numbers_needed_ = 0;
  Tagged<DescriptorArray> map_descriptors = map_->instance_descriptors();
  for (int i = 0; i < current_property_index_; ++i) {
    if (map_descriptors->GetDetails(InternalIndex(i))
            .representation()
            .IsDouble()) {
      RegisterFieldNeedsFreshHeapNumber();
    }
  }
}

void JSDataObjectBuilder::AdvanceToNextProperty() {
  DCHECK(!map_->is_deprecated());
  current_property_index_++;
}

}  // namespace internal
}  // namespace v8

#endif  // V8_OBJECTS_JS_DATA_OBJECT_BUILDER_INL_H_