Compare commits

..

6 commits

Author SHA1 Message Date
ace01a2096 Write lint to check for dashmaps entries being held
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 14:57:46 +01:00
bf589f7d28 Fix building of lints
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 12:22:12 +01:00
4e968aaea0 Make lint run
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 12:11:22 +01:00
6813a9ed43 Generate cargo-hakari deps
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 11:42:57 +01:00
ac995ea522 Add cargo-hakari
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 11:42:31 +01:00
a894c4c413 Add initial workspace
Signed-off-by: Marcel Müller <neikos@neikos.email>
2025-03-04 11:40:38 +01:00
18 changed files with 2247 additions and 5 deletions

27
.config/hakari.toml Normal file
View file

@ -0,0 +1,27 @@
# This file contains settings for `cargo hakari`.
# See https://docs.rs/cargo-hakari/latest/cargo_hakari/config for a full list of options.
hakari-package = "hakari"
# Format version for hakari's output. Version 4 requires cargo-hakari 0.9.22 or above.
dep-format-version = "4"
# Setting workspace.resolver = "2" or higher in the root Cargo.toml is HIGHLY recommended.
# Hakari works much better with the v2 resolver. (The v2 and v3 resolvers are identical from
# hakari's perspective, so you're welcome to set either.)
#
# For more about the new feature resolver, see:
# https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html#cargos-new-feature-resolver
resolver = "2"
# Add triples corresponding to platforms commonly used by developers here.
# https://doc.rust-lang.org/rustc/platform-support.html
platforms = [
# "x86_64-unknown-linux-gnu",
# "x86_64-apple-darwin",
# "aarch64-apple-darwin",
# "x86_64-pc-windows-msvc",
]
# Write out exact versions rather than a semver range. (Defaults to false.)
# exact-versions = true

5
.gitignore vendored
View file

@ -1 +1,6 @@
.direnv/
# Added by cargo
/target

1720
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[workspace]
members = [ "hakari" , "lints/*" ]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
[workspace.metadata.crane]
name = "dylints-various"

6
flake.lock generated
View file

@ -31,11 +31,11 @@
]
},
"locked": {
"lastModified": 1741084643,
"narHash": "sha256-pohp+9ekX2rBuoew8MNoqkZKaez6q4n+8j/K6SSfTrE=",
"lastModified": 1741089216,
"narHash": "sha256-5ZBaJ0XvjakoZwpHPj6oGzwCcCrcREQlNuDklQfZAxQ=",
"owner": "TheNeikos",
"repo": "nix-dylint",
"rev": "c88fde3de47277b3d0c1f16f740ff8ceb079e161",
"rev": "7ca821aada8b6fd82a5c422cd1eb821eb208f8ae",
"type": "github"
},
"original": {

View file

@ -32,6 +32,9 @@
];
};
inherit (pkgs) lib;
toolchain = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)).toolchain.channel;
rustTarget = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
dylintLib = inputs.dylint.mkLib {
@ -39,7 +42,61 @@
inherit (inputs) crane;
};
lints = [ ];
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustTarget;
src = craneLib.cleanCargoSource ./.;
commonLintArgs = {
inherit src;
strictDeps = true;
buildInputs = [ pkgs.openssl ];
nativeBuildInputs = [ pkgs.pkg-config ];
DOCS_RS = "true";
RUSTUP_TOOLCHAIN = toolchain;
};
cargoArtifacts = craneLib.buildDepsOnly commonLintArgs;
individualLintArgs = commonLintArgs // {
inherit cargoArtifacts;
inherit (craneLib.crateNameFromCargoToml { inherit src; }) version;
doCheck = false;
};
fileSetForCrate =
crate:
lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./Cargo.lock
./Cargo.toml
(craneLib.fileset.commonCargoSources ./hakari)
(craneLib.fileset.commonCargoSources crate)
];
};
dashmap-ref = craneLib.buildPackage (
individualLintArgs
// {
pname = "dashmap-ref";
cargoExtraArgs = "-p dashmap-ref";
src = fileSetForCrate ./lints/dashmap-ref;
postFixup = ''
mv $out/lib/libdashmap_ref{.so,@$RUSTUP_TOOLCHAIN.so}
'';
}
);
lints = [
{
inherit toolchain;
package = dashmap-ref;
}
];
dylint = dylintLib.mkDylint { inherit lints; };
in
{
@ -47,7 +104,14 @@
nativeBuildInputs = [
rustTarget
dylint
pkgs.cargo-hakari
pkgs.openssl
pkgs.pkg-config
];
DYLINT_DRIVER_PATH = dylint.passthru.DYLINT_DRIVER_PATH;
RUSTUP_TOOLCHAIN = toolchain;
};
}
);

