mirror of
https://github.com/SerenityOS/serenity
synced 2024-09-06 17:06:31 +00:00
LibJS: Pass this value to fallback func in Array.prototype.toString()
The existing code looks innocently correct, implementing the following step: 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. as return ObjectPrototype::to_string(vm, global_object); However, this misses the fact that the next step calls the function with the previously ToObject()'d this value (`array`): 4. Return ? Call(func, array). This doesn't happen in the current implementation, which will use the unaltered this value from the Array.prototype.toString() call, and make another, unequal object in %Object.prototype.toString%. Since both that and Array.prototype.toString() do a Get() call on said object, this behavior is observable (see newly added test). Fix this by actually doing what the spec says and calling the fallback function the regular way.
This commit is contained in:
parent
2af869d018
commit
00b8ce4a6d
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2020, Marcin Gasperowicz <xnooga@gmail.com>
|
||||
* Copyright (c) 2021, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
|
@ -381,11 +381,18 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift)
|
|||
// 23.1.3.31 Array.prototype.toString ( ), https://tc39.es/ecma262/#sec-array.prototype.tostring
|
||||
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string)
|
||||
{
|
||||
auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
|
||||
auto join_function = TRY(this_object->get(vm.names.join));
|
||||
if (!join_function.is_function())
|
||||
return ObjectPrototype::to_string(vm, global_object);
|
||||
return TRY(call(global_object, join_function.as_function(), this_object));
|
||||
// 1. Let array be ? ToObject(this value).
|
||||
auto* array = TRY(vm.this_value(global_object).to_object(global_object));
|
||||
|
||||
// 2. Let func be ? Get(array, "join").
|
||||
auto func = TRY(array->get(vm.names.join));
|
||||
|
||||
// 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%.
|
||||
if (!func.is_function())
|
||||
func = global_object.object_prototype_to_string_function();
|
||||
|
||||
// 4. Return ? Call(func, array).
|
||||
return TRY(call(global_object, func.as_function(), array));
|
||||
}
|
||||
|
||||
// 19.5.1 Array.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-array.prototype.tolocalestring
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -302,6 +302,7 @@ void GlobalObject::initialize_global_object()
|
|||
m_date_constructor_now_function = &m_date_constructor->get_without_side_effects(vm.names.now).as_function();
|
||||
m_eval_function = &get_without_side_effects(vm.names.eval).as_function();
|
||||
m_json_parse_function = &get_without_side_effects(vm.names.JSON).as_object().get_without_side_effects(vm.names.parse).as_function();
|
||||
m_object_prototype_to_string_function = &m_object_prototype->get_without_side_effects(vm.names.toString).as_function();
|
||||
}
|
||||
|
||||
GlobalObject::~GlobalObject() = default;
|
||||
|
@ -320,8 +321,9 @@ void GlobalObject::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_array_prototype_values_function);
|
||||
visitor.visit(m_date_constructor_now_function);
|
||||
visitor.visit(m_eval_function);
|
||||
visitor.visit(m_throw_type_error_function);
|
||||
visitor.visit(m_json_parse_function);
|
||||
visitor.visit(m_object_prototype_to_string_function);
|
||||
visitor.visit(m_throw_type_error_function);
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
visitor.visit(m_##snake_name##_constructor); \
|
||||
|
|
|
@ -44,8 +44,9 @@ public:
|
|||
FunctionObject* array_prototype_values_function() const { return m_array_prototype_values_function; }
|
||||
FunctionObject* date_constructor_now_function() const { return m_date_constructor_now_function; }
|
||||
FunctionObject* eval_function() const { return m_eval_function; }
|
||||
FunctionObject* throw_type_error_function() const { return m_throw_type_error_function; }
|
||||
FunctionObject* json_parse_function() const { return m_json_parse_function; }
|
||||
FunctionObject* object_prototype_to_string_function() const { return m_object_prototype_to_string_function; }
|
||||
FunctionObject* throw_type_error_function() const { return m_throw_type_error_function; }
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \
|
||||
|
@ -115,8 +116,9 @@ private:
|
|||
FunctionObject* m_array_prototype_values_function { nullptr };
|
||||
FunctionObject* m_date_constructor_now_function { nullptr };
|
||||
FunctionObject* m_eval_function { nullptr };
|
||||
FunctionObject* m_throw_type_error_function { nullptr };
|
||||
FunctionObject* m_json_parse_function { nullptr };
|
||||
FunctionObject* m_object_prototype_to_string_function { nullptr };
|
||||
FunctionObject* m_throw_type_error_function { nullptr };
|
||||
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
ConstructorName* m_##snake_name##_constructor { nullptr }; \
|
||||
|
|
|
@ -63,4 +63,36 @@ describe("normal behavior", () => {
|
|||
// [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
|
||||
expect(a.toString()).toBe("foo,,1,2,,bar");
|
||||
});
|
||||
|
||||
test("this value object remains the same in the %Object.prototype.toString% fallback", () => {
|
||||
let arrayPrototypeToStringThis;
|
||||
let objectPrototypeToStringThis;
|
||||
|
||||
// Inject a Proxy into the Number prototype chain, so we can
|
||||
// observe Get() operations on the different object created
|
||||
// from the primitive number value.
|
||||
Number.prototype.__proto__ = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(target, prop, receiver) {
|
||||
// In Array.prototype.toString():
|
||||
// 2. Let func be ? Get(array, "join").
|
||||
if (prop === "join") {
|
||||
arrayPrototypeToStringThis = receiver;
|
||||
}
|
||||
|
||||
// In Object.prototype.toString():
|
||||
// 15. Let tag be ? Get(O, @@toStringTag).
|
||||
if (prop === Symbol.toStringTag) {
|
||||
objectPrototypeToStringThis = receiver;
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
Array.prototype.toString.call(123);
|
||||
expect(typeof arrayPrototypeToStringThis).toBe("object");
|
||||
expect(typeof objectPrototypeToStringThis).toBe("object");
|
||||
expect(arrayPrototypeToStringThis).toBe(objectPrototypeToStringThis);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue