1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibJS: Do more comprehensive check if next() fast path is possible

Before this change each built-in iterator object has a boolean
`m_next_method_was_redefined`. If user code later changed the iterator’s
prototype (e.g. `Object.setPrototypeOf()`), we still believed the
built-in fast-path was safe and skipped the user supplied override,
producing wrong results.

With this change
`BuiltinIterator::as_builtin_iterator_if_next_is_not_redefined()` looks
up the current `next` property and verifies that it is still the
built-in native function.
This commit is contained in:
Aliaksandr Kalenik 2025-06-01 18:44:18 +02:00 committed by Alexander Kalenik
parent 0fcb574041
commit 285bc005cb
Notes: github-actions[bot] 2025-06-01 22:16:38 +00:00
25 changed files with 168 additions and 98 deletions

View file

@ -12,20 +12,24 @@
namespace JS::Bytecode {
// TitleCaseName, snake_case_name, base, property, argument_count
#define JS_ENUMERATE_BUILTINS(O) \
O(MathAbs, math_abs, Math, abs, 1) \
O(MathLog, math_log, Math, log, 1) \
O(MathPow, math_pow, Math, pow, 2) \
O(MathExp, math_exp, Math, exp, 1) \
O(MathCeil, math_ceil, Math, ceil, 1) \
O(MathFloor, math_floor, Math, floor, 1) \
O(MathImul, math_imul, Math, imul, 2) \
O(MathRandom, math_random, Math, random, 0) \
O(MathRound, math_round, Math, round, 1) \
O(MathSqrt, math_sqrt, Math, sqrt, 1) \
O(MathSin, math_sin, Math, sin, 1) \
O(MathCos, math_cos, Math, cos, 1) \
O(MathTan, math_tan, Math, tan, 1)
#define JS_ENUMERATE_BUILTINS(O) \
O(MathAbs, math_abs, Math, abs, 1) \
O(MathLog, math_log, Math, log, 1) \
O(MathPow, math_pow, Math, pow, 2) \
O(MathExp, math_exp, Math, exp, 1) \
O(MathCeil, math_ceil, Math, ceil, 1) \
O(MathFloor, math_floor, Math, floor, 1) \
O(MathImul, math_imul, Math, imul, 2) \
O(MathRandom, math_random, Math, random, 0) \
O(MathRound, math_round, Math, round, 1) \
O(MathSqrt, math_sqrt, Math, sqrt, 1) \
O(MathSin, math_sin, Math, sin, 1) \
O(MathCos, math_cos, Math, cos, 1) \
O(MathTan, math_tan, Math, tan, 1) \
O(ArrayIteratorPrototypeNext, array_iterator_prototype_next, ArrayIteratorPrototype, next, 0) \
O(MapIteratorPrototypeNext, map_iterator_prototype_next, MapIteratorPrototype, next, 0) \
O(SetIteratorPrototypeNext, set_iterator_prototype_next, SetIteratorPrototype, next, 0) \
O(StringIteratorPrototypeNext, string_iterator_prototype_next, StringIteratorPrototype, next, 0)
enum class Builtin : u8 {
#define DEFINE_BUILTIN_ENUM(name, ...) name,

View file

@ -1799,7 +1799,7 @@ class JS_API PropertyNameIterator final
public:
virtual ~PropertyNameIterator() override = default;
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override { return this; }
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) override { return this; }
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override
{
while (true) {
@ -2826,6 +2826,11 @@ static ThrowCompletionOr<Value> dispatch_builtin_call(Bytecode::Interpreter& int
return TRY(MathObject::cos_impl(interpreter.vm(), interpreter.get(arguments[0])));
case Builtin::MathTan:
return TRY(MathObject::tan_impl(interpreter.vm(), interpreter.get(arguments[0])));
case Builtin::ArrayIteratorPrototypeNext:
case Builtin::MapIteratorPrototypeNext:
case Builtin::SetIteratorPrototypeNext:
case Builtin::StringIteratorPrototypeNext:
VERIFY_NOT_REACHED();
case Bytecode::Builtin::__Count:
VERIFY_NOT_REACHED();
}

View file

@ -29,8 +29,6 @@ ArrayIterator::ArrayIterator(Value array, Object::PropertyKind iteration_kind, O
, m_array(array)
, m_iteration_kind(iteration_kind)
{
auto& array_iterator_prototype = as<ArrayIteratorPrototype>(prototype);
m_next_method_was_redefined = array_iterator_prototype.next_method_was_redefined();
}
void ArrayIterator::visit_edges(Cell::Visitor& visitor)
@ -39,6 +37,19 @@ void ArrayIterator::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_array);
}
BuiltinIterator* ArrayIterator::as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const& iterator_record)
{
if (iterator_record.next_method.is_object()) {
auto const& next_function = iterator_record.next_method.as_object();
if (next_function.is_native_function()) {
auto const& native_function = static_cast<NativeFunction const&>(next_function);
if (native_function.is_array_prototype_next_builtin())
return this;
}
}
return nullptr;
}
ThrowCompletionOr<void> ArrayIterator::next(VM& vm, bool& done, Value& value)
{
// 1. Let O be the this value.

View file

@ -21,12 +21,7 @@ public:
virtual ~ArrayIterator() override = default;
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override
{
if (m_next_method_was_redefined)
return nullptr;
return this;
}
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) override;
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override;
private:

View file

@ -26,7 +26,7 @@ void ArrayIteratorPrototype::initialize(Realm& realm)
auto& vm = this->vm();
Base::initialize(realm);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable, Bytecode::Builtin::ArrayIteratorPrototypeNext);
// 23.1.5.2.2 %ArrayIteratorPrototype% [ @@toStringTag ], https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Array Iterator"_string), Attribute::Configurable);

View file

@ -19,17 +19,12 @@ public:
virtual void initialize(Realm&) override;
virtual ~ArrayIteratorPrototype() override = default;
bool next_method_was_redefined() const { return m_next_method_was_redefined; }
void set_next_method_was_redefined() { m_next_method_was_redefined = true; }
virtual bool is_array_iterator_prototype() const override { return true; }
private:
explicit ArrayIteratorPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(next);
bool m_next_method_was_redefined { false };
};
template<>

View file

@ -205,7 +205,7 @@ ThrowCompletionOr<Value> iterator_value(VM& vm, Object& iterator_result)
// 7.4.9 IteratorStep ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstep
ThrowCompletionOr<IterationResultOrDone> iterator_step(VM& vm, IteratorRecord& iterator_record)
{
if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator_if_next_is_not_redefined()) {
if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator_if_next_is_not_redefined(iterator_record)) {
Value value;
bool done = false;
TRY(builtin_iterator->next(vm, done, value));
@ -283,7 +283,7 @@ static Completion iterator_close_impl(VM& vm, IteratorRecord const& iterator_rec
auto iterator = iterator_record.iterator;
// OPTIMIZATION: "return" method is not defined on any of iterators we treat as built-in.
if (iterator->as_builtin_iterator_if_next_is_not_redefined())
if (iterator->as_builtin_iterator_if_next_is_not_redefined(iterator_record))
return completion;
// 3. Let innerResult be Completion(GetMethod(iterator, "return")).

View file

@ -74,9 +74,6 @@ class BuiltinIterator {
public:
virtual ~BuiltinIterator() = default;
virtual ThrowCompletionOr<void> next(VM&, bool& done, Value& value) = 0;
protected:
bool m_next_method_was_redefined { false };
};
struct IterationResult {

View file

@ -7,6 +7,7 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/MapIterator.h>
#include <LibJS/Runtime/MapIteratorPrototype.h>
#include <LibJS/Runtime/NativeFunction.h>
namespace JS {
@ -23,8 +24,6 @@ MapIterator::MapIterator(Map& map, Object::PropertyKind iteration_kind, Object&
, m_iteration_kind(iteration_kind)
, m_iterator(static_cast<Map const&>(map).begin())
{
auto& map_iterator_prototype = as<MapIteratorPrototype>(prototype);
m_next_method_was_redefined = map_iterator_prototype.next_method_was_redefined();
}
void MapIterator::visit_edges(Cell::Visitor& visitor)
@ -33,6 +32,19 @@ void MapIterator::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_map);
}
BuiltinIterator* MapIterator::as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const& iterator_record)
{
if (iterator_record.next_method.is_object()) {
auto& next_function = iterator_record.next_method.as_object();
if (next_function.is_native_function()) {
auto const& native_function = static_cast<NativeFunction const&>(next_function);
if (native_function.is_map_prototype_next_builtin())
return this;
}
}
return nullptr;
}
ThrowCompletionOr<void> MapIterator::next(VM& vm, bool& done, Value& value)
{
if (m_done) {

View file

@ -22,12 +22,7 @@ public:
virtual ~MapIterator() override = default;
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override
{
if (m_next_method_was_redefined)
return nullptr;
return this;
}
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) override;
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override;
private:

View file

@ -23,7 +23,7 @@ void MapIteratorPrototype::initialize(Realm& realm)
auto& vm = this->vm();
Base::initialize(realm);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable, Bytecode::Builtin::MapIteratorPrototypeNext);
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Map Iterator"_string), Attribute::Configurable);
}

View file

@ -19,17 +19,12 @@ public:
virtual void initialize(Realm&) override;
virtual ~MapIteratorPrototype() override = default;
bool next_method_was_redefined() const { return m_next_method_was_redefined; }
void set_next_method_was_redefined() { m_next_method_was_redefined = true; }
virtual bool is_map_iterator_prototype() const override { return true; }
private:
MapIteratorPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(next);
bool m_next_method_was_redefined { false };
};
template<>

View file

@ -32,7 +32,7 @@ void NativeFunction::visit_edges(Cell::Visitor& visitor)
// 10.3.3 CreateBuiltinFunction ( behaviour, length, name, additionalInternalSlotsList [ , realm [ , prototype [ , prefix ] ] ] ), https://tc39.es/ecma262/#sec-createbuiltinfunction
// NOTE: This doesn't consider additionalInternalSlotsList, which is rarely used, and can either be implemented using only the `function` lambda, or needs a NativeFunction subclass.
GC::Ref<NativeFunction> NativeFunction::create(Realm& allocating_realm, Function<ThrowCompletionOr<Value>(VM&)> behaviour, i32 length, PropertyKey const& name, Optional<Realm*> realm, Optional<StringView> const& prefix)
GC::Ref<NativeFunction> NativeFunction::create(Realm& allocating_realm, Function<ThrowCompletionOr<Value>(VM&)> behaviour, i32 length, PropertyKey const& name, Optional<Realm*> realm, Optional<StringView> const& prefix, Optional<Bytecode::Builtin> builtin)
{
auto& vm = allocating_realm.vm();
@ -51,7 +51,7 @@ GC::Ref<NativeFunction> NativeFunction::create(Realm& allocating_realm, Function
// 7. Set func.[[Extensible]] to true.
// 8. Set func.[[Realm]] to realm.
// 9. Set func.[[InitialName]] to null.
auto function = allocating_realm.create<NativeFunction>(move(behaviour), prototype, *realm.value());
auto function = allocating_realm.create<NativeFunction>(move(behaviour), prototype, *realm.value(), builtin);
function->unsafe_set_shape(realm.value()->intrinsics().native_function_shape());
@ -73,8 +73,9 @@ GC::Ref<NativeFunction> NativeFunction::create(Realm& realm, FlyString const& na
return realm.create<NativeFunction>(name, move(function), realm.intrinsics().function_prototype());
}
NativeFunction::NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)> native_function, Object* prototype, Realm& realm)
NativeFunction::NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)> native_function, Object* prototype, Realm& realm, Optional<Bytecode::Builtin> builtin)
: FunctionObject(realm, prototype)
, m_builtin(builtin)
, m_native_function(move(native_function))
, m_realm(&realm)
{

View file

@ -10,6 +10,7 @@
#include <AK/Badge.h>
#include <AK/Optional.h>
#include <LibGC/Function.h>
#include <LibJS/Bytecode/Builtins.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/PropertyKey.h>
@ -21,7 +22,7 @@ class NativeFunction : public FunctionObject {
GC_DECLARE_ALLOCATOR(NativeFunction);
public:
static GC::Ref<NativeFunction> create(Realm&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> behaviour, i32 length, PropertyKey const& name = FlyString {}, Optional<Realm*> = {}, Optional<StringView> const& prefix = {});
static GC::Ref<NativeFunction> create(Realm&, ESCAPING Function<ThrowCompletionOr<Value>(VM&)> behaviour, i32 length, PropertyKey const& name = FlyString {}, Optional<Realm*> = {}, Optional<StringView> const& prefix = {}, Optional<Bytecode::Builtin> builtin = {});
static GC::Ref<NativeFunction> create(Realm&, FlyString const& name, ESCAPING Function<ThrowCompletionOr<Value>(VM&)>);
virtual ~NativeFunction() override = default;
@ -42,9 +43,14 @@ public:
Optional<FlyString> const& initial_name() const { return m_initial_name; }
void set_initial_name(Badge<FunctionObject>, FlyString initial_name) { m_initial_name = move(initial_name); }
bool is_array_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::ArrayIteratorPrototypeNext; }
bool is_map_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::MapIteratorPrototypeNext; }
bool is_set_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::SetIteratorPrototypeNext; }
bool is_string_prototype_next_builtin() const { return m_builtin.has_value() && *m_builtin == Bytecode::Builtin::StringIteratorPrototypeNext; }
protected:
NativeFunction(FlyString name, Object& prototype);
NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)>, Object* prototype, Realm& realm);
NativeFunction(AK::Function<ThrowCompletionOr<Value>(VM&)>, Object* prototype, Realm& realm, Optional<Bytecode::Builtin> builtin);
NativeFunction(FlyString name, AK::Function<ThrowCompletionOr<Value>(VM&)>, Object& prototype);
explicit NativeFunction(Object& prototype);
@ -57,6 +63,7 @@ private:
FlyString m_name;
GC::Ptr<PrimitiveString> m_name_string;
Optional<FlyString> m_initial_name; // [[InitialName]]
Optional<Bytecode::Builtin> m_builtin;
AK::Function<ThrowCompletionOr<Value>(VM&)> m_native_function;
GC::Ptr<Realm> m_realm;
};

