Bug 1141865 - Part 3: Parse new.target, add Reflect support, and tests. (r=jorendorff, r=shu)

This commit is contained in:
Eric Faust 2015-06-01 15:03:34 -07:00
parent 2a004af5c4
commit 2efa486971
44 changed files with 548 additions and 39 deletions

View File

@ -7636,6 +7636,11 @@ BytecodeEmitter::emitTree(ParseNode* pn)
ok = emitClass(pn);
break;
case PNK_NEWTARGET:
if (!emit1(JSOP_NEWTARGET))
return false;
break;
default:
MOZ_ASSERT(0);
}

View File

@ -402,6 +402,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
case PNK_CLASSNAMES:
case PNK_SUPERPROP:
case PNK_SUPERELEM:
case PNK_NEWTARGET:
MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
"some parent node without recurring to test this node");

View File

@ -299,6 +299,9 @@ class FullParseHandler
ParseNode* newSuperElement(ParseNode* expr, const TokenPos& pos) {
return new_<SuperElement>(expr, pos);
}
ParseNode* newNewTarget(const TokenPos& pos) {
return new_<NullaryNode>(PNK_NEWTARGET, pos);
}
bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) {
// Object literals with mutated [[Prototype]] are non-constant so that
@ -671,6 +674,9 @@ class FullParseHandler
MOZ_ASSERT(kind != PNK_VAR);
return new_<ListNode>(kind, op, pos());
}
ParseNode* newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
return new_<ListNode>(kind, op, TokenPos(begin, begin + 1));
}
ParseNode* newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET ||
kind == PNK_GLOBALCONST);

View File

@ -374,6 +374,7 @@ class NameResolver
case PNK_FRESHENBLOCK:
case PNK_SUPERPROP:
case PNK_OBJECT_PROPERTY_NAME:
case PNK_NEWTARGET:
MOZ_ASSERT(cur->isArity(PN_NULLARY));
break;

View File

@ -215,6 +215,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
case PNK_OBJECT_PROPERTY_NAME:
case PNK_FRESHENBLOCK:
case PNK_SUPERPROP:
case PNK_NEWTARGET:
MOZ_ASSERT(pn->isArity(PN_NULLARY));
MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately");
MOZ_ASSERT(!pn->isDefn(), "handle non-trivial cases separately");

View File

@ -155,6 +155,7 @@ class UpvarCookie
F(CLASSNAMES) \
F(SUPERPROP) \
F(SUPERELEM) \
F(NEWTARGET) \
\
/* Unary operators. */ \
F(TYPEOF) \

View File

@ -7864,27 +7864,36 @@ Parser<ParseHandler>::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool
/* Check for new expression first. */
if (tt == TOK_NEW) {
lhs = handler.newList(PNK_NEW, JSOP_NEW);
if (!lhs)
uint32_t newBegin = pos().begin;
// Make sure this wasn't a |new.target| in disguise.
Node newTarget;
if (!tryNewTarget(newTarget))
return null();
if (!tokenStream.getToken(&tt, TokenStream::Operand))
return null();
Node ctorExpr = memberExpr(yieldHandling, tt, false, PredictInvoked);
if (!ctorExpr)
return null();
handler.addList(lhs, ctorExpr);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_LP))
return null();
if (matched) {
bool isSpread = false;
if (!argumentList(yieldHandling, lhs, &isSpread))
if (newTarget) {
lhs = newTarget;
} else {
lhs = handler.newList(PNK_NEW, newBegin, JSOP_NEW);
if (!lhs)
return null();
if (isSpread)
handler.setOp(lhs, JSOP_SPREADNEW);
// Gotten by tryNewTarget
tt = tokenStream.currentToken().type;
Node ctorExpr = memberExpr(yieldHandling, tt, false, PredictInvoked);
if (!ctorExpr)
return null();
handler.addList(lhs, ctorExpr);
bool matched;
if (!tokenStream.matchToken(&matched, TOK_LP))
return null();
if (matched) {
bool isSpread = false;
if (!argumentList(yieldHandling, lhs, &isSpread))
return null();
if (isSpread)
handler.setOp(lhs, JSOP_SPREADNEW);
}
}
} else if (tt == TOK_SUPER) {
lhs = null();
@ -8603,6 +8612,44 @@ Parser<ParseHandler>::methodDefinition(YieldHandling yieldHandling, PropListType
return handler.addObjectMethodDefinition(propList, propname, fn, op);
}
template <typename ParseHandler>
bool
Parser<ParseHandler>::tryNewTarget(Node &newTarget)
{
MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_NEW));
uint32_t begin = pos().begin;
newTarget = null();
// |new| expects to look for an operand, so we will honor that.
TokenKind next;
if (!tokenStream.getToken(&next, TokenStream::Operand))
return false;
// Don't unget the token, since lookahead cannot handle someone calling
// getToken() with a different modifier. Callers should inspect currentToken().
if (next != TOK_DOT)
return true;
if (!tokenStream.getToken(&next))
return false;
if (next != TOK_NAME || tokenStream.currentName() != context->names().target) {
report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
"target", TokenKindToDesc(next));
return false;
}
if (!pc->sc->isFunctionBox() || pc->sc->asFunctionBox()->isGenerator() ||
pc->sc->asFunctionBox()->function()->isArrow())
{
reportWithOffset(ParseError, false, begin, JSMSG_BAD_NEWTARGET);
return false;
}
newTarget = handler.newNewTarget(TokenPos(begin, pos().end));
return !!newTarget;
}
template <typename ParseHandler>
typename ParseHandler::Node
Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TokenKind tt,

View File

@ -590,6 +590,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
Node parenExprOrGeneratorComprehension(YieldHandling yieldHandling);
Node exprInParens(InHandling inHandling, YieldHandling yieldHandling);
bool tryNewTarget(Node& newTarget);
bool checkAndMarkSuperScope();
bool methodDefinition(YieldHandling yieldHandling, PropListType listType, Node propList,

View File

@ -193,6 +193,7 @@ class SyntaxParseHandler
Node newClassMethodList(uint32_t begin) { return NodeGeneric; }
Node newSuperProperty(JSAtom* atom, const TokenPos& pos) { return NodeGeneric; }
Node newSuperElement(Node expr, const TokenPos& pos) { return NodeGeneric; }
Node newNewTarget(const TokenPos& pos) { return NodeGeneric; }
bool addPrototypeMutation(Node literal, uint32_t begin, Node expr) { return true; }
bool addPropertyDefinition(Node literal, Node name, Node expr) { return true; }
bool addShorthand(Node literal, Node name, Node expr) { return true; }
@ -281,6 +282,9 @@ class SyntaxParseHandler
MOZ_ASSERT(kind != PNK_VAR);
return NodeGeneric;
}
Node newList(ParseNodeKind kind, uint32_t begin, JSOp op = JSOP_NOP) {
return NodeGeneric;
}
Node newDeclarationList(ParseNodeKind kind, JSOp op = JSOP_NOP) {
MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET ||
kind == PNK_GLOBALCONST);

View File

@ -0,0 +1,6 @@
function testOSRNewTarget(expected) {
for (let i = 0; i < 1100; i++)
assertEq(new.target, expected);
}
new testOSRNewTarget(testOSRNewTarget);

View File

@ -2678,6 +2678,43 @@ BaselineCompiler::emit_JSOP_SETARG()
return emitFormalArgAccess(arg, /* get = */ false);
}
bool
BaselineCompiler::emit_JSOP_NEWTARGET()
{
MOZ_ASSERT(function());
frame.syncStack(0);
// if (!isConstructing()) push(undefined)
Label constructing, done;
masm.branchTestPtr(Assembler::NonZero, frame.addressOfCalleeToken(),
Imm32(CalleeToken_FunctionConstructing), &constructing);
masm.moveValue(UndefinedValue(), R0);
masm.jump(&done);
masm.bind(&constructing);
// else push(argv[Max(numActualArgs, numFormalArgs)])
Register argvLen = R0.scratchReg();
Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs());
masm.loadPtr(actualArgs, argvLen);
Label actualArgsSufficient;
masm.branchPtr(Assembler::AboveOrEqual, argvLen, Imm32(function()->nargs()),
&actualArgsSufficient);
masm.move32(Imm32(function()->nargs()), argvLen);
masm.bind(&actualArgsSufficient);
BaseValueIndex newTarget(BaselineFrameReg, argvLen, BaselineFrame::offsetOfArg(0));
masm.loadValue(newTarget, R0);
masm.bind(&done);
frame.push(R0);
return true;
}
typedef bool (*ThrowUninitializedLexicalFn)(JSContext* cx);
static const VMFunction ThrowUninitializedLexicalInfo =
FunctionInfo<ThrowUninitializedLexicalFn>(jit::ThrowUninitializedLexical);

View File

@ -195,7 +195,8 @@ namespace jit {
_(JSOP_CALLEE) \
_(JSOP_SETRVAL) \
_(JSOP_RETRVAL) \
_(JSOP_RETURN)
_(JSOP_RETURN) \
_(JSOP_NEWTARGET)
class BaselineCompiler : public BaselineCompilerSpecific
{

View File

@ -10074,5 +10074,40 @@ CodeGenerator::visitDebugger(LDebugger* ins)
bailoutFrom(&bail, ins->snapshot());
}
void
CodeGenerator::visitNewTarget(LNewTarget *ins)
{
ValueOperand output = GetValueOutput(ins);
// if (!isConstructing()) output = undefined
Label constructing, done;
Address calleeToken(masm.getStackPointer(), frameSize() + JitFrameLayout::offsetOfCalleeToken());
masm.branchTestPtr(Assembler::NonZero, calleeToken,
Imm32(CalleeToken_FunctionConstructing), &constructing);
masm.moveValue(UndefinedValue(), output);
masm.jump(&done);
masm.bind(&constructing);
// else output = argv[Max(numActualArgs, numFormalArgs)]
Register argvLen = output.scratchReg();
Address actualArgsPtr(masm.getStackPointer(), frameSize() + JitFrameLayout::offsetOfNumActualArgs());
masm.loadPtr(actualArgsPtr, argvLen);
Label actualArgsSufficient;
size_t numFormalArgs = ins->mirRaw()->block()->info().funMaybeLazy()->nargs();
masm.branchPtr(Assembler::AboveOrEqual, argvLen, Imm32(numFormalArgs),
&actualArgsSufficient);
masm.move32(Imm32(numFormalArgs), argvLen);
masm.bind(&actualArgsSufficient);
BaseValueIndex newTarget(masm.getStackPointer(), argvLen, frameSize() + JitFrameLayout::offsetOfActualArgs());
masm.loadValue(newTarget, output);
masm.bind(&done);
}
} // namespace jit
} // namespace js

View File

@ -325,6 +325,7 @@ class CodeGenerator : public CodeGeneratorSpecific
void visitLexicalCheck(LLexicalCheck* ins);
void visitThrowUninitializedLexical(LThrowUninitializedLexical* ins);
void visitDebugger(LDebugger* ins);
void visitNewTarget(LNewTarget* ins);
void visitCheckOverRecursed(LCheckOverRecursed* lir);
void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool);

View File

@ -2007,6 +2007,9 @@ IonBuilder::inspectOpcode(JSOp op)
// Just fall through to the unsupported bytecode case.
break;
case JSOP_NEWTARGET:
return jsop_newtarget();
#ifdef DEBUG
case JSOP_PUSHBLOCKSCOPE:
case JSOP_FRESHENBLOCKSCOPE:
@ -9425,6 +9428,26 @@ IonBuilder::jsop_arguments()
return true;
}
bool
IonBuilder::jsop_newtarget()
{
MOZ_ASSERT(info().funMaybeLazy());
if (inliningDepth_ == 0) {
MNewTarget* newTarget = MNewTarget::New(alloc());
current->add(newTarget);
current->push(newTarget);
return true;
}
if (!info().constructing()) {
pushConstant(UndefinedValue());
return true;
}
current->push(inlineCallInfo_->getNewTarget());
return true;
}
bool
IonBuilder::jsop_rest()
{

View File

@ -707,6 +707,7 @@ class IonBuilder
bool jsop_getaliasedvar(ScopeCoordinate sc);
bool jsop_setaliasedvar(ScopeCoordinate sc);
bool jsop_debugger();
bool jsop_newtarget();
/* Inlining. */

View File

@ -1533,7 +1533,7 @@ class LJSCallInstructionHelper : public LCallInstructionHelper<Defs, Operands, T
uint32_t numActualArgs() const {
return mir()->numActualArgs();
}
bool isConstructing() const {
return mir()->isConstructing();
}
@ -7053,6 +7053,12 @@ class LDebugger : public LCallInstructionHelper<0, 0, 2>
}
};
class LNewTarget : public LInstructionHelper<BOX_PIECES, 0, 0>
{
public:
LIR_HEADER(NewTarget)
};
} // namespace jit
} // namespace js

