diff --git a/crates/cairo-lang-semantic/src/diagnostic.rs b/crates/cairo-lang-semantic/src/diagnostic.rs index 33c289ae833..15725786cb6 100644 --- a/crates/cairo-lang-semantic/src/diagnostic.rs +++ b/crates/cairo-lang-semantic/src/diagnostic.rs @@ -1205,6 +1205,12 @@ impl<'db> DiagnosticEntry<'db> for SemanticDiagnostic<'db> { SemanticDiagnosticKind::UnsupportedItemInStatement => { "Item not supported as a statement.".into() } + SemanticDiagnosticKind::ExpressionNestingTooDeep => { + format!( + "Expression nesting exceeds maximum depth of {}.", + crate::expr::compute::MAX_EXPR_NESTING_DEPTH + ) + } } } fn location(&self, db: &'db dyn Database) -> SpanInFile<'db> { @@ -1468,6 +1474,7 @@ impl<'db> DiagnosticEntry<'db> for SemanticDiagnostic<'db> { SemanticDiagnosticKind::NonNeverLetElseType => error_code!(E2195), SemanticDiagnosticKind::OnlyTypeOrConstParamsInNegImpl => error_code!(E2196), SemanticDiagnosticKind::UnsupportedItemInStatement => error_code!(E2197), + SemanticDiagnosticKind::ExpressionNestingTooDeep => error_code!(E2198), SemanticDiagnosticKind::PluginDiagnostic(diag) => { diag.error_code.unwrap_or(error_code!(E2200)) } @@ -1891,6 +1898,7 @@ pub enum SemanticDiagnosticKind<'db> { NonNeverLetElseType, OnlyTypeOrConstParamsInNegImpl, UnsupportedItemInStatement, + ExpressionNestingTooDeep, } /// The kind of an expression with multiple possible return types. diff --git a/crates/cairo-lang-semantic/src/expr/compute.rs b/crates/cairo-lang-semantic/src/expr/compute.rs index ce03801fc5e..d75909680ed 100644 --- a/crates/cairo-lang-semantic/src/expr/compute.rs +++ b/crates/cairo-lang-semantic/src/expr/compute.rs @@ -269,7 +269,13 @@ pub struct ComputationContext<'ctx, 'mt> { /// Whether variables defined in the current macro scope should be injected into the parent /// scope. macro_defined_var_unhygienic: bool, + /// Current nesting depth of expression semantic computation; used to bail out with a clean + /// diagnostic before recursion overflows the native stack. + expr_depth: usize, } + +/// Cap on expression nesting depth, to avoid stack overflow in the recursive walker. +pub const MAX_EXPR_NESTING_DEPTH: usize = 256; impl<'ctx, 'mt> ComputationContext<'ctx, 'mt> { /// Creates a new computation context. pub fn new( @@ -297,6 +303,7 @@ impl<'ctx, 'mt> ComputationContext<'ctx, 'mt> { cfg_set, are_closures_in_context: false, macro_defined_var_unhygienic: false, + expr_depth: 0, } } @@ -692,7 +699,13 @@ pub fn compute_expr_semantic<'db>( ctx: &mut ComputationContext<'db, '_>, syntax: &ast::Expr<'db>, ) -> ExprAndId<'db> { - let expr = maybe_compute_expr_semantic(ctx, syntax); + ctx.expr_depth += 1; + let expr = if ctx.expr_depth > MAX_EXPR_NESTING_DEPTH { + Err(ctx.diagnostics.report(syntax.stable_ptr(ctx.db), ExpressionNestingTooDeep)) + } else { + maybe_compute_expr_semantic(ctx, syntax) + }; + ctx.expr_depth -= 1; let expr = wrap_maybe_with_missing(ctx, expr, syntax.stable_ptr(ctx.db)); let id = ctx.arenas.exprs.alloc(expr.clone()); ExprAndId { expr, id } diff --git a/crates/cairo-lang-semantic/src/expr/test.rs b/crates/cairo-lang-semantic/src/expr/test.rs index fe28ce092bf..c677a3caee0 100644 --- a/crates/cairo-lang-semantic/src/expr/test.rs +++ b/crates/cairo-lang-semantic/src/expr/test.rs @@ -9,6 +9,7 @@ use indoc::indoc; use pretty_assertions::assert_eq; use salsa::Database; +use crate::expr::compute::MAX_EXPR_NESTING_DEPTH; use crate::expr::fmt::ExprFormatter; use crate::items::function_with_body::FunctionWithBodySemantic; use crate::items::module::ModuleSemantic; @@ -303,3 +304,30 @@ fn test_function_body() { fn setup_db_for_foo<'db>(db: &'db dyn Database, foo_code: &str) -> TestFunction<'db> { setup_test_function_ex(db, foo_code, "foo", "", None, None).unwrap() } + +// Regression test for #9800: a deep expression now reports a clean diagnostic instead of +// aborting with a stack overflow. Run on a dedicated thread with a larger stack so the +// parser's own recursion has headroom on debug builds. +#[test] +fn test_deep_expression_reports_diagnostic_instead_of_overflow() { + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) + .spawn(|| { + let mut expr = String::from("0"); + for _ in 0..=MAX_EXPR_NESTING_DEPTH { + expr = format!("({expr} + 0)"); + } + + let db_val = SemanticDatabaseForTesting::default(); + let (_test_expr, diagnostics) = setup_test_expr(&db_val, &expr, "", "", None).split(); + + assert!(diagnostics.contains("E2198"), "expected E2198, got:\n{diagnostics}"); + assert!( + diagnostics.contains("Expression nesting exceeds maximum depth"), + "unexpected diagnostic text:\n{diagnostics}" + ); + }) + .unwrap() + .join() + .unwrap(); +}