fix(compiler): avoid errors with extremely long instruction chains (#45574)

Our logic for generating code from an AST uses recursion which limits the number of expressions we can nest before we reach the call stack limit. These changes add a limit in order to avoid errors in some cases where the chains become extremely long.

Fixes #45564.

PR Close #45574
This commit is contained in:
Kristiyan Kostadinov 2022-04-09 07:26:47 -07:00 committed by Jessica Janiuk
parent 7bf1cf4bf5
commit 598b759d33

View file

@ -50,6 +50,13 @@ export const NON_BINDABLE_ATTR = 'ngNonBindable';
/** Name for the variable keeping track of the context returned by `ɵɵrestoreView`. */
export const RESTORED_VIEW_CONTEXT_NAME = 'restoredCtx';
/**
* Maximum length of a single instruction chain. Because our output AST uses recursion, we're
* limited in how many expressions we can nest before we reach the call stack limit. This
* length is set very conservatively in order to reduce the chance of problems.
*/
const MAX_CHAIN_LENGTH = 500;
/** Instructions that support chaining. */
const CHAINABLE_INSTRUCTIONS = new Set([
R3.element,
@ -309,6 +316,7 @@ export function getInstructionStatements(instructions: Instruction[]): o.Stateme
const statements: o.Statement[] = [];
let pendingExpression: o.Expression|null = null;
let pendingExpressionType: o.ExternalReference|null = null;
let chainLength = 0;
for (const current of instructions) {
const resolvedParams =
@ -318,16 +326,18 @@ export function getInstructionStatements(instructions: Instruction[]): o.Stateme
// If the current instruction is the same as the previous one
// and it can be chained, add another call to the chain.
if (pendingExpressionType === current.reference &&
if (chainLength < MAX_CHAIN_LENGTH && pendingExpressionType === current.reference &&
CHAINABLE_INSTRUCTIONS.has(pendingExpressionType)) {
// We'll always have a pending expression when there's a pending expression type.
pendingExpression = pendingExpression!.callFn(params, pendingExpression!.sourceSpan);
chainLength++;
} else {
if (pendingExpression !== null) {
statements.push(pendingExpression.toStmt());
}
pendingExpression = invokeInstruction(current.span, current.reference, params);
pendingExpressionType = current.reference;
chainLength = 0;
}
}