@@ -13,138 +13,44 @@ mod derive;
13
13
// proc_macro::quote is nightly-only, so we'll cobble things together instead
14
14
use proc_macro:: TokenStream ;
15
15
use proc_macro_error:: proc_macro_error;
16
- #[ cfg( kani) ]
17
- use {
18
- quote:: quote,
19
- syn:: { parse_macro_input, ItemFn } ,
20
- } ;
21
16
22
- #[ cfg( any( not( kani) , feature = "concrete_playback" ) ) ]
23
- #[ proc_macro_attribute]
24
- pub fn proof ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
25
- // Leave the code intact, so it can be easily be edited in an IDE,
26
- // but outside Kani, this code is likely never called.
27
- let mut result = TokenStream :: new ( ) ;
28
-
29
- result. extend ( "#[allow(dead_code)]" . parse :: < TokenStream > ( ) . unwrap ( ) ) ;
30
- result. extend ( item) ;
31
- result
32
- // quote!(
33
- // #[allow(dead_code)]
34
- // $item
35
- // )
36
- }
17
+ #[ cfg( kani_sysroot) ]
18
+ use sysroot as attr_impl;
19
+
20
+ #[ cfg( not( kani_sysroot) ) ]
21
+ use regular as attr_impl;
37
22
38
23
/// Marks a Kani proof harness
39
24
///
40
25
/// For async harnesses, this will call [`kani::block_on`] (see its documentation for more information).
41
- #[ cfg( all( kani, not( feature = "concrete_playback" ) ) ) ]
42
26
#[ proc_macro_attribute]
43
27
pub fn proof ( attr : TokenStream , item : TokenStream ) -> TokenStream {
44
- let fn_item = parse_macro_input ! ( item as ItemFn ) ;
45
- let attrs = fn_item. attrs ;
46
- let vis = fn_item. vis ;
47
- let sig = fn_item. sig ;
48
- let body = fn_item. block ;
49
-
50
- let kani_attributes = quote ! (
51
- #[ allow( dead_code) ]
52
- #[ kanitool:: proof]
53
- ) ;
54
-
55
- assert ! ( attr. is_empty( ) , "#[kani::proof] does not take any arguments currently" ) ;
56
-
57
- if sig. asyncness . is_none ( ) {
58
- // Adds `#[kanitool::proof]` and other attributes
59
- quote ! (
60
- #kani_attributes
61
- #( #attrs) *
62
- #vis #sig #body
63
- )
64
- . into ( )
65
- } else {
66
- // For async functions, it translates to a synchronous function that calls `kani::block_on`.
67
- // Specifically, it translates
68
- // ```ignore
69
- // #[kani::async_proof]
70
- // #[attribute]
71
- // pub async fn harness() { ... }
72
- // ```
73
- // to
74
- // ```ignore
75
- // #[kani::proof]
76
- // #[attribute]
77
- // pub fn harness() {
78
- // async fn harness() { ... }
79
- // kani::block_on(harness())
80
- // }
81
- // ```
82
- assert ! (
83
- sig. inputs. is_empty( ) ,
84
- "#[kani::proof] cannot be applied to async functions that take inputs for now"
85
- ) ;
86
- let mut modified_sig = sig. clone ( ) ;
87
- modified_sig. asyncness = None ;
88
- let fn_name = & sig. ident ;
89
- quote ! (
90
- #kani_attributes
91
- #( #attrs) *
92
- #vis #modified_sig {
93
- #sig #body
94
- kani:: block_on( #fn_name( ) )
95
- }
96
- )
97
- . into ( )
98
- }
28
+ attr_impl:: proof ( attr, item)
99
29
}
100
30
101
- #[ cfg( any( not( kani) , feature = "concrete_playback" ) ) ]
102
- #[ proc_macro_attribute]
103
- pub fn should_panic ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
104
- // No-op in non-kani mode
105
- item
106
- }
107
-
108
- #[ cfg( all( kani, not( feature = "concrete_playback" ) ) ) ]
31
+ /// Specifies that a proof harness is expected to panic.**
32
+ ///
33
+ /// This attribute allows users to exercise *negative verification*.
34
+ /// It's analogous to how
35
+ /// [`#[should_panic]`](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html#testing-panics)
36
+ /// allows users to exercise [negative testing](https://en.wikipedia.org/wiki/Negative_testing)
37
+ /// for Rust unit tests.
38
+ ///
39
+ /// # Limitations
40
+ ///
41
+ /// The `#[kani::should_panic]` attribute verifies that there are one or more failed checks related to panics.
42
+ /// At the moment, it's not possible to pin it down to specific panics.
109
43
#[ proc_macro_attribute]
110
44
pub fn should_panic ( attr : TokenStream , item : TokenStream ) -> TokenStream {
111
- assert ! ( attr. is_empty( ) , "`#[kani::should_panic]` does not take any arguments currently" ) ;
112
- let mut result = TokenStream :: new ( ) ;
113
- let insert_string = "#[kanitool::should_panic]" ;
114
- result. extend ( insert_string. parse :: < TokenStream > ( ) . unwrap ( ) ) ;
115
-
116
- result. extend ( item) ;
117
- result
118
- }
119
-
120
- #[ cfg( any( not( kani) , feature = "concrete_playback" ) ) ]
121
- #[ proc_macro_attribute]
122
- pub fn unwind ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
123
- // When the config is not kani, we should leave the function alone
124
- item
45
+ attr_impl:: should_panic ( attr, item)
125
46
}
126
47
127
48
/// Set Loop unwind limit for proof harnesses
128
49
/// The attribute '#[kani::unwind(arg)]' can only be called alongside '#[kani::proof]'.
129
50
/// arg - Takes in a integer value (u32) that represents the unwind value for the harness.
130
- #[ cfg( all( kani, not( feature = "concrete_playback" ) ) ) ]
131
51
#[ proc_macro_attribute]
132
52
pub fn unwind ( attr : TokenStream , item : TokenStream ) -> TokenStream {
133
- let mut result = TokenStream :: new ( ) ;
134
-
135
- // Translate #[kani::unwind(arg)] to #[kanitool::unwind(arg)]
136
- let insert_string = "#[kanitool::unwind(" . to_owned ( ) + & attr. to_string ( ) + ")]" ;
137
- result. extend ( insert_string. parse :: < TokenStream > ( ) . unwrap ( ) ) ;
138
-
139
- result. extend ( item) ;
140
- result
141
- }
142
-
143
- #[ cfg( any( not( kani) , feature = "concrete_playback" ) ) ]
144
- #[ proc_macro_attribute]
145
- pub fn stub ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
146
- // When the config is not kani, we should leave the function alone
147
- item
53
+ attr_impl:: unwind ( attr, item)
148
54
}
149
55
150
56
/// Specify a function/method stub pair to use for proof harness
@@ -154,45 +60,154 @@ pub fn stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
154
60
/// # Arguments
155
61
/// * `original` - The function or method to replace, specified as a path.
156
62
/// * `replacement` - The function or method to use as a replacement, specified as a path.
157
- #[ cfg( all( kani, not( feature = "concrete_playback" ) ) ) ]
158
63
#[ proc_macro_attribute]
159
64
pub fn stub ( attr : TokenStream , item : TokenStream ) -> TokenStream {
160
- let mut result = TokenStream :: new ( ) ;
161
-
162
- // Translate #[kani::stub(original, replacement)] to #[kanitool::stub(original, replacement)]
163
- let insert_string = "#[kanitool::stub(" . to_owned ( ) + & attr. to_string ( ) + ")]" ;
164
- result. extend ( insert_string. parse :: < TokenStream > ( ) . unwrap ( ) ) ;
165
-
166
- result. extend ( item) ;
167
- result
168
- }
169
-
170
- #[ cfg( any( not( kani) , feature = "concrete_playback" ) ) ]
171
- #[ proc_macro_attribute]
172
- pub fn solver ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
173
- // No-op in non-kani mode
174
- item
65
+ attr_impl:: stub ( attr, item)
175
66
}
176
67
177
68
/// Select the SAT solver to use with CBMC for this harness
178
69
/// The attribute `#[kani::solver(arg)]` can only be used alongside `#[kani::proof]``
179
70
///
180
71
/// arg - name of solver, e.g. kissat
181
- #[ cfg( all( kani, not( feature = "concrete_playback" ) ) ) ]
182
72
#[ proc_macro_attribute]
183
73
pub fn solver ( attr : TokenStream , item : TokenStream ) -> TokenStream {
184
- let mut result = TokenStream :: new ( ) ;
185
- // Translate `#[kani::solver(arg)]` to `#[kanitool::solver(arg)]`
186
- let insert_string = "#[kanitool::solver(" . to_owned ( ) + & attr. to_string ( ) + ")]" ;
187
- result. extend ( insert_string. parse :: < TokenStream > ( ) . unwrap ( ) ) ;
188
-
189
- result. extend ( item) ;
190
- result
74
+ attr_impl:: solver ( attr, item)
191
75
}
192
-
193
76
/// Allow users to auto generate Arbitrary implementations by using `#[derive(Arbitrary)]` macro.
194
77
#[ proc_macro_error]
195
78
#[ proc_macro_derive( Arbitrary ) ]
196
79
pub fn derive_arbitrary ( item : TokenStream ) -> TokenStream {
197
80
derive:: expand_derive_arbitrary ( item)
198
81
}
82
+
83
+ /// This module implements Kani attributes in a way that only Kani's compiler can understand.
84
+ /// This code should only be activated when pre-building Kani's sysroot.
85
+ #[ cfg( kani_sysroot) ]
86
+ mod sysroot {
87
+ use super :: * ;
88
+
89
+ use {
90
+ quote:: { format_ident, quote} ,
91
+ syn:: { parse_macro_input, ItemFn } ,
92
+ } ;
93
+
94
+ /// Annotate the harness with a #[kanitool::<name>] with optional arguments.
95
+ macro_rules! kani_attribute {
96
+ ( $name: ident) => {
97
+ pub fn $name( attr: TokenStream , item: TokenStream ) -> TokenStream {
98
+ let args = proc_macro2:: TokenStream :: from( attr) ;
99
+ let fn_item = parse_macro_input!( item as ItemFn ) ;
100
+ let attribute = format_ident!( "{}" , stringify!( $name) ) ;
101
+ quote!(
102
+ #[ kanitool:: #attribute( #args) ]
103
+ #fn_item
104
+ ) . into( )
105
+ }
106
+ } ;
107
+ ( $name: ident, no_args) => {
108
+ pub fn $name( attr: TokenStream , item: TokenStream ) -> TokenStream {
109
+ assert!( attr. is_empty( ) , "`#[kani::{}]` does not take any arguments currently" , stringify!( $name) ) ;
110
+ let fn_item = parse_macro_input!( item as ItemFn ) ;
111
+ let attribute = format_ident!( "{}" , stringify!( $name) ) ;
112
+ quote!(
113
+ #[ kanitool:: #attribute]
114
+ #fn_item
115
+ ) . into( )
116
+ }
117
+ } ;
118
+ }
119
+
120
+ pub fn proof ( attr : TokenStream , item : TokenStream ) -> TokenStream {
121
+ let fn_item = parse_macro_input ! ( item as ItemFn ) ;
122
+ let attrs = fn_item. attrs ;
123
+ let vis = fn_item. vis ;
124
+ let sig = fn_item. sig ;
125
+ let body = fn_item. block ;
126
+
127
+ let kani_attributes = quote ! (
128
+ #[ allow( dead_code) ]
129
+ #[ kanitool:: proof]
130
+ ) ;
131
+
132
+ assert ! ( attr. is_empty( ) , "#[kani::proof] does not take any arguments currently" ) ;
133
+
134
+ if sig. asyncness . is_none ( ) {
135
+ // Adds `#[kanitool::proof]` and other attributes
136
+ quote ! (
137
+ #kani_attributes
138
+ #( #attrs) *
139
+ #vis #sig #body
140
+ )
141
+ . into ( )
142
+ } else {
143
+ // For async functions, it translates to a synchronous function that calls `kani::block_on`.
144
+ // Specifically, it translates
145
+ // ```ignore
146
+ // #[kani::async_proof]
147
+ // #[attribute]
148
+ // pub async fn harness() { ... }
149
+ // ```
150
+ // to
151
+ // ```ignore
152
+ // #[kani::proof]
153
+ // #[attribute]
154
+ // pub fn harness() {
155
+ // async fn harness() { ... }
156
+ // kani::block_on(harness())
157
+ // }
158
+ // ```
159
+ assert ! (
160
+ sig. inputs. is_empty( ) ,
161
+ "#[kani::proof] cannot be applied to async functions that take inputs for now"
162
+ ) ;
163
+ let mut modified_sig = sig. clone ( ) ;
164
+ modified_sig. asyncness = None ;
165
+ let fn_name = & sig. ident ;
166
+ quote ! (
167
+ #kani_attributes
168
+ #( #attrs) *
169
+ #vis #modified_sig {
170
+ #sig #body
171
+ kani:: block_on( #fn_name( ) )
172
+ }
173
+ )
174
+ . into ( )
175
+ }
176
+ }
177
+
178
+ kani_attribute ! ( should_panic, no_args) ;
179
+ kani_attribute ! ( unwind) ;
180
+ kani_attribute ! ( stub) ;
181
+ kani_attribute ! ( solver) ;
182
+ }
183
+
184
+ /// This module provides dummy implementations of Kani attributes which cannot be interpreted by
185
+ /// other tools such as MIRI and the regular rust compiler.
186
+ ///
187
+ /// This allow users to use code marked with Kani attributes, for example, during concrete playback.
188
+ #[ cfg( not( kani_sysroot) ) ]
189
+ mod regular {
190
+ use super :: * ;
191
+
192
+ /// Encode a noop proc macro which ignores the given attribute.
193
+ macro_rules! no_op {
194
+ ( $name: ident) => {
195
+ pub fn $name( _attr: TokenStream , item: TokenStream ) -> TokenStream {
196
+ item
197
+ }
198
+ } ;
199
+ }
200
+
201
+ /// Add #[allow(dead_code)] to a proof harness to avoid dead code warnings.
202
+ pub fn proof ( _attr : TokenStream , item : TokenStream ) -> TokenStream {
203
+ let mut result = TokenStream :: new ( ) ;
204
+ result. extend ( "#[allow(dead_code)]" . parse :: < TokenStream > ( ) . unwrap ( ) ) ;
205
+ result. extend ( item) ;
206
+ result
207
+ }
208
+
209
+ no_op ! ( should_panic) ;
210
+ no_op ! ( unwind) ;
211
+ no_op ! ( stub) ;
212
+ no_op ! ( solver) ;
213
+ }
0 commit comments