diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 8e3890c44f2..f71d6d7427d 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -1769,7 +1769,7 @@ class PropertyNameIterator final public: virtual ~PropertyNameIterator() override = default; - BuiltinIterator* as_builtin_iterator() override { return this; } + BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override { return this; } ThrowCompletionOr next(VM&, bool& done, Value& value) override { while (true) { @@ -3175,7 +3175,7 @@ ThrowCompletionOr IteratorNextUnpack::execute_impl(Bytecode::Interpreter& Value value; bool done = false; - if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator()) { + if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator_if_next_is_not_redefined()) { TRY(builtin_iterator->next(vm, done, value)); } else { auto result = TRY(iterator_next(vm, iterator_record)); diff --git a/Libraries/LibJS/Runtime/ArrayIterator.cpp b/Libraries/LibJS/Runtime/ArrayIterator.cpp index 6d8107f6868..e0271158ebf 100644 --- a/Libraries/LibJS/Runtime/ArrayIterator.cpp +++ b/Libraries/LibJS/Runtime/ArrayIterator.cpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace JS { @@ -28,6 +29,8 @@ ArrayIterator::ArrayIterator(Value array, Object::PropertyKind iteration_kind, O , m_array(array) , m_iteration_kind(iteration_kind) { + auto& array_iterator_prototype = as(prototype); + m_next_method_was_redefined = array_iterator_prototype.next_method_was_redefined(); } void ArrayIterator::visit_edges(Cell::Visitor& visitor) diff --git a/Libraries/LibJS/Runtime/ArrayIterator.h b/Libraries/LibJS/Runtime/ArrayIterator.h index 62e29b5fefd..5993b4e3acd 100644 --- a/Libraries/LibJS/Runtime/ArrayIterator.h +++ b/Libraries/LibJS/Runtime/ArrayIterator.h @@ -21,7 +21,12 @@ public: virtual ~ArrayIterator() override = default; - BuiltinIterator* as_builtin_iterator() override { return this; } + BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override + { + if (m_next_method_was_redefined) + return nullptr; + return this; + } ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: diff --git a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h index aac5c3d39e6..e28815622ca 100644 --- a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h +++ b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h @@ -19,10 +19,20 @@ 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<> +inline bool Object::fast_is() const { return is_array_iterator_prototype(); } + } diff --git a/Libraries/LibJS/Runtime/Iterator.cpp b/Libraries/LibJS/Runtime/Iterator.cpp index e1e6eff8a93..a3be43801fd 100644 --- a/Libraries/LibJS/Runtime/Iterator.cpp +++ b/Libraries/LibJS/Runtime/Iterator.cpp @@ -272,7 +272,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 (iterator->as_builtin_iterator_if_next_is_not_redefined()) return completion; // 3. Let innerResult be Completion(GetMethod(iterator, "return")). diff --git a/Libraries/LibJS/Runtime/Iterator.h b/Libraries/LibJS/Runtime/Iterator.h index a6904513798..3fd533fb186 100644 --- a/Libraries/LibJS/Runtime/Iterator.h +++ b/Libraries/LibJS/Runtime/Iterator.h @@ -71,6 +71,9 @@ class BuiltinIterator { public: virtual ~BuiltinIterator() = default; virtual ThrowCompletionOr next(VM&, bool& done, Value& value) = 0; + +protected: + bool m_next_method_was_redefined { false }; }; // 7.4.12 IfAbruptCloseIterator ( value, iteratorRecord ), https://tc39.es/ecma262/#sec-ifabruptcloseiterator diff --git a/Libraries/LibJS/Runtime/MapIterator.cpp b/Libraries/LibJS/Runtime/MapIterator.cpp index 4667a214332..e4606f554f6 100644 --- a/Libraries/LibJS/Runtime/MapIterator.cpp +++ b/Libraries/LibJS/Runtime/MapIterator.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace JS { @@ -22,6 +23,8 @@ MapIterator::MapIterator(Map& map, Object::PropertyKind iteration_kind, Object& , m_iteration_kind(iteration_kind) , m_iterator(static_cast(map).begin()) { + auto& map_iterator_prototype = as(prototype); + m_next_method_was_redefined = map_iterator_prototype.next_method_was_redefined(); } void MapIterator::visit_edges(Cell::Visitor& visitor) diff --git a/Libraries/LibJS/Runtime/MapIterator.h b/Libraries/LibJS/Runtime/MapIterator.h index 2e55cddb327..507d1237320 100644 --- a/Libraries/LibJS/Runtime/MapIterator.h +++ b/Libraries/LibJS/Runtime/MapIterator.h @@ -22,7 +22,12 @@ public: virtual ~MapIterator() override = default; - BuiltinIterator* as_builtin_iterator() override { return this; } + BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override + { + if (m_next_method_was_redefined) + return nullptr; + return this; + } ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: diff --git a/Libraries/LibJS/Runtime/MapIteratorPrototype.h b/Libraries/LibJS/Runtime/MapIteratorPrototype.h index e51a3f2d88b..60827aabd7a 100644 --- a/Libraries/LibJS/Runtime/MapIteratorPrototype.h +++ b/Libraries/LibJS/Runtime/MapIteratorPrototype.h @@ -19,10 +19,20 @@ 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<> +inline bool Object::fast_is() const { return is_map_iterator_prototype(); } + } diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 2723a2a8b8c..fb126f5f583 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -10,15 +10,19 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include +#include #include +#include #include namespace JS { @@ -957,6 +961,18 @@ ThrowCompletionOr 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(receiver_object)) + array_iterator_prototype->set_next_method_was_redefined(); + else if (auto* map_iterator_prototype = as_if(receiver_object)) + map_iterator_prototype->set_next_method_was_redefined(); + else if (auto* set_iterator_prototype = as_if(receiver_object)) + set_iterator_prototype->set_next_method_was_redefined(); + else if (auto* string_iterator_prototype = as_if(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)); diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 61a117588f3..d98cbdbb711 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -212,7 +212,12 @@ public: virtual bool is_array_iterator() const { return false; } virtual bool is_raw_json_object() const { return false; } - virtual BuiltinIterator* as_builtin_iterator() { return nullptr; } + virtual BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() { return nullptr; } + + virtual bool is_array_iterator_prototype() const { return false; } + virtual bool is_map_iterator_prototype() const { return false; } + virtual bool is_set_iterator_prototype() const { return false; } + virtual bool is_string_iterator_prototype() const { return false; } // B.3.7 The [[IsHTMLDDA]] Internal Slot, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot virtual bool is_htmldda() const { return false; } diff --git a/Libraries/LibJS/Runtime/SetIterator.cpp b/Libraries/LibJS/Runtime/SetIterator.cpp index 02188e96555..6ae5b03cbbe 100644 --- a/Libraries/LibJS/Runtime/SetIterator.cpp +++ b/Libraries/LibJS/Runtime/SetIterator.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace JS { @@ -22,6 +23,8 @@ SetIterator::SetIterator(Set& set, Object::PropertyKind iteration_kind, Object& , m_iteration_kind(iteration_kind) , m_iterator(static_cast(set).begin()) { + auto& set_iterator_prototype = as(prototype); + m_next_method_was_redefined = set_iterator_prototype.next_method_was_redefined(); } void SetIterator::visit_edges(Cell::Visitor& visitor) diff --git a/Libraries/LibJS/Runtime/SetIterator.h b/Libraries/LibJS/Runtime/SetIterator.h index 9d63a543e73..d504f01749d 100644 --- a/Libraries/LibJS/Runtime/SetIterator.h +++ b/Libraries/LibJS/Runtime/SetIterator.h @@ -22,7 +22,13 @@ public: virtual ~SetIterator() override = default; - BuiltinIterator* as_builtin_iterator() override { return this; } + BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override + { + if (m_next_method_was_redefined) + return nullptr; + return this; + } + ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: diff --git a/Libraries/LibJS/Runtime/SetIteratorPrototype.h b/Libraries/LibJS/Runtime/SetIteratorPrototype.h index 5f480157cdc..95895b86637 100644 --- a/Libraries/LibJS/Runtime/SetIteratorPrototype.h +++ b/Libraries/LibJS/Runtime/SetIteratorPrototype.h @@ -19,10 +19,20 @@ 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<> +inline bool Object::fast_is() const { return is_set_iterator_prototype(); } + } diff --git a/Libraries/LibJS/Runtime/StringIterator.cpp b/Libraries/LibJS/Runtime/StringIterator.cpp index cdfe30aa84b..20b8b04d927 100644 --- a/Libraries/LibJS/Runtime/StringIterator.cpp +++ b/Libraries/LibJS/Runtime/StringIterator.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace JS { @@ -22,6 +23,8 @@ StringIterator::StringIterator(String string, Object& prototype) , m_string(move(string)) , m_iterator(Utf8View(m_string).begin()) { + auto& string_iterator_prototype = as(prototype); + m_next_method_was_redefined = string_iterator_prototype.next_method_was_redefined(); } ThrowCompletionOr StringIterator::next(VM& vm, bool& done, Value& value) diff --git a/Libraries/LibJS/Runtime/StringIterator.h b/Libraries/LibJS/Runtime/StringIterator.h index 154f1832dff..02288735990 100644 --- a/Libraries/LibJS/Runtime/StringIterator.h +++ b/Libraries/LibJS/Runtime/StringIterator.h @@ -23,7 +23,12 @@ public: virtual ~StringIterator() override = default; - BuiltinIterator* as_builtin_iterator() override { return this; } + BuiltinIterator* as_builtin_iterator_if_next_is_not_redefined() override + { + if (m_next_method_was_redefined) + return nullptr; + return this; + } ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: diff --git a/Libraries/LibJS/Runtime/StringIteratorPrototype.h b/Libraries/LibJS/Runtime/StringIteratorPrototype.h index f59c548ba1c..4e3bbb41774 100644 --- a/Libraries/LibJS/Runtime/StringIteratorPrototype.h +++ b/Libraries/LibJS/Runtime/StringIteratorPrototype.h @@ -20,10 +20,20 @@ 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<> +inline bool Object::fast_is() const { return is_string_iterator_prototype(); } + } diff --git a/Libraries/LibJS/Tests/redefine-next-in-builtin-iterators.js b/Libraries/LibJS/Tests/redefine-next-in-builtin-iterators.js new file mode 100644 index 00000000000..1e0ecc78678 --- /dev/null +++ b/Libraries/LibJS/Tests/redefine-next-in-builtin-iterators.js @@ -0,0 +1,46 @@ +describe("redefine next() in built in iterators", () => { + test("redefine next() in ArrayIteratorPrototype", () => { + let arrayIteratorPrototype = Object.getPrototypeOf([].values()); + let originalNext = arrayIteratorPrototype.next; + let counter = 0; + arrayIteratorPrototype.next = function () { + counter++; + return originalNext.apply(this, arguments); + }; + for (let i of [1, 2, 3]) { + } + expect(counter).toBe(4); + }); + + test("redefine next() in MapIteratorPrototype", () => { + let m = new Map([ + [1, 1], + [2, 2], + [3, 3], + ]); + let mapIteratorPrototype = Object.getPrototypeOf(m.values()); + let originalNext = mapIteratorPrototype.next; + let counter = 0; + mapIteratorPrototype.next = function () { + counter++; + return originalNext.apply(this, arguments); + }; + for (let v of m.values()) { + } + expect(counter).toBe(4); + }); + + test("redefine next() in SetIteratorPrototype", () => { + let s = new Set([1, 2, 3]); + let setIteratorPrototype = Object.getPrototypeOf(s.values()); + let originalNext = setIteratorPrototype.next; + let counter = 0; + setIteratorPrototype.next = function () { + counter++; + return originalNext.apply(this, arguments); + }; + for (let v of s.values()) { + } + expect(counter).toBe(4); + }); +});