Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7199,6 +7199,7 @@ Released 2018-09-13
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
[`unchecked_time_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction
[`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion
[`undocumented_as_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_as_casts
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
Expand Down Expand Up @@ -7357,6 +7358,9 @@ Released 2018-09-13
[`check-incompatible-msrv-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-incompatible-msrv-in-tests
[`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
[`check-undocumented-as-any-cast`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-undocumented-as-any-cast
[`check-undocumented-as-const-ptr-cast`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-undocumented-as-const-ptr-cast
[`check-undocumented-as-mut-ptr-cast`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-undocumented-as-mut-ptr-cast
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold
[`disallowed-fields`]: https://doc.rust-lang.org/clippy/lint_configuration.html#disallowed-fields
Expand Down
30 changes: 30 additions & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,36 @@ Whether to also run the listed lints on private items.
* [`unnecessary_safety_doc`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc)


## `check-undocumented-as-any-cast`
Whether to require a `CAST:` comment for any `as` cast.

**Default Value:** `true`

---
**Affected lints:**
* [`undocumented_as_casts`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_as_casts)


## `check-undocumented-as-const-ptr-cast`
Whether to require a `CAST:` comment for casts to `*const T`.

**Default Value:** `true`

---
**Affected lints:**
* [`undocumented_as_casts`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_as_casts)


## `check-undocumented-as-mut-ptr-cast`
Whether to require a `CAST:` comment for casts to `*mut T`.

**Default Value:** `true`

---
**Affected lints:**
* [`undocumented_as_casts`](https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_as_casts)


## `cognitive-complexity-threshold`
The maximum cognitive complexity a function can have

Expand Down
9 changes: 9 additions & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,15 @@ define_Conf! {
/// Whether to also run the listed lints on private items.
#[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
check_private_items: bool = false,
/// Whether to require a `CAST:` comment for any `as` cast.
#[lints(undocumented_as_casts)]
check_undocumented_as_any_cast: bool = true,
/// Whether to require a `CAST:` comment for casts to `*const T`.
#[lints(undocumented_as_casts)]
check_undocumented_as_const_ptr_cast: bool = true,
/// Whether to require a `CAST:` comment for casts to `*mut T`.
#[lints(undocumented_as_casts)]
check_undocumented_as_mut_ptr_cast: bool = true,
/// The maximum cognitive complexity a function can have
#[lints(cognitive_complexity)]
cognitive_complexity_threshold: u64 = 25,
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 @@ -753,6 +753,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::types::TYPE_COMPLEXITY_INFO,
crate::types::VEC_BOX_INFO,
crate::unconditional_recursion::UNCONDITIONAL_RECURSION_INFO,
crate::undocumented_as_casts::UNDOCUMENTED_AS_CASTS_INFO,
crate::undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS_INFO,
crate::undocumented_unsafe_blocks::UNNECESSARY_SAFETY_COMMENT_INFO,
crate::unicode::INVISIBLE_CHARACTERS_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ mod transmute;
mod tuple_array_conversions;
mod types;
mod unconditional_recursion;
mod undocumented_as_casts;
mod undocumented_unsafe_blocks;
mod unicode;
mod uninhabited_references;
Expand Down Expand Up @@ -867,6 +868,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))),
Box::new(|_| Box::new(manual_noop_waker::ManualNoopWaker)),
Box::new(move |_| Box::new(undocumented_as_casts::UndocumentedAsCasts::new(conf))),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);
Expand Down
131 changes: 131 additions & 0 deletions clippy_lints/src/undocumented_as_casts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::text_has_marked_comment;
use rustc_hir::{Expr, ExprKind, MutTy, Mutability, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;

declare_clippy_lint! {
/// ### What it does
/// Checks for `as` casts that do not have a preceding `// CAST:` comment.
///
/// By default, this lint enforces comments for all `as` casts through
/// `check_undocumented_as_any_cast = true`.
///
/// ### Why is this bad?
/// `as` casts are powerful and potentially dangerous. Requiring a documentation comment
/// ensures the developer has considered the safety and necessity of the conversion.
///
/// Additional scenario-specific options (for example `as *mut` and `as *const`) exist so
/// existing codebases can roll out enforcement in higher-risk areas first and incrementally
/// add missing `// CAST:` comments.
///
/// ### Example
/// ```rust,ignore
/// let p: *mut u32 = &mut 42_u32;
/// let _ = p as *mut i32;
/// ```
/// Use instead:
/// ```rust,ignore
/// let p: *mut u32 = &mut 42_u32;
/// // CAST: reason for the cast
/// let _ = p as *mut i32;
///
///
/// let q: *const u32 = &42_u32;
/// // CAST: reason for the cast
/// let _ = q as *const i32;
/// ```
#[clippy::version = "1.96.0"]
pub UNDOCUMENTED_AS_CASTS,
restriction,
"`as` casts without a `// CAST:` explanation"
}

impl_lint_pass!(UndocumentedAsCasts => [UNDOCUMENTED_AS_CASTS]);

pub struct UndocumentedAsCasts {
check_undocumented_as_any_cast: bool,
check_undocumented_as_mut_ptr_cast: bool,
check_undocumented_as_const_ptr_cast: bool,
}

impl UndocumentedAsCasts {
pub fn new(conf: &'static Conf) -> Self {
Self {
check_undocumented_as_any_cast: conf.check_undocumented_as_any_cast,
check_undocumented_as_mut_ptr_cast: conf.check_undocumented_as_mut_ptr_cast,
check_undocumented_as_const_ptr_cast: conf.check_undocumented_as_const_ptr_cast,
}
}
}

#[derive(Clone, Copy)]
enum AsCastScenario {
AnyAs,
MutPtr,
ConstPtr,
}

impl AsCastScenario {
fn is_enabled(self, lint: &UndocumentedAsCasts) -> bool {
match self {
Self::AnyAs => lint.check_undocumented_as_any_cast,
Self::MutPtr => lint.check_undocumented_as_mut_ptr_cast,
Self::ConstPtr => lint.check_undocumented_as_const_ptr_cast,
}
}

fn message(self) -> &'static str {
match self {
Self::AnyAs => "`as` casts without a `// CAST:` explanation",
Self::MutPtr => "`as *mut` casts without a `// CAST:` explanation",
Self::ConstPtr => "`as *const` casts without a `// CAST:` explanation",
}
}
}

/// Returns all cast scenarios applicable to `cast_to`, with the most specific
/// scenario first. The caller should use the first enabled scenario.
fn as_cast_scenarios(cast_to: &rustc_hir::Ty<'_>) -> &'static [AsCastScenario] {
match cast_to.kind {
TyKind::Ptr(MutTy {
mutbl: Mutability::Mut, ..
}) => &[AsCastScenario::MutPtr, AsCastScenario::AnyAs],
TyKind::Ptr(MutTy {
mutbl: Mutability::Not, ..
}) => &[AsCastScenario::ConstPtr, AsCastScenario::AnyAs],
_ => &[AsCastScenario::AnyAs],
}
}

impl<'tcx> LateLintPass<'tcx> for UndocumentedAsCasts {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
let source_map = cx.sess().source_map();
if let ExprKind::Cast(_, cast_to) = expr.kind
&& !expr.span.in_external_macro(cx.sess().source_map())
&& !is_from_proc_macro(cx, expr)
&& let Some(scenario) = as_cast_scenarios(cast_to).iter().copied().find(|s| s.is_enabled(self))
&& let Ok(line_info) = source_map.lookup_line(expr.span.lo())
&& let Some(src) = line_info.sf.src.as_deref()
&& text_has_marked_comment(
src,
&line_info.sf.lines()[..=line_info.line],
line_info.sf.start_pos,
"CAST:",
true,
)
.is_none()
{
span_lint_and_help(
cx,
UNDOCUMENTED_AS_CASTS,
expr.span,
scenario.message(),
None,
"consider adding a cast comment on the preceding line",
);
}
}
}
Loading
Loading