Bug 1141863 - Part 2: Implement ES6 SuperCall. (r=jandem, r=jorendorff)

This commit is contained in:
Eric Faust 2015-10-08 17:01:49 -07:00
parent 3eab0ab893
commit 6cbeeeb97c
34 changed files with 304 additions and 126 deletions

View File

@ -3111,13 +3111,20 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_NEW:
case PNK_TAGGED_TEMPLATE:
case PNK_CALL:
case PNK_SUPERCALL:
{
ParseNode* next = pn->pn_head;
MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
RootedValue callee(cx);
if (!expression(next, &callee))
return false;
if (pn->isKind(PNK_SUPERCALL)) {
MOZ_ASSERT(next->isKind(PNK_POSHOLDER));
if (!builder.super(&next->pn_pos, &callee))
return false;
} else {
if (!expression(next, &callee))
return false;
}
NodeVector args(cx);
if (!args.reserve(pn->pn_count - 1))
@ -3135,6 +3142,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
if (pn->getKind() == PNK_TAGGED_TEMPLATE)
return builder.taggedTemplate(callee, args, &pn->pn_pos, dst);
// SUPERCALL is Call(super, args)
return pn->isKind(PNK_NEW)
? builder.newExpression(callee, args, &pn->pn_pos, dst)

View File

@ -2190,6 +2190,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_NEW:
case PNK_CALL:
case PNK_TAGGED_TEMPLATE:
case PNK_SUPERCALL:
MOZ_ASSERT(pn->isArity(PN_LIST));
*answer = true;
return true;
@ -6732,6 +6733,12 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn)
}
callop = false;
break;
case PNK_POSHOLDER:
MOZ_ASSERT(pn->isKind(PNK_SUPERCALL));
MOZ_ASSERT(parser->handler.isSuperBase(pn2, cx));
if (!emit1(JSOP_SUPERFUN))
return false;
break;
default:
if (!emitTree(pn2))
return false;
@ -6744,7 +6751,8 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn)
return false;
}
bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW;
bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW ||
pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;;
/*
* Emit code for each argument in order, then emit the JSOP_*CALL or
@ -6760,17 +6768,27 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn)
}
if (isNewOp) {
// Repush the callee as new.target
if (!emitDupAt(argc + 1))
return false;
if (pn->isKind(PNK_SUPERCALL)) {
if (!emit1(JSOP_NEWTARGET))
return false;
} else {
// Repush the callee as new.target
if (!emitDupAt(argc + 1))
return false;
}
}
} else {
if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY))
return false;
if (isNewOp) {
if (!emitDupAt(2))
return false;
if (pn->isKind(PNK_SUPERCALL)) {
if (!emit1(JSOP_NEWTARGET))
return false;
} else {
if (!emitDupAt(2))
return false;
}
}
}
emittingForInit = oldEmittingForInit;
@ -6796,6 +6814,9 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn)
if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS))
return false;
}
if (pn->isKind(PNK_SUPERCALL) && !emit1(JSOP_SETTHIS))
return false;
return true;
}
@ -7852,6 +7873,7 @@ BytecodeEmitter::emitTree(ParseNode* pn)
case PNK_TAGGED_TEMPLATE:
case PNK_CALL:
case PNK_GENEXP:
case PNK_SUPERCALL:
ok = emitCallOrNew(pn);
break;

View File

@ -414,6 +414,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_CLASSNAMES:
case PNK_NEWTARGET:
case PNK_POSHOLDER:
case PNK_SUPERCALL:
MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
"some parent node without recurring to test this node");
@ -1579,7 +1580,8 @@ static bool
FoldCall(ExclusiveContext* cx, ParseNode* node, Parser<FullParseHandler>& parser,
bool inGenexpLambda)
{
MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_TAGGED_TEMPLATE));
MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) ||
node->isKind(PNK_TAGGED_TEMPLATE));
MOZ_ASSERT(node->isArity(PN_LIST));
// Don't fold a parenthesized callable component in an invocation, as this
@ -1876,6 +1878,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
return FoldAdd(cx, pnp, parser, inGenexpLambda);
case PNK_CALL:
case PNK_SUPERCALL:
case PNK_TAGGED_TEMPLATE:
return FoldCall(cx, pn, parser, inGenexpLambda);

View File

@ -376,6 +376,7 @@ class NameResolver
case PNK_EXPORT_BATCH_SPEC:
case PNK_FRESHENBLOCK:
case PNK_OBJECT_PROPERTY_NAME:
case PNK_POSHOLDER:
MOZ_ASSERT(cur->isArity(PN_NULLARY));
break;
@ -673,6 +674,7 @@ class NameResolver
case PNK_COMMA:
case PNK_NEW:
case PNK_CALL:
case PNK_SUPERCALL:
case PNK_GENEXP:
case PNK_ARRAY:
case PNK_STATEMENTLIST:
@ -796,7 +798,6 @@ class NameResolver
case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST
case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE
case PNK_CLASSNAMES: // by PNK_CLASS
case PNK_POSHOLDER: // by PNK_NEWTARGET, PNK_DOT
MOZ_CRASH("should have been handled by a parent node");
case PNK_LIMIT: // invalid sentinel value

View File

@ -489,6 +489,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_COMMA:
case PNK_NEW:
case PNK_CALL:
case PNK_SUPERCALL:
case PNK_GENEXP:
case PNK_ARRAY:
case PNK_OBJECT:

View File

@ -175,6 +175,7 @@ class PackedScopeCoordinate
F(CLASSNAMES) \
F(NEWTARGET) \
F(POSHOLDER) \
F(SUPERCALL) \
\
/* Unary operators. */ \
F(TYPEOFNAME) \

