1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-29 03:42:07 +00:00

LibJS: Only update anonymous function names when necessary

Previously we would generate function names for anonymous functions
on every AssignmentExpression, even if we weren't assigning a function.

We were also setting names of anonymous functions in arrays, which is
apparently a SpiderMonkey specific behavior not supported by V8, JSC
or required by ECMA262. This patch removes that behavior.

This is a huge performance improvement on the CanvasCycle demo! :^)
This commit is contained in:
Andreas Kling 2021-03-21 17:14:20 +01:00
parent f89c479358
commit dc8817638e
2 changed files with 13 additions and 33 deletions

View file

@ -80,30 +80,13 @@ String ASTNode::class_name() const
return demangle(typeid(*this).name()).substring(4); return demangle(typeid(*this).name()).substring(4);
} }
static void update_function_name(Value value, const FlyString& name, HashTable<JS::Cell*>& visited)
{
if (!value.is_object())
return;
if (visited.contains(value.as_cell()))
return;
visited.set(value.as_cell());
auto& object = value.as_object();
if (object.is_function()) {
auto& function = static_cast<Function&>(object);
if (is<ScriptFunction>(function) && function.name().is_empty())
static_cast<ScriptFunction&>(function).set_name(name);
} else if (object.is_array()) {
auto& array = static_cast<Array&>(object);
array.indexed_properties().for_each_value([&](auto& array_element_value) {
update_function_name(array_element_value, name, visited);
});
}
}
static void update_function_name(Value value, const FlyString& name) static void update_function_name(Value value, const FlyString& name)
{ {
HashTable<JS::Cell*> visited; if (!value.is_function())
update_function_name(value, name, visited); return;
auto& function = value.as_function();
if (is<ScriptFunction>(function) && function.name().is_empty())
static_cast<ScriptFunction&>(function).set_name(name);
} }
static String get_function_name(GlobalObject& global_object, Value value) static String get_function_name(GlobalObject& global_object, Value value)
@ -1443,7 +1426,11 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob
interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment); interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment);
return {}; return {};
} }
update_function_name(rhs_result, get_function_name(global_object, reference.name().to_value(interpreter.vm())));
// FIXME: We should also check if the LHS is an identifier reference.
if (rhs_result.is_function())
update_function_name(rhs_result, get_function_name(global_object, reference.name().to_value(interpreter.vm())));
reference.put(global_object, rhs_result); reference.put(global_object, rhs_result);
if (interpreter.exception()) if (interpreter.exception())

View file

@ -21,9 +21,9 @@ test("function assigned to variable", () => {
test("functions in array assigned to variable", () => { test("functions in array assigned to variable", () => {
const arr = [function () {}, function () {}, function () {}]; const arr = [function () {}, function () {}, function () {}];
expect(arr[0].name).toBe("arr"); expect(arr[0].name).toBe("");
expect(arr[1].name).toBe("arr"); expect(arr[1].name).toBe("");
expect(arr[2].name).toBe("arr"); expect(arr[2].name).toBe("");
}); });
test("functions in objects", () => { test("functions in objects", () => {
@ -48,10 +48,3 @@ test("names of native functions", () => {
expect((console.debug.name = "warn")).toBe("warn"); expect((console.debug.name = "warn")).toBe("warn");
expect(console.debug.name).toBe("debug"); expect(console.debug.name).toBe("debug");
}); });
test("cyclic members should not cause infinite recursion (#3471)", () => {
let a = [() => 4];
a[1] = a;
a = a;
expect(a[0].name).toBe("a");
});