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
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.
  • Loading branch information
crisbeto committed Apr 9, 2022
1 parent e0ac614 commit b6667a3
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 b6667a3

Please sign in to comment.