mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +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
Notes:
sideshowbarker
2024-07-17 12:04:04 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/00b8ce4a6d Pull-request: https://github.com/SerenityOS/serenity/pull/13632 Reviewed-by: https://github.com/IdanHo
4 changed files with 53 additions and 10 deletions
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
* 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) 2020, Marcin Gasperowicz <xnooga@gmail.com>
|
||||||
* Copyright (c) 2021, David Tuin <davidot@serenityos.org>
|
* 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
|
// 23.1.3.31 Array.prototype.toString ( ), https://tc39.es/ecma262/#sec-array.prototype.tostring
|
||||||
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string)
|
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string)
|
||||||
{
|
{
|
||||||
auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
|
// 1. Let array be ? ToObject(this value).
|
||||||
auto join_function = TRY(this_object->get(vm.names.join));
|
auto* array = TRY(vm.this_value(global_object).to_object(global_object));
|
||||||
if (!join_function.is_function())
|
|
||||||
return ObjectPrototype::to_string(vm, global_object);
|
// 2. Let func be ? Get(array, "join").
|
||||||
return TRY(call(global_object, join_function.as_function(), this_object));
|
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
|
// 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, 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
|
* 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_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_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_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;
|
GlobalObject::~GlobalObject() = default;
|
||||||
|
@ -320,8 +321,9 @@ void GlobalObject::visit_edges(Visitor& visitor)
|
||||||
visitor.visit(m_array_prototype_values_function);
|
visitor.visit(m_array_prototype_values_function);
|
||||||
visitor.visit(m_date_constructor_now_function);
|
visitor.visit(m_date_constructor_now_function);
|
||||||
visitor.visit(m_eval_function);
|
visitor.visit(m_eval_function);
|
||||||
visitor.visit(m_throw_type_error_function);
|
|
||||||
visitor.visit(m_json_parse_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) \
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||||
visitor.visit(m_##snake_name##_constructor); \
|
visitor.visit(m_##snake_name##_constructor); \
|
||||||
|
|
|
@ -44,8 +44,9 @@ public:
|
||||||
FunctionObject* array_prototype_values_function() const { return m_array_prototype_values_function; }
|
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* date_constructor_now_function() const { return m_date_constructor_now_function; }
|
||||||
FunctionObject* eval_function() const { return m_eval_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* 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) \
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||||
ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \
|
ConstructorName* snake_name##_constructor() { return m_##snake_name##_constructor; } \
|
||||||
|
@ -115,8 +116,9 @@ private:
|
||||||
FunctionObject* m_array_prototype_values_function { nullptr };
|
FunctionObject* m_array_prototype_values_function { nullptr };
|
||||||
FunctionObject* m_date_constructor_now_function { nullptr };
|
FunctionObject* m_date_constructor_now_function { nullptr };
|
||||||
FunctionObject* m_eval_function { nullptr };
|
FunctionObject* m_eval_function { nullptr };
|
||||||
FunctionObject* m_throw_type_error_function { nullptr };
|
|
||||||
FunctionObject* m_json_parse_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) \
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||||
ConstructorName* m_##snake_name##_constructor { nullptr }; \
|
ConstructorName* m_##snake_name##_constructor { nullptr }; \
|
||||||
|
|
|
@ -63,4 +63,36 @@ describe("normal behavior", () => {
|
||||||
// [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
|
// [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
|
||||||
expect(a.toString()).toBe("foo,,1,2,,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