View file

@ -961,18 +961,6 @@ ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Va
VERIFY(!value.is_special_empty_value());
VERIFY(!receiver.is_special_empty_value());
if (receiver.is_object() && property_key == vm().names.next) {
auto& receiver_object = receiver.as_object();
if (auto* array_iterator_prototype = as_if<ArrayIteratorPrototype>(receiver_object))
array_iterator_prototype->set_next_method_was_redefined();
else if (auto* map_iterator_prototype = as_if<MapIteratorPrototype>(receiver_object))
map_iterator_prototype->set_next_method_was_redefined();
else if (auto* set_iterator_prototype = as_if<SetIteratorPrototype>(receiver_object))
set_iterator_prototype->set_next_method_was_redefined();
else if (auto* string_iterator_prototype = as_if<StringIteratorPrototype>(receiver_object))
string_iterator_prototype->set_next_method_was_redefined();
}
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
auto own_descriptor = TRY(internal_get_own_property(property_key));
@ -1349,7 +1337,7 @@ Value Object::get_without_side_effects(PropertyKey const& property_key) const
void Object::define_native_function(Realm& realm, PropertyKey const& property_key, Function<ThrowCompletionOr<Value>(VM&)> native_function, i32 length, PropertyAttributes attribute, Optional<Bytecode::Builtin> builtin)
{
auto function = NativeFunction::create(realm, move(native_function), length, property_key, &realm);
auto function = NativeFunction::create(realm, move(native_function), length, property_key, &realm, {}, builtin);
define_direct_property(property_key, function, attribute);
if (builtin.has_value())
realm.define_builtin(builtin.value(), function);

View file

@ -212,7 +212,7 @@ public:
virtual bool is_array_iterator() const { return false; }
virtual bool is_raw_json_object() const { return false; }
virtual BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() { return nullptr; }
virtual BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) { return nullptr; }
virtual bool is_array_iterator_prototype() const { return false; }
virtual bool is_map_iterator_prototype() const { return false; }

View file

@ -5,8 +5,8 @@
*/
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/SetIterator.h>
#include <LibJS/Runtime/SetIteratorPrototype.h>
namespace JS {
@ -23,8 +23,6 @@ SetIterator::SetIterator(Set& set, Object::PropertyKind iteration_kind, Object&
, m_iteration_kind(iteration_kind)
, m_iterator(static_cast<Set const&>(set).begin())
{
auto& set_iterator_prototype = as<SetIteratorPrototype>(prototype);
m_next_method_was_redefined = set_iterator_prototype.next_method_was_redefined();
}
void SetIterator::visit_edges(Cell::Visitor& visitor)
@ -33,6 +31,19 @@ void SetIterator::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_set);
}
BuiltinIterator* SetIterator::as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const& iterator_record)
{
if (iterator_record.next_method.is_object()) {
auto const& next_function = iterator_record.next_method.as_object();
if (next_function.is_native_function()) {
auto const& native_function = static_cast<NativeFunction const&>(next_function);
if (native_function.is_set_prototype_next_builtin())
return this;
}
}
return nullptr;
}
ThrowCompletionOr<void> SetIterator::next(VM& vm, bool& done, Value& value)
{
if (m_done) {

View file

@ -22,13 +22,7 @@ public:
virtual ~SetIterator() override = default;
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override
{
if (m_next_method_was_redefined)
return nullptr;
return this;
}
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) override;
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override;
private:

View file

@ -25,7 +25,7 @@ void SetIteratorPrototype::initialize(Realm& realm)
auto& vm = this->vm();
Base::initialize(realm);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable, Bytecode::Builtin::SetIteratorPrototypeNext);
// 24.2.5.2.2 %SetIteratorPrototype% [ @@toStringTag ], https://tc39.es/ecma262/#sec-%setiteratorprototype%-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Set Iterator"_string), Attribute::Configurable);

