diff --git a/CHANGELOG.md b/CHANGELOG.md index 748e283edffb..3dcc2bad23c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index c87f8e9a68de..7a916194f154 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -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 diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 41099f94b044..512529d9237d 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -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, diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c164241673a3..20ebdcddca89 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -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, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 68a8f51e7f4d..57a8285f9e17 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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; @@ -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); diff --git a/clippy_lints/src/undocumented_as_casts.rs b/clippy_lints/src/undocumented_as_casts.rs new file mode 100644 index 000000000000..99f4f3c7bbd2 --- /dev/null +++ b/clippy_lints/src/undocumented_as_casts.rs @@ -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", + ); + } + } +} diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 2bf1d8be4653..0d75b115a6cf 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -5,15 +5,14 @@ use clippy_config::Conf; use clippy_utils::consts::const_item_rhs_to_expr; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_lint_allowed; -use clippy_utils::source::walk_span_to_context; +use clippy_utils::source::{text_has_marked_comment, walk_span_to_context}; use clippy_utils::visitors::{Descend, for_each_expr}; use hir::HirId; use rustc_errors::Applicability; use rustc_hir::{self as hir, Block, BlockCheckMode, FnSig, Impl, ItemKind, Node, UnsafeSource}; -use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, Pos, RelativeBytePos, Span, SyntaxContext}; +use rustc_span::{BytePos, Pos, Span, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -589,13 +588,17 @@ fn item_has_safety_comment( return if comment_start_line.line >= unsafe_line.line { HasSafetyComment::No } else { - text_has_safety_comment( + match text_has_marked_comment( src, &unsafe_line.sf.lines() [(comment_start_line.line + usize::from(!include_first_line_of_file))..=unsafe_line.line], unsafe_line.sf.start_pos, + "SAFETY:", accept_comment_above_attributes, - ) + ) { + Some((pos, is_doc)) => HasSafetyComment::Yes(pos, is_doc), + None => HasSafetyComment::No, + } }; } HasSafetyComment::Maybe @@ -632,12 +635,16 @@ fn stmt_has_safety_comment( return if comment_start_line.line >= unsafe_line.line { HasSafetyComment::No } else { - text_has_safety_comment( + match text_has_marked_comment( src, &unsafe_line.sf.lines()[comment_start_line.line + 1..=unsafe_line.line], unsafe_line.sf.start_pos, + "SAFETY:", accept_comment_above_attributes, - ) + ) { + Some((pos, is_doc)) => HasSafetyComment::Yes(pos, is_doc), + None => HasSafetyComment::No, + } }; } HasSafetyComment::Maybe @@ -710,12 +717,16 @@ fn span_from_macro_expansion_has_safety_comment( && let Some(src) = unsafe_line.sf.src.as_deref() { if macro_line.line < unsafe_line.line { - text_has_safety_comment( + match text_has_marked_comment( src, &unsafe_line.sf.lines()[macro_line.line + 1..=unsafe_line.line], unsafe_line.sf.start_pos, + "SAFETY:", accept_comment_above_attributes, - ) + ) { + Some((pos, is_doc)) => HasSafetyComment::Yes(pos, is_doc), + None => HasSafetyComment::No, + } } else { HasSafetyComment::No } @@ -774,15 +785,14 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abov // fn foo() { some_stuff; unsafe { stuff }; other_stuff; } // ^-------------^ body_line.line < unsafe_line.line - && matches!( - text_has_safety_comment( - src, - &unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line], - unsafe_line.sf.start_pos, - accept_comment_above_attributes, - ), - HasSafetyComment::Yes(..) + && text_has_marked_comment( + src, + &unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line], + unsafe_line.sf.start_pos, + "SAFETY:", + accept_comment_above_attributes, ) + .is_some() } else { // Problem getting source text. Pretend a comment was found. true @@ -792,108 +802,6 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abov } } -/// Checks if the given text has a safety comment for the immediately proceeding line. -/// -/// If `accept_comment_above_attributes` is true, it will ignore attributes inbetween blocks of -/// comments -fn text_has_safety_comment( - src: &str, - line_starts: &[RelativeBytePos], - start_pos: BytePos, - accept_comment_above_attributes: bool, -) -> HasSafetyComment { - let mut lines = line_starts - .array_windows::<2>() - .rev() - .map_while(|[start, end]| { - let start = start.to_usize(); - let end = end.to_usize(); - let text = src.get(start..end)?; - let trimmed = text.trim_start(); - Some((start + (text.len() - trimmed.len()), trimmed)) - }) - .filter(|(_, text)| !(text.is_empty() || (accept_comment_above_attributes && is_attribute(text)))); - - let Some((line_start, line)) = lines.next() else { - return HasSafetyComment::No; - }; - - let mut in_codeblock = false; - // Check for a sequence of line comments. - if line.starts_with("//") { - let (mut line, mut line_start) = (line, line_start); - loop { - // Don't lint if the safety comment is part of a codeblock in a doc comment. - // It may or may not be required, and we can't very easily check it (and we shouldn't, since - // the safety comment isn't referring to the node we're currently checking) - if let Some(doc) = line.strip_prefix("///").or_else(|| line.strip_prefix("//!")) - && doc.trim_start().starts_with("```") - { - in_codeblock = !in_codeblock; - } - - if !in_codeblock && let Some(safety_pos) = line.to_ascii_uppercase().find("SAFETY:") { - return HasSafetyComment::Yes( - start_pos - + BytePos(u32::try_from(line_start).unwrap()) - + BytePos(u32::try_from(safety_pos).unwrap()), - line.starts_with("///"), - ); - } - match lines.next() { - Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s), - _ => return HasSafetyComment::No, - } - } - } - // Check for a comment that appears after other code on the same line (e.g., `let x = // SAFETY:`) - // This handles cases in macros where the comment is on the same line as preceding code. - // We only check the first (immediate preceding) line for this pattern. - // Only whitespace is allowed between the comment marker and `SAFETY:`. - if let Some(comment_start) = [line.find("//"), line.find("/*")].into_iter().flatten().min() - && let after_marker = &line[comment_start + 2..] // skip marker - && let trimmed = after_marker.trim_start() // skip whitespace - && trimmed.get(..7).is_some_and(|s| s.eq_ignore_ascii_case("SAFETY:")) - { - let safety_offset = 2 + (after_marker.len() - trimmed.len()); - return HasSafetyComment::Yes( - start_pos - + BytePos(u32::try_from(line_start).unwrap()) - + BytePos(u32::try_from(comment_start + safety_offset).unwrap()), - false, - ); - } - // No line comments; look for the start of a block comment. - // This will only find them if they are at the start of a line. - let (mut line_start, mut line) = (line_start, line); - loop { - if line.starts_with("/*") { - let src = &src[line_start..line_starts.last().unwrap().to_usize()]; - let mut tokens = tokenize(src, FrontmatterAllowed::No); - let a = tokens.next(); - if let Some(safety_pos) = src[..a.unwrap().len as usize].to_ascii_uppercase().find("SAFETY:") - && tokens.all(|t| t.kind == TokenKind::Whitespace) - { - return HasSafetyComment::Yes( - start_pos - + BytePos(u32::try_from(line_start).unwrap()) - + BytePos(u32::try_from(safety_pos).unwrap()), - line.starts_with("/**"), - ); - } - return HasSafetyComment::No; - } - match lines.next() { - Some(x) => (line_start, line) = x, - None => return HasSafetyComment::No, - } - } -} - -fn is_attribute(text: &str) -> bool { - (text.starts_with("#[") || text.starts_with("#![")) && text.trim_end().ends_with(']') -} - fn span_and_hid_of_item_alike_node(node: &Node<'_>) -> Option<(Span, HirId)> { match node { Node::Item(item) => Some((item.span, item.owner_id.into())), diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index f30f26f3a70b..2321f2779d3b 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -803,6 +803,111 @@ pub fn str_literal_to_char_literal( } } +/// Checks if the given text contains a comment with the given marker (e.g., `SAFETY:`) +/// that applies to the given position. If so, returns the position of the comment and +/// whether it's a doc comment. +/// +/// If `accept_comment_above_attributes` is true, it will ignore attributes inbetween +/// blocks of comments +pub fn text_has_marked_comment( + src: &str, + line_starts: &[RelativeBytePos], + start_pos: BytePos, + comment_marker: &str, + accept_comment_above_attributes: bool, +) -> Option<(BytePos, bool)> { + let mut lines = line_starts + .array_windows::<2>() + .rev() + .map_while(|[start, end]| { + let start = start.to_usize(); + let end = end.to_usize(); + let text = src.get(start..end)?; + let trimmed = text.trim_start(); + Some((start + (text.len() - trimmed.len()), trimmed)) + }) + .filter(|(_, text)| { + !(text.is_empty() + || (accept_comment_above_attributes + // is the line an attribute + && ((text.starts_with("#[") || text.starts_with("#![")) && text.trim_end().ends_with(']')))) + }); + + let (line_start, line) = lines.next()?; + + let mut in_codeblock = false; + // Check for a sequence of line comments. + if line.starts_with("//") { + let (mut line, mut line_start) = (line, line_start); + loop { + // Check for the start or end of a code block in the documentation comment. + // We want to ignore marked comment that appear in code blocks, since the + // marked comment isn't referring to the node we're currently checking. + if let Some(doc) = line.strip_prefix("///").or_else(|| line.strip_prefix("//!")) + && doc.trim_start().starts_with("```") + { + in_codeblock = !in_codeblock; + } + + if !in_codeblock && let Some(marker_pos) = line.to_ascii_uppercase().find(comment_marker) { + return Some(( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(marker_pos).unwrap()), + line.starts_with("///"), + )); + } + match lines.next() { + Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s), + _ => return None, + } + } + } + // Check for a comment that appears after other code on the same line (e.g., `let x = // SAFETY:`) + // This handles cases in macros where the comment is on the same line as preceding code. + // We only check the first (immediate preceding) line for this pattern. + // Only whitespace is allowed between the comment marker and `SAFETY:`. + if let Some(comment_start) = [line.find("//"), line.find("/*")].into_iter().flatten().min() + && let after_marker = &line[comment_start + 2..] // skip marker + && let trimmed = after_marker.trim_start() // skip whitespace + && trimmed + .get(..comment_marker.len()) + .is_some_and(|s| s.eq_ignore_ascii_case(comment_marker)) + { + let marker_offset = 2 + (after_marker.len() - trimmed.len()); + return Some(( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(comment_start + marker_offset).unwrap()), + false, + )); + } + // No line comments; look for the start of a block comment. + // This will only find them if they are at the start of a line. + let (mut line_start, mut line) = (line_start, line); + loop { + if line.starts_with("/*") { + let src = &src[line_start..line_starts.last().unwrap().to_usize()]; + let mut tokens = tokenize(src, FrontmatterAllowed::No); + let a = tokens.next(); + if let Some(marker_pos) = src[..a.unwrap().len as usize].to_ascii_uppercase().find(comment_marker) + && tokens.all(|t| t.kind == TokenKind::Whitespace) + { + return Some(( + start_pos + + BytePos(u32::try_from(line_start).unwrap()) + + BytePos(u32::try_from(marker_pos).unwrap()), + line.starts_with("/**"), + )); + } + return None; + } + + let x = lines.next()?; + (line_start, line) = x; + } +} + #[cfg(test)] mod test { use super::reindent_multiline; diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 6bb3db8db67f..e6e0ac0049e6 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -36,6 +36,9 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect check-incompatible-msrv-in-tests check-inconsistent-struct-field-initializers check-private-items + check-undocumented-as-any-cast + check-undocumented-as-const-ptr-cast + check-undocumented-as-mut-ptr-cast cognitive-complexity-threshold const-literal-digits-threshold disallowed-fields @@ -137,6 +140,9 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect check-incompatible-msrv-in-tests check-inconsistent-struct-field-initializers check-private-items + check-undocumented-as-any-cast + check-undocumented-as-const-ptr-cast + check-undocumented-as-mut-ptr-cast cognitive-complexity-threshold const-literal-digits-threshold disallowed-fields @@ -238,6 +244,9 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni check-incompatible-msrv-in-tests check-inconsistent-struct-field-initializers check-private-items + check-undocumented-as-any-cast + check-undocumented-as-const-ptr-cast + check-undocumented-as-mut-ptr-cast cognitive-complexity-threshold const-literal-digits-threshold disallowed-fields diff --git a/tests/ui-toml/undocumented_as_casts/default/clippy.toml b/tests/ui-toml/undocumented_as_casts/default/clippy.toml new file mode 100644 index 000000000000..78f429d11d12 --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/default/clippy.toml @@ -0,0 +1,4 @@ +# default configuration has +# `check-undocumented-as-any-cast` = true +# `check-undocumented-as-const-ptr-cast` = true +# `check-undocumented-as-mut-ptr-cast` = true diff --git a/tests/ui-toml/undocumented_as_casts/disable_const_ptr/clippy.toml b/tests/ui-toml/undocumented_as_casts/disable_const_ptr/clippy.toml new file mode 100644 index 000000000000..233eb7bd295f --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/disable_const_ptr/clippy.toml @@ -0,0 +1,4 @@ +# test with `check-undocumented-as-const-ptr-cast` disabled_const_ptr +check-undocumented-as-any-cast = false +check-undocumented-as-const-ptr-cast = false +check-undocumented-as-mut-ptr-cast = true diff --git a/tests/ui-toml/undocumented_as_casts/disable_mut_ptr/clippy.toml b/tests/ui-toml/undocumented_as_casts/disable_mut_ptr/clippy.toml new file mode 100644 index 000000000000..36a657a0056e --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/disable_mut_ptr/clippy.toml @@ -0,0 +1,4 @@ +# test with `check-undocumented-as-const-ptr-cast` disabled_mut_ptr +check-undocumented-as-any-cast = false +check-undocumented-as-const-ptr-cast = true +check-undocumented-as-mut-ptr-cast = false diff --git a/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.default.stderr b/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.default.stderr new file mode 100644 index 000000000000..2b0ce8758d61 --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.default.stderr @@ -0,0 +1,145 @@ +error: `as *mut` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:30:13 + | +LL | $x as $t + | ^^^^^^^^ +... +LL | mut_cast_no_comment!(p, *mut i32); + | --------------------------------- in this macro invocation + | + = help: consider adding a cast comment on the preceding line + = note: `-D clippy::undocumented-as-casts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_as_casts)]` + = note: this error originates in the macro `mut_cast_no_comment` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:39:13 + | +LL | $x as $t + | ^^^^^^^^ +... +LL | const_cast_no_comment!(p, *const i32); + | ------------------------------------- in this macro invocation + | + = help: consider adding a cast comment on the preceding line + = note: this error originates in the macro `const_cast_no_comment` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:225:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:231:13 + | +LL | let _ = p as *const i32; // CAST: reason + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:239:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:247:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:255:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:262:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:270:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:278:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:284:13 + | +LL | let x = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *mut` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:296:13 + | +LL | let y = p as *mut i32; + | ^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:304:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *mut` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:312:13 + | +LL | let y = match cond { + | _____________^ +LL | | +LL | | true => { +... | +LL | | } as *mut i32; + | |_________________^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:330:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs:338:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: aborting due to 16 previous errors + diff --git a/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs b/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs new file mode 100644 index 000000000000..0cc160399a58 --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocmented_as_casts_comment_check.rs @@ -0,0 +1,342 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_as_casts/default + +#![warn(clippy::undocumented_as_casts)] + +extern crate proc_macros; +use proc_macros::{external, with_span}; + +fn ignore_external() { + external!(&0i32 as *const i32); +} + +fn ignore_proc_macro() { + with_span!( + span + + fn converting() { + let p: *const u32 = &42_u32; + let _ = p as *mut i32; + } + ); +} + +fn declared_macro() { + let p: *const u32 = &42_u32; + + macro_rules! mut_cast_no_comment { + ($x:expr, $t:ty) => { + $x as $t + //~^ undocumented_as_casts + }; + } + + mut_cast_no_comment!(p, *mut i32); + + macro_rules! const_cast_no_comment { + ($x:expr, $t:ty) => { + $x as $t + //~^ undocumented_as_casts + }; + } + + const_cast_no_comment!(p, *const i32); +} + +// Valid Comments + +fn line_comment() { + let p: *const u32 = &42_u32; + // CAST: reason + let _ = p as *const i32; +} + +fn line_comment_lowercase() { + let p: *const u32 = &42_u32; + // cast: reason + let _ = p as *const i32; +} + +fn line_comment_mixed_case() { + let p: *const u32 = &42_u32; + // CaSt: reason + let _ = p as *const i32; +} + +fn line_comment_newlines() { + let p: *const u32 = &42_u32; + // CAST: reason + + let _ = p as *const i32; +} + +fn line_comment_empty() { + let p: *const u32 = &42_u32; + // CAST: reason + // + // + // + let _ = p as *const i32; +} + +fn line_comment_with_extras() { + let p: *const u32 = &42_u32; + // This is a description + // CAST: reason + let _ = p as *const i32; +} + +fn line_comment_multiple_casts_same_line() { + let p: *const u32 = &42_u32; + // CAST: reason for both casts + let _ = (p as *const i32, p as *mut i32); +} + +fn line_comment_multiple_casts() { + let p: *const u32 = &42_u32; + // CAST: reason for first cast + let x = p as *const i32; + // CAST: reason for second cast + let y = p as *mut i32; +} + +fn line_comment_function_return() -> *const i32 { + let p: *const u32 = &42_u32; + // CAST: reason + p as *const i32 +} + +fn line_comment_match_block_multiple_arms() { + let p: *const u32 = &42_u32; + let cond = true; + match cond { + true => { + // CAST: reason for first cast + let _ = p as *const i32; + }, + _ => { + // CAST: reason for second cast + let _ = p as *mut i32; + }, + } +} + +fn line_comment_let_match_block_multiple_arms() { + let p: *const u32 = &42_u32; + let cond = true; + let y = match cond { + true => { + // CAST: reason for first cast + p as *const i32 + }, + _ => { + // CAST: reason for second cast + p as *const i32 + }, + }; +} + +fn newline_between_cast_line_comment_and_line_comment() { + let p: *const u32 = &42_u32; + // CAST: reason + + // This is a description + let _ = p as *const i32; +} + +fn line_comment_let_match_then_cast() { + let p: *const u32 = &42_u32; + let cond = true; + // CAST: reason for match block cast + let y = match cond { + true => { + // CAST: reason for first cast + p as *const i32 + }, + _ => { + // CAST: reason for second cast + p as *const i32 + }, + } as *mut i32; +} + +fn block_comment() { + let p: *const u32 = &42_u32; + /* CAST: reason */ + let _ = p as *const i32; +} + +fn block_comment_newlines() { + let p: *const u32 = &42_u32; + /* CAST: reason */ + + let _ = p as *const i32; +} + +fn block_comment_multiple_line() { + let p: *const u32 = &42_u32; + /* This is a description + * CAST: reason + */ + let _ = p as *const i32; +} + +fn block_comment_multiple_casts_same_line() { + let p: *const u32 = &42_u32; + /* CAST: reason for both casts */ + let _ = (p as *const i32, p as *mut i32); +} + +fn block_comment_multiple_casts() { + let p: *const u32 = &42_u32; + /* CAST: reason for first cast */ + let x = p as *const i32; + /* CAST: reason for second cast */ + let y = p as *mut i32; +} + +fn block_comment_function_return() -> *const i32 { + let p: *const u32 = &42_u32; + /* CAST: reason */ + p as *const i32 +} + +fn block_comment_let_match_then_cast() { + let p: *const u32 = &42_u32; + let cond = true; + /* CAST: reason for match block cast */ + let y = match cond { + true => { + /* CAST: reason for first cast */ + p as *const i32 + }, + _ => { + /* CAST: reason for second cast */ + p as *const i32 + }, + } as *mut i32; +} + +// Invalid Comment + +fn no_comment() { + let p: *const u32 = &42_u32; + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn trailing_cast_comment() { + let p: *const u32 = &42_u32; + let _ = p as *const i32; // CAST: reason + // + //~^^ undocumented_as_casts +} + +fn non_cast_comment() { + let p: *const u32 = &42_u32; + // This is a description + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn non_cast_comment_newlines() { + let p: *const u32 = &42_u32; + // This is a description + + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn non_cast_comment_with_extras() { + let p: *const u32 = &42_u32; + // This is a description + // This is more description + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn non_cast_block_comment() { + let p: *const u32 = &42_u32; + /* This is a description */ + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn non_cast_block_comment_newlines() { + let p: *const u32 = &42_u32; + /* This is a description */ + + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn non_cast_block_comment_with_extras() { + let p: *const u32 = &42_u32; + /* This is a description + * This is more description */ + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn no_comment_first_cast() { + let p: *const u32 = &42_u32; + let x = p as *const i32; + //~^ undocumented_as_casts + + // CAST: reason + let y = p as *mut i32; +} + +fn no_comment_following_cast() { + let p: *const u32 = &42_u32; + // CAST: reason + let x = p as *const i32; + + let y = p as *mut i32; + //~^ undocumented_as_casts +} + +fn line_cast_comment_before_block_comment() { + let p: *const u32 = &42_u32; + // CAST: reason + /* This is a description */ + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn line_comment_let_match_then_cast_invalid() { + let p: *const u32 = &42_u32; + let cond = true; + + let y = match cond { + //~^ undocumented_as_casts + true => { + // CAST: reason for first cast + p as *const i32 + }, + _ => { + // CAST: reason for second cast + p as *const i32 + }, + // CAST: reason for match block cast + } as *mut i32; +} + +fn block_cast_comment_before_line_comment() { + let p: *const u32 = &42_u32; + /* CAST: reason */ + // This is a description + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn block_cast_comment_before_block_comment() { + let p: *const u32 = &42_u32; + /* CAST: reason */ + /* This is a description */ + let _ = p as *const i32; + //~^ undocumented_as_casts +} + +fn main() {} diff --git a/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.const_ptr.stderr b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.const_ptr.stderr new file mode 100644 index 000000000000..ebff794b4f9d --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.const_ptr.stderr @@ -0,0 +1,12 @@ +error: `as *mut` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs:11:13 + | +LL | let _ = p as *mut i32; + | ^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + = note: `-D clippy::undocumented-as-casts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_as_casts)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.default.stderr b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.default.stderr new file mode 100644 index 000000000000..dc5000f2c008 --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.default.stderr @@ -0,0 +1,20 @@ +error: `as *mut` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs:11:13 + | +LL | let _ = p as *mut i32; + | ^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + = note: `-D clippy::undocumented-as-casts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_as_casts)]` + +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs:18:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.mut_ptr.stderr b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.mut_ptr.stderr new file mode 100644 index 000000000000..21d0ae6a932d --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.mut_ptr.stderr @@ -0,0 +1,12 @@ +error: `as *const` casts without a `// CAST:` explanation + --> tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs:18:13 + | +LL | let _ = p as *const i32; + | ^^^^^^^^^^^^^^^ + | + = help: consider adding a cast comment on the preceding line + = note: `-D clippy::undocumented-as-casts` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::undocumented_as_casts)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs new file mode 100644 index 000000000000..da75533e52bb --- /dev/null +++ b/tests/ui-toml/undocumented_as_casts/undocumented_as_casts.rs @@ -0,0 +1,33 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default const_ptr mut_ptr +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_as_casts/default +//@[const_ptr] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_as_casts/disable_const_ptr +//@[mut_ptr] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/undocumented_as_casts/disable_mut_ptr + +#![warn(clippy::undocumented_as_casts)] + +fn lint_mut_ptr_without_comment() { + let p: *mut u32 = &mut 42_u32; + let _ = p as *mut i32; + //~[default]^ undocumented_as_casts + //~[const_ptr]^^ undocumented_as_casts +} + +fn lint_const_ptr_without_comment_default_only() { + let p: *const u32 = &42_u32; + let _ = p as *const i32; + //~[default]^ undocumented_as_casts + //~[mut_ptr]^^ undocumented_as_casts +} + +fn allow_with_cast_comment() { + 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; +} + +fn main() {}