From d8f51c9db9e6fd36d924f6d00bce8780c26c1489 Mon Sep 17 00:00:00 2001 From: Harishankar Date: Tue, 25 Nov 2025 02:11:33 +0530 Subject: [PATCH] Fix ICE with continue/break/return in while condition Fixes Rust-GCC/gccrs#3977 The predicate expression must be evaluated before type checking to ensure side effects occur even when the predicate has never type. This prevents skipping function calls, panics, or other side effects in diverging predicates. Proof of fix using -fdump-tree-gimple: __attribute__((cdecl)) struct () test::main () { struct () D.107; : : { : { : goto ; // Side-effect correctly preserved if (0 != 0) goto ; else goto ; : { } } goto ; : } goto ; return D.107; } gcc/rust/ChangeLog: * backend/rust-compile-expr.cc (CompileExpr::visit): Always evaluate predicate expression before checking for never type to preserve side effects in while loop conditions. * typecheck/rust-hir-type-check-expr.cc: Update handling of break/continue. gcc/testsuite/ChangeLog: * rust/compile/issue-3977.rs: New test. Signed-off-by: Harishankar --- gcc/rust/backend/rust-compile-expr.cc | 13 +++- .../typecheck/rust-hir-type-check-expr.cc | 16 +++-- gcc/testsuite/rust/compile/issue-3977.rs | 65 +++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/rust/compile/issue-3977.rs diff --git a/gcc/rust/backend/rust-compile-expr.cc b/gcc/rust/backend/rust-compile-expr.cc index 0a627f353524..b89033841f2a 100644 --- a/gcc/rust/backend/rust-compile-expr.cc +++ b/gcc/rust/backend/rust-compile-expr.cc @@ -803,7 +803,18 @@ CompileExpr::visit (HIR::WhileLoopExpr &expr) ctx->add_statement (loop_begin_label_decl); ctx->push_loop_begin_label (loop_begin_label); - tree condition = CompileExpr::Compile (expr.get_predicate_expr (), ctx); + HIR::Expr &predicate = expr.get_predicate_expr (); + TyTy::BaseType *predicate_type = nullptr; + bool ok + = ctx->get_tyctx ()->lookup_type (predicate.get_mappings ().get_hirid (), + &predicate_type); + rust_assert (ok && predicate_type != nullptr); + tree condition = CompileExpr::Compile (predicate, ctx); + if (predicate_type->get_kind () == TyTy::TypeKind::NEVER) + { + ctx->add_statement (condition); + condition = boolean_true_node; + } tree exit_condition = fold_build1_loc (expr.get_locus (), TRUTH_NOT_EXPR, boolean_type_node, condition); tree exit_expr = Backend::exit_expression (exit_condition, expr.get_locus ()); diff --git a/gcc/rust/typecheck/rust-hir-type-check-expr.cc b/gcc/rust/typecheck/rust-hir-type-check-expr.cc index 7885dfcf7463..5162cc6f9bbd 100644 --- a/gcc/rust/typecheck/rust-hir-type-check-expr.cc +++ b/gcc/rust/typecheck/rust-hir-type-check-expr.cc @@ -1541,18 +1541,25 @@ void TypeCheckExpr::visit (HIR::WhileLoopExpr &expr) { context->push_new_while_loop_context (expr.get_mappings ().get_hirid ()); - - TypeCheckExpr::Resolve (expr.get_predicate_expr ()); + TyTy::BaseType *predicate_type + = TypeCheckExpr::Resolve (expr.get_predicate_expr ()); + if (predicate_type->get_kind () != TyTy::TypeKind::BOOL + && predicate_type->get_kind () != TyTy::TypeKind::NEVER) + { + rust_error_at (expr.get_predicate_expr ().get_locus (), + "expected boolean expression in % condition"); + context->pop_loop_context (); + return; + } TyTy::BaseType *block_expr = TypeCheckExpr::Resolve (expr.get_loop_block ()); - if (!block_expr->is_unit ()) { rust_error_at (expr.get_loop_block ().get_locus (), "expected %<()%> got %s", block_expr->as_string ().c_str ()); + context->pop_loop_context (); return; } - context->pop_loop_context (); infered = TyTy::TupleType::get_unit_type (); } @@ -1602,7 +1609,6 @@ TypeCheckExpr::visit (HIR::ContinueExpr &expr) "% outside of a loop"); return; } - infered = new TyTy::NeverType (expr.get_mappings ().get_hirid ()); } diff --git a/gcc/testsuite/rust/compile/issue-3977.rs b/gcc/testsuite/rust/compile/issue-3977.rs new file mode 100644 index 000000000000..ba4de544d44b --- /dev/null +++ b/gcc/testsuite/rust/compile/issue-3977.rs @@ -0,0 +1,65 @@ +// Test for issue #3977 - ICE with continue/break/return in while condition + +fn diverge() -> ! { + loop {} +} + +fn test_continue() { + loop { + while continue {} + } +} + +fn test_break() { + loop { + while break {} + } +} + +fn test_return() { + loop { + while return {} + } +} + +fn test_labeled_break() { + 'outer: loop { + loop { + while break 'outer {} + } + } +} + +fn test_labeled_continue() { + 'outer: loop { + loop { + while continue 'outer {} + } + } +} + +fn test_complex_if_else() { + loop { + while if true { continue } else { break } {} + } +} + +fn foo() { + while diverge() { + break + } + let _x = 5; +} + +fn main() { + // Just reference them so they're "used" + if false { + test_continue(); + test_break(); + test_return(); + test_labeled_break(); + test_labeled_continue(); + test_complex_if_else(); + foo(); + } +} \ No newline at end of file