From 45178143e88a77d3eef13590f3b998c7df43ddcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Fri, 20 Feb 2026 22:51:51 +0100 Subject: [PATCH 1/3] feat: enhanced ci workflow --- .github/workflows/ci.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d5f84d2..9fc3cbd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,8 @@ name: Continuous Integration on: push: - branches: [master] + branches: + - master pull_request: concurrency: @@ -21,9 +22,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Checkout repository + uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@v1 + - name: Set up Rust toolchain + uses: dtolnay/rust-toolchain@v1 with: toolchain: nightly components: rustfmt, clippy, rustc-dev, rust-src, llvm-tools-preview @@ -31,9 +34,11 @@ jobs: - name: Ensure cargo in PATH run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - uses: Swatinem/rust-cache@v2 + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 - - uses: cargo-bins/cargo-binstall@main + - name: Install cargo-binstall + uses: cargo-bins/cargo-binstall@main - name: Install dependencies run: | @@ -44,5 +49,5 @@ jobs: cargo-dylint \ dylint-link - - name: Run CI + - name: Run continuous integration tasks run: cargo make ci From e2dc58970762f9e3ae43a1565b707dfc241f2a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 9 Mar 2026 18:20:34 +0100 Subject: [PATCH 2/3] fix: migrate lint emission from span_lint to emit_span_lint across all rules --- Cargo.lock | 87 +++++++++------- rules/indexing_usage/src/lib.rs | 156 +++++++++++++++-------------- rules/missing_type/src/lib.rs | 149 ++++++++++++++-------------- rules/panic_usage/src/lib.rs | 171 +++++++++++++++++++++----------- rules/unsafe_usage/src/lib.rs | 142 +++++++++++++++----------- 5 files changed, 410 insertions(+), 295 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d8b5b0..b3c210d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "bitflags" @@ -368,19 +368,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -584,9 +584,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.20" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -597,9 +597,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.20" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -630,9 +630,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libgit2-sys" @@ -650,12 +650,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags", "libc", + "plain", "redox_syscall", ] @@ -675,9 +676,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "libc", @@ -687,9 +688,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -788,9 +789,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" @@ -798,6 +799,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -843,9 +850,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -856,11 +863,17 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags", ] @@ -901,9 +914,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustfix" @@ -919,9 +932,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -1018,9 +1031,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1051,12 +1064,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1162,9 +1175,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -1429,9 +1442,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "wit-bindgen" diff --git a/rules/indexing_usage/src/lib.rs b/rules/indexing_usage/src/lib.rs index c65927c..3991706 100644 --- a/rules/indexing_usage/src/lib.rs +++ b/rules/indexing_usage/src/lib.rs @@ -7,7 +7,13 @@ extern crate rustc_middle; extern crate rustc_session; extern crate rustc_span; -use rustc_errors::Diag; +use rustc_errors::{ + Diag, + DiagCtxtHandle, + Diagnostic, + EmissionGuarantee, + Level, +}; use rustc_hir::{Expr, ExprKind, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; @@ -20,71 +26,82 @@ declare_lint! { } declare_lint_pass!(SecurityIndexingUsage => [SECURITY_INDEXING_USAGE]); +/// Wraps a static string so it can be passed to `emit_span_lint`, which +/// requires a type implementing `Diagnostic` rather than a plain closure. +/// +/// # Fields +/// - `0` (`&'static str`) - The lint message to display at the flagged site. +struct LintMsg(&'static str); + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsg { + /// Converts this message into a `Diag` at the given diagnostic level. + /// + /// # Arguments + /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the + /// compiler's diagnostic context, used to create the `Diag`. + /// - `level` (`Level`) - The severity level (error, warning, etc.) at + /// which the diagnostic will be emitted. + /// + /// # Returns + /// A `Diag<'a, G>` ready to be emitted by the compiler. + fn into_diag( + self, + diagnostic_context: DiagCtxtHandle<'a>, + level: Level, + ) -> Diag<'a, G> { + Diag::new(diagnostic_context, level, self.0) + } +} + impl<'tcx> LateLintPass<'tcx> for SecurityIndexingUsage { - /// Detect indexing and slicing operations. + /// Flags any use of the index operator `[]`, whether with a literal, + /// a range (slicing), or a dynamic value. All three forms can panic at + /// runtime on out-of-bounds access and should be replaced with safer + /// alternatives such as `.get()`. /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context, providing access - /// to compiler information and utilities. - /// * `expression` (`&'tcx Expr<'tcx>`) - The expression being checked for - /// indexing and slicing operations. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to type information and diagnostic utilities. + /// - `expression` (`&'tcx Expr<'tcx>`) - The HIR expression being + /// inspected. Only `ExprKind::Index` nodes are acted upon. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_expr( &mut self, context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { if let ExprKind::Index(_, index_expr, _) = &expression.kind { - match &index_expr.kind { - // Literal indexing: array[0]. - ExprKind::Lit(_) => { - context.span_lint( - SECURITY_INDEXING_USAGE, - expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Usage of indexing operation detected.", - ); - }, - ); - }, - - // Range slicing: array[1..], array[..], array[a..b]. + let msg = match &index_expr.kind { ExprKind::Struct(_, _, _) => { - context.span_lint( - SECURITY_INDEXING_USAGE, - expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Usage of slicing operation detected.", - ); - }, - ); - }, - - // Any other dynamic indexing: array[i]. - _ => { - context.span_lint( - SECURITY_INDEXING_USAGE, - expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Usage of indexing operation detected.", - ); - }, - ); + "Usage of slicing operation detected." }, - } + _ => "Usage of indexing operation detected.", + }; + context.emit_span_lint( + SECURITY_INDEXING_USAGE, + expression.span, + LintMsg(msg), + ); } } - /// Detect implementations of indexing traits, such as `Index` and - /// `IndexMut`. + /// Flags any `impl Index` or `impl IndexMut` block. Implementing these + /// traits introduces the `[]` operator on a type, which carries the same + /// panic risk as direct indexing and warrants explicit review. /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context, providing access - /// to compiler information and utilities. - /// * `item` (`&'tcx Item<'tcx>`) - The item being checked for trait - /// implementations. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to language item resolution and diagnostic utilities. + /// - `item` (`&'tcx Item<'tcx>`) - The HIR item being inspected. Only + /// `ItemKind::Impl` nodes that implement `Index` or `IndexMut` are acted + /// upon. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_item( &mut self, context: &LateContext<'tcx>, @@ -96,30 +113,29 @@ impl<'tcx> LateLintPass<'tcx> for SecurityIndexingUsage { && (context.tcx.lang_items().index_trait() == Some(def_id) || context.tcx.lang_items().index_mut_trait() == Some(def_id)) { - context.span_lint( + context.emit_span_lint( SECURITY_INDEXING_USAGE, item.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Implementation of Index/IndexMut trait detected.", - ); - }, + LintMsg("Implementation of Index/IndexMut trait detected."), ); } } } -/// This module defines the `SECURITY_INDEXING_USAGE` lint, which detects the -/// use of indexing and slicing operations in Rust code. The lint checks for -/// array indexing (e.g., `array[index]`), slicing (e.g., `array[1..]`), and -/// implementations of indexing traits (e.g., `Index` and `IndexMut`). The lint -/// emits a warning whenever it detects any of these patterns, helping -/// developers identify potential security issues related to indexing and -/// slicing operations. +/// Registers the lint with the compiler's lint store. Called once when the +/// Dylint library is loaded. +/// +/// # Arguments +/// - `session` (`&Session`) - The current compiler session, used to initialize +/// Dylint's configuration system. +/// - `lint_store` (`&mut LintStore`) - The compiler's lint store into which +/// `SECURITY_INDEXING_USAGE` and its late pass are registered. +/// +/// # Returns +/// `()` - Registration is performed as a side effect. #[unsafe(no_mangle)] pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::init_config(session); - lint_store.register_lints(&[SECURITY_INDEXING_USAGE]); lint_store .register_late_pass(|_: TyCtxt<'_>| Box::new(SecurityIndexingUsage)); @@ -127,19 +143,13 @@ pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::dylint_library!(); -/// This module defines the `SECURITY_INDEXING_USAGE` lint, which detects the -/// use of indexing and slicing operations in Rust code. The lint checks for -/// array indexing (e.g., `array[index]`), slicing (e.g., `array[1..]`), and -/// implementations of indexing traits (e.g., `Index` and `IndexMut`). The lint -/// emits a warning whenever it detects any of these patterns, helping -/// developers identify potential security issues related to indexing and -/// slicing operations. The lint is designed to be used in security-sensitive -/// contexts, where the use of indexing and slicing operations may lead to -/// out-of-bounds access or other vulnerabilities if not used carefully. #[cfg(test)] mod tests { use dylint_testing::ui::Test; + /// Runs UI tests against the `ui` directory, verifying that + /// `SECURITY_INDEXING_USAGE` emits the expected diagnostics for each + /// test case. #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") diff --git a/rules/missing_type/src/lib.rs b/rules/missing_type/src/lib.rs index a0a50db..b678fa8 100644 --- a/rules/missing_type/src/lib.rs +++ b/rules/missing_type/src/lib.rs @@ -7,20 +7,18 @@ extern crate rustc_middle; extern crate rustc_session; extern crate rustc_span; -use rustc_errors::Diag; -use rustc_hir::{Body, BodyId, Expr, ExprKind, LetStmt, PatKind}; +use rustc_errors::{ + Diag, + DiagCtxtHandle, + Diagnostic, + EmissionGuarantee, + Level, +}; +use rustc_hir::{Body, BodyId, ClosureKind, Expr, ExprKind, LetStmt, PatKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; use rustc_session::{Session, declare_lint, declare_lint_pass}; -// This lint detects missing explicit type annotations on let bindings, except -// when the pattern is `_`. It also detects missing explicit type annotations -// on closure parameters, except when the parameter pattern is `_`. The lint -// will emit a warning for each case where an explicit type annotation is -// missing. -// e.g. `let x = 5;` will trigger a warning, while `let _ = 5;` will not. -// Similarly, `|a, b| a + b` will trigger a warning for both parameters, while -// `|_, b| b` will only trigger a warning for the second parameter. declare_lint! { pub MISSING_LET_TYPE, Warn, @@ -38,21 +36,52 @@ declare_lint_pass!(MissingType => [ MISSING_CLOSURE_PARAM_TYPE ]); +/// Wraps a static string so it can be passed to `emit_span_lint`, which +/// requires a type implementing `Diagnostic` rather than a plain closure. +/// +/// # Fields +/// - `0` (`&'static str`) - The lint message to display at the flagged site. +struct LintMsg(&'static str); + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsg { + /// Converts this message into a `Diag` at the given diagnostic level. + /// + /// # Arguments + /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the + /// compiler's diagnostic context, used to create the `Diag`. + /// - `level` (`Level`) - The severity level (error, warning, etc.) at + /// which the diagnostic will be emitted. + /// + /// # Returns + /// A `Diag<'a, G>` ready to be emitted by the compiler. + fn into_diag( + self, + diagnostic_context: DiagCtxtHandle<'a>, + level: Level, + ) -> Diag<'a, G> { + Diag::new(diagnostic_context, level, self.0) + } +} + impl<'tcx> LateLintPass<'tcx> for MissingType { - /// Checks for missing explicit type annotations on let bindings, except - /// when the pattern is `_`. + /// Flags `let` bindings that have no explicit type annotation, unless the + /// pattern is `_`, the binding comes from a macro expansion, or the span + /// is the result of compiler desugaring (async, `?`, `for`, etc.). /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context providing access - /// to the compiler's internal state. - /// * `local` (`&'tcx LetStmt<'tcx>`) - The let statement being checked for - /// a type annotation. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to type information and diagnostic utilities. + /// - `local` (`&'tcx LetStmt<'tcx>`) - The `let` statement being + /// inspected. Only statements whose `ty` field is `None` are flagged. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_local( &mut self, context: &LateContext<'tcx>, local: &'tcx LetStmt<'tcx>, ) { - // Skip if the pattern is `_`. if matches!(local.pat.kind, PatKind::Wild) { return; } @@ -61,48 +90,40 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { return; }; - // Skip if the let statement is from a macro expansion, as it may not - // be possible to determine the type annotation in that case. if context.tcx.hir_body(body_id).value.span.from_expansion() { return; } - // Skip if the let statement is from a macro expansion, as it may not - // be possible to determine the type annotation in that case. - // Ignore anything coming from macro expansion (async_trait, derives, - // etc.) if local.span.from_expansion() { return; } - // Ignore desugared constructs (async lowering, ?, for loops, etc.) if local.span.desugaring_kind().is_some() { return; } - // Check if the let statement has an explicit type annotation. If not, - // emit a warning. if local.ty.is_none() { - context.span_lint( + context.emit_span_lint( MISSING_LET_TYPE, local.pat.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Missing explicit type annotation on let binding.", - ); - }, + LintMsg("Missing explicit type annotation on let binding."), ); } } - /// Checks for missing explicit type annotations on closure parameters, - /// except when the parameter pattern is `_`. + /// Flags closure parameters that have no explicit type annotation, unless + /// the parameter pattern is `_` or the closure is a compiler-generated + /// coroutine (async blocks, async functions). /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context providing access - /// to the compiler's internal state. - /// * `expression` (`&'tcx Expr<'tcx>`) - The expression being checked for - /// closure parameters with missing type annotations. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to HIR bodies and diagnostic utilities. + /// - `expression` (`&'tcx Expr<'tcx>`) - The expression being inspected. + /// Only `ExprKind::Closure` nodes are acted upon. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect for each parameter + /// that is missing a type annotation. fn check_expr( &mut self, context: &LateContext<'tcx>, @@ -112,79 +133,63 @@ impl<'tcx> LateLintPass<'tcx> for MissingType { return; } - // Only check closure expressions. let ExprKind::Closure(closure): &ExprKind<'tcx> = &expression.kind else { return; }; - // Skip if the expression is from a macro expansion, as it may not be - // possible to determine the type annotation in that case. - if matches!(closure.kind, rustc_hir::ClosureKind::Coroutine(_)) { + if matches!(closure.kind, ClosureKind::Coroutine(_)) { return; } - // Get the body of the closure to access its parameters. let body: &Body<'_> = context.tcx.hir_body(closure.body); - // Iterate over the parameters of the closure. For each parameter, - // check if it has an explicit type annotation. If not, and if - // the parameter pattern is not `_`, emit a warning. for param in body.params { - // Skip if the parameter pattern is `_`. if matches!(param.pat.kind, PatKind::Wild) { continue; } - // Check if the parameter has an explicit type annotation. If not, - // emit a warning. if param.ty_span.is_empty() || param.ty_span == param.pat.span { - context.span_lint( + context.emit_span_lint( MISSING_CLOSURE_PARAM_TYPE, param.pat.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Closure parameter missing explicit type annotation.", - ); - }, + LintMsg( + "Closure parameter missing explicit type annotation.", + ), ); } } } } -/// Registers the lints defined in this library with the Rust compiler. This -/// function is called by the compiler when the library is loaded as a plugin. -/// It initializes the lint configuration and registers the lints and their -/// corresponding lint pass with the compiler's lint store. +/// Registers both lints with the compiler's lint store. Called once when the +/// Dylint library is loaded. /// /// # Arguments -/// * `session` (`&Session`) - The compiler session providing access to the -/// compiler's internal state and configuration. -/// * `lint_store` (`&mut LintStore`) - The lint store where the lints defined -/// in this library will be registered. +/// - `session` (`&Session`) - The current compiler session, used to initialize +/// Dylint's configuration system. +/// - `lint_store` (`&mut LintStore`) - The compiler's lint store into which +/// `MISSING_LET_TYPE`, `MISSING_CLOSURE_PARAM_TYPE`, and their shared late +/// pass are registered. +/// +/// # Returns +/// `()` - Registration is performed as a side effect. #[unsafe(no_mangle)] pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::init_config(session); - lint_store.register_lints(&[MISSING_LET_TYPE, MISSING_CLOSURE_PARAM_TYPE]); lint_store.register_late_pass(|_: TyCtxt<'_>| Box::new(MissingType)); } dylint_linting::dylint_library!(); -/// UI test for the `missing_type` lint. This test compiles the code in the -/// `ui` directory and checks that the expected warnings are emitted for -/// missing explicit type annotations on let bindings and closure parameters, -/// while ensuring that no warnings are emitted for cases where the pattern is -/// `_`. The test uses the `dylint_testing` crate to run the UI tests with the -/// appropriate compiler flags for UI testing. The test will pass if the -/// expected warnings are emitted and fail if any unexpected warnings are -/// emitted or if the expected warnings are not emitted. #[cfg(test)] mod tests { use dylint_testing::ui::Test; + /// Runs UI tests against the `ui` directory, verifying that + /// `MISSING_LET_TYPE` and `MISSING_CLOSURE_PARAM_TYPE` emit the expected + /// diagnostics and stay silent on exempt patterns. #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") diff --git a/rules/panic_usage/src/lib.rs b/rules/panic_usage/src/lib.rs index 453a029..7e5668f 100644 --- a/rules/panic_usage/src/lib.rs +++ b/rules/panic_usage/src/lib.rs @@ -7,12 +7,18 @@ extern crate rustc_middle; extern crate rustc_session; extern crate rustc_span; -use rustc_errors::Diag; +use rustc_errors::{ + Diag, + DiagCtxtHandle, + Diagnostic, + EmissionGuarantee, + Level, +}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; use rustc_middle::ty::TyCtxt; use rustc_session::{Session, declare_lint, declare_lint_pass}; -use rustc_span::sym; +use rustc_span::symbol::Symbol; declare_lint! { pub SECURITY_PANIC_USAGE, @@ -22,20 +28,86 @@ declare_lint! { declare_lint_pass!(SecurityPanicUsage => [SECURITY_PANIC_USAGE]); -/// Enum representing the different panic backends that can be detected by the -/// `SECURITY_PANIC_USAGE` lint, such as the `panicking` module, the -/// `panic_fmt` function, the `panic_display` function, the `assert_failed` -/// function, and the `begin_panic` function in the standard library. +/// Wraps a static string so it can be passed to `emit_span_lint`, which +/// requires a type implementing `Diagnostic` rather than a plain closure. +/// +/// # Fields +/// - `0` (`&'static str`) - The lint message to display at the flagged site. +struct LintMsg(&'static str); + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsg { + /// Converts this message into a `Diag` at the given diagnostic level. + /// + /// # Arguments + /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the + /// compiler's diagnostic context, used to create the `Diag`. + /// - `level` (`Level`) - The severity level at which the diagnostic will + /// be emitted. + /// + /// # Returns + /// A `Diag<'a, G>` ready to be emitted by the compiler. + fn into_diag( + self, + diagnostic_context: DiagCtxtHandle<'a>, + level: Level, + ) -> Diag<'a, G> { + Diag::new(diagnostic_context, level, self.0) + } +} + +/// Wraps an owned string for use as a dynamic lint message with +/// `emit_span_lint`. +/// +/// # Fields +/// - `0` (`String`) - The lint message to display at the flagged site. +struct LintMsgOwned(String); + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsgOwned { + /// Converts this message into a `Diag` at the given diagnostic level. + /// + /// # Arguments + /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the + /// compiler's diagnostic context, used to create the `Diag`. + /// - `level` (`Level`) - The severity level at which the diagnostic will + /// be emitted. + /// + /// # Returns + /// A `Diag<'a, G>` ready to be emitted by the compiler. + fn into_diag( + self, + diagnostic_context: DiagCtxtHandle<'a>, + level: Level, + ) -> Diag<'a, G> { + Diag::new(diagnostic_context, level, self.0) + } +} + +/// Identifies the panic backend behind a call by inspecting the definition +/// path of the callee. Used to produce a precise diagnostic message. #[derive(Debug, Clone, Copy)] enum PanicBackend { + /// Any function inside a `panicking` module (e.g. `core::panicking`). PanickingModule, + /// The `panic_fmt` internal function. PanicFmt, + /// The `panic_display` internal function. PanicDisplay, + /// The `assert_failed` internal function emitted by `assert!`. AssertFailed, + /// The `begin_panic` function used by older panic implementations. BeginPanic, } impl PanicBackend { + /// Attempts to identify a panic backend from a definition path string. + /// + /// # Arguments + /// - `path` (`&str`) - The fully-qualified definition path of the callee + /// as returned by `TyCtxt::def_path_str`. + /// + /// # Returns + /// `Some(PanicBackend)` if the path matches a known panic backend, + /// `None` otherwise. fn from_def_path(path: &str) -> Option { if path.contains("panicking::") { Some(Self::PanickingModule) @@ -54,44 +126,40 @@ impl PanicBackend { } impl<'tcx> LateLintPass<'tcx> for SecurityPanicUsage { - /// Detect calls to panic-related functions and methods, such as `unwrap`, - /// `expect`, and functions in the standard library's panic module. + /// Flags calls to `.unwrap()` and `.expect()` by method name, as well as + /// direct calls to internal panic backends such as `panic_fmt`, + /// `panic_display`, `assert_failed`, and `begin_panic`. + /// + /// `.unwrap()` and `.expect()` are detected by method name rather than + /// diagnostic item because the relevant `sym` entries are not stable + /// across nightly versions. /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context providing access - /// to the compiler's internal state. - /// * `expression` (`&'tcx Expr<'tcx>`) - The expression being checked for - /// panic-related function and method calls. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to type-check results and diagnostic utilities. + /// - `expression` (`&'tcx Expr<'tcx>`) - The HIR expression being + /// inspected. Both `ExprKind::MethodCall` and `ExprKind::Call` nodes are + /// acted upon. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_expr( &mut self, context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { - // Detect direct calls to `unwrap` and `expect` methods. - // This checks for method calls where the method name is `unwrap` or - // `expect`, and the method is defined in the local crate (to avoid - // false positives from external crates). - if let ExprKind::MethodCall(_, _, _, _) = &expression.kind - && let Some(def_id) = context - .typeck_results() - .type_dependent_def_id(expression.hir_id) - && (context.tcx.is_diagnostic_item(sym::unwrap, def_id) - || context.tcx.is_diagnostic_item(sym::option_unwrap, def_id) - || context.tcx.is_diagnostic_item(sym::except, def_id) - || context.tcx.is_diagnostic_item(sym::option_expect, def_id)) + if let ExprKind::MethodCall(method, _, _, _) = &expression.kind + && (method.ident.name == Symbol::intern("unwrap") + || method.ident.name == Symbol::intern("expect")) { - context.span_lint( + context.emit_span_lint( SECURITY_PANIC_USAGE, expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic.primary_message( - "Call to panic backend `unwrap/expect` detected.", - ); - }, + LintMsg("Call to panic backend `unwrap/expect` detected."), ); } - // Detect calls to panic-related functions in the standard library. if let ExprKind::Call(func, _) = &expression.kind && let ExprKind::Path(path) = &func.kind && let Some(def_id) = @@ -99,38 +167,31 @@ impl<'tcx> LateLintPass<'tcx> for SecurityPanicUsage { && let Some(kind) = PanicBackend::from_def_path(&context.tcx.def_path_str(def_id)) { - context.span_lint( + context.emit_span_lint( SECURITY_PANIC_USAGE, expression.span.source_callsite(), - |diag: &mut Diag<'_, ()>| { - diag.primary_message(format!( - "Call to panic backend `{kind:?}` detected." - )); - }, + LintMsgOwned(format!( + "Call to panic backend `{kind:?}` detected." + )), ); } } } -/// Registers the `SECURITY_PANIC_USAGE` lint and its corresponding lint pass -/// with the Rust compiler. This function is called by the compiler when the -/// library is loaded as a plugin. It initializes the lint configuration and -/// registers the lint and its corresponding lint pass with the compiler's lint -/// store. This allows the `SECURITY_PANIC_USAGE` lint to be used when -/// compiling code that depends on this library, and ensures that the lint will -/// be applied to the code being compiled, allowing it to detect and warn about -/// constructs that may panic at runtime. +/// Registers the lint with the compiler's lint store. Called once when the +/// Dylint library is loaded. /// /// # Arguments -/// * `session` (`&Session`) - The compiler session providing access to the -/// compiler's internal state. -/// * `lint_store` (`&mut LintStore`) - The lint store where the -/// `SECURITY_PANIC_USAGE` lint and its corresponding lint pass will be -/// registered. +/// - `session` (`&Session`) - The current compiler session, used to initialize +/// Dylint's configuration system. +/// - `lint_store` (`&mut LintStore`) - The compiler's lint store into which +/// `SECURITY_PANIC_USAGE` and its late pass are registered. +/// +/// # Returns +/// `()` - Registration is performed as a side effect. #[unsafe(no_mangle)] pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::init_config(session); - lint_store.register_lints(&[SECURITY_PANIC_USAGE]); lint_store .register_late_pass(|_: TyCtxt<'_>| Box::new(SecurityPanicUsage)); @@ -138,15 +199,13 @@ pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::dylint_library!(); -/// UI test for the `SECURITY_PANIC_USAGE` lint. This test compiles the code in -/// the `ui` directory with the appropriate compiler flags for UI testing. The -/// test will pass if the expected warnings are emitted for calls to -/// panic-related functions and methods, and fail if any unexpected warnings -/// are emitted or if the expected warnings are not emitted. #[cfg(test)] mod tests { use dylint_testing::ui::Test; + /// Runs UI tests against the `ui` directory, verifying that + /// `SECURITY_PANIC_USAGE` emits the expected diagnostics for calls to + /// panic-prone functions and stays silent on safe alternatives. #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") diff --git a/rules/unsafe_usage/src/lib.rs b/rules/unsafe_usage/src/lib.rs index f91d6e4..74b204e 100644 --- a/rules/unsafe_usage/src/lib.rs +++ b/rules/unsafe_usage/src/lib.rs @@ -6,7 +6,13 @@ extern crate rustc_lint; extern crate rustc_middle; extern crate rustc_session; -use rustc_errors::Diag; +use rustc_errors::{ + Diag, + DiagCtxtHandle, + Diagnostic, + EmissionGuarantee, + Level, +}; use rustc_hir::{ BlockCheckMode, Expr, @@ -30,91 +36,116 @@ declare_lint! { declare_lint_pass!(SecurityUnsafeUsage => [SECURITY_UNSAFE_USAGE]); +/// Wraps a static string so it can be passed to `emit_span_lint`, which +/// requires a type implementing `Diagnostic` rather than a plain closure. +/// +/// # Fields +/// - `0` (`&'static str`) - The lint message to display at the flagged site. +struct LintMsg(&'static str); + +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsg { + /// Converts this message into a `Diag` at the given diagnostic level. + /// + /// # Arguments + /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the + /// compiler's diagnostic context, used to create the `Diag`. + /// - `level` (`Level`) - The severity level at which the diagnostic will + /// be emitted. + /// + /// # Returns + /// A `Diag<'a, G>` ready to be emitted by the compiler. + fn into_diag( + self, + diagnostic_context: DiagCtxtHandle<'a>, + level: Level, + ) -> Diag<'a, G> { + Diag::new(diagnostic_context, level, self.0) + } +} + impl<'tcx> LateLintPass<'tcx> for SecurityUnsafeUsage { - /// Detect unsafe blocks with user-provided unsafe source. + /// Flags explicitly user-written `unsafe` blocks. Compiler-generated + /// unsafe blocks (e.g. from desugaring) are ignored via the + /// `UnsafeSource::UserProvided` guard. /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context providing access - /// to the compiler's internal state. - /// * `expression` (`&'tcx Expr<'tcx>`) - The expression being checked for - /// unsafe block usage. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to diagnostic utilities. + /// - `expression` (`&'tcx Expr<'tcx>`) - The HIR expression being + /// inspected. Only `ExprKind::Block` nodes with + /// `BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)` are acted + /// upon. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_expr( &mut self, context: &LateContext<'tcx>, expression: &'tcx Expr<'tcx>, ) { - // Only check block expressions. if let ExprKind::Block(block, _) = &expression.kind && let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules { - context.span_lint( + context.emit_span_lint( SECURITY_UNSAFE_USAGE, expression.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic - .primary_message("Usage of unsafe block detected."); - }, + LintMsg("Usage of unsafe block detected."), ); } } - /// Detect unsafe function, trait and implementation definitions. + /// Flags unsafe function definitions, unsafe trait declarations, and + /// unsafe trait implementations (`unsafe impl`). /// /// # Arguments - /// * `context` (`&LateContext<'tcx>`) - The lint context providing access - /// to the compiler's internal state. - /// * `item` (`&'tcx Item<'tcx>`) - The item being checked for unsafe - /// function, trait or implementation definitions. + /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access + /// to diagnostic utilities. + /// - `item` (`&'tcx Item<'tcx>`) - The HIR item being inspected. + /// `ItemKind::Fn`, `ItemKind::Trait`, and `ItemKind::Impl` nodes are + /// acted upon when they carry the `unsafe` modifier. + /// + /// # Returns + /// `()` - Emits a lint diagnostic as a side effect if a violation is + /// found. fn check_item( &mut self, context: &LateContext<'tcx>, item: &'tcx Item<'tcx>, ) { match &item.kind { - // Unsafe function. ItemKind::Fn { sig, .. } => { if matches!( sig.header.safety, HeaderSafety::Normal(Safety::Unsafe) ) { - context.span_lint( + context.emit_span_lint( SECURITY_UNSAFE_USAGE, item.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic - .primary_message("Unsafe function detected."); - }, + LintMsg("Unsafe function detected."), ); } }, - // Unsafe trait. - ItemKind::Trait(_, _, safety, _, _, _, _) => { - if *safety == Safety::Unsafe { - context.span_lint( - SECURITY_UNSAFE_USAGE, - item.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic - .primary_message("Unsafe trait detected."); - }, - ); - } + ItemKind::Trait(_, _, safety, _, _, _, _) + if *safety == Safety::Unsafe => + { + context.emit_span_lint( + SECURITY_UNSAFE_USAGE, + item.span, + LintMsg("Unsafe trait detected."), + ); }, - // Unsafe implementation. ItemKind::Impl(impl_) => { if let Some(trait_impl) = impl_.of_trait && trait_impl.safety == Safety::Unsafe { - context.span_lint( + context.emit_span_lint( SECURITY_UNSAFE_USAGE, item.span, - |diagnostic: &mut Diag<'_, ()>| { - diagnostic - .primary_message("Unsafe impl detected."); - }, + LintMsg("Unsafe impl detected."), ); } }, @@ -124,19 +155,20 @@ impl<'tcx> LateLintPass<'tcx> for SecurityUnsafeUsage { } } -/// Registers the `SECURITY_UNSAFE_USAGE` lint and its corresponding lint pass -/// with the Rust compiler. +/// Registers the lint with the compiler's lint store. Called once when the +/// Dylint library is loaded. /// /// # Arguments -/// * `session` (`&Session`) - The compiler session providing access to the -/// compiler's internal state. -/// * `lint_store` (`&mut LintStore`) - The lint store where the -/// `SECURITY_UNSAFE_USAGE` lint and its corresponding lint pass will be -/// registered. +/// - `session` (`&Session`) - The current compiler session, used to initialize +/// Dylint's configuration system. +/// - `lint_store` (`&mut LintStore`) - The compiler's lint store into which +/// `SECURITY_UNSAFE_USAGE` and its late pass are registered. +/// +/// # Returns +/// `()` - Registration is performed as a side effect. #[unsafe(no_mangle)] pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::init_config(session); - lint_store.register_lints(&[SECURITY_UNSAFE_USAGE]); lint_store .register_late_pass(|_: TyCtxt<'_>| Box::new(SecurityUnsafeUsage)); @@ -144,18 +176,14 @@ pub fn register_lints(session: &Session, lint_store: &mut LintStore) { dylint_linting::dylint_library!(); -/// UI tests for the `SECURITY_UNSAFE_USAGE` lint. These tests are located in -/// the `ui` directory and are compiled with the appropriate compiler flags for -/// UI testing. The tests check that the expected warnings are emitted for -/// unsafe block usage, unsafe function definitions, unsafe trait definitions -/// and unsafe implementation definitions, while ensuring that no warnings are -/// emitted for safe code. The tests will pass if the expected warnings are -/// emitted and fail if any unexpected warnings are emitted or if the expected -/// warnings are not emitted. #[cfg(test)] mod tests { use dylint_testing::ui::Test; + /// Runs UI tests against the `ui` directory, verifying that + /// `SECURITY_UNSAFE_USAGE` emits the expected diagnostics for unsafe + /// blocks, functions, traits, and implementations, and stays silent on + /// safe code. #[test] fn ui() { Test::src_base(env!("CARGO_PKG_NAME"), "ui") From 216b302cff8813c9a9adb8931b4da2389c148397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9an?= Date: Mon, 9 Mar 2026 20:21:48 +0100 Subject: [PATCH 3/3] chore: remove panic_usage lint (too complex to maintain) --- Cargo.lock | 8 - Cargo.toml | 7 +- rules/panic_usage/.cargo/config.toml | 6 - rules/panic_usage/Cargo.toml | 19 --- rules/panic_usage/README.md | 36 ----- rules/panic_usage/src/lib.rs | 215 --------------------------- rules/panic_usage/ui/main.rs | 22 --- rules/panic_usage/ui/main.stderr | 62 -------- 8 files changed, 1 insertion(+), 374 deletions(-) delete mode 100644 rules/panic_usage/.cargo/config.toml delete mode 100644 rules/panic_usage/Cargo.toml delete mode 100644 rules/panic_usage/README.md delete mode 100644 rules/panic_usage/src/lib.rs delete mode 100644 rules/panic_usage/ui/main.rs delete mode 100644 rules/panic_usage/ui/main.stderr diff --git a/Cargo.lock b/Cargo.lock index b3c210d..938f340 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,14 +767,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "panic_usage" -version = "1.0.0" -dependencies = [ - "dylint_linting", - "dylint_testing", -] - [[package]] name = "paste" version = "1.0.15" diff --git a/Cargo.toml b/Cargo.toml index b526584..2a33f21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,5 @@ [workspace] -members = [ - "rules/missing_type", - "rules/unsafe_usage", - "rules/panic_usage", - "rules/indexing_usage", -] +members = ["rules/missing_type", "rules/unsafe_usage", "rules/indexing_usage"] resolver = "2" [workspace.metadata.dylint] diff --git a/rules/panic_usage/.cargo/config.toml b/rules/panic_usage/.cargo/config.toml deleted file mode 100644 index 226eca5..0000000 --- a/rules/panic_usage/.cargo/config.toml +++ /dev/null @@ -1,6 +0,0 @@ -[target.'cfg(all())'] -rustflags = ["-C", "linker=dylint-link"] - -# For Rust versions 1.74.0 and onward, the following alternative can be used -# (see https://github.com/rust-lang/cargo/pull/12535): -# linker = "dylint-link" diff --git a/rules/panic_usage/Cargo.toml b/rules/panic_usage/Cargo.toml deleted file mode 100644 index 084293a..0000000 --- a/rules/panic_usage/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "panic_usage" -version = "1.0.0" -description = "A Dylint lint that checks for panic-prone usage." -edition = "2024" -license = "MIT" -publish = false - -[lib] -crate-type = ["cdylib"] - -[dependencies] -dylint_linting = "5.0.0" - -[dev-dependencies] -dylint_testing = "5.0.0" - -[package.metadata.rust-analyzer] -rustc_private = true diff --git a/rules/panic_usage/README.md b/rules/panic_usage/README.md deleted file mode 100644 index 7e977d2..0000000 --- a/rules/panic_usage/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# panic_usage - -## What it does - -`panic_usage` is a Dylint security lint that detects any usage of Rust's `panic!`-prone features. - -It emits a denial when it encounters: - -- `panic!` macros, -- `unwrap()` and `expect()` methods, -- `todo!()` and `unimplemented!()` macros, -- `assert!` and related macros. - -The goal of this lint is to make panic-prone code explicitly visible during code review, especially in security-sensitive environments. - -## Example - -Code that triggers warnings: - -```rust -#![warn(security_panic_usage)] - -fn main() { - let x: Option = None; - x.unwrap(); // warning: Call to panic backend `Unwrap` detected. - x.expect(""); // warning: Call to panic backend `Expect` detected. - - panic!(""); // warning: Call to panic backend `BeginPanic` detected. - assert!(false); // warning: Call to panic backend `PanickingModule` detected. - assert_eq!(0, 1); // warning: Call to panic backend `PanickingModule` detected. - assert_ne!(0, 0); // warning: Call to panic backend `PanickingModule` detected. - todo!(); // warning: Call to panic backend `PanickingModule` detected. - unimplemented!(); // warning: Call to panic backend `PanickingModule` detected. - unreachable!(); // warning: Call to panic backend `PanickingModule` detected. -} -``` diff --git a/rules/panic_usage/src/lib.rs b/rules/panic_usage/src/lib.rs deleted file mode 100644 index 7e5668f..0000000 --- a/rules/panic_usage/src/lib.rs +++ /dev/null @@ -1,215 +0,0 @@ -#![feature(rustc_private)] - -extern crate rustc_errors; -extern crate rustc_hir; -extern crate rustc_lint; -extern crate rustc_middle; -extern crate rustc_session; -extern crate rustc_span; - -use rustc_errors::{ - Diag, - DiagCtxtHandle, - Diagnostic, - EmissionGuarantee, - Level, -}; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext, LintStore}; -use rustc_middle::ty::TyCtxt; -use rustc_session::{Session, declare_lint, declare_lint_pass}; -use rustc_span::symbol::Symbol; - -declare_lint! { - pub SECURITY_PANIC_USAGE, - Deny, - "Detects constructs that may panic at runtime." -} - -declare_lint_pass!(SecurityPanicUsage => [SECURITY_PANIC_USAGE]); - -/// Wraps a static string so it can be passed to `emit_span_lint`, which -/// requires a type implementing `Diagnostic` rather than a plain closure. -/// -/// # Fields -/// - `0` (`&'static str`) - The lint message to display at the flagged site. -struct LintMsg(&'static str); - -impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsg { - /// Converts this message into a `Diag` at the given diagnostic level. - /// - /// # Arguments - /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the - /// compiler's diagnostic context, used to create the `Diag`. - /// - `level` (`Level`) - The severity level at which the diagnostic will - /// be emitted. - /// - /// # Returns - /// A `Diag<'a, G>` ready to be emitted by the compiler. - fn into_diag( - self, - diagnostic_context: DiagCtxtHandle<'a>, - level: Level, - ) -> Diag<'a, G> { - Diag::new(diagnostic_context, level, self.0) - } -} - -/// Wraps an owned string for use as a dynamic lint message with -/// `emit_span_lint`. -/// -/// # Fields -/// - `0` (`String`) - The lint message to display at the flagged site. -struct LintMsgOwned(String); - -impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for LintMsgOwned { - /// Converts this message into a `Diag` at the given diagnostic level. - /// - /// # Arguments - /// - `diagnostic_context` (`DiagCtxtHandle<'a>`) - Handle to the - /// compiler's diagnostic context, used to create the `Diag`. - /// - `level` (`Level`) - The severity level at which the diagnostic will - /// be emitted. - /// - /// # Returns - /// A `Diag<'a, G>` ready to be emitted by the compiler. - fn into_diag( - self, - diagnostic_context: DiagCtxtHandle<'a>, - level: Level, - ) -> Diag<'a, G> { - Diag::new(diagnostic_context, level, self.0) - } -} - -/// Identifies the panic backend behind a call by inspecting the definition -/// path of the callee. Used to produce a precise diagnostic message. -#[derive(Debug, Clone, Copy)] -enum PanicBackend { - /// Any function inside a `panicking` module (e.g. `core::panicking`). - PanickingModule, - /// The `panic_fmt` internal function. - PanicFmt, - /// The `panic_display` internal function. - PanicDisplay, - /// The `assert_failed` internal function emitted by `assert!`. - AssertFailed, - /// The `begin_panic` function used by older panic implementations. - BeginPanic, -} - -impl PanicBackend { - /// Attempts to identify a panic backend from a definition path string. - /// - /// # Arguments - /// - `path` (`&str`) - The fully-qualified definition path of the callee - /// as returned by `TyCtxt::def_path_str`. - /// - /// # Returns - /// `Some(PanicBackend)` if the path matches a known panic backend, - /// `None` otherwise. - fn from_def_path(path: &str) -> Option { - if path.contains("panicking::") { - Some(Self::PanickingModule) - } else if path.contains("panic_fmt") { - Some(Self::PanicFmt) - } else if path.contains("panic_display") { - Some(Self::PanicDisplay) - } else if path.contains("assert_failed") { - Some(Self::AssertFailed) - } else if path.contains("begin_panic") { - Some(Self::BeginPanic) - } else { - None - } - } -} - -impl<'tcx> LateLintPass<'tcx> for SecurityPanicUsage { - /// Flags calls to `.unwrap()` and `.expect()` by method name, as well as - /// direct calls to internal panic backends such as `panic_fmt`, - /// `panic_display`, `assert_failed`, and `begin_panic`. - /// - /// `.unwrap()` and `.expect()` are detected by method name rather than - /// diagnostic item because the relevant `sym` entries are not stable - /// across nightly versions. - /// - /// # Arguments - /// - `context` (`&LateContext<'tcx>`) - The lint context, providing access - /// to type-check results and diagnostic utilities. - /// - `expression` (`&'tcx Expr<'tcx>`) - The HIR expression being - /// inspected. Both `ExprKind::MethodCall` and `ExprKind::Call` nodes are - /// acted upon. - /// - /// # Returns - /// `()` - Emits a lint diagnostic as a side effect if a violation is - /// found. - fn check_expr( - &mut self, - context: &LateContext<'tcx>, - expression: &'tcx Expr<'tcx>, - ) { - if let ExprKind::MethodCall(method, _, _, _) = &expression.kind - && (method.ident.name == Symbol::intern("unwrap") - || method.ident.name == Symbol::intern("expect")) - { - context.emit_span_lint( - SECURITY_PANIC_USAGE, - expression.span, - LintMsg("Call to panic backend `unwrap/expect` detected."), - ); - } - - if let ExprKind::Call(func, _) = &expression.kind - && let ExprKind::Path(path) = &func.kind - && let Some(def_id) = - context.qpath_res(path, func.hir_id).opt_def_id() - && let Some(kind) = - PanicBackend::from_def_path(&context.tcx.def_path_str(def_id)) - { - context.emit_span_lint( - SECURITY_PANIC_USAGE, - expression.span.source_callsite(), - LintMsgOwned(format!( - "Call to panic backend `{kind:?}` detected." - )), - ); - } - } -} - -/// Registers the lint with the compiler's lint store. Called once when the -/// Dylint library is loaded. -/// -/// # Arguments -/// - `session` (`&Session`) - The current compiler session, used to initialize -/// Dylint's configuration system. -/// - `lint_store` (`&mut LintStore`) - The compiler's lint store into which -/// `SECURITY_PANIC_USAGE` and its late pass are registered. -/// -/// # Returns -/// `()` - Registration is performed as a side effect. -#[unsafe(no_mangle)] -pub fn register_lints(session: &Session, lint_store: &mut LintStore) { - dylint_linting::init_config(session); - lint_store.register_lints(&[SECURITY_PANIC_USAGE]); - lint_store - .register_late_pass(|_: TyCtxt<'_>| Box::new(SecurityPanicUsage)); -} - -dylint_linting::dylint_library!(); - -#[cfg(test)] -mod tests { - use dylint_testing::ui::Test; - - /// Runs UI tests against the `ui` directory, verifying that - /// `SECURITY_PANIC_USAGE` emits the expected diagnostics for calls to - /// panic-prone functions and stays silent on safe alternatives. - #[test] - fn ui() { - Test::src_base(env!("CARGO_PKG_NAME"), "ui") - .rustc_flags(["--edition=2024", "-Z", "ui-testing"]) - .run(); - } -} diff --git a/rules/panic_usage/ui/main.rs b/rules/panic_usage/ui/main.rs deleted file mode 100644 index d680d1d..0000000 --- a/rules/panic_usage/ui/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![warn(security_panic_usage)] - -/// This module defines the `SECURITY_PANIC_USAGE` lint, which detects the use -/// of constructs that may cause panics at runtime, such as `unwrap()`, -/// `expect()`, `panic!()`, `assert!()`, and related macros. The lint is -/// designed to help developers identify and review panic-prone code, -/// especially in security-sensitive contexts. -fn main() { - let x: Option = None; - x.unwrap(); // Should trigger. - x.expect(""); // should trigger. - - panic!(""); // Should trigger. - - assert!(false); // Should trigger. - assert_eq!(0, 1); // Should trigger. - assert_ne!(0, 0); // Should trigger. - - todo!(); // Should trigger. - unimplemented!(); // Should trigger. - unreachable!(); // Should trigger. -} diff --git a/rules/panic_usage/ui/main.stderr b/rules/panic_usage/ui/main.stderr deleted file mode 100644 index 5675c85..0000000 --- a/rules/panic_usage/ui/main.stderr +++ /dev/null @@ -1,62 +0,0 @@ -warning: Call to panic backend `unwrap/expect` detected. - --> $DIR/main.rs:10:5 - | -LL | x.unwrap(); // Should trigger. - | ^^^^^^^^^^ - | -note: the lint level is defined here - --> $DIR/main.rs:1:9 - | -LL | #![warn(security_panic_usage)] - | ^^^^^^^^^^^^^^^^^^^^ - -warning: Call to panic backend `unwrap/expect` detected. - --> $DIR/main.rs:11:5 - | -LL | x.expect(""); // should trigger. - | ^^^^^^^^^^^^ - -warning: Call to panic backend `PanicFmt` detected. - --> $DIR/main.rs:13:5 - | -LL | panic!(""); // Should trigger. - | ^^^^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:15:5 - | -LL | assert!(false); // Should trigger. - | ^^^^^^^^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:16:5 - | -LL | assert_eq!(0, 1); // Should trigger. - | ^^^^^^^^^^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:17:5 - | -LL | assert_ne!(0, 0); // Should trigger. - | ^^^^^^^^^^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:19:5 - | -LL | todo!(); // Should trigger. - | ^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:20:5 - | -LL | unimplemented!(); // Should trigger. - | ^^^^^^^^^^^^^^^^ - -warning: Call to panic backend `PanickingModule` detected. - --> $DIR/main.rs:21:5 - | -LL | unreachable!(); // Should trigger. - | ^^^^^^^^^^^^^^ - -warning: 9 warnings emitted -