Skip to content

Add unnecessary_safety_doc lint #9822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4449,6 +4449,7 @@ Released 2018-09-13
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
[`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::doc::MISSING_PANICS_DOC_INFO,
crate::doc::MISSING_SAFETY_DOC_INFO,
crate::doc::NEEDLESS_DOCTEST_MAIN_INFO,
crate::doc::UNNECESSARY_SAFETY_DOC_INFO,
crate::double_parens::DOUBLE_PARENS_INFO,
crate::drop_forget_ref::DROP_COPY_INFO,
crate::drop_forget_ref::DROP_NON_DROP_INFO,
Expand Down
94 changes: 69 additions & 25 deletions clippy_lints/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,42 @@ declare_clippy_lint! {
"possible typo for an intra-doc link"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for the doc comments of publicly visible
/// safe functions and traits and warns if there is a `# Safety` section.
///
/// ### Why is this bad?
/// Safe functions and traits are safe to implement and therefore do not
/// need to describe safety preconditions that users are required to uphold.
///
/// ### Examples
/// ```rust
///# type Universe = ();
/// /// # Safety
/// ///
/// /// This function should not be called before the horsemen are ready.
/// pub fn start_apocalypse_but_safely(u: &mut Universe) {
/// unimplemented!();
/// }
/// ```
///
/// The function is safe, so there shouldn't be any preconditions
/// that have to be explained for safety reasons.
///
/// ```rust
///# type Universe = ();
/// /// This function should really be documented
/// pub fn start_apocalypse(u: &mut Universe) {
/// unimplemented!();
/// }
/// ```
#[clippy::version = "1.66.0"]
pub UNNECESSARY_SAFETY_DOC,
style,
"`pub fn` or `pub trait` with `# Safety` docs"
}

