Skip to content

Commit ac41ba9

Browse files
committed
Responding to PR comments
- Fixing typos - Code cleanup and more tests - Adding handling and tests for all kinds of macros
1 parent 4806c5e commit ac41ba9

File tree

9 files changed

+343
-25
lines changed

9 files changed

+343
-25
lines changed

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
849849
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
850850
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
851851
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
852-
Box::new(move |tcx| Box::new(undocumented_may_panic_call::UndocumentedMayPanicCall::new(tcx, conf)))
852+
Box::new(move |tcx| Box::new(undocumented_may_panic_call::UndocumentedMayPanicCall::new(tcx, conf))),
853853
// add late passes here, used by `cargo dev new_lint`
854854
];
855855
store.late_passes.extend(late_lints);

clippy_lints/src/undocumented_may_panic_call.rs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint;
3+
use clippy_utils::macros::macro_backtrace;
34
use clippy_utils::paths::{PathNS, lookup_path_str};
4-
use clippy_utils::{get_unique_attr, sym};
5+
use clippy_utils::{get_builtin_attr, is_from_proc_macro, sym};
56
use rustc_data_structures::fx::FxHashSet;
67
use rustc_hir as hir;
78
use rustc_hir::def_id::DefId;
@@ -24,7 +25,7 @@ declare_clippy_lint! {
2425
/// #[clippy::may_panic]
2526
/// fn my_panicable_func(n: u32) {
2627
/// if n % 2 == 0 {
27-
/// panic!("even number not allowed")
28+
/// panic!("even numbers are not allowed")
2829
/// }
2930
/// }
3031
///
@@ -38,7 +39,7 @@ declare_clippy_lint! {
3839
/// #[clippy::may_panic]
3940
/// fn my_panicable_func(n: u32) {
4041
/// if n % 2 == 0 {
41-
/// panic!("even number not allowed")
42+
/// panic!("even numbers are not allowed")
4243
/// }
4344
/// }
4445
///
@@ -83,16 +84,13 @@ impl UndocumentedMayPanicCall {
8384
// A function is a may_panic_function if it has the may_panic attribute
8485
// or is in the may-panic-functions configuration
8586
fn is_may_panic_function(&self, cx: &LateContext<'_>, def_id: DefId) -> bool {
86-
if get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(def_id), sym::may_panic).is_some() {
87-
return true;
88-
}
89-
90-
self.may_panic_def_ids.contains(&def_id)
87+
get_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(def_id), sym::may_panic).count() > 0
88+
|| self.may_panic_def_ids.contains(&def_id)
9189
}
9290
}
9391

