BytecodeGenerator ".call" and ".apply" is exponential in nesting depth
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Apr 2017 06:43:54 +0000 (06:43 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 18 Apr 2017 06:43:54 +0000 (06:43 +0000)
https://bugs.webkit.org/show_bug.cgi?id=139847
<rdar://problem/19321122>

Reviewed by Oliver Hunt.

JSTests:

* stress/call-apply-exponential-bytecode-size.js: Added.
(assert):
(const.inc):
(const.inc2):
(bar):
(randomApplyOrCall):
(baz):
(jaz):
(haz):
(foo):

Source/JavaScriptCore:

The BytecodeGenerator's .apply(...) and .call(...) code would
emit bytecode for the evaluation of its arguments twice. This
is exponential, specifically, 2^n, where n is the nesting depth of
.call(...) or .apply(...) inside other .call(...) or .apply(...).

The reason we emit code for the arguments twice is that we try
to emit efficient code for when .call or .apply is Function.prototype.call
or Function.prototype.apply. Because of this, we compare .call/.apply to
Function.prototype.call/.apply, and if they're the same, we emit a specialized
function call in bytecode. Otherwise, we emit the generalized version.

This patch makes it so that each .call(...) and .apply(...) records
its max inner nesting depth. Then, we only perform the optimization
for the bottom k (where k = 6) layers of the nesting tree. The reason we
apply the optimization to the bottom k layers instead of top k layers
is that we'll produce less code this way.

* bytecompiler/NodesCodegen.cpp:
(JSC::CallFunctionCallDotNode::emitBytecode):
(JSC::ApplyFunctionCallDotNode::emitBytecode):
* parser/ASTBuilder.h:
(JSC::ASTBuilder::makeFunctionCallNode):
* parser/NodeConstructors.h:
(JSC::CallFunctionCallDotNode::CallFunctionCallDotNode):
(JSC::ApplyFunctionCallDotNode::ApplyFunctionCallDotNode):
* parser/Nodes.h:
* parser/Parser.cpp:
(JSC::recordCallOrApplyDepth):
(JSC::Parser<LexerType>::parseMemberExpression):
* parser/Parser.h:
(JSC::Parser::CallOrApplyDepth::CallOrApplyDepth):
(JSC::Parser::CallOrApplyDepth::maxChildDepth):
(JSC::Parser::CallOrApplyDepth::~CallOrApplyDepth):
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::makeFunctionCallNode):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215453 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/call-apply-exponential-bytecode-size.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/JavaScriptCore/parser/ASTBuilder.h
Source/JavaScriptCore/parser/NodeConstructors.h
Source/JavaScriptCore/parser/Nodes.h
Source/JavaScriptCore/parser/Parser.cpp
Source/JavaScriptCore/parser/Parser.h
Source/JavaScriptCore/parser/SyntaxChecker.h

index f9c4581..867170f 100644 (file)
@@ -1,3 +1,22 @@
+2017-04-17  Saam Barati  <sbarati@apple.com>
+
+        BytecodeGenerator ".call" and ".apply" is exponential in nesting depth
+        https://bugs.webkit.org/show_bug.cgi?id=139847
+        <rdar://problem/19321122>
+
+        Reviewed by Oliver Hunt.
+
+        * stress/call-apply-exponential-bytecode-size.js: Added.
+        (assert):
+        (const.inc):
+        (const.inc2):
+        (bar):
+        (randomApplyOrCall):
+        (baz):
+        (jaz):
+        (haz):
+        (foo):
+
 2017-04-17  Mark Lam  <mark.lam@apple.com>
 
         JSArray::appendMemcpy() needs to handle copying from Undecided indexing type too.
diff --git a/JSTests/stress/call-apply-exponential-bytecode-size.js b/JSTests/stress/call-apply-exponential-bytecode-size.js
new file mode 100644 (file)
index 0000000..44163ad
--- /dev/null
@@ -0,0 +1,104 @@
+"use strict";
+
+function assert(b) {
+    if (!b)
+        throw new Error();
+}
+
+const inc = (x, y) => { return x + 1; }
+const inc2 = (x, y) => { return y ? y + 1 : x + 1; }
+function bar() {
+    return inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+      inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+        inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+          inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+            inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+              inc.call(null, 1)))))))))))))))))))));
+}
+assert(bar() === 22);
+
+function randomApplyOrCall(bias, size, dontSpreadBias = 0) {
+    let cur = `1`;
+    for (let i = 0; i < size; ++i) {
+        if (Math.random() >= bias) {
+            if (Math.random() >= dontSpreadBias)
+                cur = `inc.call(null, ${cur})`;
+            else
+                cur = `inc.call(...[null, ${cur}])`;
+        } else {
+            if (Math.random() >= dontSpreadBias)
+                cur = `inc.apply(null, [${cur}])`;
+            else
+                cur = `inc.apply(...[null, [${cur}]])`;
+        }
+    }
+
+    if (bias > 0.85) {
+        cur = `let random = ${Math.random()}; ${cur}`;
+    }
+
+    return eval(cur);
+}
+
+assert(randomApplyOrCall(0, 250) === 251);
+assert(randomApplyOrCall(1, 250) === 251);
+assert(randomApplyOrCall(0, 250, 0) === 251);
+assert(randomApplyOrCall(1, 250, 1) === 251);
+for (let i = 0; i < 1000; ++i) {
+    assert(randomApplyOrCall(Math.random(), 250) === 251);
+    assert(randomApplyOrCall(Math.random(), 252, Math.random()) === 253);
+}
+
+function baz() {
+    return inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+      inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+        inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+          inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+            inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+              inc.call(null, 1)))))))))))))))))))), 
+       inc.call(null, inc.call(null, 1)));
+}
+assert(baz() === 22);
+
+function jaz() {
+    return inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+      inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+        inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+          inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+            inc.call(null, inc.apply(null, [inc.call(null, inc.call(null,
+              inc.call(null, 1)))]))))))))))))))))),
+       inc.call(null, inc.call(null, inc.apply(null, [1]))));
+}
+assert(jaz() === 22);
+
+function haz() {
+    return inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+      inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+        inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+          inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+            inc.call(null, inc.apply(null, [inc.call(null, inc.call(null,
+              inc.call(null, 1)))]))))))))))))))))),
+        inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+          inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+            inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+              inc.call(null, inc.call(null, inc.call(null, inc.call(null,
+                inc.call(null, inc.apply(null, [inc.call(null, inc.call(null,
+                  inc.call(null, 1)))])))))))))))))))))));
+}
+assert(haz() === 22);
+
+function foo() {
+    return inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+      inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+        inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+          inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+            inc2.call(null, inc2.apply(null, [inc2.call(null, inc2.call(null,
+              inc2.call(null, 1)))]))))))))))))))))),
+        inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+          inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+            inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+              inc2.call(null, inc2.call(null, inc2.call(null, inc2.call(null,
+                inc2.call(null, inc2.apply(null, [inc2.call(null, inc2.call(null,
+                  inc2.call(null, inc2.call(null, inc.call(null, 1)))))])))))))))))))))))));
+}
+assert(foo() === 25);
index 114ec63..a594e27 100644 (file)
@@ -1,3 +1,47 @@
+2017-04-17  Saam Barati  <sbarati@apple.com>
+
+        BytecodeGenerator ".call" and ".apply" is exponential in nesting depth
+        https://bugs.webkit.org/show_bug.cgi?id=139847
+        <rdar://problem/19321122>
+
+        Reviewed by Oliver Hunt.
+
+        The BytecodeGenerator's .apply(...) and .call(...) code would
+        emit bytecode for the evaluation of its arguments twice. This
+        is exponential, specifically, 2^n, where n is the nesting depth of
+        .call(...) or .apply(...) inside other .call(...) or .apply(...).
+        
+        The reason we emit code for the arguments twice is that we try
+        to emit efficient code for when .call or .apply is Function.prototype.call
+        or Function.prototype.apply. Because of this, we compare .call/.apply to
+        Function.prototype.call/.apply, and if they're the same, we emit a specialized
+        function call in bytecode. Otherwise, we emit the generalized version.
+        
+        This patch makes it so that each .call(...) and .apply(...) records
+        its max inner nesting depth. Then, we only perform the optimization
+        for the bottom k (where k = 6) layers of the nesting tree. The reason we
+        apply the optimization to the bottom k layers instead of top k layers
+        is that we'll produce less code this way.
+
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::CallFunctionCallDotNode::emitBytecode):
+        (JSC::ApplyFunctionCallDotNode::emitBytecode):
+        * parser/ASTBuilder.h:
+        (JSC::ASTBuilder::makeFunctionCallNode):
+        * parser/NodeConstructors.h:
+        (JSC::CallFunctionCallDotNode::CallFunctionCallDotNode):
+        (JSC::ApplyFunctionCallDotNode::ApplyFunctionCallDotNode):
+        * parser/Nodes.h:
+        * parser/Parser.cpp:
+        (JSC::recordCallOrApplyDepth):
+        (JSC::Parser<LexerType>::parseMemberExpression):
+        * parser/Parser.h:
+        (JSC::Parser::CallOrApplyDepth::CallOrApplyDepth):
+        (JSC::Parser::CallOrApplyDepth::maxChildDepth):
+        (JSC::Parser::CallOrApplyDepth::~CallOrApplyDepth):
+        * parser/SyntaxChecker.h:
+        (JSC::SyntaxChecker::makeFunctionCallNode):
+
 2017-04-17  Mark Lam  <mark.lam@apple.com>
 
         JSArray::appendMemcpy() needs to handle copying from Undecided indexing type too.
