Explorar o código

LibJS: Throw TypeError when calling class constructor without 'new'

Linus Groh %!s(int64=4) %!d(string=hai) anos
pai
achega
1b0c862f3a

+ 1 - 0
Libraries/LibJS/AST.cpp

@@ -715,6 +715,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
 
 
     ASSERT(class_constructor_value.is_function() && class_constructor_value.as_function().is_script_function());
     ASSERT(class_constructor_value.is_function() && class_constructor_value.as_function().is_script_function());
     ScriptFunction* class_constructor = static_cast<ScriptFunction*>(&class_constructor_value.as_function());
     ScriptFunction* class_constructor = static_cast<ScriptFunction*>(&class_constructor_value.as_function());
+    class_constructor->set_is_class_constructor();
     Value super_constructor = js_undefined();
     Value super_constructor = js_undefined();
     if (!m_super_class.is_null()) {
     if (!m_super_class.is_null()) {
         super_constructor = m_super_class->execute(interpreter, global_object);
         super_constructor = m_super_class->execute(interpreter, global_object);

+ 1 - 0
Libraries/LibJS/Runtime/ErrorTypes.h

@@ -36,6 +36,7 @@
     M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type")                                                  \
     M(BigIntBadOperatorOtherType, "Cannot use {} operator with BigInt and other type")                                                  \
     M(BigIntIntArgument, "BigInt argument must be an integer")                                                                          \
     M(BigIntIntArgument, "BigInt argument must be an integer")                                                                          \
     M(BigIntInvalidValue, "Invalid value for BigInt: {}")                                                                               \
     M(BigIntInvalidValue, "Invalid value for BigInt: {}")                                                                               \
+    M(ClassConstructorWithoutNew, "Class constructor {} must be called with 'new'")                                                     \
     M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null")                                      \
     M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null")                                      \
     M(Convert, "Cannot convert {} to {}")                                                                                               \
     M(Convert, "Cannot convert {} to {}")                                                                                               \
     M(ConvertUndefinedToObject, "Cannot convert undefined to object")                                                                   \
     M(ConvertUndefinedToObject, "Cannot convert undefined to object")                                                                   \

+ 11 - 2
Libraries/LibJS/Runtime/ScriptFunction.cpp

@@ -110,7 +110,7 @@ LexicalEnvironment* ScriptFunction::create_environment()
     return environment;
     return environment;
 }
 }
 
 
-Value ScriptFunction::call()
+Value ScriptFunction::execute_function_body()
 {
 {
     auto& vm = this->vm();
     auto& vm = this->vm();
 
 
@@ -150,13 +150,22 @@ Value ScriptFunction::call()
     return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function);
     return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function);
 }
 }
 
 
+Value ScriptFunction::call()
+{
+    if (m_is_class_constructor) {
+        vm().throw_exception<TypeError>(global_object(), ErrorType::ClassConstructorWithoutNew, m_name);
+        return {};
+    }
+    return execute_function_body();
+}
+
 Value ScriptFunction::construct(Function&)
 Value ScriptFunction::construct(Function&)
 {
 {
     if (m_is_arrow_function) {
     if (m_is_arrow_function) {
         vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, m_name);
         vm().throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, m_name);
         return {};
         return {};
     }
     }
-    return call();
+    return execute_function_body();
 }
 }
 
 
 JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter)
 JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter)

+ 5 - 0
Libraries/LibJS/Runtime/ScriptFunction.h

@@ -50,6 +50,8 @@ public:
     virtual const FlyString& name() const override { return m_name; };
     virtual const FlyString& name() const override { return m_name; };
     void set_name(const FlyString& name) { m_name = name; };
     void set_name(const FlyString& name) { m_name = name; };
 
 
+    void set_is_class_constructor() { m_is_class_constructor = true; };
+
 protected:
 protected:
     virtual bool is_strict_mode() const final { return m_is_strict; }
     virtual bool is_strict_mode() const final { return m_is_strict; }
 
 
@@ -58,6 +60,8 @@ private:
     virtual LexicalEnvironment* create_environment() override;
     virtual LexicalEnvironment* create_environment() override;
     virtual void visit_children(Visitor&) override;
     virtual void visit_children(Visitor&) override;
 
 
+    Value execute_function_body();
+
     JS_DECLARE_NATIVE_GETTER(length_getter);
     JS_DECLARE_NATIVE_GETTER(length_getter);
     JS_DECLARE_NATIVE_GETTER(name_getter);
     JS_DECLARE_NATIVE_GETTER(name_getter);
 
 
@@ -68,6 +72,7 @@ private:
     i32 m_function_length { 0 };
     i32 m_function_length { 0 };
     bool m_is_strict { false };
     bool m_is_strict { false };
     bool m_is_arrow_function { false };
     bool m_is_arrow_function { false };
+    bool m_is_class_constructor { false };
 };
 };
 
 
 }
 }

+ 3 - 3
Libraries/LibJS/Tests/classes/class-constructor.js

@@ -46,18 +46,18 @@ test("constructor length affects class length", () => {
     expect(B).toHaveLength(2);
     expect(B).toHaveLength(2);
 });
 });
 
 
-test.skip("must be invoked with 'new'", () => {
+test("must be invoked with 'new'", () => {
     class A {
     class A {
         constructor() {}
         constructor() {}
     }
     }
 
 
     expect(() => {
     expect(() => {
         A();
         A();
-    }).toThrow(TypeError); // FIXME: Add message when this test works
+    }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'");
 
 
     expect(() => {
     expect(() => {
         A.prototype.constructor();
         A.prototype.constructor();
-    }).toThrow(TypeError); // FIXME: Add message when this test works
+    }).toThrowWithMessage(TypeError, "Class constructor A must be called with 'new'");
 });
 });
 
 
 test("implicit constructor", () => {
 test("implicit constructor", () => {