Skip to content

Commit cf26447

Browse files
authored
Move attribute handling to kani_middle and polish warnings (rust-lang#2211)
As part of removing one harness restriction from stubbing, we will need to run the rust compiler driver multiple times. However, we want to only produce one metadata file with information about all the compiled artifacts. Thus, I'm planning to move the harness metadata generation to the KaniCompiler as opposed to the GotoCCtxt. As a first step, I'm moving the attributes handling out of the GotoCCtxt. The only behavior change in this PR is related to some of the warnings we print. I tried to standardize it a bit (following our new guidelines) and I moved the stub warning to be part of the driver.
1 parent 9c0363d commit cf26447

File tree

20 files changed

+510
-374
lines changed

20 files changed

+510
-374
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/function.rs

Lines changed: 11 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,15 @@
44
//! This file contains functions related to codegenning MIR functions into gotoc
55
66
use crate::codegen_cprover_gotoc::GotocCtx;
7-
use crate::kani_middle::attributes::{extract_integer_argument, partition_kanitool_attributes};
7+
use crate::kani_middle::attributes::{extract_harness_attributes, is_test_harness_closure};
88
use cbmc::goto_program::{Expr, Stmt, Symbol};
99
use cbmc::InternString;
10-
use kani_metadata::{CbmcSolver, HarnessMetadata};
11-
use kani_queries::UserInput;
12-
use rustc_ast::{Attribute, MetaItemKind};
13-
use rustc_hir::def::DefKind;
14-
use rustc_hir::def_id::DefId;
10+
use kani_metadata::{HarnessAttributes, HarnessMetadata};
1511
use rustc_middle::mir::traversal::reverse_postorder;
1612
use rustc_middle::mir::{Body, HasLocalDecls, Local};
17-
use rustc_middle::ty::layout::FnAbiOf;
1813
use rustc_middle::ty::{self, Instance};
1914
use std::collections::BTreeMap;
20-
use std::convert::TryInto;
2115
use std::iter::FromIterator;
22-
use std::str::FromStr;
2316
use tracing::{debug, debug_span};
2417

2518
/// Codegen MIR functions into gotoc
@@ -97,7 +90,7 @@ impl<'tcx> GotocCtx<'tcx> {
9790
let body = Stmt::block(stmts, loc);
9891
self.symbol_table.update_fn_declaration_with_definition(&name, body);
9992

100-
self.handle_kanitool_attributes();
93+
self.record_kani_attributes();
10194
self.record_test_harness_metadata();
10295
}
10396
self.reset_current_fn();
@@ -252,90 +245,6 @@ impl<'tcx> GotocCtx<'tcx> {
252245
self.reset_current_fn();
253246
}
254247

255-
/// Check that if an item is tagged with a proof_attribute, it is a valid harness.
256-
fn check_proof_attribute(&self, def_id: DefId, proof_attributes: Vec<&Attribute>) {
257-
assert!(!proof_attributes.is_empty());
258-
let span = proof_attributes.first().unwrap().span;
259-
if proof_attributes.len() > 1 {
260-
self.tcx.sess.span_warn(proof_attributes[0].span, "Duplicate attribute");
261-
}
262-
263-
if self.tcx.def_kind(def_id) != DefKind::Fn {
264-
self.tcx
265-
.sess
266-
.span_err(span, "The kani::proof attribute can only be applied to functions.");
267-
} else if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
268-
self.tcx
269-
.sess
270-
.span_err(span, "The proof attribute cannot be applied to generic functions.");
271-
} else {
272-
let instance = Instance::mono(self.tcx, def_id);
273-
if !self.fn_abi_of_instance(instance, ty::List::empty()).args.is_empty() {
274-
self.tcx
275-
.sess
276-
.span_err(span, "Functions used as harnesses can not have any arguments.");
277-
}
278-
}
279-
}
280-
281-
pub fn is_proof_harness(&self, def_id: DefId) -> bool {
282-
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
283-
let (proof_attributes, _) = partition_kanitool_attributes(all_attributes);
284-
!proof_attributes.is_empty()
285-
}
286-
287-
/// Check that all attributes assigned to an item is valid.
288-
/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate
289-
/// the session in case of an error.
290-
pub fn check_attributes(&self, def_id: DefId) {
291-
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
292-
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
293-
if !proof_attributes.is_empty() {
294-
self.check_proof_attribute(def_id, proof_attributes);
295-
} else if !other_attributes.is_empty() {
296-
self.tcx.sess.span_err(
297-
other_attributes[0].1.span,
298-
format!(
299-
"The {} attribute also requires the '#[kani::proof]' attribute",
300-
other_attributes[0].0
301-
)
302-
.as_str(),
303-
);
304-
}
305-
}
306-
307-
/// Does this `def_id` have `#[rustc_test_marker]`?
308-
pub fn is_test_harness_description(&self, def_id: DefId) -> bool {
309-
let attrs = self.tcx.get_attrs_unchecked(def_id);
310-
311-
self.tcx.sess.contains_name(attrs, rustc_span::symbol::sym::rustc_test_marker)
312-
}
313-
/// Is this the closure inside of a test description const (i.e. macro expanded from a `#[test]`)?
314-
///
315-
/// We're trying to detect the closure (`||`) inside code like:
316-
///
317-
/// ```ignore
318-
/// #[rustc_test_marker]
319-
/// pub const check_2: test::TestDescAndFn = test::TestDescAndFn {
320-
/// desc: ...,
321-
/// testfn: test::StaticTestFn(|| test::assert_test_result(check_2())),
322-
/// };
323-
/// ```
324-
pub fn is_test_harness_closure(&self, def_id: DefId) -> bool {
325-
if !def_id.is_local() {
326-
return false;
327-
}
328-
329-
let local_def_id = def_id.expect_local();
330-
let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
331-
332-
// The parent item of the closure appears to reliably be the `const` declaration item.
333-
let parent_id = self.tcx.hir().get_parent_item(hir_id);
334-
let parent_def_id = parent_id.to_def_id();
335-
336-
self.is_test_harness_description(parent_def_id)
337-
}
338-
339248
/// We record test harness information in kani-metadata, just like we record
340249
/// proof harness information. This is used to support e.g. cargo-kani assess.
341250
///
@@ -345,64 +254,25 @@ impl<'tcx> GotocCtx<'tcx> {
345254
/// as it add asserts for tests that return `Result` types.
346255
fn record_test_harness_metadata(&mut self) {
347256
let def_id = self.current_fn().instance().def_id();
348-
if self.is_test_harness_closure(def_id) {
349-
let loc = self.codegen_span(&self.current_fn().mir().span);
350-
self.test_harnesses.push(HarnessMetadata {
351-
pretty_name: self.current_fn().readable_name().to_owned(),
352-
mangled_name: self.current_fn().name(),
353-
crate_name: self.current_fn().krate(),
354-
original_file: loc.filename().unwrap(),
355-
original_start_line: loc.start_line().unwrap() as usize,
356-
original_end_line: loc.end_line().unwrap() as usize,
357-
solver: None,
358-
unwind_value: None,
359-
// We record the actual path after codegen before we dump the metadata into a file.
360-
goto_file: None,
361-
})
257+
if is_test_harness_closure(self.tcx, def_id) {
258+
self.test_harnesses.push(self.generate_metadata(None))
362259
}
363260
}
364261

365262
/// This updates the goto context with any information that should be accumulated from a function's
366263
/// attributes.
367264
///
368265
/// Handle all attributes i.e. `#[kani::x]` (which kani_macros translates to `#[kanitool::x]` for us to handle here)
369-
fn handle_kanitool_attributes(&mut self) {
266+
fn record_kani_attributes(&mut self) {
370267
let def_id = self.current_fn().instance().def_id();
371-
let all_attributes = self.tcx.get_attrs_unchecked(def_id);
372-
let (proof_attributes, other_attributes) = partition_kanitool_attributes(all_attributes);
373-
if !proof_attributes.is_empty() {
374-
self.create_proof_harness(other_attributes);
375-
}
376-
}
377-
378-
/// Create the proof harness struct using the handler methods for various attributes
379-
fn create_proof_harness(&mut self, other_attributes: Vec<(String, &Attribute)>) {
380-
let mut harness = self.default_kanitool_proof();
381-
for attr in other_attributes.iter() {
382-
match attr.0.as_str() {
383-
"solver" => self.handle_kanitool_solver(attr.1, &mut harness),
384-
"stub" => {
385-
if !self.queries.get_stubbing_enabled() {
386-
self.tcx.sess.span_warn(
387-
attr.1.span,
388-
"Stubbing is not enabled; attribute `kani::stub` will be ignored",
389-
)
390-
}
391-
}
392-
"unwind" => self.handle_kanitool_unwind(attr.1, &mut harness),
393-
_ => {
394-
self.tcx.sess.span_err(
395-
attr.1.span,
396-
format!("Unsupported Annotation -> {}", attr.0.as_str()).as_str(),
397-
);
398-
}
399-
}
268+
let attributes = extract_harness_attributes(self.tcx, def_id);
269+
if attributes.is_some() {
270+
self.proof_harnesses.push(self.generate_metadata(attributes));
400271
}
401-
self.proof_harnesses.push(harness);
402272
}
403273

404274
/// Create the default proof harness for the current function
405-
fn default_kanitool_proof(&mut self) -> HarnessMetadata {
275+
fn generate_metadata(&self, attributes: Option<HarnessAttributes>) -> HarnessMetadata {
406276
let current_fn = self.current_fn();
407277
let pretty_name = current_fn.readable_name().to_owned();
408278
let mangled_name = current_fn.name();
@@ -415,102 +285,9 @@ impl<'tcx> GotocCtx<'tcx> {
415285
original_file: loc.filename().unwrap(),
416286
original_start_line: loc.start_line().unwrap() as usize,
417287
original_end_line: loc.end_line().unwrap() as usize,
418-
solver: None,
419-
unwind_value: None,
288+
attributes: attributes.unwrap_or_default(),
420289
// We record the actual path after codegen before we dump the metadata into a file.
421290
goto_file: None,
422291
}
423292
}
424-
425-
/// Updates the proof harness with new unwind value
426-
fn handle_kanitool_unwind(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
427-
// If some unwind value already exists, then the current unwind being handled is a duplicate
428-
if harness.unwind_value.is_some() {
429-
self.tcx.sess.span_err(attr.span, "Only one '#[kani::unwind]' allowed");
430-
return;
431-
}
432-
// Get Attribute value and if it's not none, assign it to the metadata
433-
match extract_integer_argument(attr) {
434-
None => {
435-
// There are no integers or too many arguments given to the attribute
436-
self.tcx
437-
.sess
438-
.span_err(attr.span, "Exactly one Unwind Argument as Integer accepted");
439-
}
440-
Some(unwind_integer_value) => {
441-
let val: Result<u32, _> = unwind_integer_value.try_into();
442-
if val.is_err() {
443-
self.tcx
444-
.sess
445-
.span_err(attr.span, "Value above maximum permitted value - u32::MAX");
446-
return;
447-
}
448-
harness.unwind_value = Some(val.unwrap());
449-
}
450-
}
451-
}
452-
453-
/// Set the solver for this proof harness
454-
fn handle_kanitool_solver(&mut self, attr: &Attribute, harness: &mut HarnessMetadata) {
455-
// Make sure the solver is not already set
456-
if harness.solver.is_some() {
457-
self.tcx
458-
.sess
459-
.span_err(attr.span, "only one '#[kani::solver]' attribute is allowed per harness");
460-
return;
461-
}
462-
harness.solver = self.extract_solver_argument(attr);
463-
}
464-
465-
fn extract_solver_argument(&mut self, attr: &Attribute) -> Option<CbmcSolver> {
466-
// TODO: Argument validation should be done as part of the `kani_macros` crate
467-
// <https://github.com/model-checking/kani/issues/2192>
468-
const ATTRIBUTE: &str = "#[kani::solver]";
469-
let invalid_arg_err = |attr: &Attribute| {
470-
self.tcx.sess.span_err(
471-
attr.span,
472-
format!("invalid argument for `{ATTRIBUTE}` attribute, expected one of the supported solvers (e.g. `kissat`) or a SAT solver binary (e.g. `bin=\"<SAT_SOLVER_BINARY>\"`)")
473-
)
474-
};
475-
476-
let attr_args = attr.meta_item_list().unwrap();
477-
if attr_args.len() != 1 {
478-
self.tcx.sess.span_err(
479-
attr.span,
480-
format!(
481-
"the `{ATTRIBUTE}` attribute expects a single argument. Got {} arguments.",
482-
attr_args.len()
483-
),
484-
);
485-
return None;
486-
}
487-
let attr_arg = &attr_args[0];
488-
let meta_item = attr_arg.meta_item();
489-
if meta_item.is_none() {
490-
invalid_arg_err(attr);
491-
return None;
492-
}
493-
let meta_item = meta_item.unwrap();
494-
let ident = meta_item.ident().unwrap();
495-
let ident_str = ident.as_str();
496-
match &meta_item.kind {
497-
MetaItemKind::Word => {
498-
let solver = CbmcSolver::from_str(ident_str);
499-
match solver {
500-
Ok(solver) => Some(solver),
501-
Err(_) => {
502-
self.tcx.sess.span_err(attr.span, format!("unknown solver `{ident_str}`"));
503-
None
504-
}
505-
}
506-
}
507-
MetaItemKind::NameValue(lit) if ident_str == "bin" && lit.kind.is_str() => {
508-
Some(CbmcSolver::Binary(lit.token_lit.symbol.to_string()))
509-
}
510-
_ => {
511-
invalid_arg_err(attr);
512-
None
513-
}
514-
}
515-
}
516293
}

kani-compiler/src/codegen_cprover_gotoc/compiler_interface.rs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
66
use crate::codegen_cprover_gotoc::archive::ArchiveBuilder;
77
use crate::codegen_cprover_gotoc::GotocCtx;
8+
use crate::kani_middle::attributes::is_proof_harness;
9+
use crate::kani_middle::attributes::is_test_harness_description;
10+
use crate::kani_middle::check_crate_items;
811
use crate::kani_middle::provide;
912
use crate::kani_middle::reachability::{
1013
collect_reachable_items, filter_closures_in_const_crate_items, filter_crate_items,
@@ -20,7 +23,6 @@ use rustc_codegen_ssa::{CodegenResults, CrateInfo};
2023
use rustc_data_structures::fx::FxHashMap;
2124
use rustc_data_structures::temp_dir::MaybeTempDir;
2225
use rustc_errors::ErrorGuaranteed;
23-
use rustc_hir::def::DefKind;
2426
use rustc_hir::def_id::LOCAL_CRATE;
2527
use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME};
2628
use rustc_metadata::EncodedMetadata;
@@ -91,7 +93,7 @@ impl CodegenBackend for GotocCodegenBackend {
9193
let mut gcx = GotocCtx::new(tcx, (*self.queries.lock().unwrap()).clone());
9294
check_target(tcx.sess);
9395
check_options(tcx.sess);
94-
check_crate_items(&gcx);
96+
check_crate_items(gcx.tcx, gcx.queries.get_ignore_global_asm());
9597

9698
let items = with_timer(|| collect_codegen_items(&gcx), "codegen reachability analysis");
9799
if items.is_empty() {
@@ -296,34 +298,6 @@ fn check_options(session: &Session) {
296298
session.abort_if_errors();
297299
}
298300

299-
/// Check that all crate items are supported and there's no misconfiguration.
300-
/// This method will exhaustively print any error / warning and it will abort at the end if any
301-
/// error was found.
302-
fn check_crate_items(gcx: &GotocCtx) {
303-
let tcx = gcx.tcx;
304-
for item in tcx.hir_crate_items(()).items() {
305-
let def_id = item.owner_id.def_id.to_def_id();
306-
gcx.check_attributes(def_id);
307-
if tcx.def_kind(def_id) == DefKind::GlobalAsm {
308-
if !gcx.queries.get_ignore_global_asm() {
309-
let error_msg = format!(
310-
"Crate {} contains global ASM, which is not supported by Kani. Rerun with \
311-
`--enable-unstable --ignore-global-asm` to suppress this error \
312-
(**Verification results may be impacted**).",
313-
gcx.short_crate_name()
314-
);
315-
tcx.sess.err(&error_msg);
316-
} else {
317-
tcx.sess.warn(format!(
318-
"Ignoring global ASM in crate {}. Verification results may be impacted.",
319-
gcx.short_crate_name()
320-
));
321-
}
322-
}
323-
}
324-
tcx.sess.abort_if_errors();
325-
}
326-
327301
/// Prints a report at the end of the compilation.
328302
fn print_report(ctx: &GotocCtx, tcx: TyCtxt) {
329303
// Print all unsupported constructs.
@@ -399,14 +373,14 @@ fn collect_codegen_items<'tcx>(gcx: &GotocCtx<'tcx>) -> Vec<MonoItem<'tcx>> {
399373
}
400374
ReachabilityType::Harnesses => {
401375
// Cross-crate collecting of all items that are reachable from the crate harnesses.
402-
let harnesses = filter_crate_items(tcx, |_, def_id| gcx.is_proof_harness(def_id));
376+
let harnesses = filter_crate_items(tcx, |_, def_id| is_proof_harness(gcx.tcx, def_id));
403377
collect_reachable_items(tcx, &harnesses).into_iter().collect()
404378
}
405379
ReachabilityType::Tests => {
406380
// We're iterating over crate items here, so what we have to codegen is the "test description" containing the
407381
// test closure that we want to execute
408382
let harnesses = filter_closures_in_const_crate_items(tcx, |_, def_id| {
409-
gcx.is_test_harness_description(def_id)
383+
is_test_harness_description(gcx.tcx, def_id)
410384
});
411385
collect_reachable_items(tcx, &harnesses).into_iter().collect()
412386
}

kani-compiler/src/codegen_cprover_gotoc/context/goto_ctx.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ impl<'tcx> GotocCtx<'tcx> {
131131
// We likely (and should) have no instances of
132132
// calling `codegen_unimplemented` without file/line.
133133
// So while we map out of `Option` here, we expect them to always be `Some`
134-
(
135-
l.filename().unwrap_or_default(),
136-
l.start_line().map(|x| x.to_string()).unwrap_or_default(),
137-
)
134+
kani_metadata::Location {
135+
filename: l.filename().unwrap_or_default(),
136+
start_line: l.start_line().unwrap_or_default(),
137+
}
138138
})
139139
.collect(),
140140
})

0 commit comments

Comments
 (0)