Skip to content

Commit c7a3aa3

Browse files
committed
Add a diagnostic item mechanism
This allows items to be annotated to be special, rather than relying on hardcoded constants. To allow klint to be used on unmodified source, a fallback mechanism is added to infer diagnostic items if not found.
1 parent 38df4bc commit c7a3aa3

File tree

10 files changed

+230
-11
lines changed

10 files changed

+230
-11
lines changed

src/attribute.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
use std::sync::Arc;
66

77
use rustc_ast::tokenstream::{self, TokenTree};
8-
use rustc_ast::{DelimArgs, token};
8+
use rustc_ast::{DelimArgs, LitKind, MetaItemLit, token};
99
use rustc_errors::{Diag, ErrorGuaranteed};
1010
use rustc_hir::{AttrArgs, AttrItem, Attribute, HirId};
1111
use rustc_middle::ty::TyCtxt;
1212
use rustc_span::symbol::Ident;
13-
use rustc_span::{Span, sym};
13+
use rustc_span::{Span, Symbol, sym};
1414

1515
use crate::preempt_count::ExpectationRange;
1616

@@ -37,6 +37,10 @@ pub enum KlintAttribute {
3737
DropPreemptionCount(PreemptionCount),
3838
ReportPreeptionCount,
3939
DumpMir,
40+
/// Make an item known to klint as special.
41+
///
42+
/// This is similar to `rustc_diagnostic_item` in the Rust standard library.
43+
DiagnosticItem(Symbol),
4044
}
4145

