@@ -19,7 +19,7 @@ use strum_macros::{AsRefStr, EnumString};
19
19
20
20
use tracing:: { debug, trace} ;
21
21
22
- use super :: resolve:: { self , resolve_fn} ;
22
+ use super :: resolve:: { self , resolve_fn, ResolveError } ;
23
23
24
24
#[ derive( Debug , Clone , Copy , AsRefStr , EnumString , PartialEq , Eq , PartialOrd , Ord ) ]
25
25
#[ strum( serialize_all = "snake_case" ) ]
@@ -31,13 +31,20 @@ enum KaniAttributeKind {
31
31
/// Attribute used to mark unstable APIs.
32
32
Unstable ,
33
33
Unwind ,
34
+ /// A sound [`Self::Stub`] that replaces a function by a stub generated from
35
+ /// its contract.
36
+ StubVerified ,
34
37
/// A harness, similar to [`Self::Proof`], but for checking a function
35
38
/// contract, e.g. the contract check is substituted for the target function
36
39
/// before the the verification runs.
37
40
ProofForContract ,
38
41
/// Attribute on a function with a contract that identifies the code
39
42
/// implementing the check for this contract.
40
43
CheckedWith ,
44
+ /// Internal attribute of the contracts implementation that identifies the
45
+ /// name of the function which was generated as the sound stub from the
46
+ /// contract of this function.
47
+ ReplacedWith ,
41
48
/// Attribute on a function that was auto-generated from expanding a
42
49
/// function contract.
43
50
IsContractGenerated ,
@@ -52,8 +59,10 @@ impl KaniAttributeKind {
52
59
| KaniAttributeKind :: Solver
53
60
| KaniAttributeKind :: Stub
54
61
| KaniAttributeKind :: ProofForContract
62
+ | KaniAttributeKind :: StubVerified
55
63
| KaniAttributeKind :: Unwind => true ,
56
64
KaniAttributeKind :: Unstable
65
+ | KaniAttributeKind :: ReplacedWith
57
66
| KaniAttributeKind :: CheckedWith
58
67
| KaniAttributeKind :: IsContractGenerated => false ,
59
68
}
@@ -134,6 +143,30 @@ impl<'tcx> KaniAttributes<'tcx> {
134
143
}
135
144
}
136
145
146
+ /// Parse, extract and resolve the target of `stub_verified(TARGET)`. The
147
+ /// returned `Symbol` and `DefId` are respectively the name and id of
148
+ /// `TARGET`. The `Span` is that of the contents of the attribute and used
149
+ /// for error reporting.
150
+ fn interpret_stub_verified_attribute (
151
+ & self ,
152
+ ) -> Vec < Result < ( Symbol , DefId , Span ) , ErrorGuaranteed > > {
153
+ self . map
154
+ . get ( & KaniAttributeKind :: StubVerified )
155
+ . map_or ( [ ] . as_slice ( ) , Vec :: as_slice)
156
+ . iter ( )
157
+ . map ( |attr| {
158
+ let name = expect_key_string_value ( self . tcx . sess , attr) ?;
159
+ let ok = self . resolve_sibling ( name. as_str ( ) ) . map_err ( |e| {
160
+ self . tcx . sess . span_err (
161
+ attr. span ,
162
+ format ! ( "Failed to resolve replacement function {}: {e}" , name. as_str( ) ) ,
163
+ )
164
+ } ) ?;
165
+ Ok ( ( name, ok, attr. span ) )
166
+ } )
167
+ . collect ( )
168
+ }
169
+
137
170
/// Parse and extract the `proof_for_contract(TARGET)` attribute. The
138
171
/// returned symbol and DefId are respectively the name and id of `TARGET`,
139
172
/// the span in the span for the attribute (contents).
@@ -142,30 +175,50 @@ impl<'tcx> KaniAttributes<'tcx> {
142
175
) -> Option < Result < ( Symbol , DefId , Span ) , ErrorGuaranteed > > {
143
176
self . expect_maybe_one ( KaniAttributeKind :: ProofForContract ) . map ( |target| {
144
177
let name = expect_key_string_value ( self . tcx . sess , target) ?;
145
- let resolved = resolve_fn (
146
- self . tcx ,
147
- self . tcx . parent_module_from_def_id ( self . item . expect_local ( ) ) . to_local_def_id ( ) ,
148
- name. as_str ( ) ,
149
- ) ;
150
- resolved. map ( |ok| ( name, ok, target. span ) ) . map_err ( |resolve_err| {
151
- self . tcx . sess . span_err (
152
- target. span ,
153
- format ! (
154
- "Failed to resolve replacement function {} because {resolve_err}" ,
155
- name. as_str( )
156
- ) ,
157
- )
158
- } )
178
+ self . resolve_sibling ( name. as_str ( ) ) . map ( |ok| ( name, ok, target. span ) ) . map_err (
179
+ |resolve_err| {
180
+ self . tcx . sess . span_err (
181
+ target. span ,
182
+ format ! (
183
+ "Failed to resolve checking function {} because {resolve_err}" ,
184
+ name. as_str( )
185
+ ) ,
186
+ )
187
+ } ,
188
+ )
159
189
} )
160
190
}
161
191
162
- /// Extract the name of the sibling function this contract is checked with
163
- /// (if any).
192
+ /// Extract the name of the sibling function this function's contract is
193
+ /// checked with (if any).
194
+ ///
195
+ /// `None` indicates this function does not use a contract, `Some(Err(_))`
196
+ /// indicates a contract does exist but an error occurred during resolution.
164
197
pub fn checked_with ( & self ) -> Option < Result < Symbol , ErrorGuaranteed > > {
165
198
self . expect_maybe_one ( KaniAttributeKind :: CheckedWith )
166
199
. map ( |target| expect_key_string_value ( self . tcx . sess , target) )
167
200
}
168
201
202
+ /// Extract the name of the sibling function this function's contract is
203
+ /// stubbed as (if any).
204
+ ///
205
+ /// `None` indicates this function does not use a contract, `Some(Err(_))`
206
+ /// indicates a contract does exist but an error occurred during resolution.
207
+ pub fn replaced_with ( & self ) -> Option < Result < Symbol , ErrorGuaranteed > > {
208
+ self . expect_maybe_one ( KaniAttributeKind :: ReplacedWith )
209
+ . map ( |target| expect_key_string_value ( self . tcx . sess , target) )
210
+ }
211
+
212
+ /// Resolve a function that is known to reside in the same module as the one
213
+ /// these attributes belong to (`self.item`).
214
+ fn resolve_sibling ( & self , path_str : & str ) -> Result < DefId , ResolveError < ' tcx > > {
215
+ resolve_fn (
216
+ self . tcx ,
217
+ self . tcx . parent_module_from_def_id ( self . item . expect_local ( ) ) . to_local_def_id ( ) ,
218
+ path_str,
219
+ )
220
+ }
221
+
169
222
/// Check that all attributes assigned to an item is valid.
170
223
/// Errors will be added to the session. Invoke self.tcx.sess.abort_if_errors() to terminate
171
224
/// the session and emit all errors found.
@@ -223,7 +276,10 @@ impl<'tcx> KaniAttributes<'tcx> {
223
276
}
224
277
expect_single ( self . tcx , kind, & attrs) ;
225
278
}
226
- KaniAttributeKind :: CheckedWith => {
279
+ KaniAttributeKind :: StubVerified => {
280
+ expect_single ( self . tcx , kind, & attrs) ;
281
+ }
282
+ KaniAttributeKind :: CheckedWith | KaniAttributeKind :: ReplacedWith => {
227
283
self . expect_maybe_one ( kind)
228
284
. map ( |attr| expect_key_string_value ( & self . tcx . sess , attr) ) ;
229
285
}
@@ -325,38 +381,87 @@ impl<'tcx> KaniAttributes<'tcx> {
325
381
harness. unwind_value = parse_unwind ( self . tcx , attributes[ 0 ] )
326
382
}
327
383
KaniAttributeKind :: Proof => harness. proof = true ,
328
- KaniAttributeKind :: ProofForContract => {
329
- harness. proof = true ;
330
- let Some ( Ok ( ( name, id, span) ) ) = self . interpret_the_for_contract_attribute ( )
331
- else {
332
- self . tcx . sess . span_err (
333
- self . tcx . def_span ( self . item ) ,
334
- format ! ( "Invalid `{}` attribute format" , kind. as_ref( ) ) ,
335
- ) ;
336
- return harness;
337
- } ;
338
- let Some ( Ok ( replacement_name) ) =
339
- KaniAttributes :: for_item ( self . tcx , id) . checked_with ( )
340
- else {
341
- self . tcx
342
- . sess
343
- . span_err ( span, "Target function for this check has no contract" ) ;
344
- return harness;
345
- } ;
346
- harness. stubs . push ( self . stub_for_relative_item ( name, replacement_name) ) ;
347
- }
384
+ KaniAttributeKind :: ProofForContract => self . handle_proof_for_contract ( & mut harness) ,
385
+ KaniAttributeKind :: StubVerified => self . handle_stub_verified ( & mut harness) ,
348
386
KaniAttributeKind :: Unstable => {
349
387
// Internal attribute which shouldn't exist here.
350
388
unreachable ! ( )
351
389
}
352
- KaniAttributeKind :: CheckedWith | KaniAttributeKind :: IsContractGenerated => {
353
- todo ! ( "Contract attributes are not supported on proofs" )
390
+ KaniAttributeKind :: CheckedWith
391
+ | KaniAttributeKind :: IsContractGenerated
392
+ | KaniAttributeKind :: ReplacedWith => {
393
+ self . tcx . sess . span_err ( self . tcx . def_span ( self . item ) , format ! ( "Contracts are not supported on harnesses. (Found the kani-internal contract attribute `{}`)" , kind. as_ref( ) ) ) ;
354
394
}
355
395
} ;
356
396
harness
357
397
} )
358
398
}
359
399
400
+ fn handle_proof_for_contract ( & self , harness : & mut HarnessAttributes ) {
401
+ let sess = self . tcx . sess ;
402
+ let ( name, id, span) = match self . interpret_the_for_contract_attribute ( ) {
403
+ None => unreachable ! (
404
+ "impossible, was asked to handle `proof_for_contract` but didn't find such an attribute."
405
+ ) ,
406
+ Some ( Err ( _) ) => return , // This error was already emitted
407
+ Some ( Ok ( values) ) => values,
408
+ } ;
409
+ let Some ( Ok ( replacement_name) ) = KaniAttributes :: for_item ( self . tcx , id) . checked_with ( )
410
+ else {
411
+ sess. struct_span_err (
412
+ span,
413
+ format ! (
414
+ "Failed to check contract: Function `{}` has no contract." ,
415
+ self . item_name( ) ,
416
+ ) ,
417
+ )
418
+ . span_note ( self . tcx . def_span ( id) , "Try adding a contract to this function." )
419
+ . emit ( ) ;
420
+ return ;
421
+ } ;
422
+ harness. stubs . push ( self . stub_for_relative_item ( name, replacement_name) ) ;
423
+ }
424
+
425
+ fn handle_stub_verified ( & self , harness : & mut HarnessAttributes ) {
426
+ let sess = self . tcx . sess ;
427
+ for contract in self . interpret_stub_verified_attribute ( ) {
428
+ let Ok ( ( name, def_id, span) ) = contract else {
429
+ // This error has already been emitted so we can ignore it now.
430
+ // Later the session will fail anyway so we can just
431
+ // optimistically forge on and try to find more errors.
432
+ continue ;
433
+ } ;
434
+ let replacement_name = match KaniAttributes :: for_item ( self . tcx , def_id) . replaced_with ( )
435
+ {
436
+ None => {
437
+ sess. struct_span_err (
438
+ span,
439
+ format ! (
440
+ "Failed to generate verified stub: Function `{}` has no contract." ,
441
+ self . item_name( ) ,
442
+ ) ,
443
+ )
444
+ . span_note (
445
+ self . tcx . def_span ( def_id) ,
446
+ format ! (
447
+ "Try adding a contract to this function or use the unsound `{}` attribute instead." ,
448
+ KaniAttributeKind :: Stub . as_ref( ) ,
449
+ )
450
+ )
451
+ . emit ( ) ;
452
+ continue ;
453
+ }
454
+ Some ( Ok ( replacement_name) ) => replacement_name,
455
+ Some ( Err ( _) ) => continue ,
456
+ } ;
457
+ harness. stubs . push ( self . stub_for_relative_item ( name, replacement_name) )
458
+ }
459
+ }
460
+
461
+ fn item_name ( & self ) -> Symbol {
462
+ self . tcx . item_name ( self . item )
463
+ }
464
+
360
465
/// Check that if this item is tagged with a proof_attribute, it is a valid harness.
361
466
fn check_proof_attribute ( & self , proof_attribute : & Attribute ) {
362
467
let span = proof_attribute. span ;
0 commit comments