#[expect(clippy::module_name_repetitions)]
#[derive(Clone)]
pub struct DocMarkdown {
Expand All @@ -243,7 +279,8 @@ impl_lint_pass!(DocMarkdown => [
MISSING_SAFETY_DOC,
MISSING_ERRORS_DOC,
MISSING_PANICS_DOC,
NEEDLESS_DOCTEST_MAIN
NEEDLESS_DOCTEST_MAIN,
UNNECESSARY_SAFETY_DOC,
]);

impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
Expand All @@ -254,7 +291,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {

fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let headers = check_attrs(cx, &self.valid_idents, attrs);
let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
match item.kind {
hir::ItemKind::Fn(ref sig, _, body_id) => {
if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
Expand All @@ -271,15 +308,20 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
hir::ItemKind::Impl(impl_) => {
self.in_trait_impl = impl_.of_trait.is_some();
},
hir::ItemKind::Trait(_, unsafety, ..) => {
if !headers.safety && unsafety == hir::Unsafety::Unsafe {
span_lint(
cx,
MISSING_SAFETY_DOC,
cx.tcx.def_span(item.def_id),
"docs for unsafe trait missing `# Safety` section",
);
}
hir::ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) {
(false, hir::Unsafety::Unsafe) => span_lint(
cx,
MISSING_SAFETY_DOC,
cx.tcx.def_span(item.def_id),
"docs for unsafe trait missing `# Safety` section",
),
(true, hir::Unsafety::Normal) => span_lint(
cx,
UNNECESSARY_SAFETY_DOC,
cx.tcx.def_span(item.def_id),
"docs for safe trait have unnecessary `# Safety` section",
),
_ => (),
},
_ => (),
}
Expand All @@ -293,7 +335,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {

fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let headers = check_attrs(cx, &self.valid_idents, attrs);
let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind {
if !in_external_macro(cx.tcx.sess, item.span) {
lint_for_missing_headers(cx, item.def_id.def_id, sig, headers, None, None);
Expand All @@ -303,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {

fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let headers = check_attrs(cx, &self.valid_idents, attrs);
let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return };
if self.in_trait_impl || in_external_macro(cx.tcx.sess, item.span) {
return;
}
Expand Down Expand Up @@ -343,14 +385,20 @@ fn lint_for_missing_headers(
}

let span = cx.tcx.def_span(def_id);

if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
span_lint(
match (headers.safety, sig.header.unsafety) {
(false, hir::Unsafety::Unsafe) => span_lint(
cx,
MISSING_SAFETY_DOC,
span,
"unsafe function's docs miss `# Safety` section",
);
),
(true, hir::Unsafety::Normal) => span_lint(
cx,
UNNECESSARY_SAFETY_DOC,
span,
"safe function's docs have unnecessary `# Safety` section",
),
_ => (),
}
if !headers.panics && panic_span.is_some() {
span_lint_and_note(
Expand Down Expand Up @@ -452,7 +500,7 @@ struct DocHeaders {
panics: bool,
}

fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> DocHeaders {
fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> {
use pulldown_cmark::{BrokenLink, CowStr, Options};
/// We don't want the parser to choke on intra doc links. Since we don't
/// actually care about rendering them, just pretend that all broken links are
Expand All @@ -473,11 +521,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
} else if attr.has_name(sym::doc) {
// ignore mix of sugared and non-sugared doc
// don't trigger the safety or errors check
return DocHeaders {
safety: true,
errors: true,
panics: true,
};
return None;
}
}

Expand All @@ -489,7 +533,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
}

if doc.is_empty() {
return DocHeaders::default();
return Some(DocHeaders::default());
}

let mut cb = fake_broken_link_callback;
Expand All @@ -512,7 +556,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
(previous, current) => Err(((previous, previous_range), (current, current_range))),
}
});
check_doc(cx, valid_idents, events, &spans)
Some(check_doc(cx, valid_idents, events, &spans))
}

const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/auxiliary/doc_unsafe_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ macro_rules! undocd_unsafe {
}
};
}
#[macro_export]
macro_rules! undocd_safe {
() => {
pub fn vey_oy() {
unimplemented!();
}
};
}
148 changes: 148 additions & 0 deletions tests/ui/doc_unnecessary_unsafe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// aux-build:doc_unsafe_macros.rs

#![allow(clippy::let_unit_value)]

#[macro_use]
extern crate doc_unsafe_macros;

/// This is has no safety section, and does not need one either
pub fn destroy_the_planet() {
unimplemented!();
}

/// This one does not need a `Safety` section
///
/// # Safety
///
/// This function shouldn't be called unless the horsemen are ready
pub fn apocalypse(universe: &mut ()) {
unimplemented!();
}

/// This is a private function, skip to match behavior with `missing_safety_doc`.
///
/// # Safety
///
/// Boo!
fn you_dont_see_me() {
unimplemented!();
}

mod private_mod {
/// This is public but unexported function, skip to match behavior with `missing_safety_doc`.
///
/// # Safety
///
/// Very safe!
pub fn only_crate_wide_accessible() {
unimplemented!();
}

/// # Safety
///
/// Unnecessary safety!
pub fn republished() {
unimplemented!();
}
}

pub use private_mod::republished;

pub trait SafeTraitSafeMethods {
fn woefully_underdocumented(self);

/// # Safety
///
/// Unnecessary!
fn documented(self);
}

pub trait SafeTrait {
fn method();
}

/// # Safety
///
/// Unnecessary!
pub trait DocumentedSafeTrait {
fn method2();
}

pub struct Struct;

impl SafeTraitSafeMethods for Struct {
fn woefully_underdocumented(self) {
// all is well
}

fn documented(self) {
// all is still well
}
}

impl SafeTrait for Struct {
fn method() {}
}

impl DocumentedSafeTrait for Struct {
fn method2() {}
}

impl Struct {
/// # Safety
///
/// Unnecessary!
pub fn documented() -> Self {
unimplemented!();
}

pub fn undocumented(&self) {
unimplemented!();
}

/// Private, fine again to stay consistent with `missing_safety_doc`.
///
/// # Safety
///
/// Unnecessary!
fn private(&self) {
unimplemented!();
}
}

macro_rules! very_safe {
() => {
pub fn whee() {
unimplemented!()
}

/// # Safety
///
/// Driving is very safe already!
pub fn drive() {
whee()
}
};
}

very_safe!();

// we don't lint code from external macros
undocd_safe!();

fn main() {}

// do not lint if any parent has `#[doc(hidden)]` attribute
// see #7347
#[doc(hidden)]
pub mod __macro {
pub struct T;
impl T {
pub unsafe fn f() {}
}
}

/// # Implementation safety
pub trait DocumentedSafeTraitWithImplementationHeader {
fn method();
}
Loading