7
7
//! Today, only stub / contracts can affect the harness codegen. Thus, we group the harnesses
8
8
//! according to their stub configuration.
9
9
10
- use crate :: args:: ReachabilityType ;
10
+ use crate :: args:: { Arguments , ReachabilityType } ;
11
11
use crate :: kani_middle:: attributes:: { KaniAttributes , is_proof_harness} ;
12
12
use crate :: kani_middle:: kani_functions:: { KaniIntrinsic , KaniModel } ;
13
13
use crate :: kani_middle:: metadata:: {
@@ -17,14 +17,17 @@ use crate::kani_middle::reachability::filter_crate_items;
17
17
use crate :: kani_middle:: resolve:: expect_resolve_fn;
18
18
use crate :: kani_middle:: stubbing:: { check_compatibility, harness_stub_map} ;
19
19
use crate :: kani_queries:: QueryDb ;
20
- use kani_metadata:: { ArtifactType , AssignsContract , HarnessKind , HarnessMetadata , KaniMetadata } ;
20
+ use kani_metadata:: {
21
+ ArtifactType , AssignsContract , AutoHarnessSkipReason , AutoHarnessSkippedFns , HarnessKind ,
22
+ HarnessMetadata , KaniMetadata ,
23
+ } ;
21
24
use rustc_hir:: def_id:: DefId ;
22
25
use rustc_middle:: ty:: TyCtxt ;
23
26
use rustc_session:: config:: OutputType ;
24
27
use rustc_smir:: rustc_internal;
25
- use stable_mir:: CrateDef ;
26
28
use stable_mir:: mir:: { TerminatorKind , mono:: Instance } ;
27
29
use stable_mir:: ty:: { FnDef , GenericArgKind , GenericArgs , IndexedVal , RigidTy , TyKind } ;
30
+ use stable_mir:: { CrateDef , CrateItem } ;
28
31
use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ;
29
32
use std:: fs:: File ;
30
33
use std:: io:: BufWriter ;
@@ -46,9 +49,10 @@ struct CrateInfo {
46
49
47
50
/// We group the harnesses that have the same stubs.
48
51
pub struct CodegenUnits {
49
- units : Vec < CodegenUnit > ,
50
- harness_info : HashMap < Harness , HarnessMetadata > ,
52
+ autoharness_skipped_fns : Option < AutoHarnessSkippedFns > ,
51
53
crate_info : CrateInfo ,
54
+ harness_info : HashMap < Harness , HarnessMetadata > ,
55
+ units : Vec < CodegenUnit > ,
52
56
}
53
57
54
58
#[ derive( Clone , Default , Debug ) ]
@@ -70,7 +74,12 @@ impl CodegenUnits {
70
74
let units = group_by_stubs ( tcx, & all_harnesses) ;
71
75
validate_units ( tcx, & units) ;
72
76
debug ! ( ?units, "CodegenUnits::new" ) ;
73
- CodegenUnits { units, harness_info : all_harnesses, crate_info }
77
+ CodegenUnits {
78
+ units,
79
+ harness_info : all_harnesses,
80
+ crate_info,
81
+ autoharness_skipped_fns : None ,
82
+ }
74
83
}
75
84
ReachabilityType :: AllFns => {
76
85
let mut all_harnesses = get_all_manual_harnesses ( tcx, base_filename) ;
@@ -80,23 +89,16 @@ impl CodegenUnits {
80
89
let kani_fns = queries. kani_functions ( ) ;
81
90
let kani_harness_intrinsic =
82
91
kani_fns. get ( & KaniIntrinsic :: AutomaticHarness . into ( ) ) . unwrap ( ) ;
83
- let kani_any_inst = kani_fns. get ( & KaniModel :: Any . into ( ) ) . unwrap ( ) ;
84
-
85
- let verifiable_fns = filter_crate_items ( tcx, |_, instance : Instance | -> bool {
86
- // If the user specified functions to include or exclude, only allow instances matching those filters.
87
- let user_included = if !args. autoharness_included_functions . is_empty ( ) {
88
- fn_list_contains_instance ( & instance, & args. autoharness_included_functions )
89
- } else if !args. autoharness_excluded_functions . is_empty ( ) {
90
- !fn_list_contains_instance ( & instance, & args. autoharness_excluded_functions )
91
- } else {
92
- true
93
- } ;
94
- user_included
95
- && is_eligible_for_automatic_harness ( tcx, instance, * kani_any_inst)
96
- } ) ;
92
+
93
+ let ( chosen, skipped) = automatic_harness_partition (
94
+ tcx,
95
+ args,
96
+ * kani_fns. get ( & KaniModel :: Any . into ( ) ) . unwrap ( ) ,
97
+ ) ;
98
+
97
99
let automatic_harnesses = get_all_automatic_harnesses (
98
100
tcx,
99
- verifiable_fns ,
101
+ chosen ,
100
102
* kani_harness_intrinsic,
101
103
base_filename,
102
104
) ;
@@ -111,14 +113,25 @@ impl CodegenUnits {
111
113
} )
112
114
. collect :: < Vec < _ > > ( ) ,
113
115
) ;
114
- all_harnesses. extend ( automatic_harnesses) ;
116
+ all_harnesses. extend ( automatic_harnesses. clone ( ) ) ;
117
+
115
118
// No need to validate the units again because validation only checks stubs, and we haven't added any stubs.
116
119
debug ! ( ?units, "CodegenUnits::new" ) ;
117
- CodegenUnits { units, harness_info : all_harnesses, crate_info }
120
+ CodegenUnits {
121
+ units,
122
+ harness_info : all_harnesses,
123
+ crate_info,
124
+ autoharness_skipped_fns : Some ( skipped) ,
125
+ }
118
126
}
119
127
_ => {
120
128
// Leave other reachability type handling as is for now.
121
- CodegenUnits { units : vec ! [ ] , harness_info : HashMap :: default ( ) , crate_info }
129
+ CodegenUnits {
130
+ units : vec ! [ ] ,
131
+ harness_info : HashMap :: default ( ) ,
132
+ crate_info,
133
+ autoharness_skipped_fns : None ,
134
+ }
122
135
}
123
136
}
124
137
}
@@ -163,6 +176,7 @@ impl CodegenUnits {
163
176
unsupported_features : vec ! [ ] ,
164
177
test_harnesses,
165
178
contracted_functions : gen_contracts_metadata ( tcx) ,
179
+ autoharness_skipped_fns : self . autoharness_skipped_fns . clone ( ) ,
166
180
}
167
181
}
168
182
}
@@ -339,47 +353,112 @@ fn get_all_automatic_harnesses(
339
353
. collect :: < HashMap < _ , _ > > ( )
340
354
}
341
355
342
- /// Determine whether `instance` is eligible for automatic verification.
343
- fn is_eligible_for_automatic_harness ( tcx : TyCtxt , instance : Instance , any_inst : FnDef ) -> bool {
344
- // `instance` is ineligble if it is a harness or has an nonexistent/empty body
345
- if is_proof_harness ( tcx, instance) || !instance. has_body ( ) {
346
- return false ;
347
- }
348
- let body = instance. body ( ) . unwrap ( ) ;
349
-
350
- // `instance` is ineligble if it is an associated item of a Kani trait implementation,
351
- // or part of Kani contract instrumentation.
352
- // FIXME -- find a less hardcoded way of checking the former condition (perhaps upstream PR to StableMIR).
353
- if instance. name ( ) . contains ( "kani::Arbitrary" )
354
- || instance. name ( ) . contains ( "kani::Invariant" )
355
- || KaniAttributes :: for_instance ( tcx, instance)
356
- . fn_marker ( )
357
- . is_some_and ( |m| m. as_str ( ) == "kani_contract_mode" )
358
- {
359
- return false ;
360
- }
356
+ /// Partition every function in the crate into (chosen, skipped), where `chosen` is a vector of the Instances for which we'll generate automatic harnesses,
357
+ /// and `skipped` is a map of function names to the reason why we skipped them.
358
+ fn automatic_harness_partition (
359
+ tcx : TyCtxt ,
360
+ args : & Arguments ,
361
+ kani_any_def : FnDef ,
362
+ ) -> ( Vec < Instance > , BTreeMap < String , AutoHarnessSkipReason > ) {
363
+ // If `filter_list` contains `name`, either as an exact match or a substring.
364
+ let filter_contains = |name : & str , filter_list : & [ String ] | -> bool {
365
+ filter_list. iter ( ) . any ( |filter_name| name. contains ( filter_name) )
366
+ } ;
367
+
368
+ // If `func` is not eligible for an automatic harness, return the reason why; if it is eligible, return None.
369
+ let skip_reason = |fn_item : CrateItem | -> Option < AutoHarnessSkipReason > {
370
+ if KaniAttributes :: for_def_id ( tcx, fn_item. def_id ( ) ) . is_kani_instrumentation ( ) {
371
+ return Some ( AutoHarnessSkipReason :: KaniImpl ) ;
372
+ }
361
373
362
- // Each argument of `instance` must implement Arbitrary.
363
- // Note that this function operates on StableMIR `Instance`s, which are eagerly monomorphized,
364
- // so none of these arguments are generic.
365
- body. arg_locals ( ) . iter ( ) . all ( |arg| {
366
- let kani_any_body =
367
- Instance :: resolve ( any_inst, & GenericArgs ( vec ! [ GenericArgKind :: Type ( arg. ty) ] ) )
368
- . unwrap ( )
369
- . body ( )
370
- . unwrap ( ) ;
371
- if let TerminatorKind :: Call { func, .. } = & kani_any_body. blocks [ 0 ] . terminator . kind {
372
- if let Some ( ( def, args) ) = func. ty ( body. arg_locals ( ) ) . unwrap ( ) . kind ( ) . fn_def ( ) {
373
- return Instance :: resolve ( def, & args) . is_ok ( ) ;
374
+ let instance = match Instance :: try_from ( fn_item) {
375
+ Ok ( inst) => inst,
376
+ Err ( _) => {
377
+ return Some ( AutoHarnessSkipReason :: GenericFn ) ;
374
378
}
379
+ } ;
380
+
381
+ if !instance. has_body ( ) {
382
+ return Some ( AutoHarnessSkipReason :: NoBody ) ;
375
383
}
376
- false
377
- } )
378
- }
379
384
380
- /// Return whether the name of `instance` is included in `fn_list`,
381
- /// either as a substring or an exact match.
382
- fn fn_list_contains_instance ( instance : & Instance , fn_list : & [ String ] ) -> bool {
383
- let pretty_name = instance. name ( ) ;
384
- fn_list. contains ( & pretty_name) || fn_list. iter ( ) . any ( |fn_name| pretty_name. contains ( fn_name) )
385
+ let name = instance. name ( ) ;
386
+ let body = instance. body ( ) . unwrap ( ) ;
387
+
388
+ if is_proof_harness ( tcx, instance)
389
+ || name. contains ( "kani::Arbitrary" )
390
+ || name. contains ( "kani::Invariant" )
391
+ {
392
+ return Some ( AutoHarnessSkipReason :: KaniImpl ) ;
393
+ }
394
+
395
+ if ( !args. autoharness_included_functions . is_empty ( )
396
+ && !filter_contains ( & name, & args. autoharness_included_functions ) )
397
+ || ( !args. autoharness_excluded_functions . is_empty ( )
398
+ && filter_contains ( & name, & args. autoharness_excluded_functions ) )
399
+ {
400
+ return Some ( AutoHarnessSkipReason :: UserFilter ) ;
401
+ }
402
+
403
+ // Each argument of `instance` must implement Arbitrary.
404
+ // Note that we've already filtered out generic functions, so we know that each of these arguments has a concrete type.
405
+ let mut problematic_args = vec ! [ ] ;
406
+ for ( idx, arg) in body. arg_locals ( ) . iter ( ) . enumerate ( ) {
407
+ let kani_any_body =
408
+ Instance :: resolve ( kani_any_def, & GenericArgs ( vec ! [ GenericArgKind :: Type ( arg. ty) ] ) )
409
+ . unwrap ( )
410
+ . body ( )
411
+ . unwrap ( ) ;
412
+
413
+ let implements_arbitrary = if let TerminatorKind :: Call { func, .. } =
414
+ & kani_any_body. blocks [ 0 ] . terminator . kind
415
+ {
416
+ if let Some ( ( def, args) ) = func. ty ( body. arg_locals ( ) ) . unwrap ( ) . kind ( ) . fn_def ( ) {
417
+ Instance :: resolve ( def, & args) . is_ok ( )
418
+ } else {
419
+ false
420
+ }
421
+ } else {
422
+ false
423
+ } ;
424
+
425
+ if !implements_arbitrary {
426
+ // Find the name of the argument by referencing var_debug_info.
427
+ // Note that enumerate() starts at 0, while StableMIR argument_index starts at 1, hence the idx+1.
428
+ let arg_debug_info = body
429
+ . var_debug_info
430
+ . iter ( )
431
+ . find ( |var| {
432
+ var. argument_index . is_some_and ( |arg_idx| idx + 1 == usize:: from ( arg_idx) )
433
+ } )
434
+ . expect ( "Arguments should have corresponding var debug info" ) ;
435
+ problematic_args. push ( arg_debug_info. name . to_string ( ) )
436
+ }
437
+ }
438
+ if !problematic_args. is_empty ( ) {
439
+ return Some ( AutoHarnessSkipReason :: MissingArbitraryImpl ( problematic_args) ) ;
440
+ }
441
+ None
442
+ } ;
443
+
444
+ let mut chosen = vec ! [ ] ;
445
+ let mut skipped = BTreeMap :: new ( ) ;
446
+
447
+ // FIXME: ideally, this filter would be matches!(item.kind(), ItemKind::Fn), since that would allow us to generate harnesses for top-level closures,
448
+ // c.f. https://github.com/model-checking/kani/issues/3832#issuecomment-2701671798.
449
+ // Note that filtering closures out here is a UX choice: we could instead call skip_reason() on closures,
450
+ // but the limitations in the linked issue would cause the user to be flooded with reports of "skipping" Kani instrumentation functions.
451
+ // Instead, we just pretend closures don't exist in our reporting of what we did and did not verify, which has the downside of also ignoring the user's top-level closures, but those are rare.
452
+ let crate_fns =
453
+ stable_mir:: all_local_items ( ) . into_iter ( ) . filter ( |item| item. ty ( ) . kind ( ) . is_fn ( ) ) ;
454
+
455
+ for func in crate_fns {
456
+ if let Some ( reason) = skip_reason ( func) {
457
+ skipped. insert ( func. name ( ) , reason) ;
458
+ } else {
459
+ chosen. push ( Instance :: try_from ( func) . unwrap ( ) ) ;
460
+ }
461
+ }
462
+
463
+ ( chosen, skipped)
385
464
}
0 commit comments