diff --git a/deps/v8/include/cppgc/heap-consistency.h b/deps/v8/include/cppgc/heap-consistency.h index 23b5d909945b..31858f0a631b 100644 --- a/deps/v8/include/cppgc/heap-consistency.h +++ b/deps/v8/include/cppgc/heap-consistency.h @@ -146,8 +146,8 @@ class HeapConsistency final { } /** - * Steele-style write barrier that re-processes an object if it has already - * been processed. + * Steele-style write barrier that processes an object regardless of whether + * or not it has already been processed. * * \param params The parameters retrieved from `GetWriteBarrierType()`. * \param object The pointer to the object which must point to an object that diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index f91b6e7018f8..11e0def9233c 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 15 #define V8_MINOR_VERSION 6 #define V8_BUILD_NUMBER 75 -#define V8_PATCH_LEVEL 25 +#define V8_PATCH_LEVEL 33 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/src/builtins/promise-any.tq b/deps/v8/src/builtins/promise-any.tq index 427d31c2840b..6f3d91f19959 100644 --- a/deps/v8/src/builtins/promise-any.tq +++ b/deps/v8/src/builtins/promise-any.tq @@ -26,9 +26,7 @@ extern operator '[]' macro LoadContextElementNoCell( // together with the errors array. Since all closures for a single Promise.any // call use the same context, we need to store the indices for the individual // closures somewhere else (we put them into the identity hash field of the -// closures), and we also need to have a separate marker for when the closure -// was called already (we slap the native context onto the closure in that -// case to mark it's done). See Promise.all which uses the same approach. +// closures). transitioning macro CreatePromiseAnyRejectElementContext( implicit context: Context)(capability: PromiseCapability, nativeContext: NativeContext): PromiseAnyRejectElementContext { @@ -75,36 +73,30 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( js-implicit context: Context, receiver: JSAny, target: JSFunction)( value: JSAny): JSAny { // 1. Let F be the active function object. - - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - - // 3. If alreadyCalled.[[Value]] is true, return undefined. - - // We use the function's context as the marker to remember whether this - // reject element closure was already called. It points to the reject - // element context (which is a FunctionContext) until it was called the - // first time, in which case we make it point to the native context here - // to mark this reject element closure as done. - if (IsNativeContext(context)) deferred { - return Undefined; - } - dcheck( context.length == SmiTag( PromiseAnyRejectElementContextSlots::kPromiseAnyRejectElementLength)); const context = %RawDownCast(context); - // 4. Set alreadyCalled.[[Value]] to true. - const nativeContext = LoadNativeContext(context); - target.context = nativeContext; - // 5. Let index be F.[[Index]]. dcheck(kPropertyArrayNoHashSentinel == 0); const identityHash = LoadJSReceiverIdentityHash(target) otherwise unreachable; dcheck(ChangeUint32ToWord(identityHash) < kSmiMaxValue); const index = Signed(ChangeUint32ToWord(identityHash)) - 1; + // 8. Let remainingElementsCount be F.[[RemainingElements]]. + let remainingElementsCount = *ContextSlot( + context, + PromiseAnyRejectElementContextSlots:: + kPromiseAnyRejectElementRemainingSlot); + + // If all promises were already rejected, the remaining count will already be + // 0. + if (remainingElementsCount == 0) deferred { + return Undefined; + } + // 6. Let errors be F.[[Errors]]. let errorsRef:&FixedArray = ContextSlot( context, @@ -113,12 +105,6 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. - let remainingElementsCount = *ContextSlot( - context, - PromiseAnyRejectElementContextSlots:: - kPromiseAnyRejectElementRemainingSlot); - // 9. Set errors[index] to x. // The max computation below is an optimization to avoid excessive allocations @@ -138,11 +124,22 @@ transitioning javascript builtin PromiseAnyRejectElementClosure( errors, 0, errors.length_intptr, newCapacity, PromiseHole); *errorsRef = errors; } + + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 3. If alreadyCalled.[[Value]] is true, return undefined. + // 4. Set alreadyCalled.[[Value]] to true. + if (errors.objects[index] != PromiseHole) deferred { + return Undefined; + } + + // Make sure that we never see the PromiseHole here as a result. + check(value != PromiseHole); errors.objects[index] = value; // 10. Set remainingElementsCount.[[Value]] to // remainingElementsCount.[[Value]] - 1. remainingElementsCount = remainingElementsCount - 1; + check(remainingElementsCount >= 0); *ContextSlot( context, PromiseAnyRejectElementContextSlots:: diff --git a/deps/v8/src/compiler/js-generic-lowering.cc b/deps/v8/src/compiler/js-generic-lowering.cc index 2affa53f2fba..d32bb061c04a 100644 --- a/deps/v8/src/compiler/js-generic-lowering.cc +++ b/deps/v8/src/compiler/js-generic-lowering.cc @@ -759,9 +759,6 @@ void JSGenericLowering::LowerJSGetTemplateObject(Node* node) { SharedFunctionInfoRef shared = p.shared(); TemplateObjectDescriptionRef description = p.description(); - DCHECK_EQ(node->op()->ControlInputCount(), 1); - node->RemoveInput(NodeProperties::FirstControlIndex(node)); - static_assert(JSGetTemplateObjectNode::FeedbackVectorIndex() == 0); node->InsertInput(zone(), 0, jsgraph()->ConstantNoHole(shared, broker())); node->InsertInput(zone(), 1, diff --git a/deps/v8/src/compiler/js-operator.cc b/deps/v8/src/compiler/js-operator.cc index 5aec87c5ad38..75810fa1a732 100644 --- a/deps/v8/src/compiler/js-operator.cc +++ b/deps/v8/src/compiler/js-operator.cc @@ -1614,7 +1614,7 @@ const Operator* JSOperatorBuilder::GetTemplateObject( GetTemplateObjectParameters parameters(description, shared, feedback); return zone()->New>( // -- IrOpcode::kJSGetTemplateObject, // opcode - Operator::kEliminatable, // properties + Operator::kNoDeopt | Operator::kNoThrow, // properties "JSGetTemplateObject", // name 1, 1, 1, 1, 1, 0, // counts parameters); // parameter diff --git a/deps/v8/src/compiler/turboshaft/pipelines.cc b/deps/v8/src/compiler/turboshaft/pipelines.cc index 3c773aeac12d..4aff92cb2a84 100644 --- a/deps/v8/src/compiler/turboshaft/pipelines.cc +++ b/deps/v8/src/compiler/turboshaft/pipelines.cc @@ -85,6 +85,21 @@ bool Pipeline::AllocateRegisters(const RegisterConfiguration* config, RUN_MAYBE_ABORT(DecideSpillingModePhase); RUN_MAYBE_ABORT(AssignSpillSlotsPhase); + + // The prologue grows the frame with a single stack pointer decrement, before + // the function entry stack check runs. A frame no larger than the smallest OS + // page cannot step over a guard page, so bail out rather than emit an + // unchecked stack pointer jump of arbitrary size. + // When replacing this bailout with a different strategy, consider doing the + // same in Maglev as well. + constexpr int kMaxStackSlots = 4 * KB / kSystemPointerSize; + if (data_->info()->IsOptimizing() && + data_->frame()->GetTotalFrameSlotCount() > kMaxStackSlots) { + data_->info()->AbortOptimization(BailoutReason::kFunctionTooBig); + data()->ClearRegisterComponent(); + return false; + } + RUN_MAYBE_ABORT(CommitAssignmentPhase); // TODO(chromium:725559): remove this check once diff --git a/deps/v8/src/heap/cppgc-internal/marker.h b/deps/v8/src/heap/cppgc-internal/marker.h index 84fec0712628..ad96e22f188b 100644 --- a/deps/v8/src/heap/cppgc-internal/marker.h +++ b/deps/v8/src/heap/cppgc-internal/marker.h @@ -10,6 +10,7 @@ #include "include/cppgc/heap.h" #include "include/cppgc/platform.h" #include "include/cppgc/visitor.h" +#include "src/base/logging.h" #include "src/base/macros.h" #include "src/base/platform/time.h" #include "src/heap/base/incremental-marking-schedule.h" @@ -238,20 +239,15 @@ class V8_EXPORT_PRIVATE Marker final : public MarkerBase { template void MarkerBase::WriteBarrierForObject(HeapObjectHeader& header) { - // The barrier optimizes for the bailout cases: - // - kDijkstra: Marked objects. - // - kSteele: Unmarked objects. - switch (type) { - case MarkerBase::WriteBarrierType::kDijkstra: - if (!header.TryMarkAtomic()) { - return; - } - break; - case MarkerBase::WriteBarrierType::kSteele: - if (!header.IsMarked()) { - return; - } - break; + // The Dijkstra barrier bails out on marked objects. + // The Steele barrier marks the object regardless of its current state. + // It's important to make sure the object is marked here since unmarking + // below for in construction objects expects a marked object. + const bool was_already_marked = !header.TryMarkAtomic(); + if (type == MarkerBase::WriteBarrierType::kDijkstra) { + if (was_already_marked) { + return; + } } // The barrier fired. Filter out in-construction objects here. This possibly @@ -267,13 +263,18 @@ void MarkerBase::WriteBarrierForObject(HeapObjectHeader& header) { return; } - switch (type) { - case MarkerBase::WriteBarrierType::kDijkstra: - mutator_marking_state_.write_barrier_worklist().Push(&header); - break; - case MarkerBase::WriteBarrierType::kSteele: - mutator_marking_state_.retrace_marked_objects_worklist().Push(&header); - break; + DCHECK_IMPLIES(was_already_marked, + type == MarkerBase::WriteBarrierType::kSteele); + if ((type == MarkerBase::WriteBarrierType::kDijkstra) || + !was_already_marked) { + // Push previously unmarked objects to the write barrier worklist, even for + // the Steele barrier, since popping from that worklist would also update + // the marked bytes counter. + mutator_marking_state_.write_barrier_worklist().Push(&header); + } else { + DCHECK((type == MarkerBase::WriteBarrierType::kSteele) && + was_already_marked); + mutator_marking_state_.retrace_marked_objects_worklist().Push(&header); } } diff --git a/deps/v8/src/maglev/maglev-compiler.cc b/deps/v8/src/maglev/maglev-compiler.cc index 06dd7c5e3010..74236225fc0a 100644 --- a/deps/v8/src/maglev/maglev-compiler.cc +++ b/deps/v8/src/maglev/maglev-compiler.cc @@ -285,6 +285,8 @@ bool MaglevCompiler::Compile(LocalIsolate* local_isolate, // FunctionEntryStackCheck runs. A frame no larger than the smallest OS page // cannot step over a guard page, so bail out rather than emit an unchecked // stack pointer jump of arbitrary size. + // When replacing this bailout with a different strategy, consider doing the + // same in Turboshaft as well. if (graph->tagged_stack_slots() + graph->untagged_stack_slots() > kMaxStackSlots) { return false; diff --git a/deps/v8/src/maglev/maglev-graph-builder.cc b/deps/v8/src/maglev/maglev-graph-builder.cc index ca199bab0fab..d9ed8b2c9fbd 100644 --- a/deps/v8/src/maglev/maglev-graph-builder.cc +++ b/deps/v8/src/maglev/maglev-graph-builder.cc @@ -6368,6 +6368,7 @@ void MaglevGraphBuilder::AdvanceThroughContinuationForPolymorphicPropertyLoad( while (iterator_.current_offset() < continuation.last_continuation) { iterator_.Advance(); UpdateSourceAndBytecodePosition(iterator_.current_offset()); + PrewalkBytecode(); } } diff --git a/deps/v8/src/objects/js-function.cc b/deps/v8/src/objects/js-function.cc index 585d97d436c3..310ed57d8ade 100644 --- a/deps/v8/src/objects/js-function.cc +++ b/deps/v8/src/objects/js-function.cc @@ -991,8 +991,9 @@ void JSFunction::EnsureHasInitialMap(Isolate* isolate, // system. // TODO(leszeks): Figure out if this CHECK is needed. CHECK(IsJSReceiver(*prototype)); - JSFunction::SetInitialMap(isolate, function, map, prototype); + // Initialize slack tracking before publishing the map to concurrent readers. map->StartInobjectSlackTracking(); + JSFunction::SetInitialMap(isolate, function, map, prototype); } namespace { @@ -1183,11 +1184,12 @@ bool FastInitializeDerivedMap(Isolate* isolate, Map::CopyInitialMap(isolate, constructor_initial_map, instance_size, in_object_properties, unused_property_fields); map->set_new_target_is_base(false); + // Initialize slack tracking before publishing the map to concurrent readers. + map->set_construction_counter(Map::kNoSlackTracking); + map->StartInobjectSlackTracking(); DirectHandle prototype(new_target->instance_prototype(), isolate); JSFunction::SetInitialMap(isolate, new_target, map, prototype, constructor); DCHECK(IsJSReceiver(new_target->instance_prototype())); - map->set_construction_counter(Map::kNoSlackTracking); - map->StartInobjectSlackTracking(); return true; } diff --git a/deps/v8/src/objects/map-inl.h b/deps/v8/src/objects/map-inl.h index 1cbddb0d9879..63cfe9edd10f 100644 --- a/deps/v8/src/objects/map-inl.h +++ b/deps/v8/src/objects/map-inl.h @@ -1258,9 +1258,12 @@ void Map::InobjectSlackTrackingStep(Isolate* isolate) { DCHECK(IsUndefined(GetBackPointer())); if (!this->IsInobjectSlackTrackingInProgress()) return; int counter = construction_counter(); - set_construction_counter(counter - 1); if (counter == kSlackTrackingCounterEnd) { + // The last decrement must happen under the map_updater_access mutex, + // together with the instance size changes. MapUpdater::CompleteInobjectSlackTracking(isolate, this); + } else { + set_construction_counter(counter - 1); } } diff --git a/deps/v8/src/objects/map-updater.cc b/deps/v8/src/objects/map-updater.cc index f1437356d69e..84999a3a34da 100644 --- a/deps/v8/src/objects/map-updater.cc +++ b/deps/v8/src/objects/map-updater.cc @@ -400,7 +400,9 @@ std::optional> MapUpdater::TryUpdateNoLock(Isolate* isolate, ElementsKind to_kind = old_map->elements_kind(); IntegrityLevelTransitionInfo info(old_map); - if (root_map->is_extensible() != old_map->is_extensible()) { + if (!root_map->is_extensible() && old_map != root_map) { + return {}; + } else if (root_map->is_extensible() != old_map->is_extensible()) { DCHECK(!old_map->is_extensible()); DCHECK(root_map->is_extensible()); info = DetectIntegrityLevelTransitions(old_map, isolate, &no_gc, cmode); @@ -412,7 +414,7 @@ std::optional> MapUpdater::TryUpdateNoLock(Isolate* isolate, DCHECK(to_kind == DICTIONARY_ELEMENTS || to_kind == SLOW_STRING_WRAPPER_ELEMENTS || IsTypedArrayOrRabGsabTypedArrayElementsKind(to_kind) || - IsAnyHoleyNonextensibleElementsKind(to_kind)); + IsAnyNonextensibleElementsKind(to_kind)); to_kind = info.integrity_level_source_map->elements_kind(); } if (from_kind != to_kind) { @@ -622,6 +624,8 @@ MapUpdater::State MapUpdater::FindRootMap() { direct_handle(root_map_->prototype(), isolate_), root_map_->instance_type())) { return Normalize("Normalize_NotEquivalent"); + } else if (!root_map_->is_extensible() && *old_map_ != *root_map_) { + return Normalize("Normalize_PrivateSymbolsOnNonExtensible"); } else if (old_map_->is_extensible() != root_map_->is_extensible()) { DCHECK(!old_map_->is_extensible()); DCHECK(root_map_->is_extensible()); diff --git a/deps/v8/src/objects/map.cc b/deps/v8/src/objects/map.cc index a6f6a26a9451..cdc41455019e 100644 --- a/deps/v8/src/objects/map.cc +++ b/deps/v8/src/objects/map.cc @@ -603,10 +603,7 @@ MaybeHandle Map::CopyWithField(Isolate* isolate, DirectHandle map, MaybeObjectDirectHandle wrapped_type = WrapFieldType(type); Descriptor d = Descriptor::DataField(name, offset, attributes, constness, representation, wrapped_type); - Handle new_map = Map::CopyAddDescriptor(isolate, map, &d, flag); - - new_map->AccountAddedPropertyField(); - return new_map; + return Map::CopyAddDescriptor(isolate, map, &d, flag); } MaybeHandle Map::CopyWithConstant(Isolate* isolate, DirectHandle map, @@ -1615,6 +1612,10 @@ Handle Map::ShareDescriptor(Isolate* isolate, DirectHandle map, DisallowGarbageCollection no_gc; descriptors->Append(descriptor); result->InitializeDescriptors(*descriptors); + // Account for the field before publishing the transition. + if (descriptor->GetDetails().location() == PropertyLocation::kField) { + result->AccountAddedPropertyField(); + } } DCHECK(result->NumberOfOwnDescriptors() == map->NumberOfOwnDescriptors() + 1); @@ -2333,7 +2334,12 @@ Handle Map::CopyAddDescriptor(Isolate* isolate, DirectHandle map, DescriptorArray::CopyUpTo(isolate, descriptors, nof, 1); new_descriptors->Append(descriptor); - return CopyReplaceDescriptors(isolate, map, new_descriptors, flag, + auto InitMap = [&](DirectHandle new_map) { + if (descriptor->GetDetails().location() == PropertyLocation::kField) { + new_map->AccountAddedPropertyField(); + } + }; + return CopyReplaceDescriptors(isolate, map, new_descriptors, flag, InitMap, descriptor->GetKey(), "CopyAddDescriptor", SIMPLE_PROPERTY_TRANSITION); } diff --git a/deps/v8/src/runtime/runtime-literals.cc b/deps/v8/src/runtime/runtime-literals.cc index 312f5ddcf904..4a3cfca512de 100644 --- a/deps/v8/src/runtime/runtime-literals.cc +++ b/deps/v8/src/runtime/runtime-literals.cc @@ -856,6 +856,14 @@ static bool IsDefaultFunctionPrototype(DirectHandle js_proto, // Object function prototype's map. Tagged proto_map = js_proto->map(); + // Check that the prototype is a plain JSObject with no elements. + if (proto_map->instance_type() != JS_OBJECT_TYPE) { + return false; + } + if (js_proto->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) { + return false; + } + // Check that given function.prototype object has a default initial state: // it's extensible. if (!proto_map->is_extensible()) { @@ -868,13 +876,24 @@ static bool IsDefaultFunctionPrototype(DirectHandle js_proto, } // it has exactly one "constructor" property installed. - if (js_proto->property_dictionary()->NumberOfElements() != 1) { - return false; - } - if (js_proto->property_dictionary() - ->FindEntry(isolate, isolate->factory()->constructor_string()) - .is_not_found()) { - return false; + if constexpr (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) { + if (js_proto->property_dictionary_swiss()->NumberOfElements() != 1) { + return false; + } + if (js_proto->property_dictionary_swiss() + ->FindEntry(isolate, isolate->factory()->constructor_string()) + .is_not_found()) { + return false; + } + } else { + if (js_proto->property_dictionary()->NumberOfElements() != 1) { + return false; + } + if (js_proto->property_dictionary() + ->FindEntry(isolate, isolate->factory()->constructor_string()) + .is_not_found()) { + return false; + } } // its prototype is the original and unmodified Object.prototype object. @@ -907,7 +926,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { } DirectHandle acc_fun = Cast(obj); - if (!acc_fun->has_prototype_slot()) { + if (!acc_fun->has_prototype_slot() || !acc_fun->has_prototype_property()) { RETURN_RESULT_OR_FAILURE( isolate, SetPrototypePropertiesSlow(isolate, context, obj, object_boilerplate_description, @@ -937,18 +956,21 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { feedback_cell_array, current_slot)); } - if (IsSpecialReceiverMap(js_proto->map())) { - RETURN_RESULT_OR_FAILURE( - isolate, SetPrototypePropertiesSlow(isolate, context, obj, - object_boilerplate_description, - feedback_cell_array, current_slot)); - } - - if (!JSObject::IsExtensible(isolate, js_proto)) { - RETURN_RESULT_OR_FAILURE( - isolate, SetPrototypePropertiesSlow(isolate, context, obj, - object_boilerplate_description, - feedback_cell_array, current_slot)); + // Use the slow path for exotic receivers, interceptors, or access checks. + { + Tagged js_proto_map = js_proto->map(); + if (IsCustomElementsReceiverMap(js_proto_map) || + IsJSTypedArrayMap(js_proto_map) || + IsAlwaysSharedSpaceJSObjectMap(js_proto_map) || + js_proto_map->is_access_check_needed() || + js_proto_map->has_named_interceptor() || + js_proto_map->has_indexed_interceptor() || + !JSObject::IsExtensible(isolate, js_proto)) { + RETURN_RESULT_OR_FAILURE( + isolate, SetPrototypePropertiesSlow( + isolate, context, obj, object_boilerplate_description, + feedback_cell_array, current_slot)); + } } bool is_default_func_prototype = @@ -977,13 +999,14 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { DirectHandle accessor_info = isolate->factory()->lazy_closure_accessor(); - JSObject::SetAccessor(js_proto, name, accessor_info, - PropertyAttributes::NONE) - .Check(); + RETURN_FAILURE_ON_EXCEPTION( + isolate, JSObject::SetAccessor(js_proto, name, accessor_info, + PropertyAttributes::NONE)); } else { - Object::TransitionAndWriteDataProperty( - &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) - .Check(); + MAYBE_RETURN( + Object::TransitionAndWriteDataProperty( + &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed), + ReadOnlyRoots(isolate).exception()); } result = value; } @@ -1029,7 +1052,8 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { current_slot++; } it.UpdateProtector(); - Object::SetDataProperty(&it, value).Check(); + MAYBE_RETURN(Object::SetDataProperty(&it, value), + ReadOnlyRoots(isolate).exception()); } else { value = InstantiateIfSharedFunctionInfo( context, isolate, js_proto, value, feedback_cell_array, @@ -1042,13 +1066,15 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) { // doesn't invalidate protectors, so do it here for e.g. `then` or // `next` added to a watched prototype. it.UpdateProtector(); - JSObject::SetAccessor(js_proto, Cast(key), accessor_info, - PropertyAttributes::NONE) - .Check(); + RETURN_FAILURE_ON_EXCEPTION( + isolate, + JSObject::SetAccessor(js_proto, Cast(key), accessor_info, + PropertyAttributes::NONE)); } else { - Object::TransitionAndWriteDataProperty( - &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed) - .Check(); + MAYBE_RETURN( + Object::TransitionAndWriteDataProperty( + &it, value, NONE, Just(kDontThrow), StoreOrigin::kNamed), + ReadOnlyRoots(isolate).exception()); } } result = value; diff --git a/deps/v8/test/mjsunit/compiler/regress-574339635.js b/deps/v8/test/mjsunit/compiler/regress-574339635.js new file mode 100644 index 000000000000..f131352bccf4 --- /dev/null +++ b/deps/v8/test/mjsunit/compiler/regress-574339635.js @@ -0,0 +1,37 @@ +// 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. + +// Flags: --turbofan --allow-natives-syntax + +const P = Array.prototype; +const Obj = Object; +let s; + +function foo(o, t) { + o[0]; + let r = 0; + if (t) r = Obj`hello`; + const v = o.x; + s = r; + return v; +} + +new WeakMap().set(P, 1); +for (const _ in []) {} +P[0] = 1; +P.x = 1; +Object.defineProperty(P, "x", {value: 1.5, enumerable: false}); + +%PrepareFunctionForOptimization(foo); +foo(P, false); foo(P, false); foo(P, false); +%OptimizeFunctionOnNextCall(foo); +assertEquals(1.5, foo(P, false)); + +for (let i = 0; i < 400; i += 3) { + P["p" + i] = "x"; + P["p" + (i + 1)] = "corrupted"; + P["p" + (i + 2)] = 0; +} + +assertEquals(1.5, foo(P, true)); diff --git a/deps/v8/test/mjsunit/compiler/regress-578688670.js b/deps/v8/test/mjsunit/compiler/regress-578688670.js new file mode 100644 index 000000000000..b16f0752f28e --- /dev/null +++ b/deps/v8/test/mjsunit/compiler/regress-578688670.js @@ -0,0 +1,56 @@ +// 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. + +// Flags: --allow-natives-syntax + +let rejected_error; +function CustomPromise(executor) { + executor(() => {}, (err) => { rejected_error = err; }); +} + +let captured_reject_closure; +CustomPromise.resolve = function(thenable) { + return thenable; +}; + +const pending_thenables = [ + { then(resolve, reject) { captured_reject_closure = reject; } }, + { then() {} }, + { then() {} }, +]; + +Promise.any.call(CustomPromise, pending_thenables); + +// Store the reject element closure on a constant holder so that inlining +// invoke_reject_closure into call_reject_closure constant-folds the closure +// target and its initial FunctionContext. +const closure_holder = { reject: captured_reject_closure }; + +function invoke_reject_closure(closure, should_call) { + if (should_call) closure('err'); +} + +function call_reject_closure(should_call) { + invoke_reject_closure(closure_holder.reject, should_call); +} + +// Make the call site inside invoke_reject_closure polymorphic before +// calling captured_reject_closure so Maglev/TurboFan compile the call without +// invoking captured_reject_closure during warmup. +%PrepareFunctionForOptimization(invoke_reject_closure); +invoke_reject_closure(() => {}, true); +invoke_reject_closure((x) => x, true); + +%PrepareFunctionForOptimization(call_reject_closure); +call_reject_closure(false); +%OptimizeFunctionOnNextCall(call_reject_closure); +call_reject_closure(false); + +// Repeated calls to the same reject element closure must be ignored after the +// first call and must not decrement remainingElementsCount to 0. +call_reject_closure(true); +call_reject_closure(true); +call_reject_closure(true); + +assertEquals(undefined, rejected_error); diff --git a/deps/v8/test/mjsunit/maglev/regress-574021176.js b/deps/v8/test/mjsunit/maglev/regress-574021176.js new file mode 100644 index 000000000000..780b5b1c8cfa --- /dev/null +++ b/deps/v8/test/mjsunit/maglev/regress-574021176.js @@ -0,0 +1,44 @@ +// 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. + +// Flags: --allow-natives-syntax + +let g = () => class {}; + +function* gen(o) { + // Context-allocated, maybe-assigned variable so the function context uses a + // ContextCell for `x`. + let x = 42; + () => x = 0; + + // Polymorphic property load with a call continuation. The class literal + // pushes a `CLASS_SCOPE` context (needed for `super.x`) before evaluating + // `extends g()` and pops it after defining the class. When `g()` aborts in + // every polymorphic arm, skipping the rest of the continuation must still pop + // that `CLASS_SCOPE`. + o.f(class extends g() { + constructor() { + super.x; + } + }); + + // In dead code after all polymorphic arms abort, `SuspendGenerator` records + // `current_scope_info_` on the `ResumeGenerator` merge state. + yield; + + // Loads `x` using the `ScopeInfo` from the resume merge state (which must be + // the generator's `FUNCTION_SCOPE` with cells, not the leaked `CLASS_SCOPE`). + return x; +} + +const suspended = gen({f() {}}); +suspended.next(); + +g = class {}; +%PrepareFunctionForOptimization(gen); +assertThrows(() => gen({f() {}}).next(), TypeError); +assertThrows(() => gen({a: 1, f() {}}).next(), TypeError); + +%OptimizeFunctionOnNextCall(gen); +assertEquals(42, suspended.next().value); diff --git a/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js b/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js new file mode 100644 index 000000000000..e01a1280d53f --- /dev/null +++ b/deps/v8/test/mjsunit/opt-proto-seq/regress-577944790.js @@ -0,0 +1,65 @@ +// 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. + +// Flags: --mock-arraybuffer-allocator + +// Test exception propagation when a TypedArray prototype rejects a BigInt store. +(function testLargeTypedArrayStoreException() { + let ta; + try { + ta = new Int8Array(4294967296); + } catch (e) { + assertInstanceof(e, RangeError); + return; + } + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1n; + F.prototype.named = 1; + }, TypeError); + + assertEquals(undefined, ta.named); +})(); + +// Test exception propagation when valueOf throws during a TypedArray store. +(function testLargeTypedArrayValueOfException() { + let ta; + try { + ta = new Uint8Array(4294967296); + } catch (e) { + assertInstanceof(e, RangeError); + return; + } + + const badValue = { + valueOf() { + throw new URIError(); + }, + }; + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = badValue; + F.prototype.named = 2; + }, URIError); + + assertEquals(undefined, ta.named); +})(); + +// Test exception propagation on a small BigInt64Array prototype. +(function testBigIntTypedArrayStoreException() { + const ta = new BigInt64Array(4); + + assertThrows(() => { + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1; + F.prototype.named = 3; + }, TypeError); + + assertEquals(undefined, ta.named); +})(); diff --git a/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js b/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js new file mode 100644 index 000000000000..30e50a33d98e --- /dev/null +++ b/deps/v8/test/mjsunit/opt-proto-seq/regress-578083257.js @@ -0,0 +1,63 @@ +// 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. + +// Create a dictionary-mode TypedArray that mimics a default function prototype. +function makeDictionaryTypedArray() { + const ta = new Uint8Array(4); + ta.temp = 1; + delete ta.temp; + ta.constructor = Object; + Object.setPrototypeOf(ta, Object.prototype); + return ta; +} + +// Test integer indices above kMaxElementIndex on a TypedArray prototype. +(function testTypedArrayLargeIntegerIndices() { + const ta = makeDictionaryTypedArray(); + function F() {} + F.prototype = ta; + F.prototype['4294967295'] = 1; + F.prototype['4294967296'] = 2; + F.prototype.named = 3; + + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '4294967295')); + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '4294967296')); + assertEquals(undefined, ta['4294967295']); + assertEquals(undefined, ta['4294967296']); + assertEquals(3, ta.named); +})(); + +// Test canonical numeric index strings on a TypedArray prototype. +(function testTypedArraySpecialNumericIndices() { + const ta = makeDictionaryTypedArray(); + function F() {} + F.prototype = ta; + F.prototype['1.5'] = 1; + F.prototype['-0'] = 2; + F.prototype.named = 3; + + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '1.5')); + assertEquals(undefined, Object.getOwnPropertyDescriptor(ta, '-0')); + assertEquals(undefined, ta['1.5']); + assertEquals(undefined, ta['-0']); + assertEquals(3, ta.named); +})(); + +// Test a dictionary-mode String wrapper as a function prototype. +(function testStringWrapperPrototype() { + const wrapper = new String('abc'); + wrapper.temp = 1; + delete wrapper.temp; + wrapper.constructor = Object; + Object.setPrototypeOf(wrapper, Object.prototype); + + function F() {} + F.prototype = wrapper; + F.prototype.first = 10; + F.prototype.second = 20; + + assertEquals(10, wrapper.first); + assertEquals(20, wrapper.second); + assertEquals('a', wrapper[0]); +})(); diff --git a/deps/v8/test/mjsunit/regress/regress-577318603.js b/deps/v8/test/mjsunit/regress/regress-577318603.js new file mode 100644 index 000000000000..e89da0196585 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-577318603.js @@ -0,0 +1,74 @@ +// 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. + +// Flags: --no-js-nonextensible-applies-to-private + +class Base { + constructor(o) { + return o; + } +} + +class InstallPrivateField extends Base { + #p = 1; + static setP(o, v) { + o.#p = v; + } + static getP(o) { + return o.#p; + } +} + +// Private field added onto a non-extensible root map before Object.seal. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + new InstallPrivateField(obj); + Object.seal(obj); + InstallPrivateField.setP(obj, 1.5); + + assertEquals(1.5, InstallPrivateField.getP(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} + +// Object.seal on a non-extensible root map before adding a private field. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + Object.seal(obj); + new InstallPrivateField(obj); + InstallPrivateField.setP(obj, 1.5); + + assertEquals(1.5, InstallPrivateField.getP(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} + +// Constness generalization of a root-map property after Object.seal on a +// non-extensible dictionary-elements root map must not drop the seal +// transition. +{ + const obj = new Date(); + obj[0] = 1; + obj.x = 1; + + Object.preventExtensions(obj); + Object.defineProperty(obj, 0, { enumerable: false }); + + Object.seal(obj); + obj.x = 2; + + assertEquals(2, obj.x); + assertTrue(Object.isSealed(obj)); + assertFalse(Object.getOwnPropertyDescriptor(obj, 'x').configurable); +} diff --git a/deps/v8/test/mjsunit/turboshaft/regress-574660146.js b/deps/v8/test/mjsunit/turboshaft/regress-574660146.js new file mode 100644 index 000000000000..bc82a2684baa --- /dev/null +++ b/deps/v8/test/mjsunit/turboshaft/regress-574660146.js @@ -0,0 +1,41 @@ +// 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. + +// Flags: --allow-natives-syntax --turbofan --no-maglev + +function cb() { + return arguments.length; +} +%NeverOptimizeFunction(cb); + +// Passing `kArgCount` distinct expressions to a non-inlined call keeps all of +// them live simultaneously at the call site, requiring > 1024 stack slots +// (> 4KB on both 32-bit and 64-bit targets) while keeping the bytecode small +// enough to be compiled. +const kArgCount = 1100; +const args = Array.from({length: kArgCount}, (_, k) => `p ^ ${k + 1}`); + +function buildWideFrame(osr) { + return Function('x', 'cb', 'p', ` + let result = 0; + for (let i = 0; i < 10; ++i) { + ${osr ? 'if (i === 5) %OptimizeOsr();' : ''} + if (x < 0) return cb(${args}); + result = (x + i) | 0; + } + return result; + `); +} + +const wide = buildWideFrame(false); +%PrepareFunctionForOptimization(wide); +assertEquals(kArgCount, wide(-1, cb, 1)); +assertEquals(10, wide(1, cb, 1)); +%OptimizeFunctionOnNextCall(wide); +assertEquals(10, wide(1, cb, 1)); + +const wideOsr = buildWideFrame(true); +%PrepareFunctionForOptimization(wideOsr); +assertEquals(kArgCount, wideOsr(-1, cb, 1)); +assertEquals(10, wideOsr(1, cb, 1)); diff --git a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc index 9df26a108ca6..bfe99ffa50e1 100644 --- a/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc +++ b/deps/v8/test/unittests/heap/cppgc/write-barrier-unittest.cc @@ -476,11 +476,11 @@ TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersWhenMarkingIsOn) { } } -TEST_F(WriteBarrierTest, SteeleWriteBarrierBailoutIfNotMarked) { +TEST_F(WriteBarrierTest, SteeleWriteBarrierTriggersEvenIfNotMarked) { auto* object1 = MakeGarbageCollected(GetAllocationHandle()); auto* object2 = MakeGarbageCollected(GetAllocationHandle(), object1); { - ExpectNoWriteBarrierFires scope(marker(), {object1}); + ExpectWriteBarrierFires scope(marker(), {object1}); WriteBarrierParams params; EXPECT_EQ(WriteBarrierType::kMarking, HeapConsistency::GetWriteBarrierType(