View File

@ -350,7 +350,8 @@
_(LexicalCheck) \
_(ThrowUninitializedLexical) \
_(NurseryObject) \
_(Debugger)
_(Debugger) \
_(NewTarget)
#if defined(JS_CODEGEN_X86)
# include "jit/x86/LOpcodes-x86.h"

View File

@ -3560,6 +3560,13 @@ LIRGenerator::visitGetFrameArgument(MGetFrameArgument* ins)
defineBox(lir, ins);
}
void
LIRGenerator::visitNewTarget(MNewTarget* ins)
{
LNewTarget* lir = new(alloc()) LNewTarget();
defineBox(lir, ins);
}
void
LIRGenerator::visitSetFrameArgument(MSetFrameArgument* ins)
{

View File

@ -297,6 +297,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitThrowUninitializedLexical(MThrowUninitializedLexical* ins);
void visitDebugger(MDebugger* ins);
void visitNurseryObject(MNurseryObject* ins);
void visitNewTarget(MNewTarget* ins);
};
} // namespace jit

View File

@ -12027,6 +12027,28 @@ class MGetFrameArgument
}
};
class MNewTarget : public MNullaryInstruction
{
MNewTarget() : MNullaryInstruction() {
setResultType(MIRType_Value);
setMovable();
}
public:
INSTRUCTION_HEADER(NewTarget)
static MNewTarget* New(TempAllocator& alloc) {
return new(alloc) MNewTarget();
}
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override {
return AliasSet::None();
}
};
// This MIR instruction is used to set an argument value in the frame.
class MSetFrameArgument
: public MUnaryInstruction,

View File

@ -268,7 +268,8 @@ namespace jit {
_(UnknownValue) \
_(LexicalCheck) \
_(ThrowUninitializedLexical) \
_(Debugger)
_(Debugger) \
_(NewTarget)
// Forward declarations of MIR types.
#define FORWARD_DECLARE(op) class M##op;

View File

@ -339,6 +339,7 @@ MSG_DEF(JSMSG_YIELD_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "yield in default exp
MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of range")
MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_DEFAULT_IN_PATTERN, 0, JSEXN_SYNTAXERR, "destructuring defaults aren't supported in this destructuring declaration")
MSG_DEF(JSMSG_BAD_NEWTARGET, 0, JSEXN_SYNTAXERR, "new.target only allowed in non-exotic functions")
// asm.js
MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}")

View File

