about summary refs log tree commit diff
path: root/clippy_lints/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-01-27 21:20:39 +0000
committerbors <bors@rust-lang.org>2023-01-27 21:20:39 +0000
commit997fe0d57e8cd5025011340c91f26dbc4a87a881 (patch)
tree407473a920ae27562396c86825d14d6f731684d7 /clippy_lints/src
parentbcb90528c06e1e350c77bca5ff2c0929d7f5c8c3 (diff)
parent5c7a65251a178d878995b5a9cb1e6eeac7506af2 (diff)
downloadrust-997fe0d57e8cd5025011340c91f26dbc4a87a881.tar.gz
rust-997fe0d57e8cd5025011340c91f26dbc4a87a881.zip
Auto merge of #107386 - flip1995:clippyup, r=Manishearth
Update Clippy

r? `@Manishearth`
Diffstat (limited to 'clippy_lints/src')
-rw-r--r--clippy_lints/src/bool_assert_comparison.rs53
-rw-r--r--clippy_lints/src/casts/cast_possible_truncation.rs32
-rw-r--r--clippy_lints/src/casts/mod.rs20
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/doc.rs2
-rw-r--r--clippy_lints/src/enum_variants.rs4
-rw-r--r--clippy_lints/src/from_raw_with_void_ptr.rs2
-rw-r--r--clippy_lints/src/instant_subtraction.rs2
-rw-r--r--clippy_lints/src/let_underscore.rs2
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/manual_is_ascii_check.rs2
-rw-r--r--clippy_lints/src/methods/mod.rs4
-rw-r--r--clippy_lints/src/missing_trait_methods.rs2
-rw-r--r--clippy_lints/src/multiple_unsafe_ops_per_block.rs185
-rw-r--r--clippy_lints/src/only_used_in_recursion.rs4
-rw-r--r--clippy_lints/src/returns.rs26
-rw-r--r--clippy_lints/src/suspicious_xor_used_as_pow.rs4
-rw-r--r--clippy_lints/src/transmute/mod.rs7
-rw-r--r--clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs58
-rw-r--r--clippy_lints/src/transmute/utils.rs24
-rw-r--r--clippy_lints/src/undocumented_unsafe_blocks.rs12
-rw-r--r--clippy_lints/src/utils/conf.rs3
-rw-r--r--clippy_lints/src/utils/internal_lints/metadata_collector.rs79
23 files changed, 436 insertions, 94 deletions
diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs
index 82d368bb8bc..556fa579000 100644
--- a/clippy_lints/src/bool_assert_comparison.rs
+++ b/clippy_lints/src/bool_assert_comparison.rs
@@ -1,10 +1,11 @@
+use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node};
-use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
+use clippy_utils::ty::{implements_trait, is_copy};
 use rustc_ast::ast::LitKind;
 use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind, Lit};
-use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::ty::{self, Ty};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::symbol::Ident;
 
@@ -43,9 +44,7 @@ fn is_bool_lit(e: &Expr<'_>) -> bool {
     ) && !e.span.from_expansion()
 }
 
-fn is_impl_not_trait_with_bool_out(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
-    let ty = cx.typeck_results().expr_ty(e);
-
+fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
     cx.tcx
         .lang_items()
         .not_trait()
@@ -77,31 +76,57 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
             return;
         }
         let Some ((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
-        if !(is_bool_lit(a) ^ is_bool_lit(b)) {
+
+        let a_span = a.span.source_callsite();
+        let b_span = b.span.source_callsite();
+
+        let (lit_span, non_lit_expr) = match (is_bool_lit(a), is_bool_lit(b)) {
+            // assert_eq!(true, b)
+            //            ^^^^^^
+            (true, false) => (a_span.until(b_span), b),
+            // assert_eq!(a, true)
+            //             ^^^^^^
+            (false, true) => (b_span.with_lo(a_span.hi()), a),
             // If there are two boolean arguments, we definitely don't understand
             // what's going on, so better leave things as is...
             //
             // Or there is simply no boolean and then we can leave things as is!
-            return;
-        }
+            _ => return,
+        };
 
-        if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
+        let non_lit_ty = cx.typeck_results().expr_ty(non_lit_expr);
+
+        if !is_impl_not_trait_with_bool_out(cx, non_lit_ty) {
             // At this point the expression which is not a boolean
             // literal does not implement Not trait with a bool output,
             // so we cannot suggest to rewrite our code
             return;
         }
 
+        if !is_copy(cx, non_lit_ty) {
+            // Only lint with types that are `Copy` because `assert!(x)` takes
+            // ownership of `x` whereas `assert_eq(x, true)` does not
+            return;
+        }
+
         let macro_name = macro_name.as_str();
         let non_eq_mac = &macro_name[..macro_name.len() - 3];
-        span_lint_and_sugg(
+        span_lint_and_then(
             cx,
             BOOL_ASSERT_COMPARISON,
             macro_call.span,
             &format!("used `{macro_name}!` with a literal bool"),
-            "replace it with",
-            format!("{non_eq_mac}!(..)"),
-            Applicability::MaybeIncorrect,
+            |diag| {
+                // assert_eq!(...)
+                // ^^^^^^^^^
+                let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!');
+
+                diag.multipart_suggestion(
+                    format!("replace it with `{non_eq_mac}!(..)`"),
+                    vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())],
+                    Applicability::MachineApplicable,
+                );
+            },
         );
     }
 }
diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs
index a6376484914..f3f8b8d8798 100644
--- a/clippy_lints/src/casts/cast_possible_truncation.rs
+++ b/clippy_lints/src/casts/cast_possible_truncation.rs
@@ -1,11 +1,14 @@
 use clippy_utils::consts::{constant, Constant};
-use clippy_utils::diagnostics::span_lint;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
 use clippy_utils::expr_or_init;
+use clippy_utils::source::snippet;
 use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
+use rustc_errors::{Applicability, SuggestionStyle};
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::{BinOpKind, Expr, ExprKind};
 use rustc_lint::LateContext;
 use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_span::Span;
 use rustc_target::abi::IntegerType;
 
 use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
@@ -74,7 +77,14 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
     }
 }
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
+pub(super) fn check(
+    cx: &LateContext<'_>,
+    expr: &Expr<'_>,
+    cast_expr: &Expr<'_>,
+    cast_from: Ty<'_>,
+    cast_to: Ty<'_>,
+    cast_to_span: Span,
+) {
     let msg = match (cast_from.kind(), cast_to.is_integral()) {
         (ty::Int(_) | ty::Uint(_), true) => {
             let from_nbits = apply_reductions(
@@ -139,7 +149,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
                 );
                 return;
             }
-            format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
+            format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}")
         },
 
         (ty::Float(_), true) => {
@@ -153,5 +163,19 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
         _ => return,
     };
 
-    span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
+    let name_of_cast_from = snippet(cx, cast_expr.span, "..");
+    let cast_to_snip = snippet(cx, cast_to_span, "..");
+    let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})");
+
+    span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| {
+        diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ...");
+        diag.span_suggestion_with_style(
+            expr.span,
+            "... or use `try_from` and handle the error accordingly",
+            suggestion,
+            Applicability::Unspecified,
+            // always show the suggestion in a separate line
+            SuggestionStyle::ShowAlways,
+        );
+    });
 }
diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs
index 161e3a698e9..362f70d12d1 100644
--- a/clippy_lints/src/casts/mod.rs
+++ b/clippy_lints/src/casts/mod.rs
@@ -80,7 +80,8 @@ declare_clippy_lint! {
     /// ### What it does
     /// Checks for casts between numerical types that may
     /// truncate large values. This is expected behavior, so the cast is `Allow` by
-    /// default.
+    /// default. It suggests user either explicitly ignore the lint,
+    /// or use `try_from()` and handle the truncation, default, or panic explicitly.
     ///
     /// ### Why is this bad?
     /// In some problem domains, it is good practice to avoid
@@ -93,6 +94,21 @@ declare_clippy_lint! {
     ///     x as u8
     /// }
     /// ```
+    /// Use instead:
+    /// ```
+    /// fn as_u8(x: u64) -> u8 {
+    ///     if let Ok(x) = u8::try_from(x) {
+    ///         x
+    ///     } else {
+    ///         todo!();
+    ///     }
+    /// }
+    /// // Or
+    /// #[allow(clippy::cast_possible_truncation)]
+    /// fn as_u16(x: u64) -> u16 {
+    ///     x as u16
+    /// }
+    /// ```
     #[clippy::version = "pre 1.29.0"]
     pub CAST_POSSIBLE_TRUNCATION,
     pedantic,
@@ -712,7 +728,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
             fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
 
             if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
-                cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
+                cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span);
                 if cast_from.is_numeric() {
                     cast_possible_wrap::check(cx, expr, cast_from, cast_to);
                     cast_precision_loss::check(cx, expr, cast_from, cast_to);
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 91ca73633f0..36a366fc974 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -422,6 +422,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::module_style::MOD_MODULE_FILES_INFO,
     crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
     crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
+    crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
     crate::mut_key::MUTABLE_KEY_TYPE_INFO,
     crate::mut_mut::MUT_MUT_INFO,
     crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO,
diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs
index cdc23a4d227..f7a3d6d53f7 100644
--- a/clippy_lints/src/doc.rs
+++ b/clippy_lints/src/doc.rs
@@ -251,7 +251,7 @@ declare_clippy_lint! {
     ///     unimplemented!();
     /// }
     /// ```
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub UNNECESSARY_SAFETY_DOC,
     restriction,
     "`pub fn` or `pub trait` with `# Safety` docs"
diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs
index b77b5621b4c..4c69dacf381 100644
--- a/clippy_lints/src/enum_variants.rs
+++ b/clippy_lints/src/enum_variants.rs
@@ -277,7 +277,7 @@ impl LateLintPass<'_> for EnumVariantNames {
                                 Some(c) if is_word_beginning(c) => span_lint(
                                     cx,
                                     MODULE_NAME_REPETITIONS,
-                                    item.span,
+                                    item.ident.span,
                                     "item name starts with its containing module's name",
                                 ),
                                 _ => (),
@@ -287,7 +287,7 @@ impl LateLintPass<'_> for EnumVariantNames {
                             span_lint(
                                 cx,
                                 MODULE_NAME_REPETITIONS,
-                                item.span,
+                                item.ident.span,
                                 "item name ends with its containing module's name",
                             );
                         }
diff --git a/clippy_lints/src/from_raw_with_void_ptr.rs b/clippy_lints/src/from_raw_with_void_ptr.rs
index 00f5ba56496..096508dc4f1 100644
--- a/clippy_lints/src/from_raw_with_void_ptr.rs
+++ b/clippy_lints/src/from_raw_with_void_ptr.rs
@@ -31,7 +31,7 @@ declare_clippy_lint! {
     /// let _ = unsafe { Box::from_raw(ptr as *mut usize) };
     /// ```
     ///
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub FROM_RAW_WITH_VOID_PTR,
     suspicious,
     "creating a `Box` from a void raw pointer"
diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs
index 9f6e8940571..668110c7cc0 100644
--- a/clippy_lints/src/instant_subtraction.rs
+++ b/clippy_lints/src/instant_subtraction.rs
@@ -59,7 +59,7 @@ declare_clippy_lint! {
     ///
     /// [`Duration`]: std::time::Duration
     /// [`Instant::now()`]: std::time::Instant::now;
-    #[clippy::version = "1.65.0"]
+    #[clippy::version = "1.67.0"]
     pub UNCHECKED_DURATION_SUBTRACTION,
     pedantic,
     "finds unchecked subtraction of a 'Duration' from an 'Instant'"
diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs
index 61f87b91400..f8e35950980 100644
--- a/clippy_lints/src/let_underscore.rs
+++ b/clippy_lints/src/let_underscore.rs
@@ -84,7 +84,7 @@ declare_clippy_lint! {
     /// let _ = foo().await;
     /// # }
     /// ```
-    #[clippy::version = "1.66"]
+    #[clippy::version = "1.67.0"]
     pub LET_UNDERSCORE_FUTURE,
     suspicious,
     "non-binding `let` on a future"
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index d8e2ae02c5a..5c4b6041044 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -198,6 +198,7 @@ mod missing_trait_methods;
 mod mixed_read_write_in_expression;
 mod module_style;
 mod multi_assignments;
+mod multiple_unsafe_ops_per_block;
 mod mut_key;
 mod mut_mut;
 mod mut_reference;
@@ -908,6 +909,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck));
     store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse));
     store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef));
