Bug 1169731 - [[Call]] on a class constructor should throw. (r=jandem)

This commit is contained in:
Eric Faust 2015-06-17 14:37:49 -07:00
parent 4c8e1b1942
commit 559021c984
12 changed files with 100 additions and 9 deletions

View File

@ -108,6 +108,7 @@ namespace JS {
_(CantInlineNoBaseline) \
_(CantInlineLazy) \
_(CantInlineNotConstructor) \
_(CantInlineClassConstructor) \
_(CantInlineDisabledIon) \
_(CantInlineTooManyArgs) \
_(CantInlineRecursive) \

View File

@ -0,0 +1,30 @@
load(libdir + "class.js");
var test = `
function test(fun) {
fun();
}
// Generate a CallAnyScripted stub in test()
for (let i = 0; i < 20; i++) {
test(function() { /* wheeee */ });
}
class foo {
constructor() { }
}
// Compile foo()
for (let i = 0; i < 11; i++)
new foo();
try {
test(foo);
throw new Error("Invoking a class constructor without new must throw");
} catch (e if e instanceof TypeError) { }
`;
if (classesEnabled())
eval(test);

View File

@ -9560,6 +9560,10 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
if (constructing && !fun->isConstructor())
return true;
// Likewise, if the callee is a class constructor, we have to throw.
if (!constructing && fun->isClassConstructor())
return true;
if (!fun->hasJITCode()) {
// Don't treat this as an unoptimizable case, as we'll add a stub
// when the callee becomes hot.
@ -10418,10 +10422,13 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm)
// Ensure the object is a function.
masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_,
&failure);
if (isConstructing_)
if (isConstructing_) {
masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure);
else
} else {
masm.branchIfFunctionHasNoScript(callee, &failure);
masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee,
regs.getAny(), &failure);
}
}
// Load the JSScript.

View File

@ -3028,10 +3028,12 @@ CodeGenerator::visitCallGeneric(LCallGeneric* call)
// Guard that calleereg is an interpreted function with a JSScript.
// If we are constructing, also ensure the callee is a constructor.
if (call->mir()->isConstructing())
if (call->mir()->isConstructing()) {
masm.branchIfNotInterpretedConstructor(calleereg, nargsreg, &invoke);
else
} else {
masm.branchIfFunctionHasNoScript(calleereg, &invoke);
masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, calleereg, objreg, &invoke);
}
// Knowing that calleereg is a non-native function, load the JSScript.
masm.loadPtr(Address(calleereg, JSFunction::offsetOfNativeOrScript()), objreg);
@ -3125,6 +3127,7 @@ CodeGenerator::visitCallKnown(LCallKnown* call)
// Native single targets are handled by LCallNative.
MOZ_ASSERT(!target->isNative());
MOZ_ASSERT_IF(target->isClassConstructor(), call->isConstructing());
// Missing arguments must have been explicitly appended by the IonBuilder.
DebugOnly<unsigned> numNonArgsOnStack = 1 + call->isConstructing();
MOZ_ASSERT(target->nargs() <= call->mir()->numStackArgs() - numNonArgsOnStack);

View File

@ -438,6 +438,11 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo)
return DontInline(inlineScript, "Callee is not a constructor");
}
if (!callInfo.constructing() && target->isClassConstructor()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineClassConstructor);
return DontInline(inlineScript, "Not constructing class constructor");
}
AnalysisMode analysisMode = info().analysisMode();
if (!CanIonCompile(inlineScript, analysisMode)) {
trackOptimizationOutcome(TrackedOutcome::CantInlineDisabledIon);

View File

@ -456,7 +456,7 @@ LIRGenerator::visitCall(MCall* call)
MOZ_ASSERT(ok, "How can we not have four temp registers?");
lir = new(alloc()) LCallDOMNative(tempFixed(cxReg), tempFixed(objReg),
tempFixed(privReg), tempFixed(argsReg));
} else if (target) {
} else if (target && !(target->isClassConstructor() && !call->isConstructing())) {
// Call known functions.
if (target->isNative()) {
Register cxReg, numReg, vpReg, tmpReg;

View File

@ -941,6 +941,15 @@ class MacroAssembler : public MacroAssemblerSpecific
branchTestClassIsProxy(proxy, scratch, label);
}
void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
Register scratch, Label* label)
{
Address flags(fun, JSFunction::offsetOfFlags());
load32(flags, scratch);
and32(Imm32(JSFunction::FUNCTION_KIND_MASK), scratch);
branch32(cond, scratch, Imm32(kind << JSFunction::FUNCTION_KIND_SHIFT), label);
}
public:
#ifndef JS_CODEGEN_ARM64
// StackPointer manipulation functions.

View File

@ -102,6 +102,7 @@ MSG_DEF(JSMSG_CANT_SET_PROTO_CYCLE, 0, JSEXN_TYPEERR, "can't set prototype: i
MSG_DEF(JSMSG_INVALID_ARG_TYPE, 3, JSEXN_TYPEERR, "Invalid type: {0} can't be a{1} {2}")
MSG_DEF(JSMSG_TERMINATED, 1, JSEXN_ERR, "Script terminated by timeout at:\n{0}")
MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0}.prototype is not an object or null")
MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|")
// JSON
MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data")

View File

@ -0,0 +1,28 @@
// Class constructors don't have a [[Call]]
var test = `
class Foo {
constructor() { }
}
assertThrowsInstanceOf(Foo, TypeError);
class Bar extends Foo {
constructor() { }
}
assertThrowsInstanceOf(Bar, TypeError);
assertThrowsInstanceOf(class { constructor() { } }, TypeError);
assertThrowsInstanceOf(class extends Foo { constructor() { } }, TypeError);
assertThrowsInstanceOf(class foo { constructor() { } }, TypeError);
assertThrowsInstanceOf(class foo extends Foo { constructor() { } }, TypeError);
`;
if (classesEnabled())
eval(test);
if (typeof reportCompare === 'function')
reportCompare(0,0,"OK");

View File

@ -44,7 +44,7 @@ for (let a of [testClass,
assertEq(aConstDesc.writable, true);
assertEq(aConstDesc.configurable, true);
assertEq(aConstDesc.enumerable, false);
aConstDesc.value();
new aConstDesc.value();
assertEq(constructorCalled, true);
// __proto__ is just an identifier for classes. No prototype changes are made.

View File

@ -699,6 +699,11 @@ js::Invoke(JSContext* cx, CallArgs args, MaybeConstruct construct)
/* Invoke native functions. */
JSFunction* fun = &args.callee().as<JSFunction>();
if (construct != CONSTRUCT && fun->isClassConstructor()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
return false;
}
if (fun->isNative()) {
MOZ_ASSERT_IF(construct, !fun->isConstructor());
return CallJSNative(cx, fun->native(), args);
@ -2947,7 +2952,9 @@ CASE(JSOP_FUNCALL)
bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
/* Don't bother trying to fast-path calls to scripted non-constructors. */
if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor()) {
if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor() ||
(!construct && maybeFun->isClassConstructor()))
{
if (construct) {
if (!InvokeConstructor(cx, args))
goto error;

View File

@ -29,11 +29,11 @@ namespace js {
*
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
*/
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 292;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 293;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 401,
static_assert(JSErr_Limit == 402,
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
"removed MSG_DEFs from js.msg, you should increment "
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "