LibJS: Make class prototype property non-writable

ClassDefinitionEvaluation calls MakeConstructor with false for the
writablePrototype argument, so class constructors get a non-writable,
non-enumerable, non-configurable prototype property. We were using the
ordinary function attributes instead, which also allowed static class
elements named "prototype" to redefine the property.

Add LibJS coverage for the constructor and prototype descriptors, and
for static methods/accessors that attempt to define "prototype".
This commit is contained in:
Andreas Kling 2026-05-21 13:10:51 +02:00 committed by Andreas Kling
parent a6e642790f
commit 991ba90d40
3 changed files with 37 additions and 1 deletions

View file

@ -97,7 +97,7 @@ ThrowCompletionOr<ECMAScriptFunctionObject*> construct_class(
class_constructor->set_name(class_name);
class_constructor->set_home_object(prototype);
class_constructor->set_is_class_constructor();
class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
class_constructor->define_direct_property(vm.names.prototype, prototype, 0);
TRY(class_constructor->internal_set_prototype_of(constructor_parent));
if (blueprint.has_super_class)

View file

@ -3,3 +3,19 @@ test("class properties", () => {
expect(A.name).toBe("A");
expect(A).toHaveLength(0);
});
test("class constructor prototype property descriptor", () => {
class A {}
const descriptor = Object.getOwnPropertyDescriptor(A, "prototype");
expect(descriptor.value).toBe(A.prototype);
expect(descriptor.writable).toBeFalse();
expect(descriptor.enumerable).toBeFalse();
expect(descriptor.configurable).toBeFalse();
const constructorDescriptor = Object.getOwnPropertyDescriptor(A.prototype, "constructor");
expect(constructorDescriptor.value).toBe(A);
expect(constructorDescriptor.writable).toBeTrue();
expect(constructorDescriptor.enumerable).toBeFalse();
expect(constructorDescriptor.configurable).toBeTrue();
});

View file

@ -71,6 +71,26 @@ test("static method overriding", () => {
expect(Child.method()).toBe(10);
});
test("static elements cannot redefine prototype property", () => {
expect(() => {
class A {
static ["prototype"]() {}
}
}).toThrow(TypeError);
expect(() => {
class A {
static get ["prototype"]() {}
}
}).toThrow(TypeError);
expect(() => {
class A {
static set ["prototype"](value) {}
}
}).toThrow(TypeError);
});
test("static function named 'async'", () => {
class A {
static async() {