1use std::collections::VecDeque;
2
3use assert_matches::assert_matches;
4use cairo_lang_diagnostics::Maybe;
5use cairo_lang_filesystem::flag::{Flag, flag_unsafe_panic};
6use cairo_lang_filesystem::ids::FlagId;
7use cairo_lang_semantic::corelib::{
8 core_submodule, get_core_enum_concrete_variant, get_function_id, get_panic_ty, never_ty,
9};
10use cairo_lang_semantic::helper::ModuleHelper;
11use cairo_lang_semantic::items::constant::ConstValue;
12use cairo_lang_semantic::{self as semantic, GenericArgumentId};
13use cairo_lang_utils::{Intern, Upcast};
14use itertools::{Itertools, chain, zip_eq};
15use semantic::{ConcreteVariant, MatchArmSelector, TypeId};
16
17use crate::blocks::BlocksBuilder;
18use crate::db::{ConcreteSCCRepresentative, LoweringGroup};
19use crate::ids::{
20 ConcreteFunctionWithBodyId, FunctionId, FunctionLongId, LocationId, SemanticFunctionIdEx,
21 Signature,
22};
23use crate::lower::context::{VarRequest, VariableAllocator};
24use crate::{
25 Block, BlockEnd, BlockId, DependencyType, Lowered, LoweringStage, MatchArm, MatchEnumInfo,
26 MatchExternInfo, MatchInfo, Statement, StatementCall, StatementEnumConstruct,
27 StatementStructConstruct, StatementStructDestructure, VarRemapping, VarUsage, VariableId,
28};
29
30pub fn lower_panics(
36 db: &dyn LoweringGroup,
37 function_id: ConcreteFunctionWithBodyId,
38 lowered: &mut Lowered,
39) -> Maybe<()> {
40 if !db.function_with_body_may_panic(function_id)? {
42 return Ok(());
43 }
44
45 let opt_trace_fn = if matches!(
46 db.get_flag(FlagId::new(db, "panic_backtrace")),
47 Some(flag) if matches!(*flag, Flag::PanicBacktrace(true)),
48 ) {
49 Some(
50 ModuleHelper::core(db)
51 .submodule("internal")
52 .function_id(
53 "trace",
54 vec![GenericArgumentId::Constant(
55 ConstValue::Int(
56 0x70616e6963u64.into(), db.core_info().felt252,
58 )
59 .intern(db),
60 )],
61 )
62 .lowered(db),
63 )
64 } else {
65 None
66 };
67
68 if flag_unsafe_panic(db) {
69 lower_unsafe_panic(db, lowered, opt_trace_fn);
70 return Ok(());
71 }
72
73 let signature = function_id.signature(db)?;
74 let panic_info = PanicSignatureInfo::new(db, &signature);
76 let variables = VariableAllocator::new(
77 db,
78 function_id.base_semantic_function(db).function_with_body_id(db),
79 std::mem::take(&mut lowered.variables),
80 )
81 .unwrap();
82 let mut ctx = PanicLoweringContext {
83 variables,
84 block_queue: VecDeque::from(lowered.blocks.get().clone()),
85 flat_blocks: BlocksBuilder::new(),
86 panic_info,
87 };
88
89 if let Some(trace_fn) = opt_trace_fn {
90 for block in ctx.block_queue.iter_mut() {
91 if let BlockEnd::Panic(end) = &block.end {
92 block.statements.push(Statement::Call(StatementCall {
93 function: trace_fn,
94 inputs: vec![],
95 with_coupon: false,
96 outputs: vec![],
97 location: end.location,
98 }));
99 }
100 }
101 }
102
103 while let Some(block) = ctx.block_queue.pop_front() {
105 ctx = handle_block(ctx, block)?;
106 }
107
108 lowered.variables = ctx.variables.variables;
109 lowered.blocks = ctx.flat_blocks.build().unwrap();
110
111 Ok(())
112}
113
114fn lower_unsafe_panic(
117 db: &dyn LoweringGroup,
118 lowered: &mut Lowered,
119 opt_trace_fn: Option<FunctionId>,
120) {
121 let panics = core_submodule(db, "panics");
122 let panic_func_id =
123 FunctionLongId::Semantic(get_function_id(db, panics, "unsafe_panic".into(), vec![]))
124 .intern(db);
125
126 for block in lowered.blocks.iter_mut() {
127 let BlockEnd::Panic(err_data) = &mut block.end else {
128 continue;
129 };
130
131 let Some(Statement::StructConstruct(tuple_construct)) = block.statements.pop() else {
133 panic!("Expected a tuple construct before the panic.");
134 };
135 assert_eq!(tuple_construct.output, err_data.var_id);
137
138 let panic_construct_statement = block.statements.pop();
139 assert_matches!(panic_construct_statement, Some(Statement::StructConstruct(panic_construct)) if panic_construct.output == tuple_construct.inputs[0].var_id);
142
143 if let Some(trace_fn) = opt_trace_fn {
144 block.statements.push(Statement::Call(StatementCall {
145 function: trace_fn,
146 inputs: vec![],
147 with_coupon: false,
148 outputs: vec![],
149 location: err_data.location,
150 }));
151 }
152
153 block.end = BlockEnd::Match {
154 info: MatchInfo::Extern(MatchExternInfo {
155 arms: vec![],
156 location: err_data.location,
157 function: panic_func_id,
158 inputs: vec![],
159 }),
160 }
161 }
162}
163
164fn handle_block(
166 mut ctx: PanicLoweringContext<'_>,
167 mut block: Block,
168) -> Maybe<PanicLoweringContext<'_>> {
169 let mut block_ctx = PanicBlockLoweringContext { ctx, statements: Vec::new() };
170 for (i, stmt) in block.statements.iter().cloned().enumerate() {
171 if let Some((cur_block_end, continuation_block)) = block_ctx.handle_statement(&stmt)? {
172 ctx = block_ctx.handle_end(cur_block_end);
176 if let Some(continuation_block) = continuation_block {
177 let block_to_edit =
180 &mut ctx.block_queue[continuation_block.0 - ctx.flat_blocks.len()];
181 block_to_edit.statements.extend(block.statements.drain(i + 1..));
182 block_to_edit.end = block.end;
183 }
184 return Ok(ctx);
185 }
186 }
187 ctx = block_ctx.handle_end(block.end);
188 Ok(ctx)
189}
190
191pub struct PanicSignatureInfo {
192 ok_ret_tys: Vec<TypeId>,
194 ok_ty: TypeId,
196 ok_variant: ConcreteVariant,
198 pub err_variant: ConcreteVariant,
200 pub actual_return_ty: TypeId,
202 pub always_panic: bool,
205}
206impl PanicSignatureInfo {
207 pub fn new(db: &dyn LoweringGroup, signature: &Signature) -> Self {
208 let extra_rets = signature.extra_rets.iter().map(|param| param.ty());
209 let original_return_ty = signature.return_type;
210
211 let ok_ret_tys = chain!(extra_rets, [original_return_ty]).collect_vec();
212 let ok_ty = semantic::TypeLongId::Tuple(ok_ret_tys.clone()).intern(db);
213 let ok_variant = get_core_enum_concrete_variant(
214 db,
215 "PanicResult",
216 vec![GenericArgumentId::Type(ok_ty)],
217 "Ok",
218 );
219 let err_variant = get_core_enum_concrete_variant(
220 db,
221 "PanicResult",
222 vec![GenericArgumentId::Type(ok_ty)],
223 "Err",
224 );
225 let always_panic = original_return_ty == never_ty(db);
226 let panic_ty = if always_panic { err_variant.ty } else { get_panic_ty(db, ok_ty) };
227
228 Self {
229 ok_ret_tys,
230 ok_ty,
231 ok_variant,
232 err_variant,
233 actual_return_ty: panic_ty,
234 always_panic,
235 }
236 }
237}
238
239struct PanicLoweringContext<'a> {
240 variables: VariableAllocator<'a>,
241 block_queue: VecDeque<Block>,
242 flat_blocks: BlocksBuilder,
243 panic_info: PanicSignatureInfo,
244}
245impl PanicLoweringContext<'_> {
246 pub fn db(&self) -> &dyn LoweringGroup {
247 self.variables.db
248 }
249
250 fn enqueue_block(&mut self, block: Block) -> BlockId {
251 self.block_queue.push_back(block);
252 BlockId(self.flat_blocks.len() + self.block_queue.len())
253 }
254}
255
256struct PanicBlockLoweringContext<'a> {
257 ctx: PanicLoweringContext<'a>,
258 statements: Vec<Statement>,
259}
260impl<'a> PanicBlockLoweringContext<'a> {
261 pub fn db(&self) -> &dyn LoweringGroup {
262 self.ctx.db()
263 }
264
265 fn new_var(&mut self, ty: TypeId, location: LocationId) -> VariableId {
266 self.ctx.variables.new_var(VarRequest { ty, location })
267 }
268
269 fn handle_statement(&mut self, stmt: &Statement) -> Maybe<Option<(BlockEnd, Option<BlockId>)>> {
276 if let Statement::Call(call) = &stmt {
277 if let Some(with_body) = call.function.body(self.db())? {
278 if self.db().function_with_body_may_panic(with_body)? {
279 return Ok(Some(self.handle_call_panic(call)?));
280 }
281 }
282 }
283 self.statements.push(stmt.clone());
284 Ok(None)
285 }
286
287 fn handle_call_panic(&mut self, call: &StatementCall) -> Maybe<(BlockEnd, Option<BlockId>)> {
291 let mut original_outputs = call.outputs.clone();
293 let location = call.location.with_auto_generation_note(self.db(), "Panic handling");
294
295 let callee_signature = call.function.signature(self.db())?;
297 let callee_info = PanicSignatureInfo::new(self.db(), &callee_signature);
298 if callee_info.always_panic {
299 let panic_result_var = self.new_var(callee_info.actual_return_ty, location);
301 self.statements.push(Statement::Call(StatementCall {
303 function: call.function,
304 inputs: call.inputs.clone(),
305 with_coupon: call.with_coupon,
306 outputs: vec![panic_result_var],
307 location,
308 }));
309 return Ok((BlockEnd::Panic(VarUsage { var_id: panic_result_var, location }), None));
310 }
311
312 let panic_result_var = self.new_var(callee_info.actual_return_ty, location);
315 let n_callee_implicits = original_outputs.len() - callee_info.ok_ret_tys.len();
316 let mut call_outputs = original_outputs.drain(..n_callee_implicits).collect_vec();
317 call_outputs.push(panic_result_var);
318 let inner_ok_value = self.new_var(callee_info.ok_ty, location);
320 let inner_ok_values = callee_info
322 .ok_ret_tys
323 .iter()
324 .copied()
325 .map(|ty| self.new_var(ty, location))
326 .collect_vec();
327
328 self.statements.push(Statement::Call(StatementCall {
330 function: call.function,
331 inputs: call.inputs.clone(),
332 with_coupon: call.with_coupon,
333 outputs: call_outputs,
334 location,
335 }));
336
337 let block_continuation =
339 self.ctx.enqueue_block(Block { statements: vec![], end: BlockEnd::NotSet });
340
341 let block_ok = self.ctx.enqueue_block(Block {
345 statements: vec![Statement::StructDestructure(StatementStructDestructure {
346 input: VarUsage { var_id: inner_ok_value, location },
347 outputs: inner_ok_values.clone(),
348 })],
349 end: BlockEnd::Goto(
350 block_continuation,
351 VarRemapping {
352 remapping: zip_eq(
353 original_outputs,
354 inner_ok_values.into_iter().map(|var_id| VarUsage { var_id, location }),
355 )
356 .collect(),
357 },
358 ),
359 });
360
361 let err_var = self.new_var(self.ctx.panic_info.err_variant.ty, location);
363 let block_err = self.ctx.enqueue_block(Block {
364 statements: vec![],
365 end: BlockEnd::Panic(VarUsage { var_id: err_var, location }),
366 });
367
368 let cur_block_end = BlockEnd::Match {
369 info: MatchInfo::Enum(MatchEnumInfo {
370 concrete_enum_id: callee_info.ok_variant.concrete_enum_id,
371 input: VarUsage { var_id: panic_result_var, location },
372 arms: vec![
373 MatchArm {
374 arm_selector: MatchArmSelector::VariantId(callee_info.ok_variant),
375 block_id: block_ok,
376 var_ids: vec![inner_ok_value],
377 },
378 MatchArm {
379 arm_selector: MatchArmSelector::VariantId(callee_info.err_variant),
380 block_id: block_err,
381 var_ids: vec![err_var],
382 },
383 ],
384 location,
385 }),
386 };
387
388 Ok((cur_block_end, Some(block_continuation)))
389 }
390
391 fn handle_end(mut self, end: BlockEnd) -> PanicLoweringContext<'a> {
392 let end = match end {
393 BlockEnd::Goto(target, remapping) => BlockEnd::Goto(target, remapping),
394 BlockEnd::Panic(err_data) => {
395 let ty = self.ctx.panic_info.actual_return_ty;
397 let location = err_data.location;
398 let output = if self.ctx.panic_info.always_panic {
399 err_data.var_id
400 } else {
401 let output = self.new_var(ty, location);
402 self.statements.push(Statement::EnumConstruct(StatementEnumConstruct {
403 variant: self.ctx.panic_info.err_variant,
404 input: err_data,
405 output,
406 }));
407 output
408 };
409 BlockEnd::Return(vec![VarUsage { var_id: output, location }], location)
410 }
411 BlockEnd::Return(returns, location) => {
412 let tupled_res = self.new_var(self.ctx.panic_info.ok_ty, location);
414 self.statements.push(Statement::StructConstruct(StatementStructConstruct {
415 inputs: returns,
416 output: tupled_res,
417 }));
418
419 let ty = self.ctx.panic_info.actual_return_ty;
421 let output = self.new_var(ty, location);
422 self.statements.push(Statement::EnumConstruct(StatementEnumConstruct {
423 variant: self.ctx.panic_info.ok_variant,
424 input: VarUsage { var_id: tupled_res, location },
425 output,
426 }));
427 BlockEnd::Return(vec![VarUsage { var_id: output, location }], location)
428 }
429 BlockEnd::NotSet => unreachable!(),
430 BlockEnd::Match { info } => BlockEnd::Match { info },
431 };
432 self.ctx.flat_blocks.alloc(Block { statements: self.statements, end });
433 self.ctx
434 }
435}
436
437pub fn function_may_panic(db: &dyn LoweringGroup, function: FunctionId) -> Maybe<bool> {
441 if let Some(body) = function.body(db)? {
442 return db.function_with_body_may_panic(body);
443 }
444 Ok(function.signature(db)?.panicable)
445}
446
447pub trait MayPanicTrait<'a>: Upcast<dyn LoweringGroup + 'a> {
449 fn function_with_body_may_panic(&self, function: ConcreteFunctionWithBodyId) -> Maybe<bool> {
451 let scc_representative = self.upcast().lowered_scc_representative(
452 function,
453 DependencyType::Call,
454 LoweringStage::Monomorphized,
455 );
456 self.upcast().scc_may_panic(scc_representative)
457 }
458}
459impl<'a, T: Upcast<dyn LoweringGroup + 'a> + ?Sized> MayPanicTrait<'a> for T {}
460
461pub fn scc_may_panic(db: &dyn LoweringGroup, scc: ConcreteSCCRepresentative) -> Maybe<bool> {
463 let scc_functions = db.lowered_scc(scc.0, DependencyType::Call, LoweringStage::Monomorphized);
465 for function in scc_functions {
466 if db.needs_withdraw_gas(function)? {
467 return Ok(true);
468 }
469 if db.has_direct_panic(function)? {
470 return Ok(true);
471 }
472 let direct_callees = db.lowered_direct_callees(
474 function,
475 DependencyType::Call,
476 LoweringStage::Monomorphized,
477 )?;
478 for direct_callee in direct_callees {
479 if let Some(callee_body) = direct_callee.body(db)? {
480 let callee_scc = db.lowered_scc_representative(
481 callee_body,
482 DependencyType::Call,
483 LoweringStage::Monomorphized,
484 );
485 if callee_scc != scc && db.scc_may_panic(callee_scc)? {
486 return Ok(true);
487 }
488 } else if direct_callee.signature(db)?.panicable {
489 return Ok(true);
490 }
491 }
492 }
493 Ok(false)
494}
495
496pub fn has_direct_panic(
498 db: &dyn LoweringGroup,
499 function_id: ConcreteFunctionWithBodyId,
500) -> Maybe<bool> {
501 let lowered_function = db.lowered_body(function_id, LoweringStage::Monomorphized)?;
502 Ok(itertools::any(&lowered_function.blocks, |(_, block)| {
503 matches!(&block.end, BlockEnd::Panic(..))
504 }))
505}