94-
impl LateLintPass<'_> for UndocumentedMayPanicCall {
95-
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ rustc_hir::Expr<'_>) {
92+
impl<'tcx> LateLintPass<'tcx> for UndocumentedMayPanicCall {
93+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
9694
let def_id = match &expr.kind {
9795
hir::ExprKind::Call(func, _args) => {
9896
if let hir::ExprKind::Path(qpath) = &func.kind {
@@ -109,23 +107,59 @@ impl LateLintPass<'_> for UndocumentedMayPanicCall {
109107

110108
if let Some(def_id) = def_id
111109
&& self.is_may_panic_function(cx, def_id)
112-
&& !has_panic_comment_above(cx, expr.span)
110+
&& let Some(lint_span) = check_for_missing_panic_comment(cx, expr)
113111
{
114112
span_lint(
115113
cx,
116114
UNDOCUMENTED_MAY_PANIC_CALL,
117-
expr.span,
115+
lint_span,
118116
"call to a function that may panic is not documented with a `// Panic:` comment",
119117
);
120118
}
121119
}
122120
}
123121

124-
/// Checks if the lines immediately preceding the call contain a "Panic:" comment.
125-
fn has_panic_comment_above(cx: &LateContext<'_>, call_span: rustc_span::Span) -> bool {
122+
/// Checks if a panic comment is missing and returns the span to lint at
123+
/// Returns `None` if a panic comment exists
124+
/// Returns `Some(span)` if a panic comment is missing
125+
fn check_for_missing_panic_comment<'tcx>(
126+
cx: &LateContext<'tcx>,
127+
expr: &'tcx hir::Expr<'tcx>,
128+
) -> Option<rustc_span::Span> {
129+
let call_span = expr.span;
130+
131+
if call_span.from_expansion() {
132+
// For external macros or proc macros, the user cannot modify the macro body,
133+
// so we only check callsites
134+
let is_external_or_proc_macro =
135+
call_span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr);
136+
137+
// For locally defined macros, check the macro body first before checking the callsite
138+
if !is_external_or_proc_macro && has_panic_comment_above_span(cx, call_span) {
139+
return None;
140+
}
141+
142+
let mut lint_span = None;
143+
for macro_call in macro_backtrace(call_span) {
144+
if has_panic_comment_above_span(cx, macro_call.span) {
145+
return None;
146+
}
147+
lint_span = Some(macro_call.span);
148+
}
149+
150+
lint_span
151+
} else if has_panic_comment_above_span(cx, call_span) {
152+
None
153+
} else {
154+
Some(call_span)
155+
}
156+
}
157+
158+
/// Checks if the lines immediately preceding a span contain a "Panic:" comment
159+
fn has_panic_comment_above_span(cx: &LateContext<'_>, span: rustc_span::Span) -> bool {
126160
let source_map = cx.sess().source_map();
127161

128-
if let Ok(call_line) = source_map.lookup_line(call_span.lo())
162+
if let Ok(call_line) = source_map.lookup_line(span.lo())
129163
&& call_line.line > 0
130164
&& let Some(src) = call_line.sf.src.as_deref()
131165
{

clippy_utils/src/attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn get_builtin_attr<'a, A: AttributeExt + 'a>(
3434
// The following attributes are for the 3rd party crate authors.
3535
// See book/src/attribs.md
3636
| sym::has_significant_drop
37-
| sym::format_args
37+
| sym::format_args
3838
| sym::may_panic => None,
3939
_ => {
4040
sess.dcx().span_err(segment2.span, "usage of unknown attribute");

tests/ui-toml/undocumented_may_panic_call/undocumented_may_panic_call.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
//@aux-build:../../ui/auxiliary/proc_macro_derive.rs
12
#![warn(clippy::undocumented_may_panic_call)]
23

4+
extern crate proc_macro_derive;
5+
use proc_macro_derive::DeriveMayPanic;
6+
37
fn main() {
48
let mut v = vec![1, 2, 3];
59

@@ -9,3 +13,13 @@ fn main() {
913
// Panic: The capaticy is less than isize::MAX, so we are safe!
1014
v.push(5);
1115
}
16+
17+
// Test proc macro derives with configured may-panic function (Vec::push)
18+
19+
#[derive(DeriveMayPanic)]
20+
//~^ undocumented_may_panic_call
21+
struct TestProcMacroNoComment;
22+
23+
// Panic: This derive macro is totally safe for this struct!
24+
#[derive(DeriveMayPanic)]
25+
struct TestProcMacroWithComment;
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
error: call to a function that may panic is not documented with a `// Panic:` comment
2-
--> tests/ui-toml/undocumented_may_panic_call/undocumented_may_panic_call.rs:6:5
2+
--> tests/ui-toml/undocumented_may_panic_call/undocumented_may_panic_call.rs:10:5
33
|
44
LL | v.push(4);
55
| ^^^^^^^^^
66
|
77
= note: `-D clippy::undocumented-may-panic-call` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::undocumented_may_panic_call)]`
99

10-
error: aborting due to 1 previous error
10+
error: call to a function that may panic is not documented with a `// Panic:` comment
11+
--> tests/ui-toml/undocumented_may_panic_call/undocumented_may_panic_call.rs:19:10
12+
|
13+
LL | #[derive(DeriveMayPanic)]
14+
| ^^^^^^^^^^^^^^
15+
16+
error: aborting due to 2 previous errors
1117

tests/ui/auxiliary/macro_rules.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,11 @@ macro_rules! double_parens {
6969
InterruptMask((($a.union($b).union($c).union($d)).into_bits()) as u32)
7070
}};
7171
}
72+
73+
/// Used for testing undocumented_may_panic_call
74+
#[macro_export]
75+
macro_rules! external_macro_may_panic {
76+
($func:path) => {
77+
$func()
78+
};
79+
}

tests/ui/auxiliary/proc_macro_derive.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,14 @@ pub fn derive_double_parens(_: TokenStream) -> TokenStream {
241241
}
242242
}
243243
}
244+
245+
#[proc_macro_derive(DeriveMayPanic)]
246+
pub fn derive_may_panic(_: TokenStream) -> TokenStream {
247+
// Used for testing the `undocumented_may_panic_call` lint's handling of proc macros
248+
quote! {
249+
fn _derived_may_panic_fn() {
250+
let mut v = Vec::new();
251+
v.push(1);
252+
}
253+
}
254+
}

tests/ui/undocumented_may_panic_call.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
//@aux-build:macro_rules.rs
12
#![warn(clippy::undocumented_may_panic_call)]
23
#![allow(clippy::short_circuit_statement)]
34
#![allow(clippy::manual_is_multiple_of)]
45

