Просмотр исходного кода

LibJS: Check if class extends value has a valid prototype

If we have a function as class extends value, we still cannot assume
that it has a prototype property and that property has a function or
null as its value - blindly calling to_object() on it may fail.

Fixes #5075.
Linus Groh 4 лет назад
Родитель
Сommit
766f30f593

+ 11 - 2
Userland/Libraries/LibJS/AST.cpp

@@ -810,7 +810,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
         if (interpreter.exception())
             return {};
         if (!super_constructor.is_function() && !super_constructor.is_null()) {
-            interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::ClassDoesNotExtendAConstructorOrNull, super_constructor.to_string_without_side_effects());
+            interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::ClassExtendsValueNotAConstructorOrNull, super_constructor.to_string_without_side_effects());
             return {};
         }
         class_constructor->set_constructor_kind(Function::ConstructorKind::Derived);
@@ -818,9 +818,18 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
 
         Object* super_constructor_prototype = nullptr;
         if (!super_constructor.is_null()) {
-            super_constructor_prototype = &super_constructor.as_object().get(vm.names.prototype).as_object();
+            auto super_constructor_prototype_value = super_constructor.as_object().get(vm.names.prototype);
             if (interpreter.exception())
                 return {};
+            if (!super_constructor_prototype_value.is_object() && !super_constructor_prototype_value.is_null()) {
+                interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_constructor_prototype_value.to_string_without_side_effects());
+                return {};
+            }
+            if (super_constructor_prototype_value.is_object()) {
+                super_constructor_prototype = &super_constructor_prototype_value.as_object();
+                if (interpreter.exception())
+                    return {};
+            }
         }
         prototype->set_prototype(super_constructor_prototype);
 

+ 2 - 1
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -36,8 +36,9 @@
     M(BigIntIntArgument, "BigInt argument must be an integer")                                                                          \
     M(BigIntInvalidValue, "Invalid value for BigInt: {}")                                                                               \
     M(ClassConstructorWithoutNew, "Class constructor {} must be called with 'new'")                                                     \
+    M(ClassExtendsValueNotAConstructorOrNull, "Class extends value {} is not a constructor or null")                                    \
+    M(ClassExtendsValueInvalidPrototype, "Class extends value has an invalid prototype {}")                                             \
     M(ClassIsAbstract, "Abstract class {} cannot be constructed directly")                                                              \
-    M(ClassDoesNotExtendAConstructorOrNull, "Class extends value {} is not a constructor or null")                                      \
     M(ConstructorWithoutNew, "{} constructor must be called with 'new'")                                                                \
     M(Convert, "Cannot convert {} to {}")                                                                                               \
     M(ConvertUndefinedToObject, "Cannot convert undefined to object")                                                                   \

+ 14 - 0
Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js

@@ -33,3 +33,17 @@ test("extending String", () => {
     const ms2 = new MyString2("abc");
     expect(ms2.charAt(1)).toBe("#b");
 });
+
+test("class extends value is invalid", () => {
+    expect(() => {
+        class A extends 123 {}
+    }).toThrowWithMessage(TypeError, "Class extends value 123 is not a constructor or null");
+});
+
+test("class extends value has invalid prototype", () => {
+    function f() {}
+    f.prototype = 123;
+    expect(() => {
+        class A extends f {}
+    }).toThrowWithMessage(TypeError, "Class extends value has an invalid prototype 123");
+});