Skip to content

Commit

Permalink
fix(compiler): avoid errors with extremely long instruction chains (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
crisbeto authored and thePunderWoman committed Apr 13, 2022
1 parent 7bf1cf4 commit 598b759
Showing 1 changed file with 11 additions and 1 deletion.
12 changes: 11 additions & 1 deletion packages/compiler/src/render3/view/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand All @@ -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;
}
}

Expand Down

0 comments on commit 598b759

Please sign in to comment.