mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-30 02:57:29 +09:00
Issue #1240 - Part 4 - Implement parser support for BigInt literals. https://bugzilla.mozilla.org/show_bug.cgi?id=1505849 Partially based on https://bugzilla.mozilla.org/show_bug.cgi?id=1456568 Un-result-ified the BigInt XDR code, so we can enable it. https://bugzilla.mozilla.org/show_bug.cgi?id=1419094 Uninitialised memory read with BigInt right-shift https://bugzilla.mozilla.org/show_bug.cgi?id=1679003
This commit is contained in:
parent
07b6c03dc7
commit
dc23241afb
27 changed files with 394 additions and 80 deletions
|
|
@ -1047,7 +1047,6 @@ MaybeWrapValue(JSContext* cx, JS::MutableHandle<JS::Value> rval)
|
||||||
if (rval.isBigInt()) {
|
if (rval.isBigInt()) {
|
||||||
return JS_WrapValue(cx, rval);
|
return JS_WrapValue(cx, rval);
|
||||||
}
|
}
|
||||||
MOZ_ASSERT(rval.isSymbol());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -667,7 +667,7 @@ class MOZ_NON_PARAM alignas(8) Value
|
||||||
#if defined(JS_NUNBOX32)
|
#if defined(JS_NUNBOX32)
|
||||||
return data.s.payload.bi;
|
return data.s.payload.bi;
|
||||||
#elif defined(JS_PUNBOX64)
|
#elif defined(JS_PUNBOX64)
|
||||||
return reinterpret_cast<JS::BigInt*>(data.asBits & JSVAL_SHIFTED_TAG_BIGINT);
|
return reinterpret_cast<JS::BigInt*>(data.asBits & JSVAL_PAYLOAD_MASK);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
#include "frontend/TokenStream.h"
|
#include "frontend/TokenStream.h"
|
||||||
#include "js/CharacterEncoding.h"
|
#include "js/CharacterEncoding.h"
|
||||||
#include "vm/RegExpObject.h"
|
#include "vm/RegExpObject.h"
|
||||||
|
#include "vm/BigIntType.h"
|
||||||
|
|
||||||
#include "jsobjinlines.h"
|
#include "jsobjinlines.h"
|
||||||
|
|
||||||
|
|
@ -3434,6 +3435,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst)
|
||||||
case PNK_STRING:
|
case PNK_STRING:
|
||||||
case PNK_REGEXP:
|
case PNK_REGEXP:
|
||||||
case PNK_NUMBER:
|
case PNK_NUMBER:
|
||||||
|
case PNK_BIGINT:
|
||||||
case PNK_TRUE:
|
case PNK_TRUE:
|
||||||
case PNK_FALSE:
|
case PNK_FALSE:
|
||||||
case PNK_NULL:
|
case PNK_NULL:
|
||||||
|
|
@ -3604,7 +3606,7 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
|
||||||
|
|
||||||
case PNK_REGEXP:
|
case PNK_REGEXP:
|
||||||
{
|
{
|
||||||
RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object);
|
RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object());
|
||||||
LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
|
LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
|
||||||
|
|
||||||
RootedObject re2(cx, CloneRegExpObject(cx, re1));
|
RootedObject re2(cx, CloneRegExpObject(cx, re1));
|
||||||
|
|
@ -3619,6 +3621,13 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
|
||||||
val.setNumber(pn->as<NumericLiteral>().value());
|
val.setNumber(pn->as<NumericLiteral>().value());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
{
|
||||||
|
BigInt* x = pn->as<BigIntLiteral>().box()->value();
|
||||||
|
val.setBigInt(x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case PNK_NULL:
|
case PNK_NULL:
|
||||||
val.setNull();
|
val.setNull();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1093,6 +1093,11 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
|
||||||
*answer = false;
|
*answer = false;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
MOZ_ASSERT(pn->is<BigIntLiteral>());
|
||||||
|
*answer = false;
|
||||||
|
return true;
|
||||||
|
|
||||||
// |this| can throw in derived class constructors, including nested arrow
|
// |this| can throw in derived class constructors, including nested arrow
|
||||||
// functions or eval.
|
// functions or eval.
|
||||||
case PNK_THIS:
|
case PNK_THIS:
|
||||||
|
|
@ -4225,6 +4230,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje
|
||||||
case PNK_NUMBER:
|
case PNK_NUMBER:
|
||||||
vp.setNumber(as<NumericLiteral>().value());
|
vp.setNumber(as<NumericLiteral>().value());
|
||||||
return true;
|
return true;
|
||||||
|
case PNK_BIGINT:
|
||||||
|
vp.setBigInt(as<BigIntLiteral>().box()->value());
|
||||||
|
return true;
|
||||||
case PNK_TEMPLATE_STRING:
|
case PNK_TEMPLATE_STRING:
|
||||||
case PNK_STRING:
|
case PNK_STRING:
|
||||||
vp.setString(as<NameNode>().atom());
|
vp.setString(as<NameNode>().atom());
|
||||||
|
|
@ -4834,6 +4842,15 @@ BytecodeEmitter::emitCopyDataProperties(CopyOption option)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BytecodeEmitter::emitBigIntOp(BigInt* bigint)
|
||||||
|
{
|
||||||
|
if (!constList.append(BigIntValue(bigint))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return emitIndex32(JSOP_BIGINT, constList.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
BytecodeEmitter::emitIterator()
|
BytecodeEmitter::emitIterator()
|
||||||
{
|
{
|
||||||
|
|
@ -9579,6 +9596,12 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case PNK_REGEXP:
|
case PNK_REGEXP:
|
||||||
if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox())))
|
if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox())))
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -10317,7 +10340,7 @@ CGConstList::finish(ConstArray* array)
|
||||||
MOZ_ASSERT(length() == array->length);
|
MOZ_ASSERT(length() == array->length);
|
||||||
|
|
||||||
for (unsigned i = 0; i < length(); i++)
|
for (unsigned i = 0; i < length(); i++)
|
||||||
array->vector[i] = list[i];
|
array->vector[i] = vector[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -10331,6 +10354,7 @@ CGConstList::finish(ConstArray* array)
|
||||||
unsigned
|
unsigned
|
||||||
CGObjectList::add(ObjectBox* objbox)
|
CGObjectList::add(ObjectBox* objbox)
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(objbox->isObjectBox());
|
||||||
MOZ_ASSERT(!objbox->emitLink);
|
MOZ_ASSERT(!objbox->emitLink);
|
||||||
objbox->emitLink = lastbox;
|
objbox->emitLink = lastbox;
|
||||||
lastbox = objbox;
|
lastbox = objbox;
|
||||||
|
|
@ -10342,7 +10366,7 @@ CGObjectList::indexOf(JSObject* obj)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(length > 0);
|
MOZ_ASSERT(length > 0);
|
||||||
unsigned index = length - 1;
|
unsigned index = length - 1;
|
||||||
for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink)
|
for (ObjectBox* box = lastbox; box->object() != obj; box = box->emitLink)
|
||||||
index--;
|
index--;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
@ -10358,8 +10382,8 @@ CGObjectList::finish(ObjectArray* array)
|
||||||
do {
|
do {
|
||||||
--cursor;
|
--cursor;
|
||||||
MOZ_ASSERT(!*cursor);
|
MOZ_ASSERT(!*cursor);
|
||||||
MOZ_ASSERT(objbox->object->isTenured());
|
MOZ_ASSERT(objbox->object()->isTenured());
|
||||||
*cursor = objbox->object;
|
*cursor = objbox->object();
|
||||||
} while ((objbox = objbox->emitLink) != nullptr);
|
} while ((objbox = objbox->emitLink) != nullptr);
|
||||||
MOZ_ASSERT(cursor == array->vector);
|
MOZ_ASSERT(cursor == array->vector);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,20 +35,21 @@ class SharedContext;
|
||||||
class TokenStream;
|
class TokenStream;
|
||||||
|
|
||||||
class CGConstList {
|
class CGConstList {
|
||||||
Vector<Value> list;
|
Rooted<ValueVector> vector;
|
||||||
public:
|
public:
|
||||||
explicit CGConstList(ExclusiveContext* cx) : list(cx) {}
|
explicit CGConstList(ExclusiveContext* cx)
|
||||||
|
: vector(cx, ValueVector(cx))
|
||||||
|
{ }
|
||||||
MOZ_MUST_USE bool append(const Value& v) {
|
MOZ_MUST_USE bool append(const Value& v) {
|
||||||
MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom());
|
return vector.append(v);
|
||||||
return list.append(v);
|
|
||||||
}
|
}
|
||||||
size_t length() const { return list.length(); }
|
size_t length() const { return vector.length(); }
|
||||||
void finish(ConstArray* array);
|
void finish(ConstArray* array);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CGObjectList {
|
struct CGObjectList {
|
||||||
uint32_t length; /* number of emitted so far objects */
|
uint32_t length; /* number of emitted so far objects */
|
||||||
ObjectBox* lastbox; /* last emitted object */
|
ObjectBox* lastbox; /* last emitted object */
|
||||||
|
|
||||||
CGObjectList() : length(0), lastbox(nullptr) {}
|
CGObjectList() : length(0), lastbox(nullptr) {}
|
||||||
|
|
||||||
|
|
@ -198,7 +199,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
||||||
return innermostEmitterScope_;
|
return innermostEmitterScope_;
|
||||||
}
|
}
|
||||||
|
|
||||||
CGConstList constList; /* constants to be included with the script */
|
CGConstList constList; /* double and bigint values used by script */
|
||||||
CGObjectList objectList; /* list of emitted objects */
|
CGObjectList objectList; /* list of emitted objects */
|
||||||
CGScopeList scopeList; /* list of emitted scopes */
|
CGScopeList scopeList; /* list of emitted scopes */
|
||||||
CGTryNoteList tryNoteList; /* list of emitted try notes */
|
CGTryNoteList tryNoteList; /* list of emitted try notes */
|
||||||
|
|
@ -478,6 +479,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
||||||
|
|
||||||
MOZ_MUST_USE bool emitNumberOp(double dval);
|
MOZ_MUST_USE bool emitNumberOp(double dval);
|
||||||
|
|
||||||
|
MOZ_MUST_USE bool emitBigIntOp(BigInt* bigint);
|
||||||
|
|
||||||
MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
|
MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
|
||||||
MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
|
MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
|
||||||
MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
|
MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
|
||||||
|
|
|
||||||
|
|
@ -395,6 +395,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
|
||||||
case PNK_THIS:
|
case PNK_THIS:
|
||||||
case PNK_ELISION:
|
case PNK_ELISION:
|
||||||
case PNK_NUMBER:
|
case PNK_NUMBER:
|
||||||
|
case PNK_BIGINT:
|
||||||
case PNK_NEW:
|
case PNK_NEW:
|
||||||
case PNK_GENERATOR:
|
case PNK_GENERATOR:
|
||||||
case PNK_GENEXP:
|
case PNK_GENEXP:
|
||||||
|
|
@ -485,6 +486,7 @@ IsEffectless(ParseNode* node)
|
||||||
node->isKind(PNK_FALSE) ||
|
node->isKind(PNK_FALSE) ||
|
||||||
node->isKind(PNK_STRING) ||
|
node->isKind(PNK_STRING) ||
|
||||||
node->isKind(PNK_TEMPLATE_STRING) ||
|
node->isKind(PNK_TEMPLATE_STRING) ||
|
||||||
|
node->isKind(PNK_BIGINT) ||
|
||||||
node->isKind(PNK_NUMBER) ||
|
node->isKind(PNK_NUMBER) ||
|
||||||
node->isKind(PNK_NULL) ||
|
node->isKind(PNK_NULL) ||
|
||||||
node->isKind(PNK_RAW_UNDEFINED) ||
|
node->isKind(PNK_RAW_UNDEFINED) ||
|
||||||
|
|
@ -503,6 +505,9 @@ Boolish(ParseNode* pn, bool isNullish = false)
|
||||||
return (isNullish || isNonZeroNumber) ? Truthy : Falsy;
|
return (isNullish || isNonZeroNumber) ? Truthy : Falsy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
return (pn->as<BigIntLiteral>().box()->value()->isZero()) ? Falsy : Truthy;
|
||||||
|
|
||||||
case PNK_STRING:
|
case PNK_STRING:
|
||||||
case PNK_TEMPLATE_STRING: {
|
case PNK_TEMPLATE_STRING: {
|
||||||
bool isNonZeroLengthString = (pn->as<NameNode>().atom()->length() > 0);
|
bool isNonZeroLengthString = (pn->as<NameNode>().atom()->length() > 0);
|
||||||
|
|
@ -591,6 +596,8 @@ FoldTypeOfExpr(ExclusiveContext* cx, UnaryNode* node, Parser<FullParseHandler>&
|
||||||
result = cx->names().string;
|
result = cx->names().string;
|
||||||
else if (expr->isKind(PNK_NUMBER))
|
else if (expr->isKind(PNK_NUMBER))
|
||||||
result = cx->names().number;
|
result = cx->names().number;
|
||||||
|
else if (expr->isKind(PNK_BIGINT))
|
||||||
|
result = cx->names().bigint;
|
||||||
else if (expr->isKind(PNK_NULL))
|
else if (expr->isKind(PNK_NULL))
|
||||||
result = cx->names().object;
|
result = cx->names().object;
|
||||||
else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE))
|
else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE))
|
||||||
|
|
@ -1699,6 +1706,10 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo
|
||||||
MOZ_ASSERT(pn->is<NumericLiteral>());
|
MOZ_ASSERT(pn->is<NumericLiteral>());
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
MOZ_ASSERT(pn->is<BigIntLiteral>());
|
||||||
|
return true;
|
||||||
|
|
||||||
case PNK_SUPERBASE:
|
case PNK_SUPERBASE:
|
||||||
case PNK_TYPEOFNAME: {
|
case PNK_TYPEOFNAME: {
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,18 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
|
||||||
return new_<NumericLiteral>(value, decimalPoint, pos);
|
return new_<NumericLiteral>(value, decimalPoint, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The Boxer object here is any object that can allocate BigIntBoxes.
|
||||||
|
// Specifically, a Boxer has a .newBigIntBox(T) method that accepts a
|
||||||
|
// BigInt* argument and returns a BigIntBox*.
|
||||||
|
template <class Boxer>
|
||||||
|
BigIntLiteralType newBigInt(BigInt* bi, const TokenPos& pos, Boxer& boxer) {
|
||||||
|
BigIntBox* box = boxer.newBigIntBox(bi);
|
||||||
|
if (!box) {
|
||||||
|
return null();
|
||||||
|
}
|
||||||
|
return new_<BigIntLiteral>(box, pos);
|
||||||
|
}
|
||||||
|
|
||||||
BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
|
BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
|
||||||
return new_<BooleanLiteral>(cond, pos);
|
return new_<BooleanLiteral>(cond, pos);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -420,6 +420,9 @@ class NameResolver
|
||||||
MOZ_ASSERT(cur->is<NumericLiteral>());
|
MOZ_ASSERT(cur->is<NumericLiteral>());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
MOZ_ASSERT(cur->is<BigIntLiteral>());
|
||||||
|
break;
|
||||||
|
|
||||||
case PNK_TYPEOFNAME:
|
case PNK_TYPEOFNAME:
|
||||||
case PNK_SUPERBASE:
|
case PNK_SUPERBASE:
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,10 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack)
|
||||||
MOZ_ASSERT(pn->is<NumericLiteral>());
|
MOZ_ASSERT(pn->is<NumericLiteral>());
|
||||||
return PushResult::Recyclable;
|
return PushResult::Recyclable;
|
||||||
|
|
||||||
|
case PNK_BIGINT:
|
||||||
|
MOZ_ASSERT(pn->is<BigIntLiteral>());
|
||||||
|
return PushResult::Recyclable;
|
||||||
|
|
||||||
// Nodes with a single non-null child.
|
// Nodes with a single non-null child.
|
||||||
case PNK_TYPEOFNAME:
|
case PNK_TYPEOFNAME:
|
||||||
case PNK_TYPEOFEXPR:
|
case PNK_TYPEOFEXPR:
|
||||||
|
|
@ -716,6 +720,9 @@ ParseNode::dump(int indent)
|
||||||
case PN_NUMBER:
|
case PN_NUMBER:
|
||||||
as<NumericLiteral>().dump(indent);
|
as<NumericLiteral>().dump(indent);
|
||||||
return;
|
return;
|
||||||
|
case PN_BIGINT:
|
||||||
|
as<BigIntLiteral>().dump(indent);
|
||||||
|
return;
|
||||||
case PN_REGEXP:
|
case PN_REGEXP:
|
||||||
as<RegExpLiteral>().dump(indent);
|
as<RegExpLiteral>().dump(indent);
|
||||||
return;
|
return;
|
||||||
|
|
@ -759,6 +766,12 @@ NumericLiteral::dump(int indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BigIntLiteral::dump(int indent)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
RegExpLiteral::dump(int indent)
|
RegExpLiteral::dump(int indent)
|
||||||
{
|
{
|
||||||
|
|
@ -962,23 +975,45 @@ LexicalScopeNode::dump(int indent)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink)
|
TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink)
|
||||||
: object(object),
|
: gcThing(gcThing),
|
||||||
traceLink(traceLink),
|
traceLink(traceLink)
|
||||||
emitLink(nullptr)
|
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!object->is<JSFunction>());
|
MOZ_ASSERT(gcThing->isTenured());
|
||||||
MOZ_ASSERT(object->isTenured());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink)
|
BigIntBox*
|
||||||
: object(function),
|
TraceListNode::asBigIntBox()
|
||||||
traceLink(traceLink),
|
{
|
||||||
|
MOZ_ASSERT(isBigIntBox());
|
||||||
|
return static_cast<BigIntBox*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectBox*
|
||||||
|
TraceListNode::asObjectBox()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(isObjectBox());
|
||||||
|
return static_cast<ObjectBox*>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigIntBox::BigIntBox(BigInt* bi, TraceListNode* traceLink)
|
||||||
|
: TraceListNode(bi, traceLink)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectBox::ObjectBox(JSObject* obj, TraceListNode* traceLink)
|
||||||
|
: TraceListNode(obj, traceLink),
|
||||||
emitLink(nullptr)
|
emitLink(nullptr)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(object->is<JSFunction>());
|
MOZ_ASSERT(!object()->is<JSFunction>());
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink)
|
||||||
|
: TraceListNode(function, traceLink),
|
||||||
|
emitLink(nullptr)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(object()->is<JSFunction>());
|
||||||
MOZ_ASSERT(asFunctionBox()->function() == function);
|
MOZ_ASSERT(asFunctionBox()->function() == function);
|
||||||
MOZ_ASSERT(object->isTenured());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionBox*
|
FunctionBox*
|
||||||
|
|
@ -989,16 +1024,17 @@ ObjectBox::asFunctionBox()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static */ void
|
/* static */ void
|
||||||
ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead)
|
TraceListNode::TraceList(JSTracer* trc, TraceListNode* listHead)
|
||||||
{
|
{
|
||||||
for (ObjectBox* box = listHead; box; box = box->traceLink)
|
for (TraceListNode* node = listHead; node; node = node->traceLink) {
|
||||||
box->trace(trc);
|
node->trace(trc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ObjectBox::trace(JSTracer* trc)
|
TraceListNode::trace(JSTracer* trc)
|
||||||
{
|
{
|
||||||
TraceRoot(trc, &object, "parser.object");
|
TraceGenericPointerRoot(trc, &gcThing, "parser.traceListNode");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "builtin/ModuleObject.h"
|
#include "builtin/ModuleObject.h"
|
||||||
#include "frontend/TokenStream.h"
|
#include "frontend/TokenStream.h"
|
||||||
|
#include "vm/BigIntType.h"
|
||||||
|
|
||||||
namespace js {
|
namespace js {
|
||||||
namespace frontend {
|
namespace frontend {
|
||||||
|
|
@ -20,6 +21,7 @@ class ParseContext;
|
||||||
class FullParseHandler;
|
class FullParseHandler;
|
||||||
class FunctionBox;
|
class FunctionBox;
|
||||||
class ObjectBox;
|
class ObjectBox;
|
||||||
|
class BigIntBox;
|
||||||
|
|
||||||
#define FOR_EACH_PARSE_NODE_KIND(F) \
|
#define FOR_EACH_PARSE_NODE_KIND(F) \
|
||||||
F(NOP) \
|
F(NOP) \
|
||||||
|
|
@ -53,6 +55,7 @@ class ObjectBox;
|
||||||
F(OBJECT_PROPERTY_NAME) \
|
F(OBJECT_PROPERTY_NAME) \
|
||||||
F(COMPUTED_NAME) \
|
F(COMPUTED_NAME) \
|
||||||
F(NUMBER) \
|
F(NUMBER) \
|
||||||
|
F(BIGINT) \
|
||||||
F(STRING) \
|
F(STRING) \
|
||||||
F(TEMPLATE_STRING_LIST) \
|
F(TEMPLATE_STRING_LIST) \
|
||||||
F(TEMPLATE_STRING) \
|
F(TEMPLATE_STRING) \
|
||||||
|
|
@ -524,6 +527,8 @@ IsTypeofKind(ParseNodeKind kind)
|
||||||
* regexp: RegExp model object
|
* regexp: RegExp model object
|
||||||
* PNK_NUMBER (NumericLiteral)
|
* PNK_NUMBER (NumericLiteral)
|
||||||
* value: double value of numeric literal
|
* value: double value of numeric literal
|
||||||
|
* PNK_BIGINT (BigIntLiteral)
|
||||||
|
* box: BigIntBox holding BigInt* value
|
||||||
* PNK_TRUE, PNK_FALSE (BooleanLiteral)
|
* PNK_TRUE, PNK_FALSE (BooleanLiteral)
|
||||||
* pn_op: JSOp bytecode
|
* pn_op: JSOp bytecode
|
||||||
* PNK_NULL (NullLiteral)
|
* PNK_NULL (NullLiteral)
|
||||||
|
|
@ -571,6 +576,7 @@ enum ParseNodeArity
|
||||||
PN_LIST, /* generic singly linked list */
|
PN_LIST, /* generic singly linked list */
|
||||||
PN_NAME, /* name, label, string */
|
PN_NAME, /* name, label, string */
|
||||||
PN_NUMBER, /* numeric literal */
|
PN_NUMBER, /* numeric literal */
|
||||||
|
PN_BIGINT, /* BigInt literal */
|
||||||
PN_REGEXP, /* regexp literal */
|
PN_REGEXP, /* regexp literal */
|
||||||
PN_LOOP, /* loop control (break/continue) */
|
PN_LOOP, /* loop control (break/continue) */
|
||||||
PN_SCOPE /* lexical scope */
|
PN_SCOPE /* lexical scope */
|
||||||
|
|
@ -613,6 +619,7 @@ enum ParseNodeArity
|
||||||
macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \
|
macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \
|
||||||
\
|
\
|
||||||
macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \
|
macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \
|
||||||
|
macro(BigIntLiteral, BigIntLiteralType, asBigIntLiteral) \
|
||||||
\
|
\
|
||||||
macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \
|
macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \
|
||||||
\
|
\
|
||||||
|
|
@ -828,6 +835,11 @@ class ParseNode
|
||||||
double value; /* aligned numeric literal value */
|
double value; /* aligned numeric literal value */
|
||||||
DecimalPoint decimalPoint; /* Whether the number has a decimal point */
|
DecimalPoint decimalPoint; /* Whether the number has a decimal point */
|
||||||
} number;
|
} number;
|
||||||
|
struct {
|
||||||
|
private:
|
||||||
|
friend class BigIntLiteral;
|
||||||
|
BigIntBox* box;
|
||||||
|
} bigint;
|
||||||
class {
|
class {
|
||||||
private:
|
private:
|
||||||
friend class LoopControlStatement;
|
friend class LoopControlStatement;
|
||||||
|
|
@ -849,6 +861,7 @@ class ParseNode
|
||||||
/* True if pn is a parsenode representing a literal constant. */
|
/* True if pn is a parsenode representing a literal constant. */
|
||||||
bool isLiteral() const {
|
bool isLiteral() const {
|
||||||
return isKind(PNK_NUMBER) ||
|
return isKind(PNK_NUMBER) ||
|
||||||
|
isKind(PNK_BIGINT) ||
|
||||||
isKind(PNK_STRING) ||
|
isKind(PNK_STRING) ||
|
||||||
isKind(PNK_TRUE) ||
|
isKind(PNK_TRUE) ||
|
||||||
isKind(PNK_FALSE) ||
|
isKind(PNK_FALSE) ||
|
||||||
|
|
@ -1631,6 +1644,30 @@ class NumericLiteral : public ParseNode
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BigIntLiteral : public ParseNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BigIntLiteral(BigIntBox* bibox, const TokenPos& pos)
|
||||||
|
: ParseNode(PNK_BIGINT, JSOP_NOP, PN_BIGINT, pos)
|
||||||
|
{
|
||||||
|
pn_u.bigint.box = bibox;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test(const ParseNode& node) {
|
||||||
|
bool match = node.isKind(PNK_BIGINT);
|
||||||
|
MOZ_ASSERT_IF(match, node.isArity(PN_BIGINT));
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
void dump(int indent);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BigIntBox* box() const {
|
||||||
|
return pn_u.bigint.box;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class LexicalScopeNode : public ParseNode
|
class LexicalScopeNode : public ParseNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -2350,25 +2387,48 @@ ParseNode::isConstant()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectBox
|
class TraceListNode
|
||||||
{
|
{
|
||||||
public:
|
protected:
|
||||||
JSObject* object;
|
js::gc::Cell* gcThing;
|
||||||
|
TraceListNode* traceLink;
|
||||||
|
|
||||||
|
TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink);
|
||||||
|
|
||||||
|
bool isBigIntBox() const { return gcThing->is<BigInt>(); }
|
||||||
|
bool isObjectBox() const { return gcThing->is<JSObject>(); }
|
||||||
|
|
||||||
|
BigIntBox* asBigIntBox();
|
||||||
|
ObjectBox* asObjectBox();
|
||||||
|
|
||||||
ObjectBox(JSObject* object, ObjectBox* traceLink);
|
|
||||||
bool isFunctionBox() { return object->is<JSFunction>(); }
|
|
||||||
FunctionBox* asFunctionBox();
|
|
||||||
virtual void trace(JSTracer* trc);
|
virtual void trace(JSTracer* trc);
|
||||||
|
|
||||||
static void TraceList(JSTracer* trc, ObjectBox* listHead);
|
public:
|
||||||
|
static void TraceList(JSTracer* trc, TraceListNode* listHead);
|
||||||
|
};
|
||||||
|
|
||||||
|
class BigIntBox : public TraceListNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BigIntBox(BigInt* bi, TraceListNode* link);
|
||||||
|
BigInt* value() const { return gcThing->as<BigInt>(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ObjectBox : public TraceListNode
|
||||||
|
{
|
||||||
protected:
|
protected:
|
||||||
friend struct CGObjectList;
|
friend struct CGObjectList;
|
||||||
|
|
||||||
ObjectBox* traceLink;
|
|
||||||
ObjectBox* emitLink;
|
ObjectBox* emitLink;
|
||||||
|
|
||||||
ObjectBox(JSFunction* function, ObjectBox* traceLink);
|
ObjectBox(JSFunction* function, TraceListNode* link);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ObjectBox(JSObject* obj, TraceListNode* link);
|
||||||
|
|
||||||
|
JSObject* object() const { return gcThing->as<JSObject>(); }
|
||||||
|
|
||||||
|
bool isFunctionBox() const { return object()->is<JSFunction>(); }
|
||||||
|
FunctionBox* asFunctionBox();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ParseReportKind
|
enum ParseReportKind
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,7 @@ UsedNameTracker::rewind(RewindToken token)
|
||||||
r.front().value().resetToScope(token.scriptId, token.scopeId);
|
r.front().value().resetToScope(token.scriptId, token.scopeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead,
|
FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead,
|
||||||
JSFunction* fun, uint32_t toStringStart,
|
JSFunction* fun, uint32_t toStringStart,
|
||||||
Directives directives, bool extraWarnings,
|
Directives directives, bool extraWarnings,
|
||||||
GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
|
GeneratorKind generatorKind, FunctionAsyncKind asyncKind)
|
||||||
|
|
@ -882,11 +882,11 @@ Parser<FullParseHandler>::setAwaitHandling(AwaitHandling awaitHandling)
|
||||||
parser->setAwaitHandling(awaitHandling);
|
parser->setAwaitHandling(awaitHandling);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ParseHandler>
|
template <typename BoxT, typename ArgT>
|
||||||
ObjectBox*
|
BoxT*
|
||||||
Parser<ParseHandler>::newObjectBox(JSObject* obj)
|
ParserBase::newTraceListNode(ArgT* arg)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(obj);
|
MOZ_ASSERT(arg);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
|
* We use JSContext.tempLifoAlloc to allocate parsed objects and place them
|
||||||
|
|
@ -896,15 +896,27 @@ Parser<ParseHandler>::newObjectBox(JSObject* obj)
|
||||||
* function.
|
* function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead);
|
BoxT* box = alloc.template new_<BoxT>(arg, traceListHead);
|
||||||
if (!objbox) {
|
if (!box) {
|
||||||
ReportOutOfMemory(context);
|
ReportOutOfMemory(context);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
traceListHead = objbox;
|
traceListHead = box;
|
||||||
|
|
||||||
return objbox;
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectBox*
|
||||||
|
ParserBase::newObjectBox(JSObject* obj)
|
||||||
|
{
|
||||||
|
return newTraceListNode<ObjectBox, JSObject>(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigIntBox*
|
||||||
|
ParserBase::newBigIntBox(BigInt* val)
|
||||||
|
{
|
||||||
|
return newTraceListNode<BigIntBox, BigInt>(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ParseHandler>
|
template <typename ParseHandler>
|
||||||
|
|
@ -10579,6 +10591,37 @@ Parser<ParseHandler>::newRegExp()
|
||||||
return handler.newRegExp(reobj, pos(), *this);
|
return handler.newRegExp(reobj, pos(), *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
BigIntLiteral*
|
||||||
|
Parser<FullParseHandler>::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.
|
||||||
|
const auto& chars = tokenStream.getTokenbuf();
|
||||||
|
mozilla::Range<const char16_t> source(chars.begin(), chars.length());
|
||||||
|
|
||||||
|
BigInt* b = js::ParseBigIntLiteral(context, source);
|
||||||
|
if (!b) {
|
||||||
|
return null();
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBigInt immediately puts "b" in a BigIntBox, which is allocated using
|
||||||
|
// tempLifoAlloc, avoiding any potential GC. Therefore it's OK to pass a
|
||||||
|
// raw pointer.
|
||||||
|
return handler.newBigInt(b, pos(), *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
SyntaxParseHandler::BigIntLiteralType
|
||||||
|
Parser<SyntaxParseHandler>::newBigInt()
|
||||||
|
{
|
||||||
|
// The tokenizer has already checked the syntax of the bigint.
|
||||||
|
|
||||||
|
return handler.newBigInt();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename ParseHandler>
|
template <typename ParseHandler>
|
||||||
void
|
void
|
||||||
Parser<ParseHandler>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos,
|
Parser<ParseHandler>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos,
|
||||||
|
|
@ -11474,6 +11517,9 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling
|
||||||
case TOK_NUMBER:
|
case TOK_NUMBER:
|
||||||
return newNumber(tokenStream.currentToken());
|
return newNumber(tokenStream.currentToken());
|
||||||
|
|
||||||
|
case TOK_BIGINT:
|
||||||
|
return newBigInt();
|
||||||
|
|
||||||
case TOK_TRUE:
|
case TOK_TRUE:
|
||||||
return handler.newBooleanLiteral(true, pos());
|
return handler.newBooleanLiteral(true, pos());
|
||||||
case TOK_FALSE:
|
case TOK_FALSE:
|
||||||
|
|
|
||||||
|
|
@ -779,8 +779,8 @@ class ParserBase : public StrictModeGetter
|
||||||
TokenStream tokenStream;
|
TokenStream tokenStream;
|
||||||
LifoAlloc::Mark tempPoolMark;
|
LifoAlloc::Mark tempPoolMark;
|
||||||
|
|
||||||
/* list of parsed objects for GC tracing */
|
/* list of parsed objects and BigInts for GC tracing */
|
||||||
ObjectBox* traceListHead;
|
TraceListNode* traceListHead;
|
||||||
|
|
||||||
/* innermost parse context (stack-allocated) */
|
/* innermost parse context (stack-allocated) */
|
||||||
ParseContext* pc;
|
ParseContext* pc;
|
||||||
|
|
@ -915,6 +915,13 @@ class ParserBase : public StrictModeGetter
|
||||||
bool warnOnceAboutExprClosure();
|
bool warnOnceAboutExprClosure();
|
||||||
bool warnOnceAboutForEach();
|
bool warnOnceAboutForEach();
|
||||||
|
|
||||||
|
ObjectBox* newObjectBox(JSObject* obj);
|
||||||
|
BigIntBox* newBigIntBox(BigInt* val);
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename BoxT, typename ArgT>
|
||||||
|
BoxT* newTraceListNode(ArgT* arg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true };
|
enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true };
|
||||||
enum ForInitLocation { InForInit, NotInForInit };
|
enum ForInitLocation { InForInit, NotInForInit };
|
||||||
|
|
@ -1085,7 +1092,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
|
||||||
{
|
{
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
LifoAlloc::Mark mark;
|
LifoAlloc::Mark mark;
|
||||||
ObjectBox* traceListHead;
|
TraceListNode* traceListHead;
|
||||||
};
|
};
|
||||||
Mark mark() const {
|
Mark mark() const {
|
||||||
Mark m;
|
Mark m;
|
||||||
|
|
@ -1174,7 +1181,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
|
||||||
* Allocate a new parsed object or function container from
|
* Allocate a new parsed object or function container from
|
||||||
* cx->tempLifoAlloc.
|
* cx->tempLifoAlloc.
|
||||||
*/
|
*/
|
||||||
ObjectBox* newObjectBox(JSObject* obj);
|
public:
|
||||||
FunctionBox* newFunctionBox(FunctionNodeType funNode, JSFunction* fun, uint32_t toStringStart,
|
FunctionBox* newFunctionBox(FunctionNodeType funNode, JSFunction* fun, uint32_t toStringStart,
|
||||||
Directives directives,
|
Directives directives,
|
||||||
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
|
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
|
||||||
|
|
@ -1660,6 +1667,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
|
||||||
const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType literal);
|
const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType literal);
|
||||||
ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError);
|
ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError);
|
||||||
RegExpLiteralType newRegExp();
|
RegExpLiteralType newRegExp();
|
||||||
|
BigIntLiteralType newBigInt();
|
||||||
|
|
||||||
ListNodeType objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError);
|
ListNodeType objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -443,7 +443,7 @@ class FunctionBox : public ObjectBox, public SharedContext
|
||||||
|
|
||||||
FunctionContextFlags funCxFlags;
|
FunctionContextFlags funCxFlags;
|
||||||
|
|
||||||
FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun,
|
FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead, JSFunction* fun,
|
||||||
uint32_t toStringStart, Directives directives, bool extraWarnings,
|
uint32_t toStringStart, Directives directives, bool extraWarnings,
|
||||||
GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
|
GeneratorKind generatorKind, FunctionAsyncKind asyncKind);
|
||||||
|
|
||||||
|
|
@ -467,7 +467,8 @@ class FunctionBox : public ObjectBox, public SharedContext
|
||||||
void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
|
void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
|
||||||
|
|
||||||
ObjectBox* toObjectBox() override { return this; }
|
ObjectBox* toObjectBox() override { return this; }
|
||||||
JSFunction* function() const { return &object->as<JSFunction>(); }
|
JSFunction* function() const { return &object()->as<JSFunction>(); }
|
||||||
|
void clobberFunction(JSFunction* function) { gcThing = function; }
|
||||||
|
|
||||||
Scope* compilationEnclosingScope() const override {
|
Scope* compilationEnclosingScope() const override {
|
||||||
// This method is used to distinguish the outermost SharedContext. If
|
// This method is used to distinguish the outermost SharedContext. If
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
|
||||||
}
|
}
|
||||||
|
|
||||||
NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; }
|
NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; }
|
||||||
|
BigIntLiteralType newBigInt() { return NodeGeneric; }
|
||||||
BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; }
|
BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; }
|
||||||
|
|
||||||
NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) {
|
NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) {
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@
|
||||||
macro(PRIVATE_NAME, "private identifier") \
|
macro(PRIVATE_NAME, "private identifier") \
|
||||||
macro(NUMBER, "numeric literal") \
|
macro(NUMBER, "numeric literal") \
|
||||||
macro(STRING, "string literal") \
|
macro(STRING, "string literal") \
|
||||||
|
macro(BIGINT, "bigint literal") \
|
||||||
\
|
\
|
||||||
/* start of template literal with substitutions */ \
|
/* start of template literal with substitutions */ \
|
||||||
macro(TEMPLATE_HEAD, "'${'") \
|
macro(TEMPLATE_HEAD, "'${'") \
|
||||||
|
|
|
||||||
|
|
@ -1382,6 +1382,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
|
||||||
const char16_t* identStart;
|
const char16_t* identStart;
|
||||||
NameVisibility identVisibility;
|
NameVisibility identVisibility;
|
||||||
bool hadUnicodeEscape;
|
bool hadUnicodeEscape;
|
||||||
|
bool isBigInt = false;
|
||||||
|
|
||||||
// Check if in the middle of a template string. Have to get this out of
|
// Check if in the middle of a template string. Have to get this out of
|
||||||
// the way first.
|
// the way first.
|
||||||
|
|
@ -1619,6 +1620,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
|
||||||
}
|
}
|
||||||
} while (true);
|
} while (true);
|
||||||
}
|
}
|
||||||
|
if (c == 'n') {
|
||||||
|
isBigInt = true;
|
||||||
|
c = getCharIgnoreEOL();
|
||||||
|
}
|
||||||
ungetCharIgnoreEOL(c);
|
ungetCharIgnoreEOL(c);
|
||||||
|
|
||||||
if (c != EOF) {
|
if (c != EOF) {
|
||||||
|
|
@ -1638,6 +1643,16 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBigInt) {
|
||||||
|
size_t length = userbuf.addressOfNextRawChar() - numStart - 1;
|
||||||
|
tokenbuf.clear();
|
||||||
|
if(!tokenbuf.reserve(length))
|
||||||
|
goto error;
|
||||||
|
tokenbuf.infallibleAppend(numStart, length);
|
||||||
|
tp->type = TOK_BIGINT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
// Unlike identifiers and strings, numbers cannot contain escaped
|
// Unlike identifiers and strings, numbers cannot contain escaped
|
||||||
// chars, so we don't need to use tokenbuf. Instead we can just
|
// chars, so we don't need to use tokenbuf. Instead we can just
|
||||||
// convert the char16_t characters in userbuf to the numeric value.
|
// convert the char16_t characters in userbuf to the numeric value.
|
||||||
|
|
@ -1777,6 +1792,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
|
||||||
hasExp = false;
|
hasExp = false;
|
||||||
goto decimal_rest;
|
goto decimal_rest;
|
||||||
}
|
}
|
||||||
|
if (c == 'n') {
|
||||||
|
isBigInt = true;
|
||||||
|
c = getCharIgnoreEOL();
|
||||||
|
}
|
||||||
ungetCharIgnoreEOL(c);
|
ungetCharIgnoreEOL(c);
|
||||||
|
|
||||||
if (c != EOF) {
|
if (c != EOF) {
|
||||||
|
|
@ -1796,6 +1815,16 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBigInt) {
|
||||||
|
size_t length = userbuf.addressOfNextRawChar() - numStart - 1;
|
||||||
|
tokenbuf.clear();
|
||||||
|
if(!tokenbuf.reserve(length))
|
||||||
|
goto error;
|
||||||
|
tokenbuf.infallibleAppend(numStart, length);
|
||||||
|
tp->type = TOK_BIGINT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
double dval;
|
double dval;
|
||||||
const char16_t* dummy;
|
const char16_t* dummy;
|
||||||
if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix,
|
if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix,
|
||||||
|
|
|
||||||
|
|
@ -1620,6 +1620,13 @@ BaselineCompiler::emit_JSOP_DOUBLE()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BaselineCompiler::emit_JSOP_BIGINT()
|
||||||
|
{
|
||||||
|
frame.push(script->getConst(GET_UINT32_INDEX(pc)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
BaselineCompiler::emit_JSOP_STRING()
|
BaselineCompiler::emit_JSOP_STRING()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ namespace jit {
|
||||||
_(JSOP_UINT16) \
|
_(JSOP_UINT16) \
|
||||||
_(JSOP_UINT24) \
|
_(JSOP_UINT24) \
|
||||||
_(JSOP_DOUBLE) \
|
_(JSOP_DOUBLE) \
|
||||||
|
_(JSOP_BIGINT) \
|
||||||
_(JSOP_STRING) \
|
_(JSOP_STRING) \
|
||||||
_(JSOP_SYMBOL) \
|
_(JSOP_SYMBOL) \
|
||||||
_(JSOP_OBJECT) \
|
_(JSOP_OBJECT) \
|
||||||
|
|
|
||||||
|
|
@ -1765,6 +1765,7 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||||
return jsop_compare(op);
|
return jsop_compare(op);
|
||||||
|
|
||||||
case JSOP_DOUBLE:
|
case JSOP_DOUBLE:
|
||||||
|
case JSOP_BIGINT:
|
||||||
pushConstant(info().getConst(pc));
|
pushConstant(info().getConst(pc));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1039,6 +1039,9 @@ js::Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case JOF_BIGINT:
|
||||||
|
// Fallthrough.
|
||||||
|
|
||||||
case JOF_DOUBLE: {
|
case JOF_DOUBLE: {
|
||||||
RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
|
RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc)));
|
||||||
JSAutoByteString bytes;
|
JSAutoByteString bytes;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ enum {
|
||||||
JOF_ATOMOBJECT = 19, /* uint16_t constant index + object index */
|
JOF_ATOMOBJECT = 19, /* uint16_t constant index + object index */
|
||||||
JOF_SCOPE = 20, /* unsigned 32-bit scope index */
|
JOF_SCOPE = 20, /* unsigned 32-bit scope index */
|
||||||
JOF_ENVCOORD = 21, /* embedded ScopeCoordinate immediate */
|
JOF_ENVCOORD = 21, /* embedded ScopeCoordinate immediate */
|
||||||
|
JOF_BIGINT = 22, /* uint32_t index for BigInt value */
|
||||||
JOF_TYPEMASK = 0x001f, /* mask for above immediate types */
|
JOF_TYPEMASK = 0x001f, /* mask for above immediate types */
|
||||||
|
|
||||||
JOF_NAME = 1 << 5, /* name operation */
|
JOF_NAME = 1 << 5, /* name operation */
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp)
|
||||||
SCRIPT_NULL,
|
SCRIPT_NULL,
|
||||||
SCRIPT_OBJECT,
|
SCRIPT_OBJECT,
|
||||||
SCRIPT_VOID,
|
SCRIPT_VOID,
|
||||||
SCRIPT_HOLE
|
SCRIPT_HOLE,
|
||||||
|
SCRIPT_BIGINT
|
||||||
};
|
};
|
||||||
|
|
||||||
ConstTag tag;
|
ConstTag tag;
|
||||||
|
|
@ -104,6 +105,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp)
|
||||||
tag = SCRIPT_OBJECT;
|
tag = SCRIPT_OBJECT;
|
||||||
} else if (vp.isMagic(JS_ELEMENTS_HOLE)) {
|
} else if (vp.isMagic(JS_ELEMENTS_HOLE)) {
|
||||||
tag = SCRIPT_HOLE;
|
tag = SCRIPT_HOLE;
|
||||||
|
} else if (vp.isBigInt()) {
|
||||||
|
tag = SCRIPT_BIGINT;
|
||||||
} else {
|
} else {
|
||||||
MOZ_ASSERT(vp.isUndefined());
|
MOZ_ASSERT(vp.isUndefined());
|
||||||
tag = SCRIPT_VOID;
|
tag = SCRIPT_VOID;
|
||||||
|
|
@ -176,6 +179,20 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp)
|
||||||
if (mode == XDR_DECODE)
|
if (mode == XDR_DECODE)
|
||||||
vp.setMagic(JS_ELEMENTS_HOLE);
|
vp.setMagic(JS_ELEMENTS_HOLE);
|
||||||
break;
|
break;
|
||||||
|
case SCRIPT_BIGINT: {
|
||||||
|
RootedBigInt bi(cx);
|
||||||
|
if (mode == XDR_ENCODE) {
|
||||||
|
bi = vp.toBigInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!XDRBigInt(xdr, &bi))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (mode == XDR_DECODE) {
|
||||||
|
vp.setBigInt(bi);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// Fail in debug, but only soft-fail in release
|
// Fail in debug, but only soft-fail in release
|
||||||
MOZ_ASSERT(false, "Bad XDR value kind");
|
MOZ_ASSERT(false, "Bad XDR value kind");
|
||||||
|
|
@ -3418,6 +3435,38 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Constants */
|
||||||
|
|
||||||
|
AutoValueVector consts(cx);
|
||||||
|
if (nconsts != 0) {
|
||||||
|
GCPtrValue* vector = src->consts()->vector;
|
||||||
|
RootedValue val(cx);
|
||||||
|
RootedValue clone(cx);
|
||||||
|
for (unsigned i = 0; i < nconsts; i++) {
|
||||||
|
val = vector[i];
|
||||||
|
if (val.isDouble()) {
|
||||||
|
clone = val;
|
||||||
|
} else if (val.isBigInt()) {
|
||||||
|
if (cx->zone() == val.toBigInt()->zone()) {
|
||||||
|
clone.setBigInt(val.toBigInt());
|
||||||
|
} else {
|
||||||
|
RootedBigInt b(cx, val.toBigInt());
|
||||||
|
BigInt* copy = BigInt::copy(cx, b);
|
||||||
|
if (!copy) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
clone.setBigInt(copy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MOZ_ASSERT_UNREACHABLE("bad script consts() element");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!consts.append(clone)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Objects */
|
/* Objects */
|
||||||
|
|
||||||
AutoObjectVector objects(cx);
|
AutoObjectVector objects(cx);
|
||||||
|
|
@ -3508,7 +3557,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst,
|
||||||
GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector);
|
GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector);
|
||||||
dst->consts()->vector = vector;
|
dst->consts()->vector = vector;
|
||||||
for (unsigned i = 0; i < nconsts; ++i)
|
for (unsigned i = 0; i < nconsts; ++i)
|
||||||
MOZ_ASSERT_IF(vector[i].isGCThing(), vector[i].toString()->isAtom());
|
vector[i].init(consts[i]);
|
||||||
}
|
}
|
||||||
if (nobjects != 0) {
|
if (nobjects != 0) {
|
||||||
GCPtrObject* vector = Rebase<GCPtrObject>(dst, src, src->objects()->vector);
|
GCPtrObject* vector = Rebase<GCPtrObject>(dst, src, src->objects()->vector);
|
||||||
|
|
|
||||||
|
|
@ -1987,6 +1987,8 @@ BigInt* BigInt::rshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (!bitsShift) {
|
if (!bitsShift) {
|
||||||
|
// If roundingCanOverflow, manually initialize the overflow digit.
|
||||||
|
result->setDigit(resultLength - 1, 0);
|
||||||
for (int i = digitShift; i < length; i++) {
|
for (int i = digitShift; i < length; i++) {
|
||||||
result->setDigit(i - digitShift, x->digit(i));
|
result->setDigit(i - digitShift, x->digit(i));
|
||||||
}
|
}
|
||||||
|
|
@ -3144,16 +3146,14 @@ JS::ubi::Node::Size JS::ubi::Concrete<BigInt>::size(
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0 // Future XDR support
|
|
||||||
template <XDRMode mode>
|
template <XDRMode mode>
|
||||||
XDRResult js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) {
|
bool js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) {
|
||||||
JSContext* cx = xdr->cx();
|
ExclusiveContext* cx = xdr->cx();
|
||||||
|
|
||||||
uint8_t sign;
|
uint8_t sign;
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
|
|
||||||
if (mode == XDR_ENCODE) {
|
if (mode == XDR_ENCODE) {
|
||||||
cx->check(bi);
|
|
||||||
sign = static_cast<uint8_t>(bi->isNegative());
|
sign = static_cast<uint8_t>(bi->isNegative());
|
||||||
uint64_t sz = bi->digitLength() * sizeof(BigInt::Digit);
|
uint64_t sz = bi->digitLength() * sizeof(BigInt::Digit);
|
||||||
// As the maximum source code size is currently UINT32_MAX code units
|
// As the maximum source code size is currently UINT32_MAX code units
|
||||||
|
|
@ -3165,8 +3165,10 @@ XDRResult js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) {
|
||||||
length = static_cast<uint32_t>(sz);
|
length = static_cast<uint32_t>(sz);
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_TRY(xdr->codeUint8(&sign));
|
if(!xdr->codeUint8(&sign))
|
||||||
MOZ_TRY(xdr->codeUint32(&length));
|
return false;
|
||||||
|
if(!xdr->codeUint32(&length))
|
||||||
|
return false;
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(length % sizeof(BigInt::Digit) == 0);
|
MOZ_RELEASE_ASSERT(length % sizeof(BigInt::Digit) == 0);
|
||||||
uint32_t digitLength = length / sizeof(BigInt::Digit);
|
uint32_t digitLength = length / sizeof(BigInt::Digit);
|
||||||
|
|
@ -3179,7 +3181,8 @@ XDRResult js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) {
|
||||||
std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get());
|
std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_TRY(xdr->codeBytes(buf.get(), length));
|
if(!xdr->codeBytes(buf.get(), length))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (mode == XDR_DECODE) {
|
if (mode == XDR_DECODE) {
|
||||||
BigInt* res = BigInt::createUninitialized(cx, digitLength, sign);
|
BigInt* res = BigInt::createUninitialized(cx, digitLength, sign);
|
||||||
|
|
@ -3190,12 +3193,10 @@ XDRResult js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) {
|
||||||
bi.set(res);
|
bi.set(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template XDRResult js::XDRBigInt(XDRState<XDR_ENCODE>* xdr,
|
template bool js::XDRBigInt(XDRState<XDR_ENCODE>* xdr, MutableHandleBigInt bi);
|
||||||
MutableHandleBigInt bi);
|
|
||||||
|
template bool js::XDRBigInt(XDRState<XDR_DECODE>* xdr, MutableHandleBigInt bi);
|
||||||
|
|
||||||
template XDRResult js::XDRBigInt(XDRState<XDR_DECODE>* xdr,
|
|
||||||
MutableHandleBigInt bi);
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
#if 0 // Future XDR support
|
|
||||||
class BigInt;
|
class BigInt;
|
||||||
|
|
||||||
} // namespace JS
|
} // namespace JS
|
||||||
|
|
@ -34,12 +33,11 @@ class BigInt;
|
||||||
namespace js {
|
namespace js {
|
||||||
|
|
||||||
template <XDRMode mode>
|
template <XDRMode mode>
|
||||||
XDRResult XDRBigInt(XDRState<mode>* xdr, MutableHandle<JS::BigInt*> bi);
|
bool XDRBigInt(XDRState<mode>* xdr, MutableHandle<JS::BigInt*> bi);
|
||||||
|
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
#endif
|
|
||||||
|
|
||||||
class BigInt final : public js::gc::TenuredCell {
|
class BigInt final : public js::gc::TenuredCell {
|
||||||
public:
|
public:
|
||||||
|
|
@ -332,11 +330,8 @@ class BigInt final : public js::gc::TenuredCell {
|
||||||
|
|
||||||
friend struct JSStructuredCloneReader;
|
friend struct JSStructuredCloneReader;
|
||||||
friend struct JSStructuredCloneWriter;
|
friend struct JSStructuredCloneWriter;
|
||||||
#if 0 // Future XDR support
|
|
||||||
template <js::XDRMode mode>
|
template <js::XDRMode mode>
|
||||||
friend js::XDRResult js::XDRBigInt(js::XDRState<mode>* xdr,
|
friend bool js::XDRBigInt(js::XDRState<mode>* xdr, MutableHandle<BigInt*> bi);
|
||||||
MutableHandle<BigInt*> bi);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BigInt() = delete;
|
BigInt() = delete;
|
||||||
BigInt(const BigInt& other) = delete;
|
BigInt(const BigInt& other) = delete;
|
||||||
|
|
|
||||||
|
|
@ -4125,6 +4125,13 @@ CASE(JSOP_IS_CONSTRUCTING)
|
||||||
PUSH_MAGIC(JS_IS_CONSTRUCTING);
|
PUSH_MAGIC(JS_IS_CONSTRUCTING);
|
||||||
END_CASE(JSOP_IS_CONSTRUCTING)
|
END_CASE(JSOP_IS_CONSTRUCTING)
|
||||||
|
|
||||||
|
CASE(JSOP_BIGINT)
|
||||||
|
{
|
||||||
|
PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc)));
|
||||||
|
MOZ_ASSERT(REGS.sp[-1].isBigInt());
|
||||||
|
}
|
||||||
|
END_CASE(JSOP_BIGINT)
|
||||||
|
|
||||||
DEFAULT()
|
DEFAULT()
|
||||||
{
|
{
|
||||||
char numBuf[12];
|
char numBuf[12];
|
||||||
|
|
|
||||||
|
|
@ -2357,14 +2357,20 @@
|
||||||
* Operands:
|
* Operands:
|
||||||
* Stack: arg => rval
|
* Stack: arg => rval
|
||||||
*/ \
|
*/ \
|
||||||
macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE)
|
macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \
|
||||||
|
/*
|
||||||
|
* Pushes a BigInt constant onto the stack.
|
||||||
|
* Category: Literals
|
||||||
|
* Type: Constants
|
||||||
|
* Operands: uint32_t constIndex
|
||||||
|
* Stack: => val
|
||||||
|
*/ \
|
||||||
|
macro(JSOP_BIGINT, 235, "bigint", NULL, 5, 0, 1, JOF_BIGINT)
|
||||||
/*
|
/*
|
||||||
* In certain circumstances it may be useful to "pad out" the opcode space to
|
* In certain circumstances it may be useful to "pad out" the opcode space to
|
||||||
* a power of two. Use this macro to do so.
|
* a power of two. Use this macro to do so.
|
||||||
*/
|
*/
|
||||||
#define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
|
#define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
|
||||||
macro(235) \
|
|
||||||
macro(236) \
|
macro(236) \
|
||||||
macro(237) \
|
macro(237) \
|
||||||
macro(238) \
|
macro(238) \
|
||||||
|
|
|
||||||
|
|
@ -8709,7 +8709,7 @@ js::CompileAsmJS(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList,
|
||||||
// generating bytecode for asm.js functions, allowing this asm.js module
|
// generating bytecode for asm.js functions, allowing this asm.js module
|
||||||
// function to be the finished result.
|
// function to be the finished result.
|
||||||
MOZ_ASSERT(funbox->function()->isInterpreted());
|
MOZ_ASSERT(funbox->function()->isInterpreted());
|
||||||
funbox->object = moduleFun;
|
funbox->clobberFunction(moduleFun);
|
||||||
|
|
||||||
// Success! Write to the console with a "warning" message.
|
// Success! Write to the console with a "warning" message.
|
||||||
*validated = true;
|
*validated = true;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue