From 58a139cf970fadfb55d24e8e4ba4fd61119cda78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Thu, 9 Jan 2020 16:02:55 +0000 Subject: [PATCH] Bug 1605835: Allow BigInts as property names. r=mgaudet BytecodeEmitter.cpp: - Drive-by change in `emitObjLiteralValue()`: Call MOZ_CRASH instead of returning `false` for unhandled parse nodes. And also assert the function is only called with valid parse nodes in the first place. NameFunctions.cpp: - BigInts aren't currently supported for guessed names, so just I've only updated the assertion condition. Parser.cpp: - Update spec reference from `NumericLiteralBase` to `NonDecimalIntegerLiteral`. - The new `bigIntAtom()` function runs counter to the deferred gc-things work, but this inevitable as long as function names are computed in the parser. Differential Revision: https://phabricator.services.mozilla.com/D58757 --HG-- extra : moz-landing-system : lando --- js/src/builtin/ReflectParse.cpp | 3 +- js/src/frontend/BytecodeEmitter.cpp | 48 ++++- js/src/frontend/BytecodeEmitter.h | 1 - js/src/frontend/FoldConstants.cpp | 7 +- js/src/frontend/FullParseHandler.h | 1 + js/src/frontend/NameFunctions.cpp | 3 +- js/src/frontend/ParseNode.cpp | 13 ++ js/src/frontend/ParseNode.h | 6 + js/src/frontend/Parser.cpp | 29 ++- js/src/frontend/Parser.h | 2 + .../BigInt/property-name-guessed-name.js | 23 +++ js/src/tests/non262/BigInt/property-name.js | 179 ++++++++++++++++++ 12 files changed, 297 insertions(+), 18 deletions(-) create mode 100644 js/src/tests/non262/BigInt/property-name-guessed-name.js create mode 100644 js/src/tests/non262/BigInt/property-name.js diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 08fdf218fbb2..134c85eb7995 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3123,7 +3123,8 @@ bool ASTSerializer::propertyName(ParseNode* key, MutableHandleValue dst) { } LOCAL_ASSERT(key->isKind(ParseNodeKind::StringExpr) || - key->isKind(ParseNodeKind::NumberExpr)); + key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)); return literal(key, dst); } diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index d1e965794dde..32c5632a4bcb 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -23,7 +23,6 @@ #include #include -#include "jsnum.h" // NumberToAtom #include "jstypes.h" // JS_BIT #include "ds/Nestable.h" // Nestable @@ -3735,6 +3734,11 @@ bool BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, // [stack]... SET? RHS LREF* RHS KEY return false; } + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + if (!emitBigIntOp(&key->as())) { + // [stack]... SET? RHS LREF* RHS KEY + return false; + } } else if (key->isKind(ParseNodeKind::ObjectPropertyName) || key->isKind(ParseNodeKind::StringExpr)) { if (!emitAtomOp(key->as().atom(), JSOP_GETPROP, @@ -7606,6 +7610,15 @@ void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj, break; } } + // BigInt keys aren't yet supported. + if (key->isKind(ParseNodeKind::BigIntExpr)) { + keysOK = false; + break; + } + + MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) || + key->isKind(ParseNodeKind::StringExpr) || + key->isKind(ParseNodeKind::NumberExpr)); AccessorType accessorType = prop->is() @@ -7753,8 +7766,18 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, if (key->isKind(ParseNodeKind::NumberExpr)) { MOZ_ASSERT(accessorType == AccessorType::None); - NumericLiteral* literal = &key->as(); - RootedAtom keyAtom(cx, NumberToAtom(cx, literal->value())); + RootedAtom keyAtom(cx, key->as().toAtom(cx)); + if (!keyAtom) { + return false; + } + if (!emitAnonymousFunctionWithName(propVal, keyAtom)) { + // [stack] CTOR? OBJ CTOR? KEY VAL + return false; + } + } else if (key->isKind(ParseNodeKind::BigIntExpr)) { + MOZ_ASSERT(accessorType == AccessorType::None); + + RootedAtom keyAtom(cx, key->as().toAtom(cx)); if (!keyAtom) { return false; } @@ -7806,15 +7829,23 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, (type == ClassBody && propdef->as().isStatic()) ? PropertyEmitter::Kind::Static : PropertyEmitter::Kind::Prototype; - if (key->isKind(ParseNodeKind::NumberExpr)) { + if (key->isKind(ParseNodeKind::NumberExpr) || + key->isKind(ParseNodeKind::BigIntExpr)) { // [stack] CTOR? OBJ if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) { // [stack] CTOR? OBJ CTOR? return false; } - if (!emitNumberOp(key->as().value())) { - // [stack] CTOR? OBJ CTOR? KEY - return false; + if (key->isKind(ParseNodeKind::NumberExpr)) { + if (!emitNumberOp(key->as().value())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } + } else { + if (!emitBigIntOp(&key->as())) { + // [stack] CTOR? OBJ CTOR? KEY + return false; + } } if (!pe.prepareForIndexPropValue()) { // [stack] CTOR? OBJ CTOR? KEY @@ -8055,6 +8086,7 @@ bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) { bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralCreationData* data, ParseNode* value) { + MOZ_ASSERT(isRHSObjLiteralCompatible(value)); if (value->isKind(ParseNodeKind::NumberExpr)) { double numValue = value->as().value(); int32_t i = 0; @@ -8093,7 +8125,7 @@ bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralCreationData* data, return false; } } else { - return false; + MOZ_CRASH("Unexpected parse node"); } return true; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index d20bbebf0243..18e321487566 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -40,7 +40,6 @@ #include "frontend/ValueUsage.h" // ValueUsage #include "js/RootingAPI.h" // JS::Rooted, JS::Handle #include "js/TypeDecls.h" // jsbytecode -#include "vm/BigIntType.h" // BigInt #include "vm/BytecodeUtil.h" // JSOp #include "vm/Instrumentation.h" // InstrumentationKind #include "vm/Interpreter.h" // CheckIsObjectKind, CheckIsCallableKind diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index ff25a397f70d..fa7a7e5b004e 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -459,7 +459,7 @@ static bool FoldType(JSContext* cx, FullParseHandler* handler, ParseNode** pnp, case ParseNodeKind::StringExpr: if (pn->isKind(ParseNodeKind::NumberExpr)) { - JSAtom* atom = NumberToAtom(cx, pn->as().value()); + JSAtom* atom = pn->as().toAtom(cx); if (!atom) { return false; } @@ -1090,12 +1090,13 @@ static bool FoldElement(JSContext* cx, FullParseHandler* handler, name = atom->asPropertyName(); } } else if (key->isKind(ParseNodeKind::NumberExpr)) { - double number = key->as().value(); + auto* numeric = &key->as(); + double number = numeric->value(); if (number != ToUint32(number)) { // Optimization 2: We have something like expr[3.14]. The number // isn't an array index, so it converts to a string ("3.14"), // enabling optimization 3 below. - JSAtom* atom = NumberToAtom(cx, number); + JSAtom* atom = numeric->toAtom(cx); if (!atom) { return false; } diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 0cd03fb6933f..683a5d835c60 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -924,6 +924,7 @@ class FullParseHandler { bool isUsableAsObjectPropertyName(Node node) { return node->isKind(ParseNodeKind::NumberExpr) || + node->isKind(ParseNodeKind::BigIntExpr) || node->isKind(ParseNodeKind::ObjectPropertyName) || node->isKind(ParseNodeKind::StringExpr) || node->isKind(ParseNodeKind::ComputedName); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index d770c134791a..9512c8042b1b 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -286,7 +286,8 @@ class NameResolver : public ParseNodeVisitor { return false; } } else { - MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName)); + MOZ_ASSERT(left->isKind(ParseNodeKind::ComputedName) || + left->isKind(ParseNodeKind::BigIntExpr)); } } else { // Don't have consecutive '<' characters, and also don't start diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index f5e8e515301c..a76dab5d69e3 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -12,6 +12,7 @@ #include "jsnum.h" #include "frontend/Parser.h" +#include "vm/BigIntType.h" #include "vm/RegExpObject.h" #include "vm/JSContext-inl.h" @@ -417,6 +418,18 @@ bool BigIntLiteral::isZero() { BigInt* BigIntLiteral::value() { return box()->value(); } +JSAtom* BigIntLiteral::toAtom(JSContext* cx) { + RootedBigInt bi(cx, getOrCreate(cx)); + if (!bi) { + return nullptr; + } + return BigIntToAtom(cx, bi); +} + +JSAtom* NumericLiteral::toAtom(JSContext* cx) const { + return NumberToAtom(cx, value()); +} + RegExpObject* RegExpCreationData::createRegExp(JSContext* cx) const { MOZ_ASSERT(buf_); return RegExpObject::createSyntaxChecked(cx, buf_.get(), length_, flags_, diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index da00e7180757..db334cb4d067 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -1514,6 +1514,9 @@ class NumericLiteral : public ParseNode { void setValue(double v) { value_ = v; } void setDecimalPoint(DecimalPoint d) { decimalPoint_ = d; } + + // Return the decimal string representation of this numeric literal. + JSAtom* toAtom(JSContext* cx) const; }; class BigIntLiteral : public ParseNode { @@ -1563,6 +1566,9 @@ class BigIntLiteral : public ParseNode { // Can be used when deferred allocation mode is enabled. BigInt* getOrCreate(JSContext* cx); + // Return the decimal string representation of this BigInt literal. + JSAtom* toAtom(JSContext* cx); + bool isZero(); }; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 855122eaefe0..6c1d1c57aa60 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -46,6 +46,7 @@ #include "frontend/TokenStream.h" #include "irregexp/RegExpParser.h" #include "js/RegExpFlags.h" // JS::RegExpFlags +#include "vm/BigIntType.h" #include "vm/BytecodeUtil.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" @@ -9690,9 +9691,9 @@ GeneralParser::newRegExp() { template BigIntLiteral* Parser::newBigInt() { // The token's charBuffer contains the DecimalIntegerLiteral or - // NumericLiteralBase production, and as such does not include the - // BigIntLiteralSuffix (the trailing "n"). Note that NumericLiteralBase - // productions may start with 0[bBoOxX], indicating binary/octal/hex. + // NonDecimalIntegerLiteral production, and as such does not include the + // BigIntLiteralSuffix (the trailing "n"). Note that NonDecimalIntegerLiteral + // productions start with 0[bBoOxX], indicating binary/octal/hex. const auto& chars = tokenStream.getCharBuffer(); if (this->parseInfo_.isDeferred()) { @@ -9737,6 +9738,19 @@ GeneralParser::newBigInt() { return asFinalParser()->newBigInt(); } +template +JSAtom* GeneralParser::bigIntAtom() { + // See newBigInt() for a description about |chars'| contents. + const auto& chars = tokenStream.getCharBuffer(); + mozilla::Range source(chars.begin(), chars.length()); + + RootedBigInt bi(cx_, js::ParseBigIntLiteral(cx_, source)); + if (!bi) { + return nullptr; + } + return BigIntToAtom(cx_, bi); +} + // |exprPossibleError| is the PossibleError state within |expr|, // |possibleError| is the surrounding PossibleError state. template @@ -10005,6 +10019,13 @@ typename ParseHandler::Node GeneralParser::propertyName( } return newNumber(anyChars.currentToken()); + case TokenKind::BigInt: + propAtom.set(bigIntAtom()); + if (!propAtom.get()) { + return null(); + } + return newBigInt(); + case TokenKind::String: { propAtom.set(anyChars.currentToken().atom()); uint32_t index; @@ -10034,7 +10055,7 @@ typename ParseHandler::Node GeneralParser::propertyName( static bool TokenKindCanStartPropertyName(TokenKind tt) { return TokenKindIsPossibleIdentifierName(tt) || tt == TokenKind::String || tt == TokenKind::Number || tt == TokenKind::LeftBracket || - tt == TokenKind::Mul; + tt == TokenKind::Mul || tt == TokenKind::BigInt; } template diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index f658d9e154f9..031840a37dbe 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1430,6 +1430,8 @@ class MOZ_STACK_CLASS GeneralParser : public PerHandlerParser { inline BigIntLiteralType newBigInt(); + JSAtom* bigIntAtom(); + protected: // Match the current token against the BindingIdentifier production with // the given Yield parameter. If there is no match, report a syntax diff --git a/js/src/tests/non262/BigInt/property-name-guessed-name.js b/js/src/tests/non262/BigInt/property-name-guessed-name.js new file mode 100644 index 000000000000..8cc408c49708 --- /dev/null +++ b/js/src/tests/non262/BigInt/property-name-guessed-name.js @@ -0,0 +1,23 @@ +// BigInts currently don't participate when computing guessed function names. + +if (typeof displayName !== "function") { + var {displayName} = SpecialPowers.Cu.getJSTestingFunctions(); +} + +var p = {}; +p[1] = function(){}; +p[2n] = function(){}; + +assertEq(displayName(p[1]), "p[1]"); +assertEq(displayName(p[2]), ""); + +var q = { + 1: [function(){}], + 2n: [function(){}], +}; + +assertEq(displayName(q[1][0]), "q[1]<"); +assertEq(displayName(q[2][0]), "q<"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/BigInt/property-name.js b/js/src/tests/non262/BigInt/property-name.js new file mode 100644 index 000000000000..cbce31fa6a0a --- /dev/null +++ b/js/src/tests/non262/BigInt/property-name.js @@ -0,0 +1,179 @@ +// BigInt literals as property keys. +{ + let o = { + 0n: "0", + 1n: "1", + + // 2**31 + 2147483647n: "2^31-1", + 2147483648n: "2^31", + 2147483649n: "2^31+1", + + // 2**32 + 4294967295n: "2^32-1", + 4294967296n: "2^32", + 4294967297n: "2^32+1", + + // 2n**63n + 9223372036854775807n: "2^63-1", + 9223372036854775808n: "2^63", + 9223372036854775809n: "2^63+1", + + // 2n**64n + 18446744073709551615n: "2^64-1", + 18446744073709551616n: "2^64", + 18446744073709551617n: "2^64+1", + }; + + assertEq(o[0], "0"); + assertEq(o[1], "1"); + + assertEq(o[2147483647], "2^31-1"); + assertEq(o[2147483648], "2^31"); + assertEq(o[2147483649], "2^31+1"); + + assertEq(o[4294967295], "2^32-1"); + assertEq(o[4294967296], "2^32"); + assertEq(o[4294967297], "2^32+1"); + + assertEq(o["9223372036854775807"], "2^63-1"); + assertEq(o["9223372036854775808"], "2^63"); + assertEq(o["9223372036854775809"], "2^63+1"); + + assertEq(o["18446744073709551615"], "2^64-1"); + assertEq(o["18446744073709551616"], "2^64"); + assertEq(o["18446744073709551617"], "2^64+1"); +} + +// With non-decimal different base. +{ + let o = { + 0b1n: "1", + 0o2n: "2", + 0x3n: "3", + }; + + assertEq(o[1], "1"); + assertEq(o[2], "2"); + assertEq(o[3], "3"); +} + +// With numeric separators. +{ + let o = { + 1_2_3n: "123", + }; + + assertEq(o[123], "123"); +} + +// BigInt literals as method property names. +{ + let o = { + 1n() {}, + *2n() {}, + async 3n() {}, + async* 4n() {}, + get 5n() {}, + set 6n(x) {}, + }; + + assertEqArray(Object.getOwnPropertyNames(o), [ + "1", "2", "3", "4", "5", "6", + ]); + + assertEq(o[1].name, "1"); + assertEq(o[2].name, "2"); + assertEq(o[3].name, "3"); + assertEq(o[4].name, "4"); + assertEq(Object.getOwnPropertyDescriptor(o, 5).get.name, "get 5"); + assertEq(Object.getOwnPropertyDescriptor(o, 6).set.name, "set 6"); +} + +// BigInt literals as class method property names. +{ + class C { + 1n() {} + *2n() {} + async 3n() {} + async* 4n() {} + get 5n() {} + set 6n(x) {} + } + let o = C.prototype; + + assertEqArray(Object.getOwnPropertyNames(o), [ + "1", "2", "3", "4", "5", "6", + "constructor", + ]); + + assertEq(o[1].name, "1"); + assertEq(o[2].name, "2"); + assertEq(o[3].name, "3"); + assertEq(o[4].name, "4"); + assertEq(Object.getOwnPropertyDescriptor(o, 5).get.name, "get 5"); + assertEq(Object.getOwnPropertyDescriptor(o, 6).set.name, "set 6"); +} + +// BigInt literals as static class method property names. +{ + class C { + static 1n() {} + static *2n() {} + static async 3n() {} + static async* 4n() {} + static get 5n() {} + static set 6n(x) {} + } + let o = C; + + // NB: Sort names because lazily resolved "length" and "name" properties are + // inserted in the wrong order. + assertEqArray(Object.getOwnPropertyNames(o).sort(), [ + "1", "2", "3", "4", "5", "6", + "length", "name", "prototype", + ]); + + assertEq(o[1].name, "1"); + assertEq(o[2].name, "2"); + assertEq(o[3].name, "3"); + assertEq(o[4].name, "4"); + assertEq(Object.getOwnPropertyDescriptor(o, 5).get.name, "get 5"); + assertEq(Object.getOwnPropertyDescriptor(o, 6).set.name, "set 6"); +} + +// BigInt literals as class field property names. +{ + let o = new class { + 1n; + 2n = "ok"; + }; + + assertEq(o[1], undefined); + assertEq(o[2], "ok"); +} + +// In binding destructuring contexts. +{ + let {0n: a} = ["ok"]; + assertEq(a, "ok"); +} + +// In assignment destructuring contexts. +{ + let a; + ({0n: a} = ["ok"]); + assertEq(a, "ok"); +} + +// BigInt literals as inferred names. +{ + let o = { + 0xan: function(){}, + }; + + assertEq(o[10].name, "10"); +} + +if (typeof reportCompare === "function") + reportCompare(true, true);