+    store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs
index d9ef7dffa02..2fd32c009ea 100644
--- a/clippy_lints/src/manual_is_ascii_check.rs
+++ b/clippy_lints/src/manual_is_ascii_check.rs
@@ -43,7 +43,7 @@ declare_clippy_lint! {
     ///     'A'.is_ascii_uppercase();
     /// }
     /// ```
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub MANUAL_IS_ASCII_CHECK,
     style,
     "use dedicated method to check ascii range"
diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs
index a7e45d5126a..0c465e5daf9 100644
--- a/clippy_lints/src/methods/mod.rs
+++ b/clippy_lints/src/methods/mod.rs
@@ -3102,7 +3102,7 @@ declare_clippy_lint! {
     ///     Ok(())
     /// }
     /// ```
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub SEEK_FROM_CURRENT,
     complexity,
     "use dedicated method for seek from current position"
@@ -3133,7 +3133,7 @@ declare_clippy_lint! {
     ///     t.rewind();
     /// }
     /// ```
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub SEEK_TO_START_INSTEAD_OF_REWIND,
     complexity,
     "jumping to the start of stream using `seek` method"
diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs
index 3371b4cce32..e99081ad062 100644
--- a/clippy_lints/src/missing_trait_methods.rs
+++ b/clippy_lints/src/missing_trait_methods.rs
@@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
                         "implement the method",
                     );
                 }
-            })
+            });
         }
     }
 }
diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs
new file mode 100644
index 00000000000..2814c92e67a
--- /dev/null
+++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs
@@ -0,0 +1,185 @@
+use clippy_utils::{
+    diagnostics::span_lint_and_then,
+    visitors::{for_each_expr_with_closures, Descend, Visitable},
+};
+use core::ops::ControlFlow::Continue;
+use hir::{
+    def::{DefKind, Res},
+    BlockCheckMode, ExprKind, QPath, UnOp, Unsafety,
+};
+use rustc_ast::Mutability;
+use rustc_hir as hir;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `unsafe` blocks that contain more than one unsafe operation.
+    ///
+    /// ### Why is this bad?
+    /// Combined with `undocumented_unsafe_blocks`,
+    /// this lint ensures that each unsafe operation must be independently justified.
+    /// Combined with `unused_unsafe`, this lint also ensures
+    /// elimination of unnecessary unsafe blocks through refactoring.
+    ///
+    /// ### Example
+    /// ```rust
+    /// /// Reads a `char` from the given pointer.
+    /// ///
+    /// /// # Safety
+    /// ///
+    /// /// `ptr` must point to four consecutive, initialized bytes which
+    /// /// form a valid `char` when interpreted in the native byte order.
+    /// fn read_char(ptr: *const u8) -> char {
+    ///     // SAFETY: The caller has guaranteed that the value pointed
+    ///     // to by `bytes` is a valid `char`.
+    ///     unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
+    /// }
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// /// Reads a `char` from the given pointer.
+    /// ///
+    /// /// # Safety
+    /// ///
+    /// /// - `ptr` must be 4-byte aligned, point to four consecutive
+    /// ///   initialized bytes, and be valid for reads of 4 bytes.
+    /// /// - The bytes pointed to by `ptr` must represent a valid
+    /// ///   `char` when interpreted in the native byte order.
+    /// fn read_char(ptr: *const u8) -> char {
+    ///     // SAFETY: `ptr` is 4-byte aligned, points to four consecutive
+    ///     // initialized bytes, and is valid for reads of 4 bytes.
+    ///     let int_value = unsafe { *ptr.cast::<u32>() };
+    ///
+    ///     // SAFETY: The caller has guaranteed that the four bytes
+    ///     // pointed to by `bytes` represent a valid `char`.
+    ///     unsafe { char::from_u32_unchecked(int_value) }
+    /// }
+    /// ```
+    #[clippy::version = "1.68.0"]
+    pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
+    restriction,
+    "more than one unsafe operation per `unsafe` block"
+}
+declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);
+
+impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
+    fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
+        if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) {
+            return;
+        }
+        let mut unsafe_ops = vec![];
+        collect_unsafe_exprs(cx, block, &mut unsafe_ops);
+        if unsafe_ops.len() > 1 {
+            span_lint_and_then(
+                cx,
+                MULTIPLE_UNSAFE_OPS_PER_BLOCK,
+                block.span,
+                &format!(
+                    "this `unsafe` block contains {} unsafe operations, expected only one",
+                    unsafe_ops.len()
+                ),
+                |diag| {
+                    for (msg, span) in unsafe_ops {
+                        diag.span_note(span, msg);
+                    }
+                },
+            );
+        }
+    }
+}
+
+fn collect_unsafe_exprs<'tcx>(
+    cx: &LateContext<'tcx>,
+    node: impl Visitable<'tcx>,
+    unsafe_ops: &mut Vec<(&'static str, Span)>,
+) {
+    for_each_expr_with_closures(cx, node, |expr| {
+        match expr.kind {
+            ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),
+
+            ExprKind::Field(e, _) => {
+                if cx.typeck_results().expr_ty(e).is_union() {
+                    unsafe_ops.push(("union field access occurs here", expr.span));
+                }
+            },
+
+            ExprKind::Path(QPath::Resolved(
+                _,
+                hir::Path {
+                    res: Res::Def(DefKind::Static(Mutability::Mut), _),
+                    ..
+                },
+            )) => {
+                unsafe_ops.push(("access of a mutable static occurs here", expr.span));
+            },
+
+            ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => {
+                unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
+            },
+
+            ExprKind::Call(path_expr, _) => match path_expr.kind {
+                ExprKind::Path(QPath::Resolved(
+                    _,
+                    hir::Path {
+                        res: Res::Def(kind, def_id),
+                        ..
+                    },
+                )) if kind.is_fn_like() => {
+                    let sig = cx.tcx.fn_sig(*def_id);
+                    if sig.0.unsafety() == Unsafety::Unsafe {
+                        unsafe_ops.push(("unsafe function call occurs here", expr.span));
+                    }
+                },
+
+                ExprKind::Path(QPath::TypeRelative(..)) => {
+                    if let Some(sig) = cx
+                        .typeck_results()
+                        .type_dependent_def_id(path_expr.hir_id)
+                        .map(|def_id| cx.tcx.fn_sig(def_id))
+                    {
+                        if sig.0.unsafety() == Unsafety::Unsafe {
+                            unsafe_ops.push(("unsafe function call occurs here", expr.span));
+                        }
+                    }
+                },
+
+                _ => {},
+            },
+
+            ExprKind::MethodCall(..) => {
+                if let Some(sig) = cx
+                    .typeck_results()
+                    .type_dependent_def_id(expr.hir_id)
+                    .map(|def_id| cx.tcx.fn_sig(def_id))
+                {
+                    if sig.0.unsafety() == Unsafety::Unsafe {
+                        unsafe_ops.push(("unsafe method call occurs here", expr.span));
+                    }
+                }
+            },
+
+            ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
+                if matches!(
+                    lhs.kind,
+                    ExprKind::Path(QPath::Resolved(
+                        _,
+                        hir::Path {
+                            res: Res::Def(DefKind::Static(Mutability::Mut), _),
+                            ..
+                        }
+                    ))
+                ) {
+                    unsafe_ops.push(("modification of a mutable static occurs here", expr.span));
+                    collect_unsafe_exprs(cx, rhs, unsafe_ops);
+                    return Continue(Descend::No);
+                }
+            },
+
+            _ => {},
+        };
+
+        Continue::<(), _>(Descend::Yes)
+    });
+}
diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs
index 7b1d974f2f8..8b77a5c99f7 100644
--- a/clippy_lints/src/only_used_in_recursion.rs
+++ b/clippy_lints/src/only_used_in_recursion.rs
@@ -7,7 +7,7 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::hir_id::HirIdMap;
 use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
+use rustc_middle::ty::subst::{EarlyBinder, GenericArgKind, SubstsRef};
 use rustc_middle::ty::{self, ConstKind};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::symbol::{kw, Ident};
@@ -244,7 +244,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
             })) => {
                 #[allow(trivial_casts)]
                 if let Some(Node::Item(item)) = get_parent_node(cx.tcx, owner_id.into())
-                    && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(|t| t.subst_identity())
+                    && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::subst_identity)
                     && let Some(trait_item_id) = cx.tcx.associated_item(owner_id).trait_item_def_id
                 {
                     (
diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs
index dc1275a3686..a3e0811700b 100644
--- a/clippy_lints/src/returns.rs
+++ b/clippy_lints/src/returns.rs
@@ -1,7 +1,7 @@
 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
 use clippy_utils::source::{snippet_opt, snippet_with_context};
 use clippy_utils::visitors::{for_each_expr, Descend};
-use clippy_utils::{fn_def_id, path_to_local_id};
+use clippy_utils::{fn_def_id, path_to_local_id, span_find_starting_semi};
 use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
@@ -151,7 +151,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
         kind: FnKind<'tcx>,
         _: &'tcx FnDecl<'tcx>,
         body: &'tcx Body<'tcx>,
-        _: Span,
+        sp: Span,
         _: HirId,
     ) {
         match kind {
@@ -166,14 +166,14 @@ impl<'tcx> LateLintPass<'tcx> for Return {
                 check_final_expr(cx, body.value, vec![], replacement);
             },
             FnKind::ItemFn(..) | FnKind::Method(..) => {
-                check_block_return(cx, &body.value.kind, vec![]);
+                check_block_return(cx, &body.value.kind, sp, vec![]);
             },
         }
     }
 }
 
 // if `expr` is a block, check if there are needless returns in it
-fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, semi_spans: Vec<Span>) {
+fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
     if let ExprKind::Block(block, _) = expr_kind {
         if let Some(block_expr) = block.expr {
             check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
@@ -183,12 +183,14 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>,
                     check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
                 },
                 StmtKind::Semi(semi_expr) => {
-                    let mut semi_spans_and_this_one = semi_spans;
-                    // we only want the span containing the semicolon so we can remove it later. From `entry.rs:382`
-                    if let Some(semicolon_span) = stmt.span.trim_start(semi_expr.span) {
-                        semi_spans_and_this_one.push(semicolon_span);
-                        check_final_expr(cx, semi_expr, semi_spans_and_this_one, RetReplacement::Empty);
+                    // Remove ending semicolons and any whitespace ' ' in between.
+                    // Without `return`, the suggestion might not compile if the semicolon is retained
+                    if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) {
+                        let semi_span_to_remove =
+                            span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
+                        semi_spans.push(semi_span_to_remove);
                     }
+                    check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty);
                 },
                 _ => (),
             }
@@ -231,9 +233,9 @@ fn check_final_expr<'tcx>(
             emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement);
         },
         ExprKind::If(_, then, else_clause_opt) => {
-            check_block_return(cx, &then.kind, semi_spans.clone());
+            check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
             if let Some(else_clause) = else_clause_opt {
-                check_block_return(cx, &else_clause.kind, semi_spans);
+                check_block_return(cx, &else_clause.kind, peeled_drop_expr.span, semi_spans);
             }
         },
         // a match expr, check all arms
@@ -246,7 +248,7 @@ fn check_final_expr<'tcx>(
             }
         },
         // if it's a whole block, check it
-        other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans),
+        other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans),
     }
 }
 
diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs
index 301aa5798bf..9c0dc8096d0 100644
--- a/clippy_lints/src/suspicious_xor_used_as_pow.rs
+++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs
@@ -9,7 +9,7 @@ declare_clippy_lint! {
     /// ### What it does
     /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal.
     /// ### Why is this bad?
-    ///	It's most probably a typo and may lead to unexpected behaviours.
+    /// It's most probably a typo and may lead to unexpected behaviours.
     /// ### Example
     /// ```rust
     /// let x = 3_i32 ^ 4_i32;
@@ -18,7 +18,7 @@ declare_clippy_lint! {
     /// ```rust
     /// let x = 3_i32.pow(4);
     /// ```
-    #[clippy::version = "1.66.0"]
+    #[clippy::version = "1.67.0"]
     pub SUSPICIOUS_XOR_USED_AS_POW,
     restriction,
     "XOR (`^`) operator possibly used as exponentiation operator"
diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs
index 691d759d773..c0d290b5adc 100644
--- a/clippy_lints/src/transmute/mod.rs
+++ b/clippy_lints/src/transmute/mod.rs
@@ -479,7 +479,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
                 // - char conversions (https://github.com/rust-lang/rust/issues/89259)
                 let const_context = in_constant(cx, e.hir_id);
 
-                let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
+                let (from_ty, from_ty_adjusted) = match cx.typeck_results().expr_adjustments(arg) {
+                    [] => (cx.typeck_results().expr_ty(arg), false),
+                    [.., a] => (a.target, true),
+                };
                 // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them.
                 let to_ty = cx.typeck_results().expr_ty(e);
 
@@ -506,7 +509,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
                     );
 
                 if !linted {
-                    transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
+                    transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, from_ty_adjusted, to_ty, arg);
                 }
             }
         }
diff --git a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
index b79d4e915a2..8530b43243f 100644
--- a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
+++ b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
@@ -1,11 +1,11 @@
-use super::utils::can_be_expressed_as_pointer_cast;
+use super::utils::check_cast;
 use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
-use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::sugg;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::sugg::Sugg;
 use rustc_errors::Applicability;
 use rustc_hir::Expr;
 use rustc_lint::LateContext;
