cairo_lint_core/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_semantic::db::SemanticGroup;
7use cairo_lang_semantic::ExprFunctionCall;
8use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode};
9use if_chain::if_chain;
10
11use crate::context::{CairoLintKind, Lint};
12use crate::queries::{get_all_function_bodies, get_all_function_calls};
13
14const PANIC: &str = "core::panics::panic";
15
16pub struct PanicInCode;
17
18/// ## What it does
19///
20/// Checks for panic usages.
21///
22/// ## Example
23///
24/// ```cairo
25/// fn main() {
26///     panic!("panic");
27/// }
28/// ```
29impl Lint for PanicInCode {
30    fn allowed_name(&self) -> &'static str {
31        "panic"
32    }
33
34    fn diagnostic_message(&self) -> &'static str {
35        "Leaving `panic` in the code is discouraged."
36    }
37
38    fn kind(&self) -> CairoLintKind {
39        CairoLintKind::Panic
40    }
41}
42
43/// Checks for panic usage.
44pub fn check_panic_usage(
45    db: &dyn SemanticGroup,
46    item: &ModuleItemId,
47    diagnostics: &mut Vec<PluginDiagnostic>,
48) {
49    let function_bodies = get_all_function_bodies(db, item);
50    for function_body in function_bodies.iter() {
51        let function_call_exprs = get_all_function_calls(function_body);
52        for function_call_expr in function_call_exprs.iter() {
53            check_single_panic_usage(db, function_call_expr, diagnostics);
54        }
55    }
56}
57
58fn check_single_panic_usage(
59    db: &dyn SemanticGroup,
60    function_call_expr: &ExprFunctionCall,
61    diagnostics: &mut Vec<PluginDiagnostic>,
62) {
63    let init_node = function_call_expr
64        .stable_ptr
65        .lookup(db.upcast())
66        .as_syntax_node()
67        .clone();
68
69    // If the function is not the panic function from the corelib return
70    if function_call_expr.function.full_path(db) != PANIC {
71        return;
72    }
73
74    // Get the origination location of this panic as there is a `panic!` macro that gerates virtual
75    // files
76    let initial_file_id =
77        StableLocation::new(function_call_expr.stable_ptr.untyped()).file_id(db.upcast());
78    let (file_id, span) = get_originating_location(
79        db.upcast(),
80        initial_file_id,
81        function_call_expr
82            .stable_ptr
83            .lookup(db.upcast())
84            .as_syntax_node()
85            .span(db.upcast()),
86        None,
87    );
88    // If the panic comes from a real file (macros generate code in new virtual files)
89    if initial_file_id == file_id {
90        diagnostics.push(PluginDiagnostic {
91            stable_ptr: init_node.stable_ptr(),
92            message: PanicInCode.diagnostic_message().to_owned(),
93            severity: Severity::Warning,
94        });
95    } else {
96        // If the originating location is a different file get the syntax node that generated the
97        // code that contains a panic.
98        if_chain! {
99            if let Some(text_position) = span.position_in_file(db.upcast(), file_id);
100            if let Ok(file_node) = db.file_syntax(file_id);
101            then {
102                let syntax_node = file_node.lookup_position(db.upcast(), text_position.start);
103                diagnostics.push(PluginDiagnostic {
104                    stable_ptr: syntax_node.stable_ptr(),
105                    message: PanicInCode.diagnostic_message().to_owned(),
106                    severity: Severity::Warning,
107                });
108            }
109        }
110    }
111}