Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/cairo-lang-semantic/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -1891,6 +1898,7 @@ pub enum SemanticDiagnosticKind<'db> {
NonNeverLetElseType,
OnlyTypeOrConstParamsInNegImpl,
UnsupportedItemInStatement,
ExpressionNestingTooDeep,
}

/// The kind of an expression with multiple possible return types.
Expand Down
15 changes: 14 additions & 1 deletion crates/cairo-lang-semantic/src/expr/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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 }
Expand Down
28 changes: 28 additions & 0 deletions crates/cairo-lang-semantic/src/expr/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}