index c929ec7..e12c10b 100644 (file)
@@ -1189,21 +1189,36 @@ RegisterID* FunctionCallDotNode::emitBytecode(BytecodeGenerator& generator, Regi
 
 RegisterID* CallFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
 {
-    Ref<Label> realCall = generator.newLabel();
-    Ref<Label> end = generator.newLabel();
     RefPtr<RegisterID> base = generator.emitNode(m_base);
     generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
     RefPtr<RegisterID> function;
-    bool emitCallCheck = !generator.isBuiltinFunction();
-    if (emitCallCheck) {
+    RefPtr<RegisterID> returnValue = generator.finalDestination(dst);
+
+    auto makeFunction = [&] {
         if (m_base->isSuperNode()) {
             RefPtr<RegisterID> thisValue = generator.ensureThis();
             function = generator.emitGetById(generator.tempDestination(dst), base.get(), thisValue.get(), generator.propertyNames().builtinNames().callPublicName());
         } else
             function = generator.emitGetById(generator.tempDestination(dst), base.get(), generator.propertyNames().builtinNames().callPublicName());
+    };
+
+    bool emitCallCheck = !generator.isBuiltinFunction();
+    if (m_callOrApplyChildDepth > 4 && emitCallCheck) {
+        makeFunction();
+        CallArguments callArguments(generator, m_args);
+        generator.emitMove(callArguments.thisRegister(), base.get());
+        generator.emitCallInTailPosition(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd(), DebuggableCall::Yes);
+        generator.moveToDestinationIfNeeded(dst, returnValue.get());
+        return returnValue.get();
+    }
+
+    Ref<Label> realCall = generator.newLabel();
+    Ref<Label> end = generator.newLabel();
+
+    if (emitCallCheck) {
+        makeFunction();
         generator.emitJumpIfNotFunctionCall(function.get(), realCall.get());
     }
-    RefPtr<RegisterID> returnValue = generator.finalDestination(dst);
     {
         if (m_args->m_listNode && m_args->m_listNode->m_expr && m_args->m_listNode->m_expr->isSpreadExpression()) {
             SpreadExpressionNode* spread = static_cast<SpreadExpressionNode*>(m_args->m_listNode->m_expr);
@@ -1256,19 +1271,32 @@ RegisterID* ApplyFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator,
     // function.apply(thisArg, [arg0, arg1, ...]) -> can be trivially coerced into function.call(thisArg, arg0, arg1, ...) and saves object allocation
     bool mayBeCall = areTrivialApplyArguments(m_args);
 
-    Ref<Label> realCall = generator.newLabel();
-    Ref<Label> end = generator.newLabel();
-    RefPtr<RegisterID> base = generator.emitNode(m_base);
-    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
     RefPtr<RegisterID> function;
-    RefPtr<RegisterID> returnValue = generator.finalDestination(dst, function.get());
-    bool emitCallCheck = !generator.isBuiltinFunction();
-    if (emitCallCheck) {
+    RefPtr<RegisterID> base = generator.emitNode(m_base);
+    RefPtr<RegisterID> returnValue = generator.finalDestination(dst);
+    auto makeFunction = [&] {
         if (m_base->isSuperNode()) {
             RefPtr<RegisterID> thisValue = generator.ensureThis();
             function = generator.emitGetById(generator.tempDestination(dst), base.get(), thisValue.get(), generator.propertyNames().builtinNames().applyPublicName());
         } else
             function = generator.emitGetById(generator.tempDestination(dst), base.get(), generator.propertyNames().builtinNames().applyPublicName());
+    };
+
+    bool emitCallCheck = !generator.isBuiltinFunction();
+    if (m_callOrApplyChildDepth > 4 && emitCallCheck) {
+        makeFunction();
+        CallArguments callArguments(generator, m_args);
+        generator.emitMove(callArguments.thisRegister(), base.get());
+        generator.emitCallInTailPosition(returnValue.get(), function.get(), NoExpectedFunction, callArguments, divot(), divotStart(), divotEnd(), DebuggableCall::Yes);
+        generator.moveToDestinationIfNeeded(dst, returnValue.get());
+        return returnValue.get();
+    }
+
+    Ref<Label> realCall = generator.newLabel();
+    Ref<Label> end = generator.newLabel();
+    generator.emitExpressionInfo(subexpressionDivot(), subexpressionStart(), subexpressionEnd());
+    if (emitCallCheck) {
+        makeFunction();
         generator.emitJumpIfNotFunctionApply(function.get(), realCall.get());
     }
     if (mayBeCall) {
index 0872c94..94237a5 100644 (file)
@@ -129,7 +129,7 @@ public:
     static const int  DontBuildStrings = 0;
 
     ExpressionNode* makeBinaryNode(const JSTokenLocation&, int token, std::pair<ExpressionNode*, BinaryOpInfo>, std::pair<ExpressionNode*, BinaryOpInfo>);
-    ExpressionNode* makeFunctionCallNode(const JSTokenLocation&, ExpressionNode* func, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd);
+    ExpressionNode* makeFunctionCallNode(const JSTokenLocation&, ExpressionNode* func, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth);
 
     JSC::SourceElements* createSourceElements() { return new (m_parserArena) JSC::SourceElements(); }
 
@@ -1301,7 +1301,7 @@ ExpressionNode* ASTBuilder::makeBitXOrNode(const JSTokenLocation& location, Expr
     return new (m_parserArena) BitXOrNode(location, expr1, expr2, rightHasAssignments);
 }
 
-ExpressionNode* ASTBuilder::makeFunctionCallNode(const JSTokenLocation& location, ExpressionNode* func, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd)
+ExpressionNode* ASTBuilder::makeFunctionCallNode(const JSTokenLocation& location, ExpressionNode* func, ArgumentsNode* args, const JSTextPosition& divotStart, const JSTextPosition& divot, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth)
 {
     ASSERT(divot.offset >= divot.lineStartOffset);
     if (func->isSuperNode())
@@ -1333,9 +1333,9 @@ ExpressionNode* ASTBuilder::makeFunctionCallNode(const JSTokenLocation& location
     DotAccessorNode* dot = static_cast<DotAccessorNode*>(func);
     FunctionCallDotNode* node;
     if (dot->identifier() == m_vm->propertyNames->builtinNames().callPublicName() || dot->identifier() == m_vm->propertyNames->builtinNames().callPrivateName())
-        node = new (m_parserArena) CallFunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd);
+        node = new (m_parserArena) CallFunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd, callOrApplyChildDepth);
     else if (dot->identifier() == m_vm->propertyNames->builtinNames().applyPublicName() || dot->identifier() == m_vm->propertyNames->builtinNames().applyPrivateName())
-        node = new (m_parserArena) ApplyFunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd);
+        node = new (m_parserArena) ApplyFunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd, callOrApplyChildDepth);
     else
         node = new (m_parserArena) FunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd);
     node->setSubexpressionInfo(dot->divot(), dot->divotEnd().offset);
index 7ab63c4..da8e03c 100644 (file)
@@ -403,13 +403,15 @@ namespace JSC {
     {
     }
 
-    inline CallFunctionCallDotNode::CallFunctionCallDotNode(const JSTokenLocation& location, ExpressionNode* base, const Identifier& ident, ArgumentsNode* args, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
+    inline CallFunctionCallDotNode::CallFunctionCallDotNode(const JSTokenLocation& location, ExpressionNode* base, const Identifier& ident, ArgumentsNode* args, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth)
         : FunctionCallDotNode(location, base, ident, args, divot, divotStart, divotEnd)
+        , m_callOrApplyChildDepth(callOrApplyChildDepth)
     {
     }
 
-    inline ApplyFunctionCallDotNode::ApplyFunctionCallDotNode(const JSTokenLocation& location, ExpressionNode* base, const Identifier& ident, ArgumentsNode* args, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd)
+    inline ApplyFunctionCallDotNode::ApplyFunctionCallDotNode(const JSTokenLocation& location, ExpressionNode* base, const Identifier& ident, ArgumentsNode* args, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth)
         : FunctionCallDotNode(location, base, ident, args, divot, divotStart, divotEnd)
+        , m_callOrApplyChildDepth(callOrApplyChildDepth)
     {
     }
 
index f5288e4..32a764e 100644 (file)
@@ -884,18 +884,20 @@ namespace JSC {
 
     class CallFunctionCallDotNode : public FunctionCallDotNode {
     public:
-        CallFunctionCallDotNode(const JSTokenLocation&, ExpressionNode* base, const Identifier&, ArgumentsNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
+        CallFunctionCallDotNode(const JSTokenLocation&, ExpressionNode* base, const Identifier&, ArgumentsNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth);
 
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+        size_t m_callOrApplyChildDepth;
     };
     
     class ApplyFunctionCallDotNode : public FunctionCallDotNode {
     public:
-        ApplyFunctionCallDotNode(const JSTokenLocation&, ExpressionNode* base, const Identifier&, ArgumentsNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
+        ApplyFunctionCallDotNode(const JSTokenLocation&, ExpressionNode* base, const Identifier&, ArgumentsNode*, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd, size_t callOrApplyChildDepth);
 
     private:
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = 0) override;
+        size_t m_callOrApplyChildDepth;
     };
 
     class DeleteResolveNode : public ExpressionNode, public ThrowableExpressionData {
index f829acb..96daf76 100644 (file)
@@ -4384,6 +4384,22 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseArgument(Tre
     return parseAssignmentExpression(context);
 }
 
+template <typename TreeBuilder, typename ParserType, typename = typename std::enable_if<std::is_same<TreeBuilder, ASTBuilder>::value>::type>
+static inline void recordCallOrApplyDepth(ParserType* parser, VM& vm, std::optional<typename ParserType::CallOrApplyDepth>& callOrApplyDepth, ExpressionNode* expression)
+{
+    if (expression->isDotAccessorNode()) {
+        DotAccessorNode* dot = static_cast<DotAccessorNode*>(expression);
+        bool isCallOrApply = dot->identifier() == vm.propertyNames->builtinNames().callPublicName() || dot->identifier() == vm.propertyNames->builtinNames().applyPublicName();
+        if (isCallOrApply)
+            callOrApplyDepth.emplace(parser);
+    }
+}
+
+template <typename TreeBuilder, typename ParserType, typename = typename std::enable_if<std::is_same<TreeBuilder, SyntaxChecker>::value>::type>
+static inline void recordCallOrApplyDepth(ParserType*, VM&, std::optional<typename ParserType::CallOrApplyDepth>&, SyntaxChecker::Expression)
+{
+}
+
 template <typename LexerType>
 template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpression(TreeBuilder& context)
 {
@@ -4498,6 +4514,9 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpres
             } else {
                 size_t usedVariablesSize = currentScope()->currentUsedVariablesSize();
                 JSTextPosition expressionEnd = lastTokenEndPosition();
+                std::optional<CallOrApplyDepth> callOrApplyDepth;
+                recordCallOrApplyDepth<TreeBuilder>(this, *m_vm, callOrApplyDepth, base);
+
                 TreeArguments arguments = parseArguments(context);
 
                 if (baseIsAsyncKeyword && (!arguments || match(ARROWFUNCTION))) {
@@ -4525,7 +4544,8 @@ template <class TreeBuilder> TreeExpression Parser<LexerType>::parseMemberExpres
                     if (currentScope()->isArrowFunction())
                         functionScope->setInnerArrowFunctionUsesSuperCall();
                 }
-                base = context.makeFunctionCallNode(startLocation, base, arguments, expressionStart, expressionEnd, lastTokenEndPosition());
+                base = context.makeFunctionCallNode(startLocation, base, arguments, expressionStart,
+                    expressionEnd, lastTokenEndPosition(), callOrApplyDepth ? callOrApplyDepth->maxChildDepth() : 0);
             }
             m_parserState.nonLHSCount = nonLHSCount;
             break;
index 09b710a..7bf2ad6 100644 (file)
@@ -887,6 +887,37 @@ public:
     JSTextPosition positionBeforeLastNewline() const { return m_lexer->positionBeforeLastNewline(); }
     JSTokenLocation locationBeforeLastToken() const { return m_lexer->lastTokenLocation(); }
 
+    struct CallOrApplyDepth {
+        CallOrApplyDepth(Parser* parser)
+            : m_parser(parser)
+            , m_parent(parser->m_callOrApplyDepth)
+            , m_depth(m_parent ? m_parent->m_depth + 1 : 0)
+            , m_childDepth(m_depth)
+        {
+            parser->m_callOrApplyDepth = this;
+        }
+
+        size_t maxChildDepth() const
+        {
+            ASSERT(m_childDepth >= m_depth);
+            return m_childDepth - m_depth;
+        }
+
+        ~CallOrApplyDepth()
+        {
+            if (m_parent)
+                m_parent->m_childDepth = std::max(m_childDepth, m_parent->m_childDepth);
+            m_parser->m_callOrApplyDepth = m_parent;
+        }
+
+    private:
+
+        Parser* m_parser;
+        CallOrApplyDepth* m_parent;
+        size_t m_depth;
+        size_t m_childDepth;
+    };
+
 private:
     struct AllowInOverride {
         AllowInOverride(Parser* parser)
@@ -1780,6 +1811,7 @@ private:
     bool m_immediateParentAllowsFunctionDeclarationInStatement;
     RefPtr<ModuleScopeData> m_moduleScopeData;
     DebuggerParseData* m_debuggerParseData;
+    CallOrApplyDepth* m_callOrApplyDepth { nullptr };
 
     struct DepthManager {
         DepthManager(int* depth)
index 08854f3..a35ed9f 100644 (file)
@@ -143,7 +143,7 @@ public:
     static const unsigned DontBuildStrings = LexerFlagsDontBuildStrings;
 
     int createSourceElements() { return SourceElementsResult; }
-    ExpressionType makeFunctionCallNode(const JSTokenLocation&, int, int, int, int, int) { return CallExpr; }
+    ExpressionType makeFunctionCallNode(const JSTokenLocation&, int, int, int, int, int, size_t) { return CallExpr; }
     ExpressionType createCommaExpr(const JSTokenLocation&, ExpressionType expr) { return expr; }
     ExpressionType appendToCommaExpr(const JSTokenLocation&, ExpressionType& head, ExpressionType, ExpressionType next) { head = next; return next; }
     ExpressionType makeAssignNode(const JSTokenLocation&, ExpressionType, Operator, ExpressionType, bool, bool, int, int, int) { return AssignmentExpr; }