chore(lint): use dylint as lint driver for tfhe-lint

This commit is contained in:
Nicolas Sarlin
2024-12-16 14:55:17 +01:00
committed by Nicolas Sarlin
parent 7103a83ce5
commit 9a64c34989
35 changed files with 199 additions and 250 deletions

View File

@@ -0,0 +1,5 @@
[build]
target-dir = "../../target/tfhe-lints"
[target.'cfg(all())']
linker = "dylint-link"

View File

@@ -0,0 +1,25 @@
[package]
name = "tfhe-lints"
version = "0.1.0"
description = "Project specific lints for TFHE-rs"
edition = "2021"
publish = false
[lib]
crate-type = ["cdylib"]
[dependencies]
clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "ff4a26d442bead94a4c96fb1de967374bc4fbd8e" }
dylint_linting = "3.2.1"
[dev-dependencies]
dylint_testing = "3.2.1"
serde = { version = "1.0", features = ["derive"] }
tfhe-versionable = "0.4.0"
[package.metadata.rust-analyzer]
rustc_private = true
[[example]]
name = "ui"
path = "ui/main.rs"

View File

@@ -0,0 +1,29 @@
# Project specific lints for TFHE-rs
This tool is based on [dylint](https://github.com/trailofbits/dylint).
## Usage
From TFHE-rs root folder:
```
make tfhe_lints
```
## `serialize_without_versionize`
### What it does
For every type that implements `Serialize`, checks that it also implement `Versionize`
### Why is this bad?
If a type is serializable but does not implement Versionize, it is likely that the
implementation has been forgotten.
### Example
```rust
#[derive(Serialize)]
pub struct MyStruct {}
```
Use instead:
```rust
#[derive(Serialize, Versionize)]
#[versionize(MyStructVersions)]
pub struct MyStruct {}
```

View File

@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly-2024-11-28"
components = ["llvm-tools-preview", "rustc-dev"]

View File

@@ -0,0 +1,12 @@
#![feature(rustc_private)]
#![feature(let_chains)]
#![warn(unused_extern_crates)]
extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
extern crate rustc_span;
mod serialize_without_versionize;
mod utils;

View File

@@ -0,0 +1,131 @@
use std::sync::{Arc, OnceLock};
use rustc_hir::def_id::DefId;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_span::sym;
use crate::utils::{get_def_id_from_ty, is_allowed_lint, symbols_list_from_str};
#[derive(Default)]
pub struct SerializeWithoutVersionizeInner {
pub versionize_trait: OnceLock<Option<DefId>>,
}
const VERSIONIZE_TRAIT: [&str; 2] = ["tfhe_versionable", "Versionize"];
const SERIALIZE_TRAIT: [&str; 3] = ["serde", "ser", "Serialize"];
const LINT_NAME: &str = "serialize_without_versionize";
impl SerializeWithoutVersionizeInner {
/// Tries to find the definition of the `Versionize` trait. The value is memoized and is
/// instantly accessed after the first lookup.
pub fn versionize_trait(&self, cx: &LateContext<'_>) -> Option<DefId> {
self.versionize_trait
.get_or_init(|| {
let versionize_trait = cx.tcx.all_traits().find(|def_id| {
cx.match_def_path(*def_id, symbols_list_from_str(&VERSIONIZE_TRAIT).as_slice())
});
versionize_trait
})
.to_owned()
}
}
#[derive(Default, Clone)]
pub struct SerializeWithoutVersionize(pub Arc<SerializeWithoutVersionizeInner>);
dylint_linting::impl_late_lint! {
/// ### What it does
/// For every type that implements `Serialize`, checks that it also implement `Versionize`
///
/// ### Why is this bad?
/// If a type is serializable but does not implement Versionize, it is likely that the
/// implementation has been forgotten.
///
/// ### Example
/// ```rust
/// #[derive(Serialize)]
/// pub struct MyStruct {}
/// ```
/// Use instead:
/// ```rust
/// #[derive(Serialize, Versionize)]
/// #[versionize(MyStructVersions)]
/// pub struct MyStruct {}
/// ```
pub SERIALIZE_WITHOUT_VERSIONIZE,
Warn,
"Detects types that implement Serialize without implementing Versionize",
SerializeWithoutVersionize::default()
}
impl<'tcx> LateLintPass<'tcx> for SerializeWithoutVersionize {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
// If the currently checked item is a trait impl
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind
{
// Gets the target type of the implementation
let ty: rustc_middle::ty::Ty<'tcx> =
cx.tcx.type_of(item.owner_id).instantiate_identity();
if let Some(type_def_id) = get_def_id_from_ty(ty) {
// If the type has been automatically generated, skip it
if cx.tcx.has_attr(type_def_id, sym::automatically_derived) {
return;
}
// Skip it if the user explicitly allowed it.
if is_allowed_lint(cx, type_def_id, LINT_NAME) {
return;
}
// Check if the implemented trait is `Serialize`
if let Some(def_id) = trait_ref.trait_def_id() {
if cx.match_def_path(def_id, symbols_list_from_str(&SERIALIZE_TRAIT).as_slice())
{
// Try to find an implementation of versionize for this type
let mut found_impl = false;
if let Some(versionize_trait) = self.0.versionize_trait(cx) {
cx.tcx
.for_each_relevant_impl(versionize_trait, ty, |impl_id| {
if !found_impl {
let trait_ref = cx
.tcx
.impl_trait_ref(impl_id)
.expect("must be a trait implementation");
if trait_ref.instantiate_identity().args.type_at(0) == ty {
found_impl = true;
}
}
});
}
if !found_impl {
// Emit a warning
cx.span_lint(
SERIALIZE_WITHOUT_VERSIONIZE,
cx.tcx.def_span(type_def_id),
|diag| {
diag.primary_message(format!("Type {ty} implements `Serialize` but does not implement `Versionize`"));
diag.note("Add `#[derive(Versionize)]` for this type or silence this warning using \
`#[cfg_attr(dylint_lib = \"tfhe_lints\", allow(serialize_without_versionize))]`");
diag.span_note(item.span, "`Serialize` derived here");
},
);
}
}
}
}
}
}
}
#[test]
fn ui() {
dylint_testing::ui_test_example(env!("CARGO_PKG_NAME"), "ui");
}

View File

@@ -0,0 +1,45 @@
use rustc_ast::tokenstream::TokenTree;
use rustc_hir::def_id::DefId;
use rustc_lint::LateContext;
use rustc_middle::ty::{Ty, TyKind};
use rustc_span::Symbol;
/// Converts an array of str into a Vec of [`Symbol`]
pub fn symbols_list_from_str(list: &[&str]) -> Vec<Symbol> {
list.iter().map(|s| Symbol::intern(s)).collect()
}
/// Checks if the lint is allowed for the item represented by [`DefId`].
/// This shouldn't be necessary since the lints are declared with the
/// `declare_tool_lint` macro but for a mysterious reason this does not
/// work automatically.
pub fn is_allowed_lint(cx: &LateContext<'_>, target: DefId, lint_name: &str) -> bool {
for attr in cx.tcx.get_attrs(target, Symbol::intern("allow")) {
let tokens = attr.get_normal_item().args.inner_tokens();
let mut trees = tokens.trees();
if let Some(TokenTree::Token(tool_token, _)) = trees.next() {
if tool_token.is_ident_named(Symbol::intern(lint_name)) {
return true;
}
}
}
false
}
/// Gets the [`DefId`] of a type
pub fn get_def_id_from_ty(ty: Ty<'_>) -> Option<DefId> {
match ty.kind() {
TyKind::Adt(adt_def, _) => Some(adt_def.did()),
TyKind::Alias(_, alias_ty) => Some(alias_ty.def_id),
TyKind::Dynamic(predicates, ..) => predicates.principal_def_id(),
TyKind::FnDef(def_id, _)
| TyKind::Foreign(def_id)
| TyKind::Closure(def_id, ..)
| TyKind::CoroutineClosure(def_id, _)
| TyKind::Coroutine(def_id, _)
| TyKind::CoroutineWitness(def_id, _) => Some(*def_id),
_ => None,
}
}

View File

@@ -0,0 +1,11 @@
use serde::Serialize;
#[derive(Serialize)]
struct MyStruct {
value: u64,
}
fn main() {
let st = MyStruct { value: 42 };
println!("{}", st.value);
}

View File

@@ -0,0 +1,17 @@
warning: Type MyStruct implements `Serialize` but does not implement `Versionize`
--> $DIR/main.rs:4:1
|
LL | struct MyStruct {
| ^^^^^^^^^^^^^^^
|
= note: Add `#[derive(Versionize)]` for this type or silence this warning using `#[cfg_attr(dylint_lib = "tfhe_lints", allow(serialize_without_versionize))]`
note: `Serialize` derived here
--> $DIR/main.rs:3:10
|
LL | #[derive(Serialize)]
| ^^^^^^^^^
= note: `#[warn(serialize_without_versionize)]` on by default
= note: this warning originates in the derive macro `Serialize` (in Nightly builds, run with -Z macro-backtrace for more info)
warning: 1 warning emitted