-
Notifications
You must be signed in to change notification settings - Fork 883
/
blind_except.rs
158 lines (149 loc) · 4.84 KB
/
blind_except.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_python_semantic::analyze::logging;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `except` clauses that catch all exceptions.
///
/// ## Why is this bad?
/// Overly broad `except` clauses can lead to unexpected behavior, such as
/// catching `KeyboardInterrupt` or `SystemExit` exceptions that prevent the
/// user from exiting the program.
///
/// Instead of catching all exceptions, catch only those that are expected to
/// be raised in the `try` block.
///
/// ## Example
/// ```python
/// try:
/// foo()
/// except BaseException:
/// ...
/// ```
///
/// Use instead:
/// ```python
/// try:
/// foo()
/// except FileNotFoundError:
/// ...
/// ```
///
/// ## References
/// - [Python documentation: The `try` statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)
/// - [Python documentation: Exception hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
#[violation]
pub struct BlindExcept {
name: String,
}
impl Violation for BlindExcept {
#[derive_message_formats]
fn message(&self) -> String {
let BlindExcept { name } = self;
format!("Do not catch blind exception: `{name}`")
}
}
/// BLE001
pub(crate) fn blind_except(
checker: &mut Checker,
type_: Option<&Expr>,
name: Option<&str>,
body: &[Stmt],
) {
let Some(type_) = type_ else {
return;
};
let Expr::Name(ast::ExprName { id, .. }) = &type_ else {
return;
};
if !matches!(id.as_str(), "BaseException" | "Exception") {
return;
}
if !checker.semantic().is_builtin(id) {
return;
}
// If the exception is re-raised, don't flag an error.
if body.iter().any(|stmt| {
if let Stmt::Raise(ast::StmtRaise { exc, .. }) = stmt {
if let Some(exc) = exc {
if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() {
name.is_some_and(|name| id == name)
} else {
false
}
} else {
true
}
} else {
false
}
}) {
return;
}
// If the exception is logged, don't flag an error.
if body.iter().any(|stmt| {
if let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt {
if let Expr::Call(ast::ExprCall {
func, arguments, ..
}) = value.as_ref()
{
match func.as_ref() {
Expr::Attribute(ast::ExprAttribute { attr, .. }) => {
if logging::is_logger_candidate(
func,
checker.semantic(),
&checker.settings.logger_objects,
) {
match attr.as_str() {
"exception" => return true,
"error" => {
if let Some(keyword) = arguments.find_keyword("exc_info") {
if is_const_true(&keyword.value) {
return true;
}
}
}
_ => {}
}
}
}
Expr::Name(ast::ExprName { .. }) => {
if checker
.semantic()
.resolve_call_path(func.as_ref())
.is_some_and(|call_path| match call_path.as_slice() {
["logging", "exception"] => true,
["logging", "error"] => {
if let Some(keyword) = arguments.find_keyword("exc_info") {
if is_const_true(&keyword.value) {
return true;
}
}
false
}
_ => false,
})
{
return true;
}
}
_ => {
return false;
}
}
}
}
false
}) {
return;
}
checker.diagnostics.push(Diagnostic::new(
BlindExcept {
name: id.to_string(),
},
type_.range(),
));
}