4246
struct Cursor<'a> {
@@ -454,6 +458,26 @@ impl<'tcx> AttrParser<'tcx> {
454458
)),
455459
crate::symbol::report_preempt_count => Some(KlintAttribute::ReportPreeptionCount),
456460
crate::symbol::dump_mir => Some(KlintAttribute::DumpMir),
461+
crate::symbol::diagnostic_item => {
462+
let AttrArgs::Eq {
463+
eq_span: _,
464+
expr:
465+
MetaItemLit {
466+
kind: LitKind::Str(value, _),
467+
..
468+
},
469+
} = item.args
470+
else {
471+
self.error(attr.span(), |diag| {
472+
diag.help(
473+
r#"correct usage looks like `#[kint::diagnostic_item = "name"]`"#,
474+
);
475+
})
476+
.ok()?;
477+
};
478+
479+
Some(KlintAttribute::DiagnosticItem(value))
480+
}
457481
_ => {
458482
self.tcx.node_span_lint(
459483
crate::INCORRECT_ATTRIBUTE,

src/binary_analysis/build_error.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ struct BuildErrorReferencedWithoutDebug<'tcx> {
3232
struct BuildErrorReferenced;
3333

3434
pub fn build_error_detection<'tcx, 'obj>(cx: &AnalysisCtxt<'tcx>, file: &File<'obj>) {
35-
let Some(build_error_symbol) = file.symbol_by_name("rust_build_error") else {
35+
let Some(build_error) = cx.get_klint_diagnostic_item(crate::symbol::build_error) else {
36+
return;
37+
};
38+
let build_error_symbol_name = cx.symbol_name(Instance::mono(cx.tcx, build_error)).name;
39+
40+
let Some(build_error_symbol) = file.symbol_by_name(build_error_symbol_name) else {
3641
// This object file contains no reference to `build_error`, all good!
3742
return;
3843
};
@@ -114,7 +119,7 @@ pub fn build_error_detection<'tcx, 'obj>(cx: &AnalysisCtxt<'tcx>, file: &File<'o
114119
if let Some((_, site)) = super::reconstruct::recover_fn_call_span(
115120
cx.tcx,
116121
frame,
117-
"rust_build_error",
122+
build_error_symbol_name,
118123
Some(&loc),
119124
) {
120125
recovered_call_stack.push(UseSite {

src/ctxt.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ impl<'tcx> AnalysisCtxt<'tcx> {
318318
ret.sql_create_table::<crate::preempt_count::adjustment::instance_adjustment>();
319319
ret.sql_create_table::<crate::preempt_count::expectation::instance_expectation>();
320320
ret.sql_create_table::<crate::mir::analysis_mir>();
321+
ret.sql_create_table::<crate::diagnostic_items::klint_diagnostic_items>();
321322
ret
322323
}
323324
}

src/diagnostic_items/mod.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
mod out_of_band;
2+
3+
use std::sync::Arc;
4+
5+
use rustc_data_structures::fx::FxIndexMap;
6+
use rustc_hir::CRATE_OWNER_ID;
7+
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
8+
use rustc_hir::diagnostic_items::DiagnosticItems;
9+
use rustc_middle::ty::TyCtxt;
10+
use rustc_serialize::{Decodable, Encodable};
11+
use rustc_span::{Span, Symbol};
12+
13+
use crate::ctxt::{AnalysisCtxt, QueryValueDecodable};
14+
use crate::{attribute::KlintAttribute, ctxt::PersistentQuery};
15+
16+
#[derive(Diagnostic)]
17+
#[diag(klint_duplicate_diagnostic_item_in_crate)]
18+
struct DuplicateDiagnosticItemInCrate {
19+
#[primary_span]
20+
pub duplicate_span: Option<Span>,
21+
#[note(klint_diagnostic_item_first_defined)]
22+
pub orig_span: Option<Span>,
23+
#[note]
24+
pub different_crates: bool,
25+
pub crate_name: Symbol,
26+
pub orig_crate_name: Symbol,
27+
pub name: Symbol,
28+
}
29+
30+
fn report_duplicate_item(
31+
tcx: TyCtxt<'_>,
32+
name: Symbol,
33+
original_def_id: DefId,
34+
item_def_id: DefId,
35+
) {
36+
let orig_span = tcx.hir_span_if_local(original_def_id);
37+
let duplicate_span = tcx.hir_span_if_local(item_def_id);
38+
tcx.dcx().emit_err(DuplicateDiagnosticItemInCrate {
39+
duplicate_span,
40+
orig_span,
41+
crate_name: tcx.crate_name(item_def_id.krate),
42+
orig_crate_name: tcx.crate_name(original_def_id.krate),
43+
different_crates: (item_def_id.krate != original_def_id.krate),
44+
name,
45+
});
46+
}
47+
48+
fn collect_item(tcx: TyCtxt<'_>, items: &mut DiagnosticItems, name: Symbol, item_def_id: DefId) {
49+
items.id_to_name.insert(item_def_id, name);
50+
if let Some(original_def_id) = items.name_to_id.insert(name, item_def_id) {
51+
if original_def_id != item_def_id {
52+
report_duplicate_item(tcx, name, original_def_id, item_def_id);
53+
}
54+
}
55+
}
56+
57+
memoize!(
58+
pub fn klint_diagnostic_items<'tcx>(
59+
cx: &AnalysisCtxt<'tcx>,
60+
krate_num: CrateNum,
61+
) -> Arc<DiagnosticItems> {
62+
if krate_num != LOCAL_CRATE {
63+
return cx
64+
.sql_load::<klint_diagnostic_items>(krate_num)
65+
.unwrap_or_default();
66+
}
67+
68+
let mut items = DiagnosticItems::default();
69+
70+
let crate_items = cx.hir_crate_items(());
71+
for owner in crate_items.owners().chain(std::iter::once(CRATE_OWNER_ID)) {
72+
for attr in cx.klint_attributes(owner.into()).iter() {
73+
if let KlintAttribute::DiagnosticItem(name) = *attr {
74+
collect_item(cx.tcx, &mut items, name, owner.to_def_id());
75+
}
76+
}
77+
}
78+
79+
out_of_band::infer_missing_items(cx.tcx, &mut items);
80+
81+
let ret = Arc::new(items);
82+
cx.sql_store::<klint_diagnostic_items>(krate_num, ret.clone());
83+
ret
84+
}
85+
);
86+
87+
impl QueryValueDecodable for klint_diagnostic_items {
88+
fn encode_value<'tcx>(value: &Self::Value<'tcx>, cx: &mut crate::serde::EncodeContext<'tcx>) {
89+
value.name_to_id.encode(cx);
90+
}
91+
92+
fn decode_value<'a, 'tcx>(cx: &mut crate::serde::DecodeContext<'a, 'tcx>) -> Self::Value<'tcx> {
93+
let name_to_id = FxIndexMap::decode(cx);
94+
let id_to_name = name_to_id.iter().map(|(&name, &id)| (id, name)).collect();
95+
Arc::new(DiagnosticItems {
96+
name_to_id,
97+
id_to_name,
98+
})
99+
}
100+
}
101+
102+
impl PersistentQuery for klint_diagnostic_items {
103+
type LocalKey<'tcx> = ();
104+
105+
fn into_crate_and_local<'tcx>(key: CrateNum) -> (CrateNum, Self::LocalKey<'tcx>) {
106+
(key, ())
107+
}
108+
}
109+
110+
memoize!(
111+
pub fn klint_all_diagnostic_items<'tcx>(cx: &AnalysisCtxt<'tcx>) -> Arc<DiagnosticItems> {
112+
let mut items = DiagnosticItems::default();
113+
114+
for cnum in cx
115+
.crates(())
116+
.iter()
117+
.copied()
118+
.filter(|cnum| cx.is_user_visible_dep(*cnum))
119+
.chain(std::iter::once(LOCAL_CRATE))
120+
{
121+
for (&name, &def_id) in &cx.klint_diagnostic_items(cnum).name_to_id {
122+
collect_item(cx.tcx, &mut items, name, def_id);
123+
}
124+
}
125+
126+
Arc::new(items)
127+
}
128+
);
129+
130+
impl<'tcx> AnalysisCtxt<'tcx> {
131+
pub fn get_klint_diagnostic_item(&self, name: Symbol) -> Option<DefId> {
132+
self.klint_all_diagnostic_items()
133+
.name_to_id
134+
.get(&name)
135+
.copied()
136+
}
137+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! Out-of-band attributes attached without source code changes.
2+
3+
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
4+
use rustc_hir::diagnostic_items::DiagnosticItems;
5+
use rustc_middle::middle::exported_symbols::ExportedSymbol;
6+
use rustc_middle::ty::TyCtxt;
7+
8+
pub fn infer_missing_items<'tcx>(tcx: TyCtxt<'tcx>, items: &mut DiagnosticItems) {
9+
if !items.name_to_id.contains_key(&crate::symbol::build_error) {
10+
if let Some(def_id) = infer_build_error_diagnostic_item(tcx) {
11+
super::collect_item(tcx, items, crate::symbol::build_error, def_id);
12+
}
13+
}
14+
}
15+
16+
pub fn infer_build_error_diagnostic_item<'tcx>(tcx: TyCtxt<'tcx>) -> Option<DefId> {
17+
for exported in tcx.exported_non_generic_symbols(LOCAL_CRATE) {
18+
if let ExportedSymbol::NonGeneric(def_id) = exported.0
19+
&& exported.0.symbol_name_for_local_instance(tcx).name == "rust_build_error"
20+
{
21+
return Some(def_id);
22+
}
23+
}
24+
25+
None
26+
}

src/main.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ mod atomic_context;
6262
mod attribute;
6363
mod binary_analysis;
6464
mod diagnostic;
65+
mod diagnostic_items;
6566
mod driver;
6667
mod infallible_allocation;
6768
mod lattice;
@@ -116,6 +117,20 @@ impl Callbacks for MyCallbacks {
116117
});
117118
}));
118119
}
120+
121+
fn after_analysis<'tcx>(
122+
&mut self,
123+
_compiler: &rustc_interface::interface::Compiler,
124+
tcx: TyCtxt<'tcx>,
125+
) -> rustc_driver::Compilation {
126+
let cx = driver::cx::<MyCallbacks>(tcx);
127+
128+
// Ensure this query is run at least once, even without diagnostics emission, to
129+
// catch duplicate item errors.
130+
let _ = cx.klint_all_diagnostic_items();
131+
132+
rustc_driver::Compilation::Continue
133+
}
119134
}
120135