@ -55,6 +55,7 @@ UNIFIED_SOURCES += [
'testMappedArrayBuffer.cpp',
'testMutedErrors.cpp',
'testNewObject.cpp',
'testNewTargetInvokeConstructor.cpp',
'testNullRoot.cpp',
'testObjectEmulatingUndefined.cpp',
'testOOM.cpp',

View File

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jsapi.h"
#include "jsapi-tests/tests.h"
BEGIN_TEST(testNewTargetInvokeConstructor)
{
JS::RootedValue func(cx);
EVAL("(function(expected) { if (expected !== new.target) throw new Error('whoops'); })",
&func);
JS::AutoValueArray<1> args(cx);
args[0].set(func);
JS::RootedValue rval(cx);
CHECK(JS::Construct(cx, func, args, &rval));
return true;
}
END_TEST(testNewTargetInvokeConstructor)

View File

@ -38,6 +38,7 @@ ASTDEF(AST_COMP_EXPR, "ComprehensionExpression", "comprehensi
ASTDEF(AST_GENERATOR_EXPR, "GeneratorExpression", "generatorExpression")
ASTDEF(AST_YIELD_EXPR, "YieldExpression", "yieldExpression")
ASTDEF(AST_CLASS_EXPR, "ClassExpression", "classExpression")
ASTDEF(AST_NEWTARGET_EXPR, "NewTargetExpression", "newTargetExpression")
ASTDEF(AST_EMPTY_STMT, "EmptyStatement", "emptyStatement")
ASTDEF(AST_BLOCK_STMT, "BlockStatement", "blockStatement")

View File

@ -1274,6 +1274,8 @@ ExpressionDecompiler::decompilePC(jsbytecode* pc)
// |this| could convert to a very long object initialiser, so cite it by
// its keyword name.
return write(js_this_str);
case JSOP_NEWTARGET:
return write("new.target");
case JSOP_CALL:
case JSOP_FUNCALL:
return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&

View File

@ -682,6 +682,8 @@ class NodeBuilder
bool generatorExpression(HandleValue body, NodeVector& blocks, HandleValue filter,
bool isLegacy, TokenPos* pos, MutableHandleValue dst);
bool newTargetExpression(TokenPos* pos, MutableHandleValue dst);
/*
* declarations
*/
@ -1749,6 +1751,16 @@ NodeBuilder::classDefinition(bool expr, HandleValue name, HandleValue heritage,
dst);
}
bool
NodeBuilder::newTargetExpression(TokenPos* pos, MutableHandleValue dst)
{
RootedValue cb(cx, callbacks[AST_NEWTARGET_EXPR]);
if (!cb.isNull())
return callback(cb, pos, dst);
return newNode(AST_NEWTARGET_EXPR, pos, dst);
}
namespace {
/*
@ -3167,6 +3179,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
case PNK_CLASS:
return classDefinition(pn, true, dst);
case PNK_NEWTARGET:
return builder.newTargetExpression(&pn->pn_pos, dst);
default:
LOCAL_NOT_REACHED("unexpected expression type");
}

View File

@ -0,0 +1,44 @@
// Since we put new.target at the end of the arguments vector, ensrue that it
// doesn't interact with the arguments object
var argsContent;
function argsWithNewTarget(foo) {
assertEq(arguments.length, argsContent.length);
for (let i = 0; i < arguments.length; i++)
assertEq(arguments[i], argsContent[i]);
let nt = new.target;
// Assigning to the arguments object shouldn't infect new.target, either
arguments[arguments.length] = 42;
assertEq(new.target, nt);
}
// Test constructing invocations, with under and overflow
argsContent = [];
for (let i = 0; i < 100; i++)
new argsWithNewTarget();
argsContent = [1];
for (let i = 0; i < 100; i++)
new argsWithNewTarget(1);
argsContent = [1,2,3];
for (let i = 0; i < 100; i++)
new argsWithNewTarget(1, 2, 3);
// Test spreadnew as well.
argsContent = [];
for (let i = 0; i < 100; i++)
new argsWithNewTarget(...[]);
argsContent = [1];
for (let i = 0; i < 100; i++)
new argsWithNewTarget(...[1]);
argsContent = [1,2,3];
for (let i = 0; i < 100; i++)
new argsWithNewTarget(...[1,2,3]);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");

View File

@ -0,0 +1,16 @@
function boundTarget(expected) {
assertEq(new.target, expected);
}
let bound = boundTarget.bind(undefined);
const TEST_ITERATIONS = 550;
for (let i = 0; i < TEST_ITERATIONS; i++)
bound(undefined);
for (let i = 0; i < TEST_ITERATIONS; i++)
new bound(boundTarget);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");

View File

@ -0,0 +1,11 @@
// new.target is valid inside Function() invocations
var func = new Function("new.target");
// new.target is invalid inside eval, even (for now!) eval inside a function.
assertThrowsInstanceOf(() => eval('new.target'), SyntaxError);
function evalInFunction() { eval('new.target'); }
assertThrowsInstanceOf(() => evalInFunction(), SyntaxError);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");

View File

@ -0,0 +1,7 @@
function thunk() {
new.target();
}
assertThrownErrorContains(thunk, "new.target");
if (typeof reportCompare === "function")
reportCompare(0, 0, "OK");

View File

@ -0,0 +1,47 @@
// Note that this will also test new.target in ion inlines. When the toplevel
// script is compiled, assertNewTarget will be inlined.
function assertNewTarget(expected, unused) { assertEq(new.target, expected); }
// Test non-constructing invocations, with arg underflow, overflow, and correct
// numbers
for (let i = 0; i < 100; i++)
assertNewTarget(undefined, null);
for (let i = 0; i < 100; i++)
assertNewTarget(undefined);
for (let i = 0; i < 100; i++)
assertNewTarget(undefined, null, 1);
// Test spread-call
for (let i = 0; i < 100; i++)
assertNewTarget(...[undefined]);
for (let i = 0; i < 100; i++)
assertNewTarget(...[undefined, null]);
for (let i = 0; i < 100; i++)
assertNewTarget(...[undefined, null, 1]);
// Test constructing invocations, again with under and overflow
for (let i = 0; i < 100; i++)
new assertNewTarget(assertNewTarget, null);
for (let i = 0; i < 100; i++)
new assertNewTarget(assertNewTarget);
for (let i = 0; i < 100; i++)
new assertNewTarget(assertNewTarget, null, 1);
// Test spreadnew as well.
for (let i = 0; i < 100; i++)
new assertNewTarget(...[assertNewTarget]);
for (let i = 0; i < 100; i++)
new assertNewTarget(...[assertNewTarget, null]);
for (let i = 0; i < 100; i++)
new assertNewTarget(...[assertNewTarget, null, 1]);
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");

View File

@ -0,0 +1,52 @@
// Just like newTargetDirectInvoke, except to prove it works in functions
// defined with method syntax as well. Note that methods, getters, and setters
// are not constructible.
let ol = {
olTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); },
get ol() { assertEq(new.target, undefined); },
set ol(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }
}
class cl {
constructor() { assertEq(new.target, cl); }
clTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }
get cl() { assertEq(new.target, undefined); }
set cl(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }
static staticclTest(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }
static get staticcl() { assertEq(new.target, undefined); }
static set staticcl(arg) { assertEq(arg, 4); assertEq(new.target, undefined); }
}
const TEST_ITERATIONS = 150;
for (let i = 0; i < TEST_ITERATIONS; i++)
ol.olTest(4);
for (let i = 0; i < TEST_ITERATIONS; i++)
ol.ol;
for (let i = 0; i < TEST_ITERATIONS; i++)
ol.ol = 4;
for (let i = 0; i < TEST_ITERATIONS; i++)
cl.staticclTest(4);
for (let i = 0; i < TEST_ITERATIONS; i++)
cl.staticcl;
for (let i = 0; i < TEST_ITERATIONS; i++)
cl.staticcl = 4;
for (let i = 0; i < TEST_ITERATIONS; i++)
new cl();
let clInst = new cl();
for (let i = 0; i < TEST_ITERATIONS; i++)
clInst.clTest(4);
for (let i = 0; i < TEST_ITERATIONS; i++)
clInst.cl;
for (let i = 0; i < TEST_ITERATIONS; i++)
clInst.cl = 4;
if (typeof reportCompare === "function")
reportCompare(0,0,"OK");

View File

@ -12,3 +12,14 @@ function classesEnabled() {
return false;
}
}
function assertThrownErrorContains(thunk, substr) {
try {
thunk();
} catch (e) {
if (e.message.indexOf(substr) !== -1)
return;
throw new Error("Expected error containing " + substr + ", got " + e);
}
throw new Error("Expected error containing " + substr + ", no exception thrown");
}

View File

@ -2,17 +2,6 @@
var test = `
function assertThrownErrorContains(thunk, substr) {
try {
thunk();
throw new Error("Expected error containing " + substr + ", no exception thrown");
} catch (e) {
if (e.message.indexOf(substr) !== -1)
return;
throw new Error("Expected error containing " + substr + ", got " + e);
}
}
class testNonExistent {
constructor() {
super["prop"]();

View File

@ -66,6 +66,11 @@ function assertStmt(src, patt) {
assertBlockStmt(src, patt);
}
function assertInFunctionExpr(src, patt) {
assertLocalExpr(src, patt);
assertBlockExpr(src, patt);
}
function assertExpr(src, patt) {
assertLocalExpr(src, patt);
assertGlobalExpr(src, patt);

View File

@ -170,6 +170,9 @@ function arrowExpr(args, body) {
body: body });
}
function newTarget() {
return Pattern({ type: "NewTargetExpression" });
}
function unExpr(op, arg) {
return Pattern({ type: "UnaryExpression", operator: op, argument: arg });
}

View File

@ -0,0 +1,41 @@
// |reftest| skip-if(!xulRuntime.shell)
function testNewTarget() {
// new.target is currently valid inside any non-arrow, non-generator function
assertInFunctionExpr("new.target", newTarget());
// even with gratuitous whitespace.
assertInFunctionExpr(`new.
target`, newTarget());
// invalid in top-level scripts
assertError("new.target", SyntaxError);
// invalid (for now!) in any arrow function
assertError("function foo() { (() => new.target) }", SyntaxError);
assertError("(() => new.target))", SyntaxError);
// invalid (for now!) in generators
assertError("function *foo() { new.target; }", SyntaxError);
// new.target is a member expression. You should be able to call, invoke, or
// access properties of it.
assertInFunctionExpr("new.target.foo", dotExpr(newTarget(), ident("foo")));
assertInFunctionExpr("new.target[\"foo\"]", memExpr(newTarget(), literal("foo")));
assertInFunctionExpr("new.target()", callExpr(newTarget(), []));
assertInFunctionExpr("new new.target()", newExpr(newTarget(), []));
// assignment to newTarget is an error
assertError("new.target = 4", SyntaxError);
// only new.target is a valid production, no shorn names or other names
assertError("new.", SyntaxError);
assertError("new.foo", SyntaxError);
assertError("new.targe", SyntaxError);
// obj.new.target is still a member expression
assertExpr("obj.new.target", dotExpr(dotExpr(ident("obj"), ident("new")), ident("target")));
}
runtest(testNewTarget);

View File

@ -196,6 +196,7 @@
macro(StructType, StructType, "StructType") \
macro(style, style, "style") \
macro(super, super, "super") \
macro(target, target, "target") \
macro(test, test, "test") \
macro(throw, throw_, "throw") \
macro(timestamp, timestamp, "timestamp") \

View File

@ -1950,7 +1950,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_UNUSED2)
CASE(JSOP_UNUSED148)
CASE(JSOP_BACKPATCH)
CASE(JSOP_UNUSED150)
CASE(JSOP_UNUSED161)
@ -3963,6 +3962,10 @@ CASE(JSOP_SUPERBASE)
}
END_CASE(JSOP_SUPERBASE)
CASE(JSOP_NEWTARGET)
PUSH_COPY(REGS.fp()->newTarget());
END_CASE(JSOP_NEWTARGET)
DEFAULT()
{
char numBuf[12];

View File

@ -1514,8 +1514,15 @@
* Stack: obj, val => obj
*/ \
macro(JSOP_INITHIDDENPROP, 147,"inithiddenprop", NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) \
/* Unused. */ \
macro(JSOP_UNUSED148, 148,"unused148", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Push "new.target"
*
* Category: Variables and Scopes
* Type: Arguments
* Operands:
* Stack: => new.target
*/ \
macro(JSOP_NEWTARGET, 148, "newtarget", NULL, 1, 0, 1, JOF_BYTE) \
\
/*
* Placeholder opcode used during bytecode generation. This never

View File

@ -12,6 +12,7 @@
#include "jsfun.h"
#include "jsscript.h"
#include "jsutil.h"
#include "asmjs/AsmJSFrameIterator.h"
#include "jit/JitFrameIterator.h"
@ -739,6 +740,23 @@ class InterpreterFrame
return CallReceiverFromArgv(argv());
}
/*
* New Target
*
* Only function frames have a meaningful newTarget. An eval frame in a
* function will have a copy of the newTarget of the enclosing function
* frame.
*/
Value newTarget() const {
// new.target in eval() NYI.
MOZ_ASSERT(isNonEvalFunctionFrame());
if (isConstructing()) {
unsigned pushedArgs = Max(numFormalArgs(), numActualArgs());
return argv()[pushedArgs];
}
return UndefinedValue();
}
/*
* Frame compartment
*

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 = 288;
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 289;
static const uint32_t XDR_BYTECODE_VERSION =
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
static_assert(JSErr_Limit == 398,
static_assert(JSErr_Limit == 399,
"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 "