View File

@ -8648,9 +8648,31 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool
tt == TOK_NO_SUBS_TEMPLATE)
{
if (handler.isSuperBase(lhs, context)) {
// For now...
report(ParseError, false, null(), JSMSG_BAD_SUPER);
return null();
if (!pc->sc->isFunctionBox() || !pc->sc->asFunctionBox()->isDerivedClassConstructor()) {
report(ParseError, false, null(), JSMSG_BAD_SUPERCALL);
return null();
}
if (tt != TOK_LP) {
report(ParseError, false, null(), JSMSG_BAD_SUPER);
return null();
}
nextMember = handler.newList(PNK_SUPERCALL, lhs, JSOP_SUPERCALL);
if (!nextMember)
return null();
// Despite the fact that it's impossible to have |super()| is a
// generator, we still inherity the yieldHandling of the
// memberExpression, per spec. Curious.
bool isSpread = false;
if (!argumentList(yieldHandling, nextMember, &isSpread))
return null();
if (isSpread)
handler.setOp(nextMember, JSOP_SPREADSUPERCALL);
return nextMember;
}
nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate();

View File

@ -374,6 +374,7 @@ class FunctionBox : public ObjectBox, public SharedContext
hasExtensibleScope() ||
needsDeclEnvObject() ||
needsHomeObject() ||
isDerivedClassConstructor() ||
isGenerator();
}
};

View File

