Skip to content

Commit 28794e9

Browse files
committed
Auto merge of rust-lang#6523 - brightly-salty:missing-panic-doc, r=flip1995
Add new lint "missing_panics_doc" fixes rust-lang#1974 changelog: Added the "missing_panics_doc" lint which lints when public functions that may panic are missing "# Panics" in their doc comment
2 parents f870876 + bde667a commit 28794e9

File tree

16 files changed

+346
-38
lines changed

16 files changed

+346
-38
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,7 @@ Released 2018-09-13
20792079
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
20802080
[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
20812081
[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
2082+
[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
20822083
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
20832084
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
20842085
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals

clippy_dev/src/bless.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::ne
2424
fs::metadata(path).ok()?.modified().ok()
2525
});
2626

27+
/// # Panics
28+
///
29+
/// Panics if the path to a test file is broken
2730
pub fn bless(ignore_timestamp: bool) {
2831
let test_suite_dirs = [
2932
clippy_project_root().join("tests").join("ui"),

clippy_dev/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ pub struct FileChange {
236236
/// `path` is the relative path to the file on which you want to perform the replacement.
237237
///
238238
/// See `replace_region_in_text` for documentation of the other options.
239+
///
240+
/// # Panics
241+
///
242+
/// Panics if the path could not read or then written
239243
pub fn replace_region_in_file<F>(
240244
path: &Path,
241245
start: &str,
@@ -283,6 +287,10 @@ where
283287
/// .new_lines;
284288
/// assert_eq!("replace_start\na different\ntext\nreplace_end", result);
285289
/// ```
290+
///
291+
/// # Panics
292+
///
293+
/// Panics if start or end is not valid regex
286294
pub fn replace_region_in_text<F>(text: &str, start: &str, end: &str, replace_start: bool, replacements: F) -> FileChange
287295
where
288296
F: FnOnce() -> Vec<String>,
@@ -329,6 +337,11 @@ where
329337
}
330338

331339
/// Returns the path to the Clippy project directory
340+
///
341+
/// # Panics
342+
///
343+
/// Panics if the current directory could not be retrieved, there was an error reading any of the
344+
/// Cargo.toml files or ancestor directory is the clippy root directory
332345
#[must_use]
333346
pub fn clippy_project_root() -> PathBuf {
334347
let current_dir = std::env::current_dir().unwrap();

clippy_dev/src/ra_setup.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::path::{Path, PathBuf};
88
// This allows rust analyzer to analyze rustc internals and show proper information inside clippy
99
// code. See https://github.com/rust-analyzer/rust-analyzer/issues/3517 and https://github.com/rust-lang/rust-clippy/issues/5514 for details
1010

11+
/// # Panics
12+
///
13+
/// Panics if `rustc_path` does not lead to a rustc repo or the files could not be read
1114
pub fn run(rustc_path: Option<&str>) {
1215
// we can unwrap here because the arg is required by clap
1316
let rustc_path = PathBuf::from(rustc_path.unwrap());

clippy_dev/src/serve.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use std::process::Command;
44
use std::thread;
55
use std::time::{Duration, SystemTime};
66

7+
/// # Panics
8+
///
9+
/// Panics if the python commands could not be spawned
710
pub fn run(port: u16, lint: Option<&str>) -> ! {
811
let mut url = Some(match lint {
912
None => format!("http://localhost:{}", port),

clippy_lints/src/doc.rs

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::utils::{implements_trait, is_entrypoint_fn, is_type_diagnostic_item, return_ty, span_lint};
1+
use crate::utils::{
2+
implements_trait, is_entrypoint_fn, is_type_diagnostic_item, match_panic_def_id, method_chain_args, return_ty,
3+
span_lint, span_lint_and_note,
4+
};
25
use if_chain::if_chain;
36
use itertools::Itertools;
47
use rustc_ast::ast::{Async, AttrKind, Attribute, FnRetTy, ItemKind};
@@ -8,7 +11,10 @@ use rustc_data_structures::sync::Lrc;
811
use rustc_errors::emitter::EmitterWriter;
912
use rustc_errors::Handler;
1013
use rustc_hir as hir;
14+
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
15+
use rustc_hir::{Expr, ExprKind, QPath};
1116
use rustc_lint::{LateContext, LateLintPass};
17+
use rustc_middle::hir::map::Map;
1218
use rustc_middle::lint::in_external_macro;
1319
use rustc_middle::ty;
1420
use rustc_parse::maybe_new_parser_from_source_str;
@@ -122,6 +128,37 @@ declare_clippy_lint! {
122128
"`pub fn` returns `Result` without `# Errors` in doc comment"
123129
}
124130

131+
declare_clippy_lint! {
132+
/// **What it does:** Checks the doc comments of publicly visible functions that
133+
/// may panic and warns if there is no `# Panics` section.
134+
///
135+
/// **Why is this bad?** Documenting the scenarios in which panicking occurs
136+
/// can help callers who do not want to panic to avoid those situations.
137+
///
138+
/// **Known problems:** None.
139+
///
140+
/// **Examples:**
141+
///
142+
/// Since the following function may panic it has a `# Panics` section in
143+
/// its doc comment:
144+
///
145+
/// ```rust
146+
/// /// # Panics
147+
/// ///
148+
/// /// Will panic if y is 0
149+
/// pub fn divide_by(x: i32, y: i32) -> i32 {
150+
/// if y == 0 {
151+
/// panic!("Cannot divide by 0")
152+
/// } else {
153+
/// x / y
154+
/// }
155+
/// }
156+
/// ```
157+
pub MISSING_PANICS_DOC,
158+
pedantic,
159+
"`pub fn` may panic without `# Panics` in doc comment"
160+
}
161+
125162
declare_clippy_lint! {
126163
/// **What it does:** Checks for `fn main() { .. }` in doctests
127164
///
@@ -166,7 +203,9 @@ impl DocMarkdown {
166203
}
167204
}
168205

169-
impl_lint_pass!(DocMarkdown => [DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, NEEDLESS_DOCTEST_MAIN]);
206+
impl_lint_pass!(DocMarkdown =>
207+
[DOC_MARKDOWN, MISSING_SAFETY_DOC, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, NEEDLESS_DOCTEST_MAIN]
208+
);
170209

171210
impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
172211
fn check_crate(&mut self, cx: &LateContext<'tcx>, krate: &'tcx hir::Crate<'_>) {
@@ -180,7 +219,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
180219
if !(is_entrypoint_fn(cx, cx.tcx.hir().local_def_id(item.hir_id).to_def_id())
181220
|| in_external_macro(cx.tcx.sess, item.span))
182221
{
183-
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id));
222+
let body = cx.tcx.hir().body(body_id);
223+
let impl_item_def_id = cx.tcx.hir().local_def_id(item.hir_id);
224+
let mut fpu = FindPanicUnwrap {
225+
cx,
226+
typeck_results: cx.tcx.typeck(impl_item_def_id),
227+
panic_span: None,
228+
};
229+
fpu.visit_expr(&body.value);
230+
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
184231
}
185232
},
186233
hir::ItemKind::Impl(ref impl_) => {
@@ -200,7 +247,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
200247
let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
201248
if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
202249
if !in_external_macro(cx.tcx.sess, item.span) {
203-
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, None);
250+
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, None, None);
204251
}
205252
}
206253
}
@@ -211,7 +258,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
211258
return;
212259
}
213260
if let hir::ImplItemKind::Fn(ref sig, body_id) = item.kind {
214-
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id));
261+
let body = cx.tcx.hir().body(body_id);
262+
let impl_item_def_id = cx.tcx.hir().local_def_id(item.hir_id);
263+
let mut fpu = FindPanicUnwrap {
264+
cx,
265+
typeck_results: cx.tcx.typeck(impl_item_def_id),
266+
panic_span: None,
267+
};
268+
fpu.visit_expr(&body.value);
269+
lint_for_missing_headers(cx, item.hir_id, item.span, sig, headers, Some(body_id), fpu.panic_span);
215270
}
216271
}
217272
}
@@ -223,6 +278,7 @@ fn lint_for_missing_headers<'tcx>(
223278
sig: &hir::FnSig<'_>,
224279
headers: DocHeaders,
225280
body_id: Option<hir::BodyId>,
281+
panic_span: Option<Span>,
226282
) {
227283
if !cx.access_levels.is_exported(hir_id) {
228284
return; // Private functions do not require doc comments
@@ -235,6 +291,16 @@ fn lint_for_missing_headers<'tcx>(
235291
"unsafe function's docs miss `# Safety` section",
236292
);
237293
}
294+
if !headers.panics && panic_span.is_some() {
295+
span_lint_and_note(
296+
cx,
297+
MISSING_PANICS_DOC,
298+
span,
299+
"docs for function which may panic missing `# Panics` section",
300+
panic_span,
301+
"first possible panic found here",
302+
);
303+
}
238304
if !headers.errors {
239305
if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) {
240306
span_lint(
@@ -321,6 +387,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span:
321387
struct DocHeaders {
322388
safety: bool,
323389
errors: bool,
390+
panics: bool,
324391
}
325392

326393
fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &'a [Attribute]) -> DocHeaders {
@@ -338,6 +405,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
338405
return DocHeaders {
339406
safety: true,
340407
errors: true,
408+
panics: true,
341409
};
342410
}
343411
}
@@ -353,6 +421,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
353421
return DocHeaders {
354422
safety: false,
355423
errors: false,
424+
panics: false,
356425
};
357426
}
358427

@@ -394,6 +463,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
394463
let mut headers = DocHeaders {
395464
safety: false,
396465
errors: false,
466+
panics: false,
397467
};
398468
let mut in_code = false;
399469
let mut in_link = None;
@@ -439,6 +509,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
439509
}
440510
headers.safety |= in_heading && text.trim() == "Safety";
441511
headers.errors |= in_heading && text.trim() == "Errors";
512+
headers.panics |= in_heading && text.trim() == "Panics";
442513
let index = match spans.binary_search_by(|c| c.0.cmp(&range.start)) {
443514
Ok(o) => o,
444515
Err(e) => e - 1,
@@ -607,3 +678,47 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
607678
);
608679
}
609680
}
681+
682+
struct FindPanicUnwrap<'a, 'tcx> {
683+
cx: &'a LateContext<'tcx>,
684+
panic_span: Option<Span>,
685+
typeck_results: &'tcx ty::TypeckResults<'tcx>,
686+
}
687+
688+
impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
689+
type Map = Map<'tcx>;
690+
691+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
692+
if self.panic_span.is_some() {
693+
return;
694+
}
695+
696+
// check for `begin_panic`
697+
if_chain! {
698+
if let ExprKind::Call(ref func_expr, _) = expr.kind;
699+
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
700+
if let Some(path_def_id) = path.res.opt_def_id();
701+
if match_panic_def_id(self.cx, path_def_id);
702+
then {
703+
self.panic_span = Some(expr.span);
704+
}
705+
}
706+
707+
// check for `unwrap`
708+
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
709+
let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
710+
if is_type_diagnostic_item(self.cx, reciever_ty, sym::option_type)
711+
|| is_type_diagnostic_item(self.cx, reciever_ty, sym::result_type)
712+
{
713+
self.panic_span = Some(expr.span);
714+
}
715+
}
716+
717+
// and check sub-expressions
718+
intravisit::walk_expr(self, expr);
719+
}
720+
721+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
722+
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
723+
}
724+
}

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
592592
&disallowed_method::DISALLOWED_METHOD,
593593
&doc::DOC_MARKDOWN,
594594
&doc::MISSING_ERRORS_DOC,
595+
&doc::MISSING_PANICS_DOC,
595596
&doc::MISSING_SAFETY_DOC,
596597
&doc::NEEDLESS_DOCTEST_MAIN,
597598
&double_comparison::DOUBLE_COMPARISONS,
@@ -1317,6 +1318,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13171318
LintId::of(&derive::UNSAFE_DERIVE_DESERIALIZE),
13181319
LintId::of(&doc::DOC_MARKDOWN),
13191320
LintId::of(&doc::MISSING_ERRORS_DOC),
1321+
LintId::of(&doc::MISSING_PANICS_DOC),
13201322
LintId::of(&empty_enum::EMPTY_ENUM),
13211323
LintId::of(&enum_variants::MODULE_NAME_REPETITIONS),
13221324
LintId::of(&enum_variants::PUB_ENUM_VARIANT_NAMES),

clippy_lints/src/utils/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub fn span_lint_and_help<'a, T: LintContext>(
110110
pub fn span_lint_and_note<'a, T: LintContext>(
111111
cx: &'a T,
112112
lint: &'static Lint,
113-
span: Span,
113+
span: impl Into<MultiSpan>,
114114
msg: &str,
115115
note_span: Option<Span>,
116116
note: &str,

mini-macro/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ extern crate proc_macro;
77
use proc_macro::{quote, TokenStream};
88

99
#[proc_macro_derive(ClippyMiniMacroTest)]
10+
/// # Panics
11+
///
12+
/// Panics if the macro derivation fails
1013
pub fn mini_macro(_: TokenStream) -> TokenStream {
1114
quote!(
1215
#[allow(unused)]

0 commit comments

Comments
 (0)