-use rustc_middle::ty::Ty;
+use rustc_middle::ty::{cast::CastKind, Ty};
 
 /// Checks for `transmutes_expressible_as_ptr_casts` lint.
 /// Returns `true` if it's triggered, otherwise returns `false`.
@@ -13,24 +13,40 @@ pub(super) fn check<'tcx>(
     cx: &LateContext<'tcx>,
     e: &'tcx Expr<'_>,
     from_ty: Ty<'tcx>,
+    from_ty_adjusted: bool,
     to_ty: Ty<'tcx>,
     arg: &'tcx Expr<'_>,
 ) -> bool {
-    if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
-        span_lint_and_then(
-            cx,
-            TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
-            e.span,
-            &format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"),
-            |diag| {
-                if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
-                    let sugg = arg.as_ty(to_ty.to_string()).to_string();
-                    diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
-                }
-            },
-        );
-        true
-    } else {
-        false
-    }
+    use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
+    let mut app = Applicability::MachineApplicable;
+    let sugg = match check_cast(cx, e, from_ty, to_ty) {
+        Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => {
+            Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app)
+                .as_ty(to_ty.to_string())
+                .to_string()
+        },
+        Some(PtrAddrCast) if !from_ty_adjusted => Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app)
+            .as_ty(to_ty.to_string())
+            .to_string(),
+
+        // The only adjustments here would be ref-to-ptr and unsize coercions. The result of an unsize coercions can't
+        // be transmuted to a usize. For ref-to-ptr coercions, borrows need to be cast to a pointer before being cast to
+        // a usize.
+        Some(PtrAddrCast) => format!(
+            "{} as {to_ty}",
+            Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app).as_ty(from_ty)
+        ),
+        _ => return false,
+    };
+
+    span_lint_and_sugg(
+        cx,
+        TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
+        e.span,
+        &format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"),
+        "try",
+        sugg,
+        app,
+    );
+    true
 }
diff --git a/clippy_lints/src/transmute/utils.rs b/clippy_lints/src/transmute/utils.rs
index b59d52dfc4d..cddaf9450ea 100644
--- a/clippy_lints/src/transmute/utils.rs
+++ b/clippy_lints/src/transmute/utils.rs
@@ -20,28 +20,16 @@ pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx
     }
 }
 
-/// Check if the type conversion can be expressed as a pointer cast, instead of
-/// a transmute. In certain cases, including some invalid casts from array
-/// references to pointers, this may cause additional errors to be emitted and/or
-/// ICE error messages. This function will panic if that occurs.
-pub(super) fn can_be_expressed_as_pointer_cast<'tcx>(
-    cx: &LateContext<'tcx>,
-    e: &'tcx Expr<'_>,
-    from_ty: Ty<'tcx>,
-    to_ty: Ty<'tcx>,
-) -> bool {
-    use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
-    matches!(
-        check_cast(cx, e, from_ty, to_ty),
-        Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast)
-    )
-}
-
 /// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of
 /// the cast. In certain cases, including some invalid casts from array references
 /// to pointers, this may cause additional errors to be emitted and/or ICE error
 /// messages. This function will panic if that occurs.
-fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> {
+pub(super) fn check_cast<'tcx>(
+    cx: &LateContext<'tcx>,
+    e: &'tcx Expr<'_>,
+    from_ty: Ty<'tcx>,
+    to_ty: Ty<'tcx>,
+) -> Option<CastKind> {
     let hir_id = e.hir_id;
     let local_def_id = hir_id.owner.def_id;
 
diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs
index 2e1b6d8d4ea..2920684ade3 100644
--- a/clippy_lints/src/undocumented_unsafe_blocks.rs
+++ b/clippy_lints/src/undocumented_unsafe_blocks.rs
@@ -263,6 +263,18 @@ fn expr_has_unnecessary_safety_comment<'tcx>(
     expr: &'tcx hir::Expr<'tcx>,
     comment_pos: BytePos,
 ) -> Option<Span> {
+    if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| {
+        matches!(
+            node,
+            Node::Block(&Block {
+                rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
+                ..
+            }),
+        )
+    }) {
+        return None;
+    }
+
     // this should roughly be the reverse of `block_parents_have_safety_comment`
     if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
         hir::ExprKind::Block(
diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs
index c1589c771c4..f48be27592b 100644
--- a/clippy_lints/src/utils/conf.rs
+++ b/clippy_lints/src/utils/conf.rs
@@ -219,7 +219,8 @@ define_Conf! {
     ///
     /// #### Noteworthy
     ///
-    /// A type, say `SomeType`, listed in this configuration has the same behavior of `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`.
+    /// A type, say `SomeType`, listed in this configuration has the same behavior of
+    /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`.
     (arithmetic_side_effects_allowed: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
     /// Lint: ARITHMETIC_SIDE_EFFECTS.
     ///
diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs
index c4d8c28f060..b1b5164ffb3 100644
--- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs
@@ -14,6 +14,7 @@ use clippy_utils::diagnostics::span_lint;
 use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
 use clippy_utils::{last_path_segment, match_def_path, match_function_call, match_path, paths};
 use if_chain::if_chain;
+use itertools::Itertools;
 use rustc_ast as ast;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::{
@@ -34,8 +35,10 @@ use std::path::Path;
 use std::path::PathBuf;
 use std::process::Command;
 
-/// This is the output file of the lint collector.
-const OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
+/// This is the json output file of the lint collector.
+const JSON_OUTPUT_FILE: &str = "../util/gh-pages/lints.json";
+/// This is the markdown output file of the lint collector.
+const MARKDOWN_OUTPUT_FILE: &str = "../book/src/lint_configuration.md";
 /// These lints are excluded from the export.
 const BLACK_LISTED_LINTS: &[&str] = &["lint_author", "dump_hir", "internal_metadata_collector"];
 /// These groups will be ignored by the lint group matcher. This is useful for collections like
@@ -176,6 +179,23 @@ This lint has the following configuration variables:
                 )
             })
     }
+
+    fn configs_to_markdown(&self, map_fn: fn(&ClippyConfiguration) -> String) -> String {
+        self.config
+            .iter()
+            .filter(|config| config.deprecation_reason.is_none())
+            .filter(|config| !config.lints.is_empty())
+            .map(map_fn)
+            .join("\n")
+    }
+
+    fn get_markdown_docs(&self) -> String {
+        format!(
+            "## Lint Configuration Options\n| <div style=\"width:290px\">Option</div> | Default Value |\n|--|--|\n{}\n\n{}\n",
+            self.configs_to_markdown(ClippyConfiguration::to_markdown_table_entry),
+            self.configs_to_markdown(ClippyConfiguration::to_markdown_paragraph),
+        )
+    }
 }
 
 impl Drop for MetadataCollector {
@@ -199,12 +219,37 @@ impl Drop for MetadataCollector {
 
         collect_renames(&mut lints);
 
-        // Outputting
-        if Path::new(OUTPUT_FILE).exists() {
-            fs::remove_file(OUTPUT_FILE).unwrap();
+        // Outputting json
+        if Path::new(JSON_OUTPUT_FILE).exists() {
+            fs::remove_file(JSON_OUTPUT_FILE).unwrap();
         }
-        let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
+        let mut file = OpenOptions::new()
+            .write(true)
+            .create(true)
+            .open(JSON_OUTPUT_FILE)
+            .unwrap();
         writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
+
+        // Outputting markdown
+        if Path::new(MARKDOWN_OUTPUT_FILE).exists() {
+            fs::remove_file(MARKDOWN_OUTPUT_FILE).unwrap();
+        }
+        let mut file = OpenOptions::new()
+            .write(true)
+            .create(true)
+            .open(MARKDOWN_OUTPUT_FILE)
+            .unwrap();
+        writeln!(
+            file,
+            "<!--
+This file is generated by `cargo collect-metadata`.
+Please use that command to update the file and do not edit it by hand.
+-->
+
+{}",
+            self.get_markdown_docs(),
+        )
+        .unwrap();
     }
 }
 
@@ -505,6 +550,28 @@ impl ClippyConfiguration {
             deprecation_reason,
         }
     }
+
+    fn to_markdown_paragraph(&self) -> String {
+        format!(
+            "### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n",
+            self.name,
+            self.doc
+                .lines()
+                .map(|line| line.strip_prefix("    ").unwrap_or(line))
+                .join("\n"),
+            self.default,
+            self.config_type,
+            self.lints
+                .iter()
+                .map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
+                .map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
+                .join("\n"),
+        )
+    }
+
+    fn to_markdown_table_entry(&self) -> String {
+        format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default)
+    }
 }
 
 fn collect_configs() -> Vec<ClippyConfiguration> {