View file

@ -19,17 +19,12 @@ public:
virtual void initialize(Realm&) override;
virtual ~SetIteratorPrototype() override = default;
bool next_method_was_redefined() const { return m_next_method_was_redefined; }
void set_next_method_was_redefined() { m_next_method_was_redefined = true; }
virtual bool is_set_iterator_prototype() const override { return true; }
private:
explicit SetIteratorPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(next);
bool m_next_method_was_redefined { false };
};
template<>

View file

@ -6,8 +6,8 @@
#include <AK/Utf8View.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/StringIterator.h>
#include <LibJS/Runtime/StringIteratorPrototype.h>
namespace JS {
@ -23,8 +23,19 @@ StringIterator::StringIterator(String string, Object& prototype)
, m_string(move(string))
, m_iterator(Utf8View(m_string).begin())
{
auto& string_iterator_prototype = as<StringIteratorPrototype>(prototype);
m_next_method_was_redefined = string_iterator_prototype.next_method_was_redefined();
}
BuiltinIterator* StringIterator::as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const& iterator_record)
{
if (iterator_record.next_method.is_object()) {
auto const& next_function = iterator_record.next_method.as_object();
if (next_function.is_native_function()) {
auto const& native_function = static_cast<NativeFunction const&>(next_function);
if (native_function.is_string_prototype_next_builtin())
return this;
}
}
return nullptr;
}
ThrowCompletionOr<void> StringIterator::next(VM& vm, bool& done, Value& value)

View file

@ -23,12 +23,7 @@ public:
virtual ~StringIterator() override = default;
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override
{
if (m_next_method_was_redefined)
return nullptr;
return this;
}
BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined(IteratorRecord const&) override;
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override;
private:

View file

@ -23,7 +23,7 @@ void StringIteratorPrototype::initialize(Realm& realm)
{
auto& vm = this->vm();
Base::initialize(realm);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
define_native_function(realm, vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable, Bytecode::Builtin::StringIteratorPrototypeNext);
// 22.1.5.1.2 %StringIteratorPrototype% [ @@toStringTag ], https://tc39.es/ecma262/#sec-%stringiteratorprototype%-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "String Iterator"_string), Attribute::Configurable);

View file

@ -20,17 +20,12 @@ public:
virtual void initialize(Realm&) override;
virtual ~StringIteratorPrototype() override = default;
bool next_method_was_redefined() const { return m_next_method_was_redefined; }
void set_next_method_was_redefined() { m_next_method_was_redefined = true; }
virtual bool is_string_iterator_prototype() const override { return true; }
private:
explicit StringIteratorPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(next);
bool m_next_method_was_redefined { false };
};
template<>

View file

@ -0,0 +1,64 @@
describe("redefine next() in built in iterators", () => {
test("override next() on individual Array iterator via setPrototypeOf", () => {
const iter = [1, 2, 3][Symbol.iterator]();
const originalNext = iter.next;
let count = 0;
const newProto = {
next(...args) {
count++;
return originalNext.apply(this, args);
},
[Symbol.iterator]() {
return this;
},
};
Object.setPrototypeOf(iter, newProto);
for (const v of iter) {
}
expect(count).toBe(4);
});
test("override next() on individual Map iterator via setPrototypeOf", () => {
const map = new Map([
[1, 1],
[2, 2],
[3, 3],
]);
const iter = map.values();
const originalNext = iter.next;
let count = 0;
const newProto = {
next(...args) {
count++;
return originalNext.apply(this, args);
},
[Symbol.iterator]() {
return this;
},
};
Object.setPrototypeOf(iter, newProto);
for (const v of iter) {
}
expect(count).toBe(4);
});
test("override next() on individual Set iterator via setPrototypeOf", () => {
const set = new Set([1, 2, 3]);
const iter = set.values();
const originalNext = iter.next;
let count = 0;
const newProto = {
next(...args) {
count++;
return originalNext.apply(this, args);
},
[Symbol.iterator]() {
return this;
},
};
Object.setPrototypeOf(iter, newProto);
for (const v of iter) {
}
expect(count).toBe(4);
});
});