121136
impl driver::CallbacksExt for MyCallbacks {

src/messages.ftl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,10 @@ klint_stack_frame_limit_invalid =
3333
klint_stack_frame_too_large =
3434
stack size of `{$instance}` is {$stack_size} bytes, exceeds the {$frame_limit}-byte limit
3535
.note = the stack size is inferred from instruction `{$insn}` at {$section}+{$offset}
36+
37+
klint_duplicate_diagnostic_item_in_crate =
38+
duplicate klint diagnostic item in crate `{$crate_name}`: `{$name}`
39+
.note = the diagnostic item is first defined in crate `{$orig_crate_name}`
40+
41+
klint_diagnostic_item_first_defined =
42+
the klint diagnostic item is first defined here

src/symbol.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,14 @@ def! {
4141
sort,
4242
quicksort,
4343
partition,
44+
diagnostic_item,
4445

4546
any_context,
4647
atomic_context,
4748
atomic_context_only,
4849
process_context,
4950

51+
build_error,
52+
5053
CONFIG_FRAME_WARN,
5154
}

tests/ui/build_error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
unsafe extern "C" {
2+
#[klint::diagnostic_item = "build_error"]
23
safe fn rust_build_error();
34
}
45

tests/ui/build_error.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
WARN klint::atomic_context Unable to determine property for FFI function `gen_build_error`
22
WARN klint::atomic_context Unable to determine property for FFI function `gen_build_error`
33
error: this `build_error` reference is not optimized away
4-
--> $DIR/build_error.rs:8:13
4+
--> $DIR/build_error.rs:9:13
55
|
6-
8 | rust_build_error();
6+
9 | rust_build_error();
77
| ^^^^^^^^^^^^^^^^^^
88
...
9-
15 | build_assert!(false);
9+
16 | build_assert!(false);
1010
| -------------------- in this macro invocation
1111
|
1212
note: which is called from here
13-
--> $DIR/build_error.rs:20:5
13+
--> $DIR/build_error.rs:21:5
1414
|
15-
20 | inline_call();
15+
21 | inline_call();
1616
| ^^^^^^^^^^^^^
1717
note: reference contained in `fn gen_build_error`
18-
--> $DIR/build_error.rs:19:1
18+
--> $DIR/build_error.rs:20:1
1919
|
20-
19 | fn gen_build_error() {
20+
20 | fn gen_build_error() {
2121
| ^^^^^^^^^^^^^^^^^^^^
2222
= note: this error originates in the macro `build_assert` (in Nightly builds, run with -Z macro-backtrace for more info)
2323

0 commit comments

Comments
 (0)