#include "frontend/FoldConstants.h"
#include "mozilla/FloatingPoint.h"
#include "jslibmath.h"
#include "jsnum.h"
#include "frontend/ParseNode.h"
#include "frontend/ParseNodeVisitor.h"
#include "frontend/Parser.h"
#include "js/Conversions.h"
#include "vm/StringType.h"
using namespace js;
using namespace js::frontend;
using JS::GenericNaN;
using JS::ToInt32;
using JS::ToUint32;
using mozilla::IsNaN;
using mozilla::IsNegative;
using mozilla::NegativeInfinity;
using mozilla::PositiveInfinity;
inline MOZ_MUST_USE bool TryReplaceNode(ParseNode** pnp, ParseNode* pn) {
if (!pn) {
return false;
}
pn->setInParens((*pnp)->isInParens());
pn->setDirectRHSAnonFunction((*pnp)->isDirectRHSAnonFunction());
ReplaceNode(pnp, pn);
return true;
}
static bool ContainsHoistedDeclaration(JSContext* cx, ParseNode* node,
bool* result);
static bool ListContainsHoistedDeclaration(JSContext* cx, ListNode* list,
bool* result) {
for (ParseNode* node : list->contents()) {
if (!ContainsHoistedDeclaration(cx, node, result)) {
return false;
}
if (*result) {
return true;
}
}
*result = false;
return true;
}
static bool ContainsHoistedDeclaration(JSContext* cx, ParseNode* node,
bool* result) {
if (!CheckRecursionLimit(cx)) {
return false;
}
restart:
switch (node->getKind()) {
case ParseNodeKind::VarStmt:
*result = true;
return true;
case ParseNodeKind::LetDecl:
case ParseNodeKind::ConstDecl:
MOZ_ASSERT(node->is<ListNode>());
*result = false;
return true;
case ParseNodeKind::ClassDecl:
MOZ_ASSERT(node->is<ClassNode>());
*result = false;
return true;
case ParseNodeKind::Function:
*result = false;
return true;
case ParseNodeKind::Module:
*result = false;
return true;
case ParseNodeKind::EmptyStmt:
MOZ_ASSERT(node->is<NullaryNode>());
*result = false;
return true;
case ParseNodeKind::DebuggerStmt:
MOZ_ASSERT(node->is<DebuggerStatement>());
*result = false;
return true;
case ParseNodeKind::ExpressionStmt:
case ParseNodeKind::ThrowStmt:
case ParseNodeKind::ReturnStmt:
MOZ_ASSERT(node->is<UnaryNode>());
*result = false;
return true;
case ParseNodeKind::InitialYield:
case ParseNodeKind::YieldStarExpr:
case ParseNodeKind::YieldExpr:
MOZ_ASSERT(node->is<UnaryNode>());
*result = false;
return true;
case ParseNodeKind::BreakStmt:
case ParseNodeKind::ContinueStmt:
case ParseNodeKind::ImportDecl:
case ParseNodeKind::ImportSpecList:
case ParseNodeKind::ImportSpec:
case ParseNodeKind::ExportFromStmt:
case ParseNodeKind::ExportDefaultStmt:
case ParseNodeKind::ExportSpecList:
case ParseNodeKind::ExportSpec:
case ParseNodeKind::ExportStmt:
case ParseNodeKind::ExportBatchSpecStmt:
case ParseNodeKind::CallImportExpr:
*result = false;
return true;
case ParseNodeKind::DoWhileStmt:
return ContainsHoistedDeclaration(cx, node->as<BinaryNode>().left(),
result);
case ParseNodeKind::WhileStmt:
case ParseNodeKind::WithStmt:
return ContainsHoistedDeclaration(cx, node->as<BinaryNode>().right(),
result);
case ParseNodeKind::LabelStmt:
return ContainsHoistedDeclaration(
cx, node->as<LabeledStatement>().statement(), result);
case ParseNodeKind::IfStmt: {
TernaryNode* ifNode = &node->as<TernaryNode>();
ParseNode* consequent = ifNode->kid2();
if (!ContainsHoistedDeclaration(cx, consequent, result)) {
return false;
}
if (*result) {
return true;
}
if ((node = ifNode->kid3())) {
goto restart;
}
*result = false;
return true;
}
case ParseNodeKind::TryStmt: {
TernaryNode* tryNode = &node->as<TernaryNode>();
MOZ_ASSERT(tryNode->kid2() || tryNode->kid3(),
"must have either catch or finally");
ParseNode* tryBlock = tryNode->kid1();
if (!ContainsHoistedDeclaration(cx, tryBlock, result)) {
return false;
}
if (*result) {
return true;
}
if (ParseNode* catchScope = tryNode->kid2()) {
BinaryNode* catchNode =
&catchScope->as<LexicalScopeNode>().scopeBody()->as<BinaryNode>();
MOZ_ASSERT(catchNode->isKind(ParseNodeKind::Catch));
ParseNode* catchStatements = catchNode->right();
if (!ContainsHoistedDeclaration(cx, catchStatements, result)) {
return false;
}
if (*result) {
return true;
}
}
if (ParseNode* finallyBlock = tryNode->kid3()) {
return ContainsHoistedDeclaration(cx, finallyBlock, result);
}
*result = false;
return true;
}
case ParseNodeKind::SwitchStmt: {
SwitchStatement* switchNode = &node->as<SwitchStatement>();
return ContainsHoistedDeclaration(cx, &switchNode->lexicalForCaseList(),
result);
}
case ParseNodeKind::Case: {
CaseClause* caseClause = &node->as<CaseClause>();
return ContainsHoistedDeclaration(cx, caseClause->statementList(),
result);
}
case ParseNodeKind::ForStmt: {
ForNode* forNode = &node->as<ForNode>();
TernaryNode* loopHead = forNode->head();
MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForHead) ||
loopHead->isKind(ParseNodeKind::ForIn) ||
loopHead->isKind(ParseNodeKind::ForOf));
if (loopHead->isKind(ParseNodeKind::ForHead)) {
ParseNode* init = loopHead->kid1();
if (init && init->isKind(ParseNodeKind::VarStmt)) {
*result = true;
return true;
}
} else {
MOZ_ASSERT(loopHead->isKind(ParseNodeKind::ForIn) ||
loopHead->isKind(ParseNodeKind::ForOf));
ParseNode* decl = loopHead->kid1();
if (decl && decl->isKind(ParseNodeKind::VarStmt)) {
*result = true;
return true;
}
}
ParseNode* loopBody = forNode->body();
return ContainsHoistedDeclaration(cx, loopBody, result);
}
case ParseNodeKind::LexicalScope: {
LexicalScopeNode* scope = &node->as<LexicalScopeNode>();
ParseNode* expr = scope->scopeBody();
if (expr->isKind(ParseNodeKind::ForStmt) || expr->is<FunctionNode>()) {
return ContainsHoistedDeclaration(cx, expr, result);
}
MOZ_ASSERT(expr->isKind(ParseNodeKind::StatementList));
return ListContainsHoistedDeclaration(
cx, &scope->scopeBody()->as<ListNode>(), result);
}
case ParseNodeKind::StatementList:
return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), result);
case ParseNodeKind::ObjectPropertyName:
case ParseNodeKind::ComputedName:
case ParseNodeKind::Spread:
case ParseNodeKind::MutateProto:
case ParseNodeKind::Colon:
case ParseNodeKind::Shorthand:
case ParseNodeKind::ConditionalExpr:
case ParseNodeKind::TypeOfNameExpr:
case ParseNodeKind::TypeOfExpr:
case ParseNodeKind::AwaitExpr:
case ParseNodeKind::VoidExpr:
case ParseNodeKind::NotExpr:
case ParseNodeKind::BitNotExpr:
case ParseNodeKind::DeleteNameExpr:
case ParseNodeKind::DeletePropExpr:
case ParseNodeKind::DeleteElemExpr:
case ParseNodeKind::DeleteExpr:
case ParseNodeKind::PosExpr:
case ParseNodeKind::NegExpr:
case ParseNodeKind::PreIncrementExpr:
case ParseNodeKind::PostIncrementExpr:
case ParseNodeKind::PreDecrementExpr:
case ParseNodeKind::PostDecrementExpr:
case ParseNodeKind::OrExpr:
case ParseNodeKind::AndExpr:
case ParseNodeKind::BitOrExpr:
case ParseNodeKind::BitXorExpr:
case ParseNodeKind::BitAndExpr:
case ParseNodeKind::StrictEqExpr:
case ParseNodeKind::EqExpr:
case ParseNodeKind::StrictNeExpr:
case ParseNodeKind::NeExpr:
case ParseNodeKind::LtExpr:
case ParseNodeKind::LeExpr:
case ParseNodeKind::GtExpr:
case ParseNodeKind::GeExpr:
case ParseNodeKind::InstanceOfExpr:
case ParseNodeKind::InExpr:
case ParseNodeKind::LshExpr:
case ParseNodeKind::RshExpr:
case ParseNodeKind::UrshExpr:
case ParseNodeKind::AddExpr:
case ParseNodeKind::SubExpr:
case ParseNodeKind::MulExpr:
case ParseNodeKind::DivExpr:
case ParseNodeKind::ModExpr:
case ParseNodeKind::PowExpr:
case ParseNodeKind::AssignExpr:
case ParseNodeKind::AddAssignExpr:
case ParseNodeKind::SubAssignExpr:
case ParseNodeKind::BitOrAssignExpr:
case ParseNodeKind::BitXorAssignExpr:
case ParseNodeKind::BitAndAssignExpr:
case ParseNodeKind::LshAssignExpr:
case ParseNodeKind::RshAssignExpr:
case ParseNodeKind::UrshAssignExpr:
case ParseNodeKind::MulAssignExpr:
case ParseNodeKind::DivAssignExpr:
case ParseNodeKind::ModAssignExpr:
case ParseNodeKind::PowAssignExpr:
case ParseNodeKind::CommaExpr:
case ParseNodeKind::ArrayExpr:
case ParseNodeKind::ObjectExpr:
case ParseNodeKind::PropertyNameExpr:
case ParseNodeKind::DotExpr:
case ParseNodeKind::ElemExpr:
case ParseNodeKind::Arguments:
case ParseNodeKind::CallExpr:
case ParseNodeKind::Name:
case ParseNodeKind::PrivateName:
case ParseNodeKind::TemplateStringExpr:
case ParseNodeKind::TemplateStringListExpr:
case ParseNodeKind::TaggedTemplateExpr:
case ParseNodeKind::CallSiteObj:
case ParseNodeKind::StringExpr:
case ParseNodeKind::RegExpExpr:
case ParseNodeKind::TrueExpr:
case ParseNodeKind::FalseExpr:
case ParseNodeKind::NullExpr:
case ParseNodeKind::RawUndefinedExpr:
case ParseNodeKind::ThisExpr:
case ParseNodeKind::Elision:
case ParseNodeKind::NumberExpr:
case ParseNodeKind::BigIntExpr:
case ParseNodeKind::NewExpr:
case ParseNodeKind::Generator:
case ParseNodeKind::ParamsBody:
case ParseNodeKind::Catch:
case ParseNodeKind::ForIn:
case ParseNodeKind::ForOf:
case ParseNodeKind::ForHead:
case ParseNodeKind::ClassMethod:
case ParseNodeKind::ClassField:
case ParseNodeKind::ClassMemberList:
case ParseNodeKind::ClassNames:
case ParseNodeKind::NewTargetExpr:
case ParseNodeKind::ImportMetaExpr:
case ParseNodeKind::PosHolder:
case ParseNodeKind::SuperCallExpr:
case ParseNodeKind::SuperBase:
case ParseNodeKind::SetThis:
MOZ_CRASH(
"ContainsHoistedDeclaration should have indicated false on "
"some parent node without recurring to test this node");
case ParseNodeKind::PipelineExpr:
MOZ_ASSERT(node->is<ListNode>());
*result = false;
return true;
case ParseNodeKind::Limit: MOZ_CRASH("unexpected ParseNodeKind::Limit in node");
}
MOZ_CRASH("invalid node kind");
}
static bool FoldType(JSContext* cx, FullParseHandler* handler, ParseNode** pnp,
ParseNodeKind kind) {
const ParseNode* pn = *pnp;
if (!pn->isKind(kind)) {
switch (kind) {
case ParseNodeKind::NumberExpr:
if (pn->isKind(ParseNodeKind::StringExpr)) {
double d;
if (!StringToNumber(cx, pn->as<NameNode>().atom(), &d)) {
return false;
}
if (!TryReplaceNode(pnp,
handler->newNumber(d, NoDecimal, pn->pn_pos))) {
return false;
}
}
break;
case ParseNodeKind::StringExpr:
if (pn->isKind(ParseNodeKind::NumberExpr)) {
JSAtom* atom = NumberToAtom(cx, pn->as<NumericLiteral>().value());
if (!atom) {
return false;
}
if (!TryReplaceNode(pnp,
handler->newStringLiteral(atom, pn->pn_pos))) {
return false;
}
}
break;
default:
MOZ_CRASH("Invalid type in constant folding FoldType");
}
}
return true;
}
static bool IsEffectless(ParseNode* node) {
return node->isKind(ParseNodeKind::TrueExpr) ||
node->isKind(ParseNodeKind::FalseExpr) ||
node->isKind(ParseNodeKind::StringExpr) ||
node->isKind(ParseNodeKind::TemplateStringExpr) ||
node->isKind(ParseNodeKind::NumberExpr) ||
node->isKind(ParseNodeKind::BigIntExpr) ||
node->isKind(ParseNodeKind::NullExpr) ||
node->isKind(ParseNodeKind::RawUndefinedExpr) ||
node->isKind(ParseNodeKind::Function);
}
enum Truthiness { Truthy, Falsy, Unknown };
static Truthiness Boolish(ParseNode* pn) {
switch (pn->getKind()) {
case ParseNodeKind::NumberExpr:
return (pn->as<NumericLiteral>().value() != 0 &&
!IsNaN(pn->as<NumericLiteral>().value()))
? Truthy
: Falsy;
case ParseNodeKind::BigIntExpr:
return (pn->as<BigIntLiteral>().box()->value()->isZero()) ? Falsy
: Truthy;
case ParseNodeKind::StringExpr:
case ParseNodeKind::TemplateStringExpr:
return (pn->as<NameNode>().atom()->length() > 0) ? Truthy : Falsy;
case ParseNodeKind::TrueExpr:
case ParseNodeKind::Function:
return Truthy;
case ParseNodeKind::FalseExpr:
case ParseNodeKind::NullExpr:
case ParseNodeKind::RawUndefinedExpr:
return Falsy;
case ParseNodeKind::VoidExpr: {
do {
pn = pn->as<UnaryNode>().kid();
} while (pn->isKind(ParseNodeKind::VoidExpr));
return IsEffectless(pn) ? Falsy : Unknown;
}
default:
return Unknown;
}
}
static bool SimplifyCondition(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ParseNode* node = *nodePtr;
Truthiness t = Boolish(node);
if (t != Unknown) {
if (!TryReplaceNode(
nodePtr, handler->newBooleanLiteral(t == Truthy, node->pn_pos))) {
return false;
}
}
return true;
}
static bool FoldTypeOfExpr(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::TypeOfExpr));
ParseNode* expr = node->kid();
RootedPropertyName result(cx);
if (expr->isKind(ParseNodeKind::StringExpr) ||
expr->isKind(ParseNodeKind::TemplateStringExpr)) {
result = cx->names().string;
} else if (expr->isKind(ParseNodeKind::NumberExpr)) {
result = cx->names().number;
} else if (expr->isKind(ParseNodeKind::BigIntExpr)) {
result = cx->names().bigint;
} else if (expr->isKind(ParseNodeKind::NullExpr)) {
result = cx->names().object;
} else if (expr->isKind(ParseNodeKind::TrueExpr) ||
expr->isKind(ParseNodeKind::FalseExpr)) {
result = cx->names().boolean;
} else if (expr->is<FunctionNode>()) {
result = cx->names().function;
}
if (result) {
if (!TryReplaceNode(nodePtr,
handler->newStringLiteral(result, node->pn_pos))) {
return false;
}
}
return true;
}
static bool FoldDeleteExpr(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteExpr));
ParseNode* expr = node->kid();
if (IsEffectless(expr)) {
if (!TryReplaceNode(nodePtr,
handler->newBooleanLiteral(true, node->pn_pos))) {
return false;
}
}
return true;
}
static bool FoldDeleteElement(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::DeleteElemExpr));
ParseNode* expr = node->kid();
MOZ_ASSERT(expr->isKind(ParseNodeKind::ElemExpr) ||
expr->isKind(ParseNodeKind::DotExpr));
if (expr->isKind(ParseNodeKind::DotExpr)) {
if (!TryReplaceNode(nodePtr,
handler->newDelete(node->pn_pos.begin, expr))) {
return false;
}
MOZ_ASSERT((*nodePtr)->getKind() == ParseNodeKind::DeletePropExpr);
}
return true;
}
static bool FoldNot(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::NotExpr));
if (!SimplifyCondition(cx, handler, node->unsafeKidReference())) {
return false;
}
ParseNode* expr = node->kid();
if (expr->isKind(ParseNodeKind::TrueExpr) ||
expr->isKind(ParseNodeKind::FalseExpr)) {
bool newval = !expr->isKind(ParseNodeKind::TrueExpr);
if (!TryReplaceNode(nodePtr,
handler->newBooleanLiteral(newval, node->pn_pos))) {
return false;
}
}
return true;
}
static bool FoldUnaryArithmetic(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::BitNotExpr) ||
node->isKind(ParseNodeKind::PosExpr) ||
node->isKind(ParseNodeKind::NegExpr),
"need a different method for this node kind");
ParseNode* expr = node->kid();
if (expr->isKind(ParseNodeKind::NumberExpr) ||
expr->isKind(ParseNodeKind::TrueExpr) ||
expr->isKind(ParseNodeKind::FalseExpr)) {
double d = expr->isKind(ParseNodeKind::NumberExpr)
? expr->as<NumericLiteral>().value()
: double(expr->isKind(ParseNodeKind::TrueExpr));
if (node->isKind(ParseNodeKind::BitNotExpr)) {
d = ~ToInt32(d);
} else if (node->isKind(ParseNodeKind::NegExpr)) {
d = -d;
} else {
MOZ_ASSERT(node->isKind(ParseNodeKind::PosExpr)); }
if (!TryReplaceNode(nodePtr,
handler->newNumber(d, NoDecimal, node->pn_pos))) {
return false;
}
}
return true;
}
static bool FoldAndOr(JSContext* cx, ParseNode** nodePtr) {
ListNode* node = &(*nodePtr)->as<ListNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::AndExpr) ||
node->isKind(ParseNodeKind::OrExpr));
bool isOrNode = node->isKind(ParseNodeKind::OrExpr);
ParseNode** elem = node->unsafeHeadReference();
do {
Truthiness t = Boolish(*elem);
if (t == Unknown) {
elem = &(*elem)->pn_next;
continue;
}
if ((t == Truthy) == isOrNode) {
for (ParseNode* next = (*elem)->pn_next; next; next = next->pn_next) {
node->unsafeDecrementCount();
}
(*elem)->pn_next = nullptr;
elem = &(*elem)->pn_next;
break;
}
MOZ_ASSERT((t == Truthy) == !isOrNode);
if ((*elem)->pn_next) {
ParseNode* elt = *elem;
*elem = elt->pn_next;
node->unsafeDecrementCount();
} else {
elem = &(*elem)->pn_next;
break;
}
} while (*elem);
node->unsafeReplaceTail(elem);
if (node->count() == 1) {
ParseNode* first = node->head();
ReplaceNode(nodePtr, first);
}
return true;
}
static bool Fold(JSContext* cx, FullParseHandler* handler, ParseNode** pnp);
static bool FoldConditional(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ParseNode** nextNode = nodePtr;
do {
nodePtr = nextNode;
nextNode = nullptr;
TernaryNode* node = &(*nodePtr)->as<TernaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::ConditionalExpr));
ParseNode** expr = node->unsafeKid1Reference();
if (!Fold(cx, handler, expr)) {
return false;
}
if (!SimplifyCondition(cx, handler, expr)) {
return false;
}
ParseNode** ifTruthy = node->unsafeKid2Reference();
if (!Fold(cx, handler, ifTruthy)) {
return false;
}
ParseNode** ifFalsy = node->unsafeKid3Reference();
if ((*ifFalsy)->isKind(ParseNodeKind::ConditionalExpr)) {
MOZ_ASSERT((*ifFalsy)->is<TernaryNode>());
nextNode = ifFalsy;
} else {
if (!Fold(cx, handler, ifFalsy)) {
return false;
}
}
Truthiness t = Boolish(*expr);
if (t == Unknown) {
continue;
}
ParseNode* replacement = t == Truthy ? *ifTruthy : *ifFalsy;
if (nextNode) {
nextNode = (*nextNode == replacement) ? nodePtr : nullptr;
}
ReplaceNode(nodePtr, replacement);
} while (nextNode);
return true;
}
static bool FoldIf(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ParseNode** nextNode = nodePtr;
do {
nodePtr = nextNode;
nextNode = nullptr;
TernaryNode* node = &(*nodePtr)->as<TernaryNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::IfStmt));
ParseNode** expr = node->unsafeKid1Reference();
if (!Fold(cx, handler, expr)) {
return false;
}
if (!SimplifyCondition(cx, handler, expr)) {
return false;
}
ParseNode** consequent = node->unsafeKid2Reference();
if (!Fold(cx, handler, consequent)) {
return false;
}
ParseNode** alternative = node->unsafeKid3Reference();
if (*alternative) {
if ((*alternative)->isKind(ParseNodeKind::IfStmt)) {
MOZ_ASSERT((*alternative)->is<TernaryNode>());
nextNode = alternative;
} else {
if (!Fold(cx, handler, alternative)) {
return false;
}
}
}
Truthiness t = Boolish(*expr);
if (t == Unknown) {
continue;
}
ParseNode* replacement;
ParseNode* discarded;
if (t == Truthy) {
replacement = *consequent;
discarded = *alternative;
} else {
replacement = *alternative;
discarded = *consequent;
}
bool performReplacement = true;
if (discarded) {
bool containsHoistedDecls;
if (!ContainsHoistedDeclaration(cx, discarded, &containsHoistedDecls)) {
return false;
}
performReplacement = !containsHoistedDecls;
}
if (!performReplacement) {
continue;
}
if (!replacement) {
if (!TryReplaceNode(nodePtr, handler->newStatementList(node->pn_pos))) {
return false;
}
} else {
if (nextNode) {
nextNode = (*nextNode == replacement) ? nodePtr : nullptr;
}
ReplaceNode(nodePtr, replacement);
}
} while (nextNode);
return true;
}
static double ComputeBinary(ParseNodeKind kind, double left, double right) {
if (kind == ParseNodeKind::AddExpr) {
return left + right;
}
if (kind == ParseNodeKind::SubExpr) {
return left - right;
}
if (kind == ParseNodeKind::MulExpr) {
return left * right;
}
if (kind == ParseNodeKind::ModExpr) {
return NumberMod(left, right);
}
if (kind == ParseNodeKind::UrshExpr) {
return ToUint32(left) >> (ToUint32(right) & 31);
}
if (kind == ParseNodeKind::DivExpr) {
return NumberDiv(left, right);
}
MOZ_ASSERT(kind == ParseNodeKind::LshExpr || kind == ParseNodeKind::RshExpr);
int32_t i = ToInt32(left);
uint32_t j = ToUint32(right) & 31;
return int32_t((kind == ParseNodeKind::LshExpr) ? uint32_t(i) << j : i >> j);
}
static bool FoldBinaryArithmetic(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ListNode* node = &(*nodePtr)->as<ListNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::SubExpr) ||
node->isKind(ParseNodeKind::MulExpr) ||
node->isKind(ParseNodeKind::LshExpr) ||
node->isKind(ParseNodeKind::RshExpr) ||
node->isKind(ParseNodeKind::UrshExpr) ||
node->isKind(ParseNodeKind::DivExpr) ||
node->isKind(ParseNodeKind::ModExpr));
MOZ_ASSERT(node->count() >= 2);
ParseNode** listp = node->unsafeHeadReference();
for (; *listp; listp = &(*listp)->pn_next) {
if (!FoldType(cx, handler, listp, ParseNodeKind::NumberExpr)) {
return false;
}
}
node->unsafeReplaceTail(listp);
ParseNode** elem = node->unsafeHeadReference();
ParseNode** next = &(*elem)->pn_next;
if ((*elem)->isKind(ParseNodeKind::NumberExpr)) {
ParseNodeKind kind = node->getKind();
while (true) {
if (!*next || !(*next)->isKind(ParseNodeKind::NumberExpr)) {
break;
}
double d = ComputeBinary(kind, (*elem)->as<NumericLiteral>().value(),
(*next)->as<NumericLiteral>().value());
TokenPos pos((*elem)->pn_pos.begin, (*next)->pn_pos.end);
if (!TryReplaceNode(elem, handler->newNumber(d, NoDecimal, pos))) {
return false;
}
(*elem)->pn_next = (*next)->pn_next;
next = &(*elem)->pn_next;
node->unsafeDecrementCount();
}
if (node->count() == 1) {
MOZ_ASSERT(node->head() == *elem);
MOZ_ASSERT((*elem)->isKind(ParseNodeKind::NumberExpr));
if (!TryReplaceNode(nodePtr, *elem)) {
return false;
}
}
}
return true;
}
static bool FoldExponentiation(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ListNode* node = &(*nodePtr)->as<ListNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::PowExpr));
MOZ_ASSERT(node->count() >= 2);
ParseNode** listp = node->unsafeHeadReference();
for (; *listp; listp = &(*listp)->pn_next) {
if (!FoldType(cx, handler, listp, ParseNodeKind::NumberExpr)) {
return false;
}
}
node->unsafeReplaceTail(listp);
if (node->count() > 2) {
return true;
}
ParseNode* base = node->head();
ParseNode* exponent = base->pn_next;
if (!base->isKind(ParseNodeKind::NumberExpr) ||
!exponent->isKind(ParseNodeKind::NumberExpr)) {
return true;
}
double d1 = base->as<NumericLiteral>().value();
double d2 = exponent->as<NumericLiteral>().value();
return TryReplaceNode(
nodePtr, handler->newNumber(ecmaPow(d1, d2), NoDecimal, node->pn_pos));
}
static bool FoldElement(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
PropertyByValue* elem = &(*nodePtr)->as<PropertyByValue>();
ParseNode* expr = &elem->expression();
ParseNode* key = &elem->key();
PropertyName* name = nullptr;
if (key->isKind(ParseNodeKind::StringExpr)) {
JSAtom* atom = key->as<NameNode>().atom();
uint32_t index;
if (atom->isIndex(&index)) {
if (!TryReplaceNode(elem->unsafeRightReference(),
handler->newNumber(index, NoDecimal, key->pn_pos))) {
return false;
}
key = &elem->key();
} else {
name = atom->asPropertyName();
}
} else if (key->isKind(ParseNodeKind::NumberExpr)) {
double number = key->as<NumericLiteral>().value();
if (number != ToUint32(number)) {
JSAtom* atom = NumberToAtom(cx, number);
if (!atom) {
return false;
}
name = atom->asPropertyName();
}
}
if (!name) {
return true;
}
NameNode* propertyNameExpr = handler->newPropertyName(name, key->pn_pos);
if (!propertyNameExpr) {
return false;
}
if (!TryReplaceNode(nodePtr,
handler->newPropertyAccess(expr, propertyNameExpr))) {
return false;
}
return true;
}
static bool FoldAdd(JSContext* cx, FullParseHandler* handler,
ParseNode** nodePtr) {
ListNode* node = &(*nodePtr)->as<ListNode>();
MOZ_ASSERT(node->isKind(ParseNodeKind::AddExpr));
MOZ_ASSERT(node->count() >= 2);
ParseNode** current = node->unsafeHeadReference();
ParseNode** next = &(*current)->pn_next;
if ((*current)->isKind(ParseNodeKind::NumberExpr)) {
do {
if (!(*next)->isKind(ParseNodeKind::NumberExpr)) {
break;
}
double left = (*current)->as<NumericLiteral>().value();
double right = (*next)->as<NumericLiteral>().value();
TokenPos pos((*current)->pn_pos.begin, (*next)->pn_pos.end);
if (!TryReplaceNode(current,
handler->newNumber(left + right, NoDecimal, pos))) {
return false;
}
(*current)->pn_next = (*next)->pn_next;
next = &(*current)->pn_next;
node->unsafeDecrementCount();
} while (*next);
}
do {
if (!*next) {
break;
}
if ((*current)->isKind(ParseNodeKind::NumberExpr) &&
(*next)->isKind(ParseNodeKind::StringExpr)) {
if (!FoldType(cx, handler, current, ParseNodeKind::StringExpr)) {
return false;
}
next = &(*current)->pn_next;
}
do {
if ((*current)->isKind(ParseNodeKind::StringExpr)) {
break;
}
current = next;
next = &(*current)->pn_next;
} while (*next);
if (!*next) {
break;
}
RootedString combination(cx);
RootedString tmp(cx);
do {
MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
combination = (*current)->as<NameNode>().atom();
do {
if (!FoldType(cx, handler, next, ParseNodeKind::StringExpr)) {
return false;
}
if (!(*next)->isKind(ParseNodeKind::StringExpr)) {
break;
}
tmp = (*next)->as<NameNode>().atom();
combination = ConcatStrings<CanGC>(cx, combination, tmp);
if (!combination) {
return false;
}
(*current)->pn_next = (*next)->pn_next;
next = &(*current)->pn_next;
node->unsafeDecrementCount();
} while (*next);
MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
combination = AtomizeString(cx, combination);
if (!combination) {
return false;
}
(*current)->as<NameNode>().setAtom(&combination->asAtom());
if (!*next) {
break;
}
current = next;
next = &(*current)->pn_next;
if (!*next) {
break;
}
do {
current = next;
if (!FoldType(cx, handler, current, ParseNodeKind::StringExpr)) {
return false;
}
next = &(*current)->pn_next;
} while (!(*current)->isKind(ParseNodeKind::StringExpr) && *next);
} while (*next);
} while (false);
MOZ_ASSERT(!*next, "must have considered all nodes here");
MOZ_ASSERT(!(*current)->pn_next, "current node must be the last node");
node->unsafeReplaceTail(&(*current)->pn_next);
if (node->count() == 1) {
ReplaceNode(nodePtr, *current);
}
return true;
}
class FoldVisitor : public RewritingParseNodeVisitor<FoldVisitor> {
using Base = RewritingParseNodeVisitor;
FullParseHandler* handler;
public:
explicit FoldVisitor(JSContext* cx, FullParseHandler* handler)
: RewritingParseNodeVisitor(cx), handler(handler) {}
bool visitElemExpr(ParseNode*& pn) {
return Base::visitElemExpr(pn) && FoldElement(cx_, handler, &pn);
}
bool visitTypeOfExpr(ParseNode*& pn) {
return Base::visitTypeOfExpr(pn) && FoldTypeOfExpr(cx_, handler, &pn);
}
bool visitDeleteExpr(ParseNode*& pn) {
return Base::visitDeleteExpr(pn) && FoldDeleteExpr(cx_, handler, &pn);
}
bool visitDeleteElemExpr(ParseNode*& pn) {
return Base::visitDeleteElemExpr(pn) &&
FoldDeleteElement(cx_, handler, &pn);
}
bool visitNotExpr(ParseNode*& pn) {
return Base::visitNotExpr(pn) && FoldNot(cx_, handler, &pn);
}
bool visitBitNotExpr(ParseNode*& pn) {
return Base::visitBitNotExpr(pn) && FoldUnaryArithmetic(cx_, handler, &pn);
}
bool visitPosExpr(ParseNode*& pn) {
return Base::visitPosExpr(pn) && FoldUnaryArithmetic(cx_, handler, &pn);
}
bool visitNegExpr(ParseNode*& pn) {
return Base::visitNegExpr(pn) && FoldUnaryArithmetic(cx_, handler, &pn);
}
bool visitPowExpr(ParseNode*& pn) {
return Base::visitPowExpr(pn) && FoldExponentiation(cx_, handler, &pn);
}
bool visitMulExpr(ParseNode*& pn) {
return Base::visitMulExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitDivExpr(ParseNode*& pn) {
return Base::visitDivExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitModExpr(ParseNode*& pn) {
return Base::visitModExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitAddExpr(ParseNode*& pn) {
return Base::visitAddExpr(pn) && FoldAdd(cx_, handler, &pn);
}
bool visitSubExpr(ParseNode*& pn) {
return Base::visitSubExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitLshExpr(ParseNode*& pn) {
return Base::visitLshExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitRshExpr(ParseNode*& pn) {
return Base::visitRshExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitUrshExpr(ParseNode*& pn) {
return Base::visitUrshExpr(pn) && FoldBinaryArithmetic(cx_, handler, &pn);
}
bool visitAndExpr(ParseNode*& pn) {
return Base::visitAndExpr(pn) && FoldAndOr(cx_, &pn);
}
bool visitOrExpr(ParseNode*& pn) {
return Base::visitOrExpr(pn) && FoldAndOr(cx_, &pn);
}
bool visitConditionalExpr(ParseNode*& pn) {
return FoldConditional(cx_, handler, &pn);
}
private:
bool internalVisitCall(BinaryNode* node) {
MOZ_ASSERT(node->isKind(ParseNodeKind::CallExpr) ||
node->isKind(ParseNodeKind::SuperCallExpr) ||
node->isKind(ParseNodeKind::NewExpr) ||
node->isKind(ParseNodeKind::TaggedTemplateExpr));
ParseNode* callee = node->left();
if (node->isKind(ParseNodeKind::NewExpr) || !callee->isInParens() ||
callee->is<FunctionNode>()) {
if (!visit(*node->unsafeLeftReference())) {
return false;
}
}
if (!visit(*node->unsafeRightReference())) {
return false;
}
return true;
}
public:
bool visitCallExpr(ParseNode*& pn) {
return internalVisitCall(&pn->as<BinaryNode>());
}
bool visitNewExpr(ParseNode*& pn) {
return internalVisitCall(&pn->as<BinaryNode>());
}
bool visitSuperCallExpr(ParseNode*& pn) {
return internalVisitCall(&pn->as<BinaryNode>());
}
bool visitTaggedTemplateExpr(ParseNode*& pn) {
return internalVisitCall(&pn->as<BinaryNode>());
}
bool visitIfStmt(ParseNode*& pn) {
return FoldIf(cx_, handler, &pn);
}
bool visitForStmt(ParseNode*& pn) {
if (!Base::visitForStmt(pn)) {
return false;
}
ForNode& stmt = pn->as<ForNode>();
if (stmt.left()->isKind(ParseNodeKind::ForHead)) {
TernaryNode& head = stmt.left()->as<TernaryNode>();
ParseNode** test = head.unsafeKid2Reference();
if (*test) {
if (!SimplifyCondition(cx_, handler, test)) {
return false;
}
if ((*test)->isKind(ParseNodeKind::TrueExpr)) {
*test = nullptr;
}
}
}
return true;
}
bool visitWhileStmt(ParseNode*& pn) {
BinaryNode& node = pn->as<BinaryNode>();
return Base::visitWhileStmt(pn) &&
SimplifyCondition(cx_, handler, node.unsafeLeftReference());
}
bool visitDoWhileStmt(ParseNode*& pn) {
BinaryNode& node = pn->as<BinaryNode>();
return Base::visitDoWhileStmt(pn) &&
SimplifyCondition(cx_, handler, node.unsafeRightReference());
}
bool visitFunction(ParseNode*& pn) {
FunctionNode& node = pn->as<FunctionNode>();
if (node.funbox()->useAsmOrInsideUseAsm()) {
return true;
}
return Base::visitFunction(pn);
}
bool visitArrayExpr(ParseNode*& pn) {
if (!Base::visitArrayExpr(pn)) {
return false;
}
ListNode* list = &pn->as<ListNode>();
if (list->hasNonConstInitializer() && list->count() > 0) {
for (ParseNode* node : list->contents()) {
if (!node->isConstant()) {
return true;
}
}
list->unsetHasNonConstInitializer();
}
return true;
}
bool visitObjectExpr(ParseNode*& pn) {
if (!Base::visitObjectExpr(pn)) {
return false;
}
ListNode* list = &pn->as<ListNode>();
if (list->hasNonConstInitializer()) {
for (ParseNode* node : list->contents()) {
if (node->getKind() != ParseNodeKind::Colon) {
return true;
}
BinaryNode* binary = &node->as<BinaryNode>();
if (binary->left()->isKind(ParseNodeKind::ComputedName)) {
return true;
}
if (!binary->right()->isConstant()) {
return true;
}
}
list->unsetHasNonConstInitializer();
}
return true;
}
};
bool Fold(JSContext* cx, FullParseHandler* handler, ParseNode** pnp) {
FoldVisitor visitor(cx, handler);
return visitor.visit(*pnp);
}
bool frontend::FoldConstants(JSContext* cx, ParseNode** pnp,
FullParseHandler* handler) {
AutoTraceLog traceLog(TraceLoggerForCurrentThread(cx),
TraceLogger_BytecodeFoldConstants);
return Fold(cx, handler, pnp);
}