4
hakari/.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
# Avoid putting conflict markers in the generated Cargo.toml file, since their presence breaks
# Cargo.
# Also do not check out the file as CRLF on Windows, as that's what hakari needs.
Cargo.toml merge=binary -crlf

36
hakari/Cargo.toml Normal file
View file

@ -0,0 +1,36 @@
# This file is generated by `cargo hakari`.
# To regenerate, run:
# cargo hakari generate
[package]
name = "hakari"
version = "0.1.0"
edition = "2021"
description = "workspace-hack package, managed by hakari"
# You can choose to publish this crate: see https://docs.rs/cargo-hakari/latest/cargo_hakari/publishing.
publish = false
# The parts of the file between the BEGIN HAKARI SECTION and END HAKARI SECTION comments
# are managed by hakari.
### BEGIN HAKARI SECTION
[dependencies]
aho-corasick = { version = "1" }
dylint_internal = { version = "4", default-features = false, features = ["config", "git", "packaging", "rustup", "sed"] }
log = { version = "0.4", default-features = false, features = ["std"] }
regex-automata = { version = "0.4", default-features = false, features = ["dfa-onepass", "hybrid", "meta", "nfa", "perf", "unicode"] }
regex-syntax = { version = "0.8" }
serde = { version = "1", features = ["derive"] }
smallvec = { version = "1", default-features = false, features = ["const_generics", "union"] }
[build-dependencies]
aho-corasick = { version = "1" }
dylint_internal = { version = "4", default-features = false, features = ["config", "git", "packaging", "rustup", "sed"] }
log = { version = "0.4", default-features = false, features = ["std"] }
regex-automata = { version = "0.4", default-features = false, features = ["dfa-onepass", "hybrid", "meta", "nfa", "perf", "unicode"] }
regex-syntax = { version = "0.8" }
serde = { version = "1", features = ["derive"] }
smallvec = { version = "1", default-features = false, features = ["const_generics", "union"] }
syn = { version = "2", features = ["extra-traits", "fold", "visit"] }
### END HAKARI SECTION

2
hakari/build.rs Normal file
View file

@ -0,0 +1,2 @@
// A build script is required for cargo to consider build dependencies.
fn main() {}

1
hakari/src/lib.rs Normal file
View file

@ -0,0 +1 @@
// This is a stub lib.rs.

View file

@ -0,0 +1,6 @@
[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"

1
lints/dashmap-ref/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

View file

@ -0,0 +1,24 @@
[package]
name = "dashmap-ref"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
crate-type = ["cdylib"]
[[example]]
name = "ui"
path = "ui/main.rs"
[dependencies]
clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "19e305bb57a7595f2a8d81f521c0dd8bf854e739" }
dylint_linting = "4.0.0"
hakari = { version = "0.1", path = "../../hakari" }
[dev-dependencies]
dashmap = "6"
dylint_testing = "4.0.0"
[package.metadata.rust-analyzer]
rustc_private = true

View file

@ -0,0 +1,21 @@
# template
### What it does
### Why is this bad?
### Known problems
Remove if none.
### Example
```rust
// example code where a warning is issued
```
Use instead:
```rust
// example code that does not raise a warning
```

View file

@ -0,0 +1,142 @@
#![feature(rustc_private)]
#![feature(let_chains)]
extern crate rustc_hir;
extern crate rustc_middle;
extern crate rustc_span;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::match_any_def_paths;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_lint::LateLintPass;
use rustc_middle::mir::CoroutineLayout;
use rustc_middle::ty::Adt;
use rustc_middle::ty::Ty;
use rustc_span::sym;
dylint_linting::declare_late_lint! {
/// ### What it does
/// Check whether a DashMap reference is held across an await point.
///
/// ### Why is this bad?
///
/// This can cause issues as the dashmap may deadlock
///
/// ### Known problems
///
/// Remove if none.
///
/// ### Example
///
/// ```rust
/// // example code where a warning is issued
/// ```
///
/// Use instead:
///
/// ```rust
/// // example code that does not raise a warning
/// ```
pub DASHMAP_REF,
Warn,
"Check whether a dashmap reference is held over an await point"
}
const DASHMAP_REF_GUARD: [&str; 4] = ["dashmap", "mapref", "one", "Ref"];
const DASHMAP_REF_MUT_GUARD: [&str; 4] = ["dashmap", "mapref", "one", "RefMut"];
const DASHMAP_ENTRY_GUARD: [&str; 4] = ["dashmap", "mapref", "entry", "Entry"];
const DASHMAP_ITER: [&str; 3] = ["dashmap", "iter", "Iter"];
const DASHMAP_ITER_MUT: [&str; 3] = ["dashmap", "iter", "IterMut"];
impl LateLintPass<'_> for DashmapRef {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ hir::Expr<'_>) {
if let hir::ExprKind::Closure(hir::Closure {
def_id,
kind:
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async,
_,
)),
..
}) = expr.kind
{
if let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) {
check_interior_types(cx, coroutine_layout);
}
}
}
}
fn check_interior_types(cx: &LateContext<'_>, coroutine: &CoroutineLayout<'_>) {
for (ty_index, ty_cause) in coroutine.field_tys.iter_enumerated() {
if let Adt(adt, _) = ty_cause.ty.kind() {
let await_points = || {
coroutine
.variant_source_info
.iter_enumerated()
.filter_map(|(variant, source_info)| {
coroutine.variant_fields[variant]
.raw
.contains(&ty_index)
.then_some(source_info.span)
})
.collect::<Vec<_>>()
};
if is_dashmap_ref(cx, adt.did(), &ty_cause.ty) {
span_lint_and_then(
cx,
DASHMAP_REF,
ty_cause.source_info.span,
"this dashmap reference is held \
across an 'await' point. Either drop it before the 'await', \
or read it again afterwards.",
|diag| {
diag.span_note(
await_points(),
"these are all the await points this ref is held through",
);
},
);
}
}
}
}
fn is_dashmap_ref(cx: &LateContext<'_>, def_id: DefId, ty: &Ty<'_>) -> bool {
if let Adt(adt, args) = ty.kind() {
if let Some(ty) = cx
.tcx
.is_diagnostic_item(sym::Option, adt.did())
.then(|| args.type_at(0))
{
if let Adt(adt, ..) = ty.kind() {
is_dashmap_ref(cx, adt.did(), &ty)
} else {
false
}
} else {
match_any_def_paths(
cx,
def_id,
&[
&DASHMAP_REF_GUARD,
&DASHMAP_REF_MUT_GUARD,
&DASHMAP_ENTRY_GUARD,
&DASHMAP_ITER,
&DASHMAP_ITER_MUT,
],
)
.is_some()
}
} else {
false
}
}
#[test]
fn ui() {
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "ui");
}

View file

@ -0,0 +1,50 @@
#[expect(unused_must_use)]
fn main() {
held_across();
held_across_multiple();
dropped_before();
all_kinds();
}
async fn held_across() {
let map = dashmap::DashMap::<String, String>::default();
let reference = map.get("Hello");
std::future::pending::<()>().await;
println!("{reference:?}");
}
async fn held_across_multiple() {
let map = dashmap::DashMap::<String, String>::default();
let _reference = map.get("Hello");
std::future::pending::<()>().await;
std::future::pending::<()>().await;
}
async fn dropped_before() {
let map = dashmap::DashMap::<String, String>::default();
let _ = map.get("Hello");
std::future::pending::<()>().await;
}
async fn all_kinds() {
let map = dashmap::DashMap::<String, String>::default();
let _ref = map.get("Hello");
let _ref_mut = map.get_mut("Hello");
let _entry = map.entry("Hello".to_string());
let _opt_entry = map.try_entry("Hello".to_string());
let _iter_entry = map.iter();
let _iter_mut_entry = map.iter_mut();
let _direct_ref = map.get("Hello").unwrap();
let _direct_ref_mut = map.get_mut("Hello").unwrap();
std::future::pending::<()>().await;
}

View file

@ -0,0 +1,126 @@
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:12:9
|
LL | let reference = map.get("Hello");
| ^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:14:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
= note: `#[warn(dashmap_ref)]` on by default
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:22:9
|
LL | let _reference = map.get("Hello");
| ^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:24:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
LL |
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:40:9
|
LL | let _ref = map.get("Hello");
| ^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:41:9
|
LL | let _ref_mut = map.get_mut("Hello");
| ^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:42:9
|
LL | let _entry = map.entry("Hello".to_string());
| ^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:43:9
|
LL | let _opt_entry = map.try_entry("Hello".to_string());
| ^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:44:9
|
LL | let _iter_entry = map.iter();
| ^^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:45:9
|
LL | let _iter_mut_entry = map.iter_mut();
| ^^^^^^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:46:9
|
LL | let _direct_ref = map.get("Hello").unwrap();
| ^^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: this dashmap reference is held across an 'await' point. Either drop it before the 'await', or read it again afterwards.
--> $DIR/main.rs:47:9
|
LL | let _direct_ref_mut = map.get_mut("Hello").unwrap();
| ^^^^^^^^^^^^^^^
|
note: these are all the await points this ref is held through
--> $DIR/main.rs:49:34
|
LL | std::future::pending::<()>().await;
| ^^^^^
warning: 10 warnings emitted

View file

@ -1,2 +1,3 @@
[toolchain]
channel = "1.85.0"
channel = "nightly-2025-01-09"
components = ["llvm-tools-preview", "rustc-dev"]