mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +00:00
LibJS: Handle circular references in Array.prototype.toLocaleString()
Also use ArmedScopeGuard for removing seen objects to account for early returns. Fixes #3963.
This commit is contained in:
parent
15bc42479a
commit
82b42cefbd
Notes:
sideshowbarker
2024-07-19 01:32:38 +09:00
Author: https://github.com/linusg Commit: https://github.com/SerenityOS/serenity/commit/82b42cefbd2 Pull-request: https://github.com/SerenityOS/serenity/pull/3965 Issue: https://github.com/SerenityOS/serenity/issues/3963
3 changed files with 33 additions and 4 deletions
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/ArrayIterator.h>
|
||||
|
@ -288,10 +289,19 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
|
|||
auto* this_object = vm.this_value(global_object).to_object(global_object);
|
||||
if (!this_object)
|
||||
return {};
|
||||
String separator = ","; // NOTE: This is implementation-specific.
|
||||
|
||||
if (s_array_join_seen_objects.contains(this_object))
|
||||
return js_string(vm, "");
|
||||
s_array_join_seen_objects.set(this_object);
|
||||
ArmedScopeGuard unsee_object_guard = [&] {
|
||||
s_array_join_seen_objects.remove(this_object);
|
||||
};
|
||||
|
||||
auto length = get_length(vm, *this_object);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
||||
String separator = ","; // NOTE: This is implementation-specific.
|
||||
StringBuilder builder;
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (i > 0)
|
||||
|
@ -302,7 +312,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string)
|
|||
if (value.is_nullish())
|
||||
continue;
|
||||
auto* value_object = value.to_object(global_object);
|
||||
ASSERT(value_object);
|
||||
if (!value_object)
|
||||
return {};
|
||||
auto locale_string_result = value_object->invoke("toLocaleString");
|
||||
if (vm.exception())
|
||||
return {};
|
||||
|
@ -322,9 +333,13 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join)
|
|||
|
||||
// This is not part of the spec, but all major engines do some kind of circular reference checks.
|
||||
// FWIW: engine262, a "100% spec compliant" ECMA-262 impl, aborts with "too much recursion".
|
||||
// Same applies to Array.prototype.toLocaleString().
|
||||
if (s_array_join_seen_objects.contains(this_object))
|
||||
return js_string(vm, "");
|
||||
s_array_join_seen_objects.set(this_object);
|
||||
ArmedScopeGuard unsee_object_guard = [&] {
|
||||
s_array_join_seen_objects.remove(this_object);
|
||||
};
|
||||
|
||||
auto length = get_length(vm, *this_object);
|
||||
if (vm.exception())
|
||||
|
@ -350,8 +365,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join)
|
|||
builder.append(string);
|
||||
}
|
||||
|
||||
s_array_join_seen_objects.remove(this_object);
|
||||
|
||||
return js_string(vm, builder.to_string());
|
||||
}
|
||||
|
||||
|
|
|
@ -51,4 +51,12 @@ describe("normal behavior", () => {
|
|||
expect([o, undefined, o, null, o].toLocaleString()).toBe("o,,o,,o");
|
||||
expect(toStringCalled).toBe(3);
|
||||
});
|
||||
|
||||
test("array with circular references", () => {
|
||||
const a = ["foo", [], [1, 2, []], ["bar"]];
|
||||
a[1] = a;
|
||||
a[2][2] = a;
|
||||
// [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
|
||||
expect(a.toLocaleString()).toBe("foo,,1,2,,bar");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -55,4 +55,12 @@ describe("normal behavior", () => {
|
|||
expect([o, undefined, o, null, o].toString()).toBe("o,,o,,o");
|
||||
expect(toStringCalled).toBe(3);
|
||||
});
|
||||
|
||||
test("array with circular references", () => {
|
||||
const a = ["foo", [], [1, 2, []], ["bar"]];
|
||||
a[1] = a;
|
||||
a[2][2] = a;
|
||||
// [ "foo", <circular>, [ 1, 2, <circular> ], [ "bar" ] ]
|
||||
expect(a.toString()).toBe("foo,,1,2,,bar");
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue