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