[ES6] Support subclassing Function.
https://bugs.webkit.org/show_bug.cgi?id=153081
Reviewed by Geoffrey Garen.
Source/JavaScriptCore:
This patch enables subclassing the Function object. It also fixes an existing
bug that prevented users from subclassing functions that have a function in
the superclass's prototype property.
* bytecompiler/NodesCodegen.cpp:
(JSC::ClassExprNode::emitBytecode):
* runtime/FunctionConstructor.cpp:
(JSC::constructWithFunctionConstructor):
(JSC::constructFunction):
(JSC::constructFunctionSkippingEvalEnabledCheck):
* runtime/FunctionConstructor.h:
* runtime/JSFunction.cpp:
(JSC::JSFunction::create):
* runtime/JSFunction.h:
(JSC::JSFunction::createImpl):
* runtime/JSFunctionInlines.h:
(JSC::JSFunction::createWithInvalidatedReallocationWatchpoint):
(JSC::JSFunction::JSFunction): Deleted.
* tests/stress/class-subclassing-function.js: Added.
LayoutTests:
Rebasline tests with the new clearer error message.
* js/class-syntax-extends-expected.txt:
* js/script-tests/class-syntax-extends.js:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@195070 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 3d99b05..e1c84d1 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,15 @@
+2016-01-14 Keith Miller <keith_miller@apple.com>
+
+ [ES6] Support subclassing Function.
+ https://bugs.webkit.org/show_bug.cgi?id=153081
+
+ Reviewed by Geoffrey Garen.
+
+ Rebasline tests with the new clearer error message.
+
+ * js/class-syntax-extends-expected.txt:
+ * js/script-tests/class-syntax-extends.js:
+
2016-01-14 Zalan Bujtas <zalan@apple.com>
ASSERTION FAILED: !newRelayoutRoot.container() || !newRelayoutRoot.container()->needsLayout() in WebCore::FrameView::scheduleRelayoutOfSubtree
diff --git a/LayoutTests/js/class-syntax-extends-expected.txt b/LayoutTests/js/class-syntax-extends-expected.txt
index d1b2ada..c47f5fc 100644
--- a/LayoutTests/js/class-syntax-extends-expected.txt
+++ b/LayoutTests/js/class-syntax-extends-expected.txt
@@ -26,9 +26,9 @@
PASS x = class extends 3 { constructor() { } }; x.__proto__:::TypeError: The superclass is not an object.
PASS x = class extends "abc" { constructor() { } }; x.__proto__:::TypeError: The superclass is not an object.
PASS baseWithBadPrototype = function () {}; baseWithBadPrototype.prototype = 3; new baseWithBadPrototype
-PASS x = class extends baseWithBadPrototype { constructor() { } }:::TypeError: The superclass's prototype is not an object.
+PASS x = class extends baseWithBadPrototype { constructor() { } }:::TypeError: The value of the superclass's prototype property is not an object.
PASS baseWithBadPrototype.prototype = "abc"
-PASS x = class extends baseWithBadPrototype { constructor() { } }:::TypeError: The superclass's prototype is not an object.
+PASS x = class extends baseWithBadPrototype { constructor() { } }:::TypeError: The value of the superclass's prototype property is not an object.
PASS baseWithBadPrototype.prototype = null; x = class extends baseWithBadPrototype { constructor() { } }
PASS x = 1; c = class extends ++x { constructor() { } };:::SyntaxError: Unexpected token '++'
PASS x = 1; c = class extends x++ { constructor() { } };:::SyntaxError: Unexpected token '++'. Expected opening '{' at the start of a class body.
@@ -48,8 +48,8 @@
PASS namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends false||null||namespace.A { constructor() { } }:::SyntaxError: Unexpected token '||'. Expected opening '{' at the start of a class body.
PASS x = 1; namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends (x++, namespace.A) { constructor() { } };
PASS x = 1; namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends (namespace.A, x++) { constructor() { } };:::TypeError: The superclass is not an object.
-PASS namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends new namespace.A { constructor() { } }:::TypeError: The superclass's prototype is not an object.
-PASS namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends new namespace.A() { constructor() { } }:::TypeError: The superclass's prototype is not an object.
+PASS namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends new namespace.A { constructor() { } }:::TypeError: The value of the superclass's prototype property is not an object.
+PASS namespace = {}; namespace.A = class { constructor() { } }; namespace.B = class extends new namespace.A() { constructor() { } }:::TypeError: The value of the superclass's prototype property is not an object.
PASS x = 1; namespace = {}; namespace.A = class { constructor() { } }; try { namespace.B = class extends (x++, namespace.A) { constructor() { } } } catch (e) { } x:::2
PASS x = 1; namespace = {}; namespace.A = class { constructor() { } }; try { namespace.B = class extends (namespace.A, x++) { constructor() { } } } catch (e) { } x:::2
PASS Object.getPrototypeOf((class { constructor () { } }).prototype):::Object.prototype
diff --git a/LayoutTests/js/script-tests/class-syntax-extends.js b/LayoutTests/js/script-tests/class-syntax-extends.js
index 2d649ac..38e5e5c 100644
--- a/LayoutTests/js/script-tests/class-syntax-extends.js
+++ b/LayoutTests/js/script-tests/class-syntax-extends.js
@@ -81,9 +81,9 @@
shouldThrow('x = class extends 3 { constructor() { } }; x.__proto__', '"TypeError: The superclass is not an object."');
shouldThrow('x = class extends "abc" { constructor() { } }; x.__proto__', '"TypeError: The superclass is not an object."');
shouldNotThrow('baseWithBadPrototype = function () {}; baseWithBadPrototype.prototype = 3; new baseWithBadPrototype');
-shouldThrow('x = class extends baseWithBadPrototype { constructor() { } }', '"TypeError: The superclass\'s prototype is not an object."');
+shouldThrow('x = class extends baseWithBadPrototype { constructor() { } }', '"TypeError: The value of the superclass\'s prototype property is not an object."');
shouldNotThrow('baseWithBadPrototype.prototype = "abc"');
-shouldThrow('x = class extends baseWithBadPrototype { constructor() { } }', '"TypeError: The superclass\'s prototype is not an object."');
+shouldThrow('x = class extends baseWithBadPrototype { constructor() { } }', '"TypeError: The value of the superclass\'s prototype property is not an object."');
shouldNotThrow('baseWithBadPrototype.prototype = null; x = class extends baseWithBadPrototype { constructor() { } }');
shouldThrow('x = 1; c = class extends ++x { constructor() { } };');
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index cd5d37a..3fb0c6d 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,30 @@
+2016-01-14 Keith Miller <keith_miller@apple.com>
+
+ [ES6] Support subclassing Function.
+ https://bugs.webkit.org/show_bug.cgi?id=153081
+
+ Reviewed by Geoffrey Garen.
+
+ This patch enables subclassing the Function object. It also fixes an existing
+ bug that prevented users from subclassing functions that have a function in
+ the superclass's prototype property.
+
+ * bytecompiler/NodesCodegen.cpp:
+ (JSC::ClassExprNode::emitBytecode):
+ * runtime/FunctionConstructor.cpp:
+ (JSC::constructWithFunctionConstructor):
+ (JSC::constructFunction):
+ (JSC::constructFunctionSkippingEvalEnabledCheck):
+ * runtime/FunctionConstructor.h:
+ * runtime/JSFunction.cpp:
+ (JSC::JSFunction::create):
+ * runtime/JSFunction.h:
+ (JSC::JSFunction::createImpl):
+ * runtime/JSFunctionInlines.h:
+ (JSC::JSFunction::createWithInvalidatedReallocationWatchpoint):
+ (JSC::JSFunction::JSFunction): Deleted.
+ * tests/stress/class-subclassing-function.js: Added.
+
2016-01-13 Carlos Garcia Campos <cgarcia@igalia.com>
[CMake] Do not use LLVM static libraries for FTL JIT
diff --git a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
index cc00be6..3c4f20b 100644
--- a/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+++ b/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
@@ -3228,7 +3228,8 @@
RefPtr<Label> protoParentIsObjectOrNullLabel = generator.newLabel();
generator.emitJumpIfTrue(generator.emitUnaryOp(op_is_object_or_null, tempRegister.get(), protoParent.get()), protoParentIsObjectOrNullLabel.get());
- generator.emitThrowTypeError(ASCIILiteral("The superclass's prototype is not an object."));
+ generator.emitJumpIfTrue(generator.emitUnaryOp(op_is_function, tempRegister.get(), protoParent.get()), protoParentIsObjectOrNullLabel.get());
+ generator.emitThrowTypeError(ASCIILiteral("The value of the superclass's prototype property is not an object."));
generator.emitLabel(protoParentIsObjectOrNullLabel.get());
generator.emitDirectPutById(constructor.get(), generator.propertyNames().underscoreProto, superclass.get(), PropertyNode::Unknown);
diff --git a/Source/JavaScriptCore/runtime/FunctionConstructor.cpp b/Source/JavaScriptCore/runtime/FunctionConstructor.cpp
index 6c39a0d..cda30b9 100644
--- a/Source/JavaScriptCore/runtime/FunctionConstructor.cpp
+++ b/Source/JavaScriptCore/runtime/FunctionConstructor.cpp
@@ -56,7 +56,7 @@
static EncodedJSValue JSC_HOST_CALL constructWithFunctionConstructor(ExecState* exec)
{
ArgList args(exec);
- return JSValue::encode(constructFunction(exec, asInternalFunction(exec->callee())->globalObject(), args));
+ return JSValue::encode(constructFunction(exec, asInternalFunction(exec->callee())->globalObject(), args, FunctionConstructionMode::Function, exec->newTarget()));
}
ConstructType FunctionConstructor::getConstructData(JSCell*, ConstructData& constructData)
@@ -79,17 +79,17 @@
}
// ECMA 15.3.2 The Function Constructor
-JSObject* constructFunction(ExecState* exec, JSGlobalObject* globalObject, const ArgList& args, const Identifier& functionName, const String& sourceURL, const TextPosition& position, FunctionConstructionMode functionConstructionMode)
+JSObject* constructFunction(ExecState* exec, JSGlobalObject* globalObject, const ArgList& args, const Identifier& functionName, const String& sourceURL, const TextPosition& position, FunctionConstructionMode functionConstructionMode, JSValue newTarget)
{
if (!globalObject->evalEnabled())
return exec->vm().throwException(exec, createEvalError(exec, globalObject->evalDisabledErrorMessage()));
- return constructFunctionSkippingEvalEnabledCheck(exec, globalObject, args, functionName, sourceURL, position, -1, functionConstructionMode);
+ return constructFunctionSkippingEvalEnabledCheck(exec, globalObject, args, functionName, sourceURL, position, -1, functionConstructionMode, newTarget);
}
JSObject* constructFunctionSkippingEvalEnabledCheck(
ExecState* exec, JSGlobalObject* globalObject, const ArgList& args,
const Identifier& functionName, const String& sourceURL,
- const TextPosition& position, int overrideLineNumber, FunctionConstructionMode functionConstructionMode)
+ const TextPosition& position, int overrideLineNumber, FunctionConstructionMode functionConstructionMode, JSValue newTarget)
{
// How we stringify functions is sometimes important for web compatibility.
// See https://bugs.webkit.org/show_bug.cgi?id=24350.
@@ -124,13 +124,14 @@
return exec->vm().throwException(exec, exception);
}
- return JSFunction::create(exec->vm(), function, globalObject);
+
+ return JSFunction::create(exec->vm(), function, globalObject, InternalFunction::createSubclassStructure(exec, newTarget, globalObject->functionStructure()));
}
// ECMA 15.3.2 The Function Constructor
-JSObject* constructFunction(ExecState* exec, JSGlobalObject* globalObject, const ArgList& args, FunctionConstructionMode functionConstructionMode)
+JSObject* constructFunction(ExecState* exec, JSGlobalObject* globalObject, const ArgList& args, FunctionConstructionMode functionConstructionMode, JSValue newTarget)
{
- return constructFunction(exec, globalObject, args, exec->propertyNames().anonymous, String(), TextPosition::minimumPosition(), functionConstructionMode);
+ return constructFunction(exec, globalObject, args, exec->propertyNames().anonymous, String(), TextPosition::minimumPosition(), functionConstructionMode, newTarget);
}
} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/FunctionConstructor.h b/Source/JavaScriptCore/runtime/FunctionConstructor.h
index d11fb2a..63bc408 100644
--- a/Source/JavaScriptCore/runtime/FunctionConstructor.h
+++ b/Source/JavaScriptCore/runtime/FunctionConstructor.h
@@ -61,12 +61,13 @@
Generator,
};
-JSObject* constructFunction(ExecState*, JSGlobalObject*, const ArgList&, const Identifier& functionName, const String& sourceURL, const WTF::TextPosition&, FunctionConstructionMode = FunctionConstructionMode::Function);
-JSObject* constructFunction(ExecState*, JSGlobalObject*, const ArgList&, FunctionConstructionMode = FunctionConstructionMode::Function);
+JSObject* constructFunction(ExecState*, JSGlobalObject*, const ArgList&, const Identifier& functionName, const String& sourceURL, const WTF::TextPosition&, FunctionConstructionMode = FunctionConstructionMode::Function, JSValue newTarget = JSValue());
+JSObject* constructFunction(ExecState*, JSGlobalObject*, const ArgList&, FunctionConstructionMode = FunctionConstructionMode::Function, JSValue newTarget = JSValue());
JS_EXPORT_PRIVATE JSObject* constructFunctionSkippingEvalEnabledCheck(
ExecState*, JSGlobalObject*, const ArgList&, const Identifier&,
- const String&, const WTF::TextPosition&, int overrideLineNumber = -1, FunctionConstructionMode = FunctionConstructionMode::Function);
+ const String&, const WTF::TextPosition&, int overrideLineNumber = -1,
+ FunctionConstructionMode = FunctionConstructionMode::Function, JSValue newTarget = JSValue());
} // namespace JSC
diff --git a/Source/JavaScriptCore/runtime/JSFunction.cpp b/Source/JavaScriptCore/runtime/JSFunction.cpp
index 5cff8bc..5687161 100644
--- a/Source/JavaScriptCore/runtime/JSFunction.cpp
+++ b/Source/JavaScriptCore/runtime/JSFunction.cpp
@@ -63,7 +63,12 @@
JSFunction* JSFunction::create(VM& vm, FunctionExecutable* executable, JSScope* scope)
{
- JSFunction* result = createImpl(vm, executable, scope);
+ return create(vm, executable, scope, scope->globalObject()->functionStructure());
+}
+
+JSFunction* JSFunction::create(VM& vm, FunctionExecutable* executable, JSScope* scope, Structure* structure)
+{
+ JSFunction* result = createImpl(vm, executable, scope, structure);
executable->singletonFunction()->notifyWrite(vm, result, "Allocating a function");
return result;
}
diff --git a/Source/JavaScriptCore/runtime/JSFunction.h b/Source/JavaScriptCore/runtime/JSFunction.h
index 7878348..63fed33 100644
--- a/Source/JavaScriptCore/runtime/JSFunction.h
+++ b/Source/JavaScriptCore/runtime/JSFunction.h
@@ -73,6 +73,7 @@
static JSFunction* createWithInvalidatedReallocationWatchpoint(VM&, FunctionExecutable*, JSScope*);
static JSFunction* create(VM&, FunctionExecutable*, JSScope*);
+ static JSFunction* create(VM&, FunctionExecutable*, JSScope*, Structure*);
#if ENABLE(WEBASSEMBLY)
static JSFunction* create(VM&, WebAssemblyExecutable*, JSScope*);
#endif
@@ -151,7 +152,6 @@
protected:
JS_EXPORT_PRIVATE JSFunction(VM&, JSGlobalObject*, Structure*);
- JSFunction(VM&, FunctionExecutable*, JSScope*);
JSFunction(VM&, FunctionExecutable*, JSScope*, Structure*);
#if ENABLE(WEBASSEMBLY)
@@ -179,9 +179,9 @@
static NativeExecutable* lookUpOrCreateNativeExecutable(VM&, NativeFunction, Intrinsic, NativeFunction nativeConstructor, const String& name);
private:
- static JSFunction* createImpl(VM& vm, FunctionExecutable* executable, JSScope* scope)
+ static JSFunction* createImpl(VM& vm, FunctionExecutable* executable, JSScope* scope, Structure* structure)
{
- JSFunction* function = new (NotNull, allocateCell<JSFunction>(vm.heap)) JSFunction(vm, executable, scope);
+ JSFunction* function = new (NotNull, allocateCell<JSFunction>(vm.heap)) JSFunction(vm, executable, scope, structure);
ASSERT(function->structure()->globalObject());
function->finishCreation(vm);
return function;
diff --git a/Source/JavaScriptCore/runtime/JSFunctionInlines.h b/Source/JavaScriptCore/runtime/JSFunctionInlines.h
index a1f7c67..ef43d32 100644
--- a/Source/JavaScriptCore/runtime/JSFunctionInlines.h
+++ b/Source/JavaScriptCore/runtime/JSFunctionInlines.h
@@ -35,16 +35,9 @@
VM& vm, FunctionExecutable* executable, JSScope* scope)
{
ASSERT(executable->singletonFunction()->hasBeenInvalidated());
- return createImpl(vm, executable, scope);
+ return createImpl(vm, executable, scope, scope->globalObject()->functionStructure());
}
-inline JSFunction::JSFunction(VM& vm, FunctionExecutable* executable, JSScope* scope)
- : Base(vm, scope, scope->globalObject()->functionStructure())
- , m_executable(vm, this, executable)
- , m_rareData()
-{
-}
-
inline JSFunction::JSFunction(VM& vm, FunctionExecutable* executable, JSScope* scope, Structure* structure)
: Base(vm, scope, structure)
, m_executable(vm, this, executable)
diff --git a/Source/JavaScriptCore/tests/stress/class-subclassing-function.js b/Source/JavaScriptCore/tests/stress/class-subclassing-function.js
new file mode 100644
index 0000000..7b8ea99
--- /dev/null
+++ b/Source/JavaScriptCore/tests/stress/class-subclassing-function.js
@@ -0,0 +1,33 @@
+F = class extends Function { }
+
+function test(i) {
+
+ let f = new F("x", "return x + " + i + ";");
+ let C = new F("x", "this.x = x; this.i = " + i);
+
+ if (!(f instanceof Function && f instanceof F))
+ throw "bad chain";
+
+ if (f(1) !== i+1)
+ throw "function was not called correctly";
+
+ let o = new C("hello");
+ if (o.x !== "hello" || o.i !== i)
+ throw "function as constructor was not correct";
+
+ let g = new F("x", "y", "return this.foo + x + y");
+ if (g.call({foo:1}, 1, 1) !== 3)
+ throw "function was not .callable";
+
+ let g2 = g.bind({foo:1}, 1);
+ if (g2 instanceof F)
+ throw "the binding of a subclass should not inherit from the original function";
+
+ if (g2(1) !== 3)
+ throw "binding didn't work";
+
+}
+noInline(test);
+
+for (i = 0; i < 10000; i++)
+ test(i);