Skip to main content

cairo_lint/lints/
panic.rs

1use cairo_lang_defs::diagnostic_utils::StableLocation;
2use cairo_lang_defs::ids::ModuleItemId;
3use cairo_lang_defs::plugin::PluginDiagnostic;
4use cairo_lang_diagnostics::Severity;
5use cairo_lang_filesystem::db::get_originating_location;
6use cairo_lang_parser::db::ParserGroup;
7use cairo_lang_semantic::ExprFunctionCall;
8use cairo_lang_semantic::items::functions::GenericFunctionId;
9use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode};
10use if_chain::if_chain;
11use itertools::Itertools;
12
13use crate::context::{CairoLintKind, Lint};
14
15use crate::LinterGroup;
16use crate::helper::ASSERT_FORMATTER_NAME;
17use crate::queries::{get_all_function_bodies, get_all_function_calls};
18use cairo_lang_filesystem::ids::SpanInFile;
19use salsa::Database;
20
21pub struct PanicInCode;
22
23/// ## What it does
24///
25/// Checks for panic usages.
26///
27/// ## Example
28///
29/// ```cairo
30/// fn main() {
31///     panic!("panic");
32/// }
33/// ```
34impl Lint for PanicInCode {
35    fn allowed_name(&self) -> &'static str {
36        "panic"
37    }
38
39    fn diagnostic_message(&self) -> &'static str {
40        "Leaving `panic` in the code is discouraged."
41    }
42
43    fn kind(&self) -> CairoLintKind {
44        CairoLintKind::Panic
45    }
46
47    fn is_enabled(&self) -> bool {
48        false
49    }
50}
51
52#[tracing::instrument(skip_all, level = "trace")]
53pub fn check_panic_usage<'db>(
54    db: &'db dyn Database,
55    item: &ModuleItemId<'db>,
56    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
57) {
58    let function_bodies = get_all_function_bodies(db, item);
59    for function_body in function_bodies.iter() {
60        let function_call_exprs = get_all_function_calls(function_body);
61        for function_call_expr in function_call_exprs.unique() {
62            check_single_panic_usage(db, &function_call_expr, diagnostics);
63        }
64    }
65}
66
67fn check_single_panic_usage<'db>(
68    db: &'db dyn Database,
69    function_call_expr: &ExprFunctionCall<'db>,
70    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
71) {
72    let init_node = function_call_expr.stable_ptr.lookup(db).as_syntax_node();
73
74    let concrete_function_id = function_call_expr
75        .function
76        .get_concrete(db)
77        .generic_function;
78
79    let corelib_context = db.corelib_context();
80
81    // If the function is the panic function from the corelib.
82    let is_panic = if let GenericFunctionId::Extern(id) = concrete_function_id
83        && id == corelib_context.get_panic_function_id()
84    {
85        true
86    } else {
87        false
88    };
89
90    // If the function is the panic_with_byte_array function from the corelib.
91    let is_panic_with_byte_array = if let GenericFunctionId::Free(id) = concrete_function_id
92        && id == corelib_context.get_panic_with_byte_array_function_id()
93    {
94        true
95    } else {
96        false
97    };
98
99    // We check if the panic comes from the `assert!` macro.
100    let is_assert_panic = is_panic_with_byte_array
101        && function_call_expr
102            .stable_ptr
103            .lookup(db)
104            .as_syntax_node()
105            .get_text(db)
106            .contains(ASSERT_FORMATTER_NAME);
107
108    // We skip non panic calls, and panic calls in assert macros.
109    if !(is_panic || is_panic_with_byte_array) || is_assert_panic {
110        return;
111    }
112
113    // Get the origination location of this panic as there is a `panic!` macro that generates virtual
114    // files
115    let initial_file_id = StableLocation::new(function_call_expr.stable_ptr.untyped()).file_id(db);
116    let SpanInFile { file_id, span } = get_originating_location(
117        db,
118        SpanInFile {
119            file_id: initial_file_id,
120            span: function_call_expr
121                .stable_ptr
122                .lookup(db)
123                .as_syntax_node()
124                .span(db),
125        },
126        None,
127    );
128    // If the panic comes from a real file (macros generate code in new virtual files)
129    if initial_file_id == file_id {
130        diagnostics.push(PluginDiagnostic {
131            stable_ptr: init_node.stable_ptr(db),
132            message: PanicInCode.diagnostic_message().to_owned(),
133            severity: Severity::Warning,
134            inner_span: None,
135            error_code: None,
136        });
137    } else {
138        // If the originating location is a different file get the syntax node that generated the
139        // code that contains a panic.
140        if_chain! {
141            if let Some(text_position) = span.position_in_file(db, file_id);
142            if let Ok(file_node) = db.file_syntax(file_id);
143            then {
144                let syntax_node = file_node.lookup_position(db, text_position.start);
145                diagnostics.push(PluginDiagnostic {
146                    stable_ptr: syntax_node.stable_ptr(db),
147                    message: PanicInCode.diagnostic_message().to_owned(),
148                    severity: Severity::Warning,
149                    inner_span: None,
150                    error_code: None,
151                });
152            }
153        }
154    }
155}