@@ -39,6 +39,7 @@ use std::collections::{BTreeMap, HashMap};
39
39
use std:: fs:: File ;
40
40
use std:: io:: BufWriter ;
41
41
use std:: mem;
42
+ use std:: path:: { Path , PathBuf } ;
42
43
use std:: process:: ExitCode ;
43
44
use std:: sync:: { Arc , Mutex } ;
44
45
use tracing:: debug;
@@ -71,12 +72,21 @@ type HarnessId = DefPathHash;
71
72
/// A set of stubs.
72
73
type Stubs = BTreeMap < DefPathHash , DefPathHash > ;
73
74
74
- #[ derive( Debug ) ]
75
+ #[ derive( Clone , Debug ) ]
75
76
struct HarnessInfo {
76
77
pub metadata : HarnessMetadata ,
77
78
pub stub_map : Stubs ,
78
79
}
79
80
81
+ /// Store some relevant information about the crate compilation.
82
+ #[ derive( Clone , Debug ) ]
83
+ struct CrateInfo {
84
+ /// The name of the crate being compiled.
85
+ pub name : String ,
86
+ /// The metadata output path that shall be generated as part of the crate compilation.
87
+ pub output_path : PathBuf ,
88
+ }
89
+
80
90
/// Represents the current compilation stage.
81
91
///
82
92
/// The Kani compiler may run the Rust compiler multiple times since stubbing has to be applied
@@ -85,20 +95,28 @@ struct HarnessInfo {
85
95
/// - We always start in the [CompilationStage::Init].
86
96
/// - After [CompilationStage::Init] we transition to either
87
97
/// - [CompilationStage::CodegenNoStubs] on a regular crate compilation, this will follow Init.
88
- /// - [CompilationStage::Done ], running the compiler to gather information, such as `--version`
89
- /// will skip code generation completely, and there is no work to be done.
98
+ /// - [CompilationStage::CompilationSkipped ], running the compiler to gather information, such as
99
+ /// `--version` will skip code generation completely, and there is no work to be done.
90
100
/// - After the [CompilationStage::CodegenNoStubs], we transition to either
91
101
/// - [CompilationStage::CodegenWithStubs] when there is at least one harness with stubs.
92
102
/// - [CompilationStage::Done] where there is no harness left to process.
93
103
/// - The [CompilationStage::CodegenWithStubs] can last multiple Rust compiler runs. Once there is
94
104
/// no harness left, we move to [CompilationStage::Done].
105
+ /// - The final stages are either [CompilationStage::Done] or [CompilationStage::CompilationSkipped].
106
+ /// - [CompilationStage::Done] represents the final state of the compiler after a successful
107
+ /// compilation. The crate metadata is stored here (even if no codegen was actually performed).
108
+ /// - [CompilationStage::CompilationSkipped] no compilation was actually performed.
109
+ /// No work needs to be done.
110
+ /// - Note: In a scenario where the compilation fails, the compiler will exit immediately,
111
+ /// independent on the stage. Any artifact produced shouldn't be used.
95
112
/// I.e.:
96
113
/// ```dot
97
114
/// graph CompilationStage {
98
- /// Init -> {CodegenNoStubs, Done }
115
+ /// Init -> {CodegenNoStubs, CompilationSkipped }
99
116
/// CodegenNoStubs -> {CodegenStubs, Done}
100
117
/// // Loop up to N harnesses times.
101
118
/// CodegenStubs -> {CodegenStubs, Done}
119
+ /// CompilationSkipped
102
120
/// Done
103
121
/// }
104
122
/// ```
@@ -108,11 +126,14 @@ enum CompilationStage {
108
126
/// Initial state that the compiler is always instantiated with.
109
127
/// In this stage, we initialize the Query and collect all harnesses.
110
128
Init ,
129
+ /// State where the compiler ran but didn't actually compile anything (e.g.: --version).
130
+ CompilationSkipped ,
111
131
/// Stage where the compiler will perform codegen of all harnesses that don't use stub.
112
132
CodegenNoStubs {
113
133
target_harnesses : Vec < HarnessId > ,
114
134
next_harnesses : Vec < Vec < HarnessId > > ,
115
135
all_harnesses : HashMap < HarnessId , HarnessInfo > ,
136
+ crate_info : CrateInfo ,
116
137
} ,
117
138
/// Stage where the compiler will codegen harnesses that use stub, one group at a time.
118
139
/// The harnesses at this stage are grouped according to the stubs they are using. For now,
@@ -121,18 +142,17 @@ enum CompilationStage {
121
142
target_harnesses : Vec < HarnessId > ,
122
143
next_harnesses : Vec < Vec < HarnessId > > ,
123
144
all_harnesses : HashMap < HarnessId , HarnessInfo > ,
145
+ crate_info : CrateInfo ,
146
+ } ,
147
+ Done {
148
+ metadata : Option < ( KaniMetadata , CrateInfo ) > ,
124
149
} ,
125
- Done ,
126
150
}
127
151
128
152
impl CompilationStage {
129
153
pub fn is_init ( & self ) -> bool {
130
154
matches ! ( self , CompilationStage :: Init )
131
155
}
132
-
133
- pub fn is_done ( & self ) -> bool {
134
- matches ! ( self , CompilationStage :: Done )
135
- }
136
156
}
137
157
138
158
/// This object controls the compiler behavior.
@@ -160,7 +180,7 @@ impl KaniCompiler {
160
180
/// Since harnesses may have different attributes that affect compilation, Kani compiler can
161
181
/// actually invoke the rust compiler multiple times.
162
182
pub fn run ( & mut self , orig_args : Vec < String > ) -> Result < ( ) , ErrorGuaranteed > {
163
- while ! self . stage . is_done ( ) {
183
+ loop {
164
184
debug ! ( next=?self . stage, "run" ) ;
165
185
match & self . stage {
166
186
CompilationStage :: Init => {
@@ -178,37 +198,64 @@ impl KaniCompiler {
178
198
args. push ( extra_arg) ;
179
199
self . run_compilation_session ( & args) ?;
180
200
}
181
- CompilationStage :: Done => {
182
- unreachable ! ( "There's nothing to be done here." )
201
+ CompilationStage :: Done { metadata : Some ( ( kani_metadata, crate_info) ) } => {
202
+ // Only store metadata for harnesses for now.
203
+ // TODO: This should only skip None.
204
+ // https://github.com/model-checking/kani/issues/2493
205
+ if self . queries . lock ( ) . unwrap ( ) . reachability_analysis
206
+ == ReachabilityType :: Harnesses
207
+ {
208
+ // Store metadata file.
209
+ // We delay storing the metadata so we can include information collected
210
+ // during codegen.
211
+ self . store_metadata ( & kani_metadata, & crate_info. output_path ) ;
212
+ }
213
+ return Ok ( ( ) ) ;
214
+ }
215
+ CompilationStage :: Done { metadata : None }
216
+ | CompilationStage :: CompilationSkipped => {
217
+ return Ok ( ( ) ) ;
183
218
}
184
219
} ;
185
220
186
221
self . next_stage ( ) ;
187
222
}
188
- Ok ( ( ) )
189
223
}
190
224
191
225
/// Set up the next compilation stage after a `rustc` run.
192
226
fn next_stage ( & mut self ) {
193
227
self . stage = match & mut self . stage {
194
228
CompilationStage :: Init => {
195
229
// This may occur when user passes arguments like --version or --help.
196
- CompilationStage :: Done
230
+ CompilationStage :: Done { metadata : None }
197
231
}
198
- CompilationStage :: CodegenNoStubs { next_harnesses, all_harnesses, .. }
199
- | CompilationStage :: CodegenWithStubs { next_harnesses, all_harnesses, .. } => {
232
+ CompilationStage :: CodegenNoStubs {
233
+ next_harnesses, all_harnesses, crate_info, ..
234
+ }
235
+ | CompilationStage :: CodegenWithStubs {
236
+ next_harnesses,
237
+ all_harnesses,
238
+ crate_info,
239
+ ..
240
+ } => {
200
241
if let Some ( target_harnesses) = next_harnesses. pop ( ) {
201
242
assert ! ( !target_harnesses. is_empty( ) , "expected at least one target harness" ) ;
202
243
CompilationStage :: CodegenWithStubs {
203
244
target_harnesses,
204
245
next_harnesses : mem:: take ( next_harnesses) ,
205
246
all_harnesses : mem:: take ( all_harnesses) ,
247
+ crate_info : crate_info. clone ( ) ,
206
248
}
207
249
} else {
208
- CompilationStage :: Done
250
+ CompilationStage :: Done {
251
+ metadata : Some ( (
252
+ generate_metadata ( & crate_info, all_harnesses) ,
253
+ crate_info. clone ( ) ,
254
+ ) ) ,
255
+ }
209
256
}
210
257
}
211
- CompilationStage :: Done => {
258
+ CompilationStage :: Done { .. } | CompilationStage :: CompilationSkipped => {
212
259
unreachable ! ( )
213
260
}
214
261
} ;
@@ -225,6 +272,10 @@ impl KaniCompiler {
225
272
226
273
/// Gather and process all harnesses from this crate that shall be compiled.
227
274
fn process_harnesses ( & self , tcx : TyCtxt ) -> CompilationStage {
275
+ let crate_info = CrateInfo {
276
+ name : tcx. crate_name ( LOCAL_CRATE ) . as_str ( ) . into ( ) ,
277
+ output_path : metadata_output_path ( tcx) ,
278
+ } ;
228
279
if self . queries . lock ( ) . unwrap ( ) . reachability_analysis == ReachabilityType :: Harnesses {
229
280
let base_filename = tcx. output_filenames ( ( ) ) . output_path ( OutputType :: Object ) ;
230
281
let harnesses = filter_crate_items ( tcx, |_, def_id| is_proof_harness ( tcx, def_id) ) ;
@@ -250,21 +301,21 @@ impl KaniCompiler {
250
301
// Generate code without stubs.
251
302
( all_harnesses. keys ( ) . cloned ( ) . collect ( ) , vec ! [ ] )
252
303
} ;
253
- // Store metadata file.
254
- self . store_metadata ( tcx, & all_harnesses) ;
255
304
256
- // Even if no_stubs is empty we still need to store metadata.
305
+ // Even if no_stubs is empty we still need to store rustc metadata.
257
306
CompilationStage :: CodegenNoStubs {
258
307
target_harnesses : no_stubs,
259
308
next_harnesses : group_by_stubs ( with_stubs, & all_harnesses) ,
260
309
all_harnesses,
310
+ crate_info,
261
311
}
262
312
} else {
263
313
// Leave other reachability type handling as is for now.
264
314
CompilationStage :: CodegenNoStubs {
265
315
target_harnesses : vec ! [ ] ,
266
316
next_harnesses : vec ! [ ] ,
267
317
all_harnesses : HashMap :: default ( ) ,
318
+ crate_info,
268
319
}
269
320
}
270
321
}
@@ -291,25 +342,14 @@ impl KaniCompiler {
291
342
. collect ( ) ;
292
343
Compilation :: Continue
293
344
}
294
- CompilationStage :: Init | CompilationStage :: Done => unreachable ! ( ) ,
345
+ CompilationStage :: Init
346
+ | CompilationStage :: Done { .. }
347
+ | CompilationStage :: CompilationSkipped => unreachable ! ( ) ,
295
348
}
296
349
}
297
350
298
351
/// Write the metadata to a file
299
- fn store_metadata ( & self , tcx : TyCtxt , all_harnesses : & HashMap < HarnessId , HarnessInfo > ) {
300
- let ( proof_harnesses, test_harnesses) = all_harnesses
301
- . values ( )
302
- . map ( |info| & info. metadata )
303
- . cloned ( )
304
- . partition ( |md| md. attributes . proof ) ;
305
- let metadata = KaniMetadata {
306
- crate_name : tcx. crate_name ( LOCAL_CRATE ) . as_str ( ) . into ( ) ,
307
- proof_harnesses,
308
- unsupported_features : vec ! [ ] ,
309
- test_harnesses,
310
- } ;
311
- let mut filename = tcx. output_filenames ( ( ) ) . output_path ( OutputType :: Object ) ;
312
- filename. set_extension ( ArtifactType :: Metadata ) ;
352
+ fn store_metadata ( & self , metadata : & KaniMetadata , filename : & Path ) {
313
353
debug ! ( ?filename, "write_metadata" ) ;
314
354
let out_file = File :: create ( & filename) . unwrap ( ) ;
315
355
let writer = BufWriter :: new ( out_file) ;
@@ -388,10 +428,34 @@ impl Callbacks for KaniCompiler {
388
428
}
389
429
}
390
430
431
+ /// Generate [KaniMetadata] for the target crate.
432
+ fn generate_metadata (
433
+ crate_info : & CrateInfo ,
434
+ all_harnesses : & HashMap < HarnessId , HarnessInfo > ,
435
+ ) -> KaniMetadata {
436
+ let ( proof_harnesses, test_harnesses) = all_harnesses
437
+ . values ( )
438
+ . map ( |info| & info. metadata )
439
+ . cloned ( )
440
+ . partition ( |md| md. attributes . proof ) ;
441
+ KaniMetadata {
442
+ crate_name : crate_info. name . clone ( ) ,
443
+ proof_harnesses,
444
+ unsupported_features : vec ! [ ] ,
445
+ test_harnesses,
446
+ }
447
+ }
448
+
449
+ /// Extract the filename for the metadata file.
450
+ fn metadata_output_path ( tcx : TyCtxt ) -> PathBuf {
451
+ let mut filename = tcx. output_filenames ( ( ) ) . output_path ( OutputType :: Object ) ;
452
+ filename. set_extension ( ArtifactType :: Metadata ) ;
453
+ filename
454
+ }
455
+
391
456
#[ cfg( test) ]
392
457
mod tests {
393
- use super :: { HarnessInfo , Stubs } ;
394
- use crate :: kani_compiler:: { group_by_stubs, HarnessId } ;
458
+ use super :: * ;
395
459
use kani_metadata:: { HarnessAttributes , HarnessMetadata } ;
396
460
use rustc_data_structures:: fingerprint:: Fingerprint ;
397
461
use rustc_hir:: definitions:: DefPathHash ;
@@ -404,12 +468,12 @@ mod tests {
404
468
DefPathHash ( Fingerprint :: new ( id, 0 ) )
405
469
}
406
470
407
- fn mock_metadata ( ) -> HarnessMetadata {
471
+ fn mock_metadata ( name : String , krate : String ) -> HarnessMetadata {
408
472
HarnessMetadata {
409
- pretty_name : String :: from ( "dummy" ) ,
410
- mangled_name : String :: from ( "dummy" ) ,
411
- crate_name : String :: from ( "dummy" ) ,
412
- original_file : String :: from ( "dummy" ) ,
473
+ pretty_name : name . clone ( ) ,
474
+ mangled_name : name . clone ( ) ,
475
+ original_file : format ! ( "{}.rs" , krate ) ,
476
+ crate_name : krate ,
413
477
original_start_line : 10 ,
414
478
original_end_line : 20 ,
415
479
goto_file : None ,
@@ -418,7 +482,7 @@ mod tests {
418
482
}
419
483
420
484
fn mock_info_with_stubs ( stub_map : Stubs ) -> HarnessInfo {
421
- HarnessInfo { metadata : mock_metadata ( ) , stub_map }
485
+ HarnessInfo { metadata : mock_metadata ( "dummy" . to_string ( ) , "crate" . to_string ( ) ) , stub_map }
422
486
}
423
487
424
488
#[ test]
@@ -458,4 +522,67 @@ mod tests {
458
522
) ;
459
523
assert ! ( grouped. contains( & vec![ harness_2] ) ) ;
460
524
}
525
+
526
+ #[ test]
527
+ fn test_generate_metadata ( ) {
528
+ // Mock inputs.
529
+ let name = "my_crate" . to_string ( ) ;
530
+ let crate_info = CrateInfo { name : name. clone ( ) , output_path : PathBuf :: default ( ) } ;
531
+
532
+ let mut info = mock_info_with_stubs ( Stubs :: default ( ) ) ;
533
+ info. metadata . attributes . proof = true ;
534
+ let id = mock_next_id ( ) ;
535
+ let all_harnesses = HashMap :: from ( [ ( id, info. clone ( ) ) ] ) ;
536
+
537
+ // Call generate metadata.
538
+ let metadata = generate_metadata ( & crate_info, & all_harnesses) ;
539
+
540
+ // Check output.
541
+ assert_eq ! ( metadata. crate_name, name) ;
542
+ assert_eq ! ( metadata. proof_harnesses. len( ) , 1 ) ;
543
+ assert_eq ! ( * metadata. proof_harnesses. first( ) . unwrap( ) , info. metadata) ;
544
+ }
545
+
546
+ #[ test]
547
+ fn test_generate_empty_metadata ( ) {
548
+ // Mock inputs.
549
+ let name = "my_crate" . to_string ( ) ;
550
+ let crate_info = CrateInfo { name : name. clone ( ) , output_path : PathBuf :: default ( ) } ;
551
+ let all_harnesses = HashMap :: new ( ) ;
552
+
553
+ // Call generate metadata.
554
+ let metadata = generate_metadata ( & crate_info, & all_harnesses) ;
555
+
556
+ // Check output.
557
+ assert_eq ! ( metadata. crate_name, name) ;
558
+ assert_eq ! ( metadata. proof_harnesses. len( ) , 0 ) ;
559
+ }
560
+
561
+ #[ test]
562
+ fn test_generate_metadata_with_multiple_harness ( ) {
563
+ // Mock inputs.
564
+ let krate = "my_crate" . to_string ( ) ;
565
+ let crate_info = CrateInfo { name : krate. clone ( ) , output_path : PathBuf :: default ( ) } ;
566
+
567
+ let harnesses = [ "h1" , "h2" , "h3" ] ;
568
+ let infos = harnesses. map ( |harness| {
569
+ let mut metadata = mock_metadata ( harness. to_string ( ) , krate. clone ( ) ) ;
570
+ metadata. attributes . proof = true ;
571
+ ( mock_next_id ( ) , HarnessInfo { stub_map : Stubs :: default ( ) , metadata } )
572
+ } ) ;
573
+ let all_harnesses = HashMap :: from ( infos. clone ( ) ) ;
574
+
575
+ // Call generate metadata.
576
+ let metadata = generate_metadata ( & crate_info, & all_harnesses) ;
577
+
578
+ // Check output.
579
+ assert_eq ! ( metadata. crate_name, krate) ;
580
+ assert_eq ! ( metadata. proof_harnesses. len( ) , infos. len( ) ) ;
581
+ assert ! (
582
+ metadata
583
+ . proof_harnesses
584
+ . iter( )
585
+ . all( |harness| harnesses. contains( &&* harness. pretty_name) )
586
+ ) ;
587
+ }
461
588
}
0 commit comments