@ -2903,7 +2903,7 @@ BaselineCompiler::emitCall()
{
MOZ_ASSERT(IsCallPC(pc));
bool construct = JSOp(*pc) == JSOP_NEW;
bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL;
uint32_t argc = GET_ARGC(pc);
frame.syncStack(0);
@ -2930,13 +2930,13 @@ BaselineCompiler::emitSpreadCall()
masm.move32(Imm32(1), R0.scratchReg());
// Call IC
ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ JSOp(*pc) == JSOP_SPREADNEW,
bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL;
ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct,
/* isSpread = */ true);
if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
return false;
// Update FrameInfo.
bool construct = JSOp(*pc) == JSOP_SPREADNEW;
frame.popn(3 + construct);
frame.push(R0);
return true;
@ -2954,6 +2954,12 @@ BaselineCompiler::emit_JSOP_NEW()
return emitCall();
}
bool
BaselineCompiler::emit_JSOP_SUPERCALL()
{
return emitCall();
}
bool
BaselineCompiler::emit_JSOP_FUNCALL()
{
@ -2990,6 +2996,12 @@ BaselineCompiler::emit_JSOP_SPREADNEW()
return emitSpreadCall();
}
bool
BaselineCompiler::emit_JSOP_SPREADSUPERCALL()
{
return emitSpreadCall();
}
bool
BaselineCompiler::emit_JSOP_SPREADEVAL()
{

View File

@ -201,7 +201,9 @@ namespace jit {
_(JSOP_SETRVAL) \
_(JSOP_RETRVAL) \
_(JSOP_RETURN) \
_(JSOP_NEWTARGET)
_(JSOP_NEWTARGET) \
_(JSOP_SUPERCALL) \
_(JSOP_SPREADSUPERCALL)
class BaselineCompiler : public BaselineCompilerSpecific
{

View File

@ -8644,6 +8644,8 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
JSOp op, uint32_t argc, Value* vp, bool constructing, bool isSpread,
bool createSingleton, bool* handled)
{
bool isSuper = op == JSOP_SUPERCALL || op == JSOP_SPREADSUPERCALL;
if (createSingleton || op == JSOP_EVAL || op == JSOP_STRICTEVAL)
return true;
@ -8751,9 +8753,11 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype));
// Remember the template object associated with any script being called
// as a constructor, for later use during Ion compilation.
// as a constructor, for later use during Ion compilation. This is unsound
// for super(), as a single callsite can have multiple possible prototype object
// created (via different newTargets)
RootedObject templateObject(cx);
if (constructing) {
if (constructing && !isSuper) {
// If we are calling a constructor for which the new script
// properties analysis has not been performed yet, don't attach a
// stub. After the analysis is performed, CreateThisForFunction may
@ -8853,7 +8857,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
}
RootedObject templateObject(cx);
if (MOZ_LIKELY(!isSpread)) {
if (MOZ_LIKELY(!isSpread && !isSuper)) {
bool skipAttach = false;
CallArgs args = CallArgsFromVp(argc, vp);
if (!GetTemplateObjectForNative(cx, fun->native(), args, &templateObject, &skipAttach))

View File

@ -1867,7 +1867,8 @@ IonBuilder::inspectOpcode(JSOp op)
case JSOP_CALL:
case JSOP_NEW:
return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW);
case JSOP_SUPERCALL:
return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL);
case JSOP_EVAL:
case JSOP_STRICTEVAL:
@ -6140,6 +6141,8 @@ IonBuilder::createThisScriptedBaseline(MDefinition* callee)
return nullptr;
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject)
return nullptr;
if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
return nullptr;

View File

@ -214,6 +214,7 @@ MSG_DEF(JSMSG_BAD_STRICT_ASSIGN, 1, JSEXN_SYNTAXERR, "can't assign to {0}
MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch statement")
MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'")
MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods")
MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors")
MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension")
MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list")
MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression")
@ -509,6 +510,7 @@ MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an
// Super
MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'")
MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor")
// Modules
MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *")

View File

@ -156,6 +156,7 @@ class JSFunction : public js::NativeObject
nonLazyScript()->funHasExtensibleScope() ||
nonLazyScript()->funNeedsDeclEnvObject() ||
nonLazyScript()->needsHomeObject() ||
nonLazyScript()->isDerivedClassConstructor() ||
isGenerator();
}

View File

@ -125,6 +125,7 @@ js::StackUses(JSScript* script, jsbytecode* pc)
case JSOP_POPN:
return GET_UINT16(pc);
case JSOP_NEW:
case JSOP_SUPERCALL:
return 2 + GET_ARGC(pc) + 1;
default:
/* stack: fun, this, [argc arguments] */

View File

@ -36,16 +36,14 @@ class base {
override() { overrideCalled = "base" }
}
class derived extends base {
constructor() { };
constructor() { super(); };
override() { overrideCalled = "derived"; }
}
var derivedExpr = class extends base {
constructor() { };
constructor() { super(); };
override() { overrideCalled = "derived"; }
};
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
// Make sure we get the right object layouts.
for (let extension of [derived, derivedExpr]) {
baseMethodCalled = false;
@ -67,7 +65,6 @@ for (let extension of [derived, derivedExpr]) {
extension.staticMethod();
assertEq(staticMethodCalled, true);
}
*/
// Gotta extend an object, or null.
function nope() {

View File

@ -28,9 +28,7 @@ var g = newGlobal();
var dbg = Debugger(g);
dbg.onDebuggerStatement = function(frame) { assertThrowsInstanceOf(()=>frame.eval(''), InternalError); };
// Remove the assertion and add super() when super() is implemented!
assertThrownErrorContains(() => g.eval("new class foo extends null { constructor() { debugger; } }"), "|this|");
// g.eval("new class foo extends null { constructor() { debugger; } }()");
g.eval("new class foo extends null { constructor() { debugger; return {}; } }()");
`;
if (classesEnabled())

View File

@ -13,7 +13,7 @@ class base {
}
class derived extends base {
constructor() {}
constructor() { super(); }
get a() { return super.getValue(); }
set a(v) { super.setValue(v); }
@ -31,11 +31,8 @@ class derived extends base {
}
}
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
var derivedInstance = new derived();
derivedInstance.test();
*/
`;

View File

@ -4,19 +4,17 @@ class Base {
constructor() {}
}
class Mid extends Base {
constructor() {}
constructor() { super(); }
f() { return new super.constructor(); }
}
class Derived extends Mid {
constructor() {}
constructor() { super(); }
}
assertThrowsInstanceOf(()=>new Derived(), TypeError, "You implemented |super()|?!");
/*
let d = new Derived();
var df = d.f();
assertEq(df.constructor, Base);
*/
`;
if (classesEnabled())

View File

@ -9,7 +9,7 @@ class base {
}
class middle extends base {
constructor() { }
constructor() { super(); }
testChain() {
this.middleCalled = true;
super.testChain();
@ -17,7 +17,7 @@ class middle extends base {
}
class derived extends middle {
constructor() { }
constructor() { super(); }
testChain() {
super.testChain();
assertEq(this.middleCalled, true);
@ -25,8 +25,6 @@ class derived extends middle {
}
}
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
new derived().testChain();
// Super even chains in a wellbehaved fashion with normal functions.
@ -34,7 +32,7 @@ function bootlegMiddle() { }
bootlegMiddle.prototype = middle.prototype;
new class extends bootlegMiddle {
constructor() { }
constructor() { super(); }
testChain() {
super.testChain();
assertEq(this.middleCalled, true);
@ -45,11 +43,11 @@ new class extends bootlegMiddle {
// Now let's try out some "long" chains
base.prototype.x = "yeehaw";
let chain = class extends base { constructor() { } }
let chain = class extends base { constructor() { super(); } }
const CHAIN_LENGTH = 100;
for (let i = 0; i < CHAIN_LENGTH; i++)
chain = class extends chain { constructor() { } }
chain = class extends chain { constructor() { super(); } }
// Now we poke the chain
let inst = new chain();
@ -57,7 +55,7 @@ inst.testChain();
assertEq(inst.baseCalled, true);
assertEq(inst.x, "yeehaw");
*/
`;
if (classesEnabled())

View File

@ -8,7 +8,7 @@ class base {
}
class derived extends base {
constructor() { }
constructor() { super(); }
testDeleteProp() { delete super.prop; }
testDeleteElem() {
let sideEffect = 0;
@ -22,9 +22,6 @@ class derived extends base {
}
}
assertThrowsInstanceOf(()=> new derived(), TypeError, "You implemented |super()|?!");
/*
var d = new derived();
assertThrowsInstanceOf(() => d.testDeleteProp(), ReferenceError);
d.testDeleteElem();
@ -46,7 +43,7 @@ Object.setPrototypeOf(thing2, new Proxy({}, {
deleteProperty(x) { throw "FAIL"; }
}));
assertThrowsInstanceOf(() => thing2.go(), ReferenceError);
*/
`;
if (classesEnabled())

View File

@ -26,7 +26,7 @@ class base {
}
class derived extends base {
constructor() { }
constructor() { super(); }
// |super| actually checks the chain, not |this|
method() { throw "FAIL"; }
@ -70,13 +70,11 @@ class derived extends base {
}
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
derivedInstance = new derived();
derivedInstance.test();
derivedInstance.testInEval();
derivedInstance.testInArrow();
*/
`;
if (classesEnabled())

View File

@ -22,7 +22,7 @@ Object.defineProperty(base.prototype, "intendent",
const testArr = [525600, "Fred"];
class derived extends base {
constructor() { }
constructor() { super(); }
prepForTest() { seenValues = []; }
testAsserts() { assertDeepEq(seenValues, testArr); }
testProps() {
@ -37,12 +37,10 @@ class derived extends base {
}
}
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
let d = new derived();
d.testProps();
d.testElems();
*/
`;
if (classesEnabled())

View File

@ -14,14 +14,12 @@ class base {
}
class derived extends base {
constructor() { }
constructor() { super(); }
test(expected) { super.test(expected); }
testArrow() { return (() => super.test(this)); }
["testCPN"](expected) { super.test(expected); }
}
assertThrowsInstanceOf(()=>new derived(), TypeError, "You implemented |super()|?!");
/*
let derivedInstance = new derived();
derivedInstance.test(derivedInstance);
derivedInstance.testCPN(derivedInstance);
@ -57,11 +55,11 @@ class base2 {
let animals = [];
for (let exprBase of [base1, base2])
new class extends exprBase {
constructor() { }
constructor() { super(); }
test() { animals.push(super["test"]()); }
}().test();
assertDeepEq(animals, ["llama", "alpaca"]);
*/
`;
if (classesEnabled())

View File

@ -6,7 +6,7 @@ class base {
}
class derived extends base {
constructor() { this.methodCalled = 0; }
constructor() { super(); this.methodCalled = 0; }
// Test orderings of various evaluations relative to the superbase
@ -70,8 +70,6 @@ function reset() {
Object.setPrototypeOf(derived.prototype, base.prototype);
}
assertThrowsInstanceOf(() => new derived(), TypeError, "You implemented |super()|?!");
/*
let instance = new derived();
assertThrowsInstanceOf(() => instance.testElem(), TypeError);
reset();
@ -92,7 +90,7 @@ instance.testAssignElemPropValChange();
instance.testAssignProp();
instance.testCompoundAssignProp();
*/
`;
if (classesEnabled())

View File

@ -10,7 +10,7 @@ class base {
let standin = { test() { return true; } };
class derived extends base {
constructor() { }
constructor() { super(); }
test() {
assertEq(super.test(), false);
Object.setPrototypeOf(derived.prototype, standin);
@ -18,8 +18,7 @@ class derived extends base {
}
}
// This shouldn't throw, but we don't have |super()| yet.
assertThrowsInstanceOf(() => new derived().test(), TypeError);
new derived().test();
`;

View File

@ -11,7 +11,7 @@ let midStaticHandler = { };
let getterCalled, setterCalled;
class mid extends new Proxy(base, midStaticHandler) {
constructor() { }
constructor() { super(); }
testSuperInProxy() {
super.prop = "looking";
assertEq(setterCalled, true);
@ -21,7 +21,7 @@ class mid extends new Proxy(base, midStaticHandler) {
}
class child extends mid {
constructor() { }
constructor() { super(); }
static testStaticLookups() {
// This funtion is called more than once.
this.called = false;
@ -30,9 +30,6 @@ class child extends mid {
}
}
assertThrowsInstanceOf(()=> new mid(), TypeError, "You implemented |super()|?!");
/*
let midInstance = new mid();
// Make sure proxies are searched properly on the prototype chain
@ -83,7 +80,7 @@ var wrappedBase = g.eval("({ method() { return this.__secretProp__; } })");
var unwrappedDerived = { __secretProp__: 42, method() { return super.method(); } }
Object.setPrototypeOf(unwrappedDerived, wrappedBase);
assertEq(unwrappedDerived.method(), 42);
*/
`;
if (classesEnabled())

View File

@ -9,7 +9,7 @@ class base {
}
class derived extends base {
constructor() { this.prop = "flamingo"; }
constructor() { super(); this.prop = "flamingo"; }
toString() { throw "No!"; }
@ -38,13 +38,11 @@ class derived extends base {
Object.defineProperty(derived.prototype, "nonWritableProp", { writable: false, value: "pony" });
assertThrowsInstanceOf(()=> new derived(), TypeError, "You implemented |super()|?!");
/*
let instance = new derived();
instance.testSkipGet();
instance.testSkipDerivedOverrides();
instance.testSkipSet();
*/
`;
if (classesEnabled())

View File

@ -207,6 +207,9 @@ function newExpr(callee, args) {
function callExpr(callee, args) {
return Pattern({ type: "CallExpression", callee: callee, arguments: args });
}
function superCallExpr(args) {
return callExpr({ type: "Super" }, args);
}
function arrExpr(elts) {
return Pattern({ type: "ArrayExpression", elements: elts });
}

View File

@ -437,6 +437,30 @@ function testClasses() {
assertError("{ foo() { super } }", SyntaxError);
assertClassError("class NAME { constructor() { super; } }", SyntaxError);
/* SuperCall */
// SuperCall is invalid outside derived class constructors.
assertError("super()", SyntaxError);
assertError("(function() { super(); })", SyntaxError);
// Even in class constructors
assertClassError("class NAME { constructor() { super(); } }", SyntaxError);
function superConstructor(args) {
return classMethod(ident("constructor"),
methodFun("constructor", "method", false,
[], [exprStmt(superCallExpr(args))]),
"method", false);
}
// SuperCall works with various argument configurations.
assertClass("class NAME extends null { constructor() { super() } }",
[superConstructor([])], lit(null));
assertClass("class NAME extends null { constructor() { super(1) } }",
[superConstructor([lit(1)])], lit(null));
assertClass("class NAME extends null { constructor() { super(...[]) } }",
[superConstructor([spread(arrExpr([]))])], lit(null));
/* EOF */
// Clipped classes should throw a syntax error
assertClassError("class NAME {", SyntaxError);

View File

@ -875,9 +875,9 @@ StackCheckIsConstructorCalleeNewTarget(JSContext* cx, HandleValue callee, Handle
return false;
}
// The new.target for a stack construction attempt is just the callee: no
// need to check that it's a constructor.
MOZ_ASSERT(&callee.toObject() == &newTarget.toObject());
// The new.target has already been vetted by previous calls, or is the callee.
// We can just assert that it's a constructor.
MOZ_ASSERT(IsConstructor(newTarget));
return true;
}
@ -1750,6 +1750,34 @@ SetObjectElementOperation(JSContext* cx, HandleObject obj, HandleValue receiver,
result.checkStrictErrorOrWarning(cx, obj, id, strict);
}
/*
* Get the innermost enclosing function that has a 'this' binding.
*
* Implements ES6 12.3.5.2 GetSuperConstructor() steps 1-3, including
* the loop in ES6 8.3.2 GetThisEnvironment(). Our implementation of
* ES6 12.3.5.3 MakeSuperPropertyReference() also uses this code.
*/
static JSFunction&
GetSuperEnvFunction(JSContext *cx, InterpreterRegs& regs)
{
ScopeIter si(cx, regs.fp()->scopeChain(), regs.fp()->script()->innermostStaticScope(regs.pc));
for (; !si.done(); ++si) {
if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) {
JSFunction& callee = si.scope().as<CallObject>().callee();
// Arrow functions don't have the information we're looking for,
// their enclosing scopes do. Nevertheless, they might have call
// objects. Skip them to find what we came for.
if (callee.isArrow())
continue;
return callee;
}
}
MOZ_CRASH("unexpected scope chain for GetSuperEnvFunction");
}
/*
* As an optimization, the interpreter creates a handful of reserved Rooted<T>
* variables at the beginning, thus inserting them into the Rooted list once
@ -2059,10 +2087,6 @@ CASE(JSOP_NOP)
CASE(JSOP_UNUSED2)
CASE(JSOP_UNUSED14)
CASE(JSOP_BACKPATCH)
CASE(JSOP_UNUSED163)
CASE(JSOP_UNUSED164)
CASE(JSOP_UNUSED165)
CASE(JSOP_UNUSED166)
CASE(JSOP_UNUSED167)
CASE(JSOP_UNUSED168)
CASE(JSOP_UNUSED169)
@ -3006,6 +3030,7 @@ END_CASE(JSOP_EVAL)
CASE(JSOP_SPREADNEW)
CASE(JSOP_SPREADCALL)
CASE(JSOP_SPREADSUPERCALL)
if (REGS.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, REGS.pc);
/* FALL THROUGH */
@ -3015,7 +3040,7 @@ CASE(JSOP_STRICTSPREADEVAL)
{
static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH,
"spreadeval and strictspreadeval must be the same size");
bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW;
bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW || JSOp(*REGS.pc) == JSOP_SPREADSUPERCALL;;
MOZ_ASSERT(REGS.stackDepth() >= 3u + construct);
@ -3047,12 +3072,13 @@ CASE(JSOP_FUNAPPLY)
CASE(JSOP_NEW)
CASE(JSOP_CALL)
CASE(JSOP_SUPERCALL)
CASE(JSOP_FUNCALL)
{
if (REGS.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, REGS.pc);
bool construct = (*REGS.pc == JSOP_NEW);
bool construct = (*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
@ -4075,37 +4101,22 @@ END_CASE(JSOP_INITHOMEOBJECT)
CASE(JSOP_SUPERBASE)
{
ScopeIter si(cx, REGS.fp()->scopeChain(), REGS.fp()->script()->innermostStaticScope(REGS.pc));
for (; !si.done(); ++si) {
if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) {
JSFunction& callee = si.scope().as<CallObject>().callee();
JSFunction& superEnvFunc = GetSuperEnvFunction(cx, REGS);
MOZ_ASSERT(superEnvFunc.allowSuperProperty());
MOZ_ASSERT(superEnvFunc.nonLazyScript()->needsHomeObject());
const Value& homeObjVal = superEnvFunc.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT);
// Arrow functions don't have the information we're looking for,
// their enclosing scopes do. Nevertheless, they might have call
// objects. Skip them to find what we came for.
if (callee.isArrow())
continue;
ReservedRooted<JSObject*> homeObj(&rootObject0, &homeObjVal.toObject());
ReservedRooted<JSObject*> superBase(&rootObject1);
if (!GetPrototype(cx, homeObj, &superBase))
goto error;
MOZ_ASSERT(callee.allowSuperProperty());
MOZ_ASSERT(callee.nonLazyScript()->needsHomeObject());
const Value& homeObjVal = callee.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT);
ReservedRooted<JSObject*> homeObj(&rootObject0, &homeObjVal.toObject());
ReservedRooted<JSObject*> superBase(&rootObject1);
if (!GetPrototype(cx, homeObj, &superBase))
goto error;
if (!superBase) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
"null", "object");
goto error;
}
PUSH_OBJECT(*superBase);
break;
}
if (!superBase) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
"null", "object");
goto error;
}
if (si.done())
MOZ_CRASH("Unexpected scope chain in superbase");
PUSH_OBJECT(*superBase);
}
END_CASE(JSOP_SUPERBASE)
@ -4114,6 +4125,48 @@ CASE(JSOP_NEWTARGET)
MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined());
END_CASE(JSOP_NEWTARGET)
CASE(JSOP_SUPERFUN)
{
ReservedRooted<JSObject*> superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS));
MOZ_ASSERT(superEnvFunc->as<JSFunction>().isClassConstructor());
MOZ_ASSERT(superEnvFunc->as<JSFunction>().nonLazyScript()->isDerivedClassConstructor());
ReservedRooted<JSObject*> superFun(&rootObject1);
if (!GetPrototype(cx, superEnvFunc, &superFun))
goto error;
ReservedRooted<Value> superFunVal(&rootValue0, UndefinedValue());
if (!superFun)
superFunVal = NullValue();
else if (!superFun->isConstructor())
superFunVal = ObjectValue(*superFun);
if (superFunVal.isObjectOrNull()) {
ReportIsNotFunction(cx, superFunVal, JSDVG_IGNORE_STACK, CONSTRUCT);
goto error;
}
PUSH_OBJECT(*superFun);
}
END_CASE(JSOP_SUPERFUN)
CASE(JSOP_SETTHIS)
{
MOZ_ASSERT(REGS.fp()->isNonEvalFunctionFrame());
MOZ_ASSERT(REGS.fp()->script()->isDerivedClassConstructor());
MOZ_ASSERT(REGS.fp()->callee().isClassConstructor());
if (!REGS.fp()->thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS);
goto error;
}
ReservedRooted<JSObject*> thisv(&rootObject0, &REGS.sp[-1].toObject());
REGS.fp()->setDerivedConstructorThis(thisv);
}
END_CASE(JSOP_SETTHIS)
DEFAULT()
{
char numBuf[12];
@ -4714,7 +4767,7 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand
RootedArrayObject aobj(cx, &arr.toObject().as<ArrayObject>());
uint32_t length = aobj->length();
JSOp op = JSOp(*pc);
bool constructing = op == JSOP_SPREADNEW;
bool constructing = op == JSOP_SPREADNEW || op == JSOP_SPREADSUPERCALL;
if (length > ARGS_LENGTH_MAX) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
@ -4732,7 +4785,7 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand
MOZ_ASSERT(!aobj->getDenseElement(i).isMagic());
#endif
if (op == JSOP_SPREADNEW) {
if (constructing) {
if (!StackCheckIsConstructorCalleeNewTarget(cx, callee, newTarget))
return false;

View File

@ -1669,10 +1669,45 @@
*/ \
macro(JSOP_DEFLET, 162,"deflet", NULL, 5, 0, 0, JOF_ATOM) \
\
macro(JSOP_UNUSED163, 163,"unused163", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED164, 164,"unused164", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED165, 165,"unused165", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED166, 166,"unused166", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Bind the |this| value of a function to the supplied value.
*
* Category: Variables and Scopes
* Type: This
* Operands:
* Stack: this => this
*/ \
macro(JSOP_SETTHIS , 163,"setthis", NULL, 1, 1, 1, JOF_BYTE) \
/*
* Find the function to invoke with |super()| on the scope chain.
*
* Category: Variables and Scopes
* Type: Super
* Operands:
* Stack: => superFun
*/ \
macro(JSOP_SUPERFUN, 164,"superfun", NULL, 1, 0, 1, JOF_BYTE) \
/*
* Behaves exactly like JSOP_NEW, but allows JITs to distinguish the two cases.
*
* Category: Statements
* Type: Function
* Operands: uint16_t argc
* Stack: callee, this, args[0], ..., args[argc-1], newTarget => rval
* nuses: (argc+3)
*/ \
macro(JSOP_SUPERCALL, 165,"supercall", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \
/*
* spreadcall variant of JSOP_SUPERCALL.
*
* Behaves exactly like JSOP_SPREADNEW.
*
* Category: Statements
* Type: Function
* Operands:
* Stack: callee, this, args, newTarget => rval
*/ \
macro(JSOP_SPREADSUPERCALL, 166, "spreadsupercall", NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \
macro(JSOP_UNUSED167, 167,"unused167", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED168, 168,"unused168", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED169, 169,"unused169", NULL, 1, 0, 0, JOF_BYTE) \

View File

@ -741,6 +741,14 @@ class InterpreterFrame
return argv()[-1];
}
void setDerivedConstructorThis(HandleObject thisv) {
MOZ_ASSERT(isNonEvalFunctionFrame());
MOZ_ASSERT(script()->isDerivedClassConstructor());
MOZ_ASSERT(callee().isClassConstructor());
MOZ_ASSERT(thisValue().isMagic(JS_UNINITIALIZED_LEXICAL));
argv()[-1] = ObjectValue(*thisv);
}
/*
* Callee
*

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 = 311;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 312;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 417,
static_assert(JSErr_Limit == 419,
"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 "