From 598b759d33221cc2bbfad4895ca0d2499a0547f8 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 9 Apr 2022 07:26:47 -0700 Subject: [PATCH] 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 --- packages/compiler/src/render3/view/util.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index 612ecfe2e0a..d30f2cb5733 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -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; } }