6+
extern crate macro_rules;
7+
58
#[clippy::may_panic]
69
pub fn dangerous(n: usize) -> bool {
710
if n % 2 == 0 {
@@ -84,3 +87,181 @@ fn test_trait_object(t: &dyn MyTrait) {
8487
// Panic: This is safe, just trust me.
8588
let _ = t.trait_panic_method();
8689
}
90+
91+
// Test macro handling
92+
macro_rules! call_dangerous {
93+
() => {
94+
dangerous(1)
95+
};
96+
}
97+
98+
macro_rules! call_dangerous_with_panic_comment {
99+
() => {
100+
// Panic: This is documented inside the macro
101+
dangerous(1)
102+
};
103+
}
104+
105+
fn test_macros() {
106+
// Should lint: macro callsite has no comment
107+
let _ = call_dangerous!();
108+
//~^ undocumented_may_panic_call
109+
110+
// Panic: This documents the macro callsite
111+
let _ = call_dangerous!();
112+
113+
// Should not lint: macro body has the comment
114+
let _ = call_dangerous_with_panic_comment!();
115+
}
116+
117+
macro_rules! nested_macro {
118+
() => {
119+
call_dangerous!()
120+
};
121+
}
122+
123+
fn test_nested_macros() {
124+
// Should lint: no comment at any level
125+
let _ = nested_macro!();
126+
//~^ undocumented_may_panic_call
127+
128+
// Panic: This documents the outermost macro callsite
129+
let _ = nested_macro!();
130+
}
131+
132+
#[allow(clippy::needless_bool, clippy::unnecessary_operation, clippy::no_effect)]
133+
fn weird_exprs(t: &dyn MyTrait) {
134+
// The Panic comment should be in the second If statement
135+
if if if true { true } else { false } {
136+
t.trait_panic_method() == 0
137+
//~^ undocumented_may_panic_call
138+
} else {
139+
false
140+
} {
141+
true
142+
} else {
143+
false
144+
};
145+
if if if true { true } else { false } {
146+
// PANIC: This is safe!
147+
t.trait_panic_method() == 0
148+
} else {
149+
false
150+
} {
151+
true
152+
} else {
153+
false
154+
};
155+
#[clippy::may_panic]
156+
fn panic<T>(val: T) -> T {
157+
val
158+
}
159+
macro_rules! mac {
160+
($t:tt) => {
161+
$t
162+
};
163+
}
164+
// Panic comment should go on the line before the panic() function
165+
async {
166+
{};
167+
({
168+
(u8::max)(
169+
// PANIC: Safe!
170+
panic(1) + mac!(2),
171+
mac!(2),
172+
)
173+
});
174+
};
175+
}
176+
177+
// Test external macros
178+
use macro_rules::external_macro_may_panic;
179+
180+
#[clippy::may_panic]
181+
fn external_may_panic() {}
182+
183+
// External macro wrapped in local macro
184+
macro_rules! wrap_external {
185+
() => {
186+
external_macro_may_panic!(external_may_panic)
187+
};
188+
}
189+
190+
// External macro wrapped in local macro but documented
191+
macro_rules! wrap_external_with_panic_doc {
192+
() => {
193+
// Panic: documenting may_panic here
194+
external_macro_may_panic!(external_may_panic)
195+
};
196+
}
197+
198+
fn test_external_macro() {
199+
// Should lint: external macro callsite has no comment
200+
external_macro_may_panic!(external_may_panic);
201+
//~^ undocumented_may_panic_call
202+
203+
// Panic: This documents the external macro callsite
204+
external_macro_may_panic!(external_may_panic);
205+
206+
// External macro wrapped in local - should lint at wrap_external callsite
207+
wrap_external!();
208+
//~^ undocumented_may_panic_call
209+
210+
// Panic: wrapping an external macro
211+
wrap_external!();
212+
213+
// No comment needed, call is documented inside the local macro
214+
wrap_external_with_panic_doc!();
215+
}
216+
217+
// Macro that expands to multiple calls
218+
macro_rules! multi_call {
219+
() => {{
220+
dangerous(1);
221+
dangerous(3);
222+
dangerous(5)
223+
}};
224+
}
225+
226+
// Macro with the panic comment between calls (should still lint the first)
227+
macro_rules! comment_between {
228+
() => {{
229+
dangerous(1);
230+
// Panic: only covers the second call
231+
dangerous(3)
232+
}};
233+
}
234+
235+
fn test_macro_with_mutiple_may_panic_calls() {
236+
// Multiple calls in one macro - all should lint pointing to same callsite
237+
multi_call!();
238+
//~^ undocumented_may_panic_call
239+
//~| undocumented_may_panic_call
240+
//~| undocumented_may_panic_call
241+
242+
// Panic: Extremely safe!
243+
multi_call!();
244+
245+
// Comment between calls in macro - but macro still has a dangerous call, so lint
246+
comment_between!();
247+
//~^ undocumented_may_panic_call
248+
249+
// Panic: We are safe now!
250+
comment_between!();
251+
}
252+
253+
// Macro taking an expression that contains a may_panic call
254+
macro_rules! wrap_expr {
255+
($e:expr) => {
256+
$e
257+
};
258+
}
259+
260+
fn test_macro_with_may_panic_arg() {
261+
// The may_panic call is in the argument, not the macro body
262+
wrap_expr!(dangerous(1));
263+
//~^ undocumented_may_panic_call
264+
265+
// Panic: Very safe!
266+
wrap_expr!(dangerous(1));
267+
}

0 commit comments

Comments
 (0)