@@ -26,7 +26,7 @@ use once_cell::sync::Lazy;
26
26
use pathdiff:: diff_paths;
27
27
use regex:: Regex ;
28
28
use rustc_demangle:: demangle;
29
- use serde:: Deserialize ;
29
+ use serde:: { Deserialize , Deserializer } ;
30
30
use std:: {
31
31
collections:: HashMap ,
32
32
env,
@@ -215,14 +215,123 @@ impl ParserItem {
215
215
#[ derive( Clone , Debug , Deserialize ) ]
216
216
pub struct Property {
217
217
pub description : String ,
218
- pub property : String ,
218
+ #[ serde( rename = "property" ) ]
219
+ pub property_id : PropertyId ,
219
220
#[ serde( rename = "sourceLocation" ) ]
220
221
pub source_location : SourceLocation ,
221
222
pub status : CheckStatus ,
222
223
pub reach : Option < CheckStatus > ,
223
224
pub trace : Option < Vec < TraceItem > > ,
224
225
}
225
226
227
+ #[ derive( Clone , Debug ) ]
228
+ pub struct PropertyId {
229
+ pub fn_name : Option < String > ,
230
+ pub class : String ,
231
+ pub id : u32 ,
232
+ }
233
+
234
+ impl Property {
235
+ pub fn property_class ( & self ) -> String {
236
+ self . property_id . class . clone ( )
237
+ }
238
+
239
+ pub fn property_name ( & self ) -> Result < String > {
240
+ use std:: fmt:: Write ;
241
+ let mut fmt_str = String :: new ( ) ;
242
+ let prop_id = & self . property_id ;
243
+ if let Some ( name) = & prop_id. fn_name {
244
+ write ! ( & mut fmt_str, "{name}." ) ?;
245
+ }
246
+ let class = & prop_id. class ;
247
+ let id = prop_id. id ;
248
+ write ! ( & mut fmt_str, "{class}." ) ?;
249
+ write ! ( & mut fmt_str, "{id}" ) ?;
250
+ Ok ( fmt_str)
251
+ }
252
+
253
+ pub fn has_property_class_format ( string : & str ) -> bool {
254
+ string == "NaN" || string. chars ( ) . all ( |c| c. is_ascii_lowercase ( ) || c == '_' || c == '-' )
255
+ }
256
+ }
257
+
258
+ impl < ' de > serde:: Deserialize < ' de > for PropertyId {
259
+ /// Gets all property attributes from the property ID.
260
+ ///
261
+ /// In general, property IDs have the format `<function>.<class>.<counter>`.
262
+ ///
263
+ /// However, there are cases where we only get two attributes:
264
+ /// * `<class>.<counter>` (the function is a CBMC builtin)
265
+ /// * `<function>.<counter>` (missing function definition)
266
+ ///
267
+ /// In these cases, we try to determine if the attribute is a function or not
268
+ /// based on its characters (we assume property classes are a combination
269
+ /// of lowercase letters and the characters `_` and `-`). But this is not completely
270
+ /// reliable. CBMC should be able to provide these attributes as separate fields
271
+ /// in the JSON output: <https://github.com/diffblue/cbmc/issues/7069>
272
+ fn deserialize < D > ( d : D ) -> Result < Self , D :: Error >
273
+ where
274
+ D : Deserializer < ' de > ,
275
+ {
276
+ let id_str = String :: deserialize ( d) ?;
277
+
278
+ // Handle a special case that doesn't respect any format. It appears at
279
+ // least in the test `tests/expected/dynamic-error-trait/main.rs` and its
280
+ // description is is: "recursion unwinding assertion"
281
+ if id_str == ".recursion" {
282
+ return Ok ( PropertyId { fn_name : None , class : "recursion" . to_owned ( ) , id : 1 } ) ;
283
+ } ;
284
+
285
+ // Split the property name into three from the end, using `.` as the separator
286
+ let property_attributes: Vec < & str > = id_str. rsplitn ( 3 , '.' ) . collect ( ) ;
287
+ let attributes_tuple = match property_attributes. len ( ) {
288
+ // The general case, where we get all the attributes
289
+ 3 => {
290
+ // Since mangled function names may contain `.`, we check if
291
+ // `property_attributes[1]` has the class format. If it doesn't,
292
+ // it means we've split a function name, so we rebuild it and
293
+ // demangle it.
294
+ if Property :: has_property_class_format ( property_attributes[ 1 ] ) {
295
+ let name = format ! ( "{:#}" , demangle( property_attributes[ 2 ] ) ) ;
296
+ ( Some ( name) , property_attributes[ 1 ] , property_attributes[ 0 ] )
297
+ } else {
298
+ let full_name =
299
+ format ! ( "{}.{}" , property_attributes[ 2 ] , property_attributes[ 1 ] ) ;
300
+ let name = format ! ( "{:#}" , demangle( & full_name) ) ;
301
+ ( Some ( name) , "missing_definition" , property_attributes[ 0 ] )
302
+ }
303
+ }
304
+ 2 => {
305
+ // The case where `property_attributes[1]` could be a function
306
+ // or a class. If it has the class format, then it's likely a
307
+ // class (functions are usually mangled names which contain many
308
+ // other symbols).
309
+ if Property :: has_property_class_format ( property_attributes[ 1 ] ) {
310
+ ( None , property_attributes[ 1 ] , property_attributes[ 0 ] )
311
+ } else {
312
+ let name = format ! ( "{:#}" , demangle( property_attributes[ 1 ] ) ) ;
313
+ ( Some ( name) , "missing_definition" , property_attributes[ 0 ] )
314
+ }
315
+ }
316
+ // The case we don't expect. It's best to fail with an informative message.
317
+ _ => unreachable ! ( "Found property which doesn't have 2 or 3 attributes" ) ,
318
+ } ;
319
+ // Do more sanity checks, just in case.
320
+ assert ! (
321
+ attributes_tuple. 2 . chars( ) . all( |c| c. is_ascii_digit( ) ) ,
322
+ "Found property counter that doesn't match number format"
323
+ ) ;
324
+ // Return tuple after converting counter from string into number.
325
+ // Safe to do because we've checked the format earlier.
326
+ let class = String :: from ( attributes_tuple. 1 ) ;
327
+ Ok ( PropertyId {
328
+ fn_name : attributes_tuple. 0 ,
329
+ class,
330
+ id : attributes_tuple. 2 . parse ( ) . unwrap ( ) ,
331
+ } )
332
+ }
333
+ }
334
+
226
335
/// Struct that represents a source location.
227
336
///
228
337
/// Source locations may be completely empty, which is why
@@ -625,7 +734,7 @@ fn format_result(properties: &Vec<Property>, show_checks: bool) -> String {
625
734
}
626
735
627
736
for prop in properties {
628
- let name = & prop. property ;
737
+ let name = prop. property_name ( ) . unwrap ( ) ;
629
738
let status = & prop. status ;
630
739
let description = & prop. description ;
631
740
let location = & prop. source_location ;
@@ -848,9 +957,9 @@ fn modify_undefined_function_checks(mut properties: Vec<Property>) -> (Vec<Prope
848
957
/// temporary variable in their descriptions.
849
958
fn get_readable_description ( property : & Property ) -> String {
850
959
let original = property. description . clone ( ) ;
851
- let class_id = extract_property_class ( property) . unwrap ( ) ;
960
+ let class_id = property. property_class ( ) ;
852
961
853
- let description_alternatives = CBMC_ALT_DESCRIPTIONS . get ( class_id) ;
962
+ let description_alternatives = CBMC_ALT_DESCRIPTIONS . get ( & class_id as & str ) ;
854
963
if let Some ( alt_descriptions) = description_alternatives {
855
964
for ( desc_to_match, opt_desc_to_replace) in alt_descriptions {
856
965
if original. contains ( desc_to_match) {
@@ -914,14 +1023,6 @@ fn remove_check_ids_from_description(mut properties: Vec<Property>) -> Vec<Prope
914
1023
properties
915
1024
}
916
1025
917
- /// Extracts the property class from the property string.
918
- ///
919
- /// Property strings have the format `([<function>.]<property_class_id>.<counter>)`
920
- pub fn extract_property_class ( property : & Property ) -> Option < & str > {
921
- let property_class: Vec < & str > = property. property . rsplitn ( 3 , '.' ) . collect ( ) ;
922
- if property_class. len ( ) > 1 { Some ( property_class[ 1 ] ) } else { None }
923
- }
924
-
925
1026
/// Given a description, this splits properties into two groups:
926
1027
/// 1. Properties that don't contain the description
927
1028
/// 2. Properties that contain the description
@@ -948,8 +1049,7 @@ fn filter_sanity_checks(properties: Vec<Property>) -> Vec<Property> {
948
1049
properties
949
1050
. into_iter ( )
950
1051
. filter ( |prop| {
951
- !( extract_property_class ( prop) . unwrap ( ) == "sanity_check"
952
- && prop. status == CheckStatus :: Success )
1052
+ !( prop. property_class ( ) == "sanity_check" && prop. status == CheckStatus :: Success )
953
1053
} )
954
1054
. collect ( )
955
1055
}
@@ -962,8 +1062,8 @@ fn filter_ptr_checks(properties: Vec<Property>) -> Vec<Property> {
962
1062
properties
963
1063
. into_iter ( )
964
1064
. filter ( |prop| {
965
- !extract_property_class ( prop) . unwrap ( ) . contains ( "pointer_arithmetic" )
966
- && !extract_property_class ( prop) . unwrap ( ) . contains ( "pointer_primitives" )
1065
+ !prop. property_class ( ) . contains ( "pointer_arithmetic" )
1066
+ && !prop. property_class ( ) . contains ( "pointer_primitives" )
967
1067
} )
968
1068
. collect ( )
969
1069
}
@@ -1032,3 +1132,137 @@ fn determine_verification_result(properties: &[Property]) -> bool {
1032
1132
properties. iter ( ) . filter ( |prop| prop. status == CheckStatus :: Failure ) . count ( ) ;
1033
1133
number_failed_properties == 0
1034
1134
}
1135
+
1136
+ #[ cfg( test) ]
1137
+ mod tests {
1138
+ use super :: * ;
1139
+
1140
+ #[ test]
1141
+ fn check_property_id_deserialization_general ( ) {
1142
+ let prop_id_string = "\" alloc::raw_vec::RawVec::<u8>::allocate_in.sanity_check.1\" " ;
1143
+ let prop_id_result: Result < PropertyId , serde_json:: Error > =
1144
+ serde_json:: from_str ( prop_id_string) ;
1145
+ let prop_id = prop_id_result. unwrap ( ) ;
1146
+ assert_eq ! (
1147
+ prop_id. fn_name,
1148
+ Some ( String :: from( "alloc::raw_vec::RawVec::<u8>::allocate_in" ) )
1149
+ ) ;
1150
+ assert_eq ! ( prop_id. class, String :: from( "sanity_check" ) ) ;
1151
+ assert_eq ! ( prop_id. id, 1 ) ;
1152
+
1153
+ let dummy_prop = Property {
1154
+ description : "" . to_string ( ) ,
1155
+ property_id : prop_id,
1156
+ source_location : SourceLocation {
1157
+ function : None ,
1158
+ file : None ,
1159
+ column : None ,
1160
+ line : None ,
1161
+ } ,
1162
+ status : CheckStatus :: Success ,
1163
+ reach : None ,
1164
+ trace : None ,
1165
+ } ;
1166
+ assert_eq ! (
1167
+ dummy_prop. property_name( ) . unwrap( ) ,
1168
+ prop_id_string[ 1 ..prop_id_string. len( ) - 1 ]
1169
+ ) ;
1170
+ }
1171
+
1172
+ #[ test]
1173
+ fn check_property_id_deserialization_only_name ( ) {
1174
+ let prop_id_string = "\" alloc::raw_vec::RawVec::<u8>::allocate_in.1\" " ;
1175
+ let prop_id_result: Result < PropertyId , serde_json:: Error > =
1176
+ serde_json:: from_str ( prop_id_string) ;
1177
+ dbg ! ( & prop_id_result) ;
1178
+ let prop_id = prop_id_result. unwrap ( ) ;
1179
+ assert_eq ! (
1180
+ prop_id. fn_name,
1181
+ Some ( String :: from( "alloc::raw_vec::RawVec::<u8>::allocate_in" ) )
1182
+ ) ;
1183
+ assert_eq ! ( prop_id. class, "missing_definition" ) ;
1184
+ assert_eq ! ( prop_id. id, 1 ) ;
1185
+
1186
+ let dummy_prop = Property {
1187
+ description : "" . to_string ( ) ,
1188
+ property_id : prop_id,
1189
+ source_location : SourceLocation {
1190
+ function : None ,
1191
+ file : None ,
1192
+ column : None ,
1193
+ line : None ,
1194
+ } ,
1195
+ status : CheckStatus :: Success ,
1196
+ reach : None ,
1197
+ trace : None ,
1198
+ } ;
1199
+ assert_eq ! (
1200
+ dummy_prop. property_name( ) . unwrap( ) ,
1201
+ "alloc::raw_vec::RawVec::<u8>::allocate_in.missing_definition.1"
1202
+ ) ;
1203
+ }
1204
+
1205
+ #[ test]
1206
+ fn check_property_id_deserialization_only_class ( ) {
1207
+ let prop_id_string = "\" assertion.1\" " ;
1208
+ let prop_id_result: Result < PropertyId , serde_json:: Error > =
1209
+ serde_json:: from_str ( prop_id_string) ;
1210
+ let prop_id = prop_id_result. unwrap ( ) ;
1211
+ assert_eq ! ( prop_id. fn_name, None ) ;
1212
+ assert_eq ! ( prop_id. class, String :: from( "assertion" ) ) ;
1213
+ assert_eq ! ( prop_id. id, 1 ) ;
1214
+
1215
+ let dummy_prop = Property {
1216
+ description : "" . to_string ( ) ,
1217
+ property_id : prop_id,
1218
+ source_location : SourceLocation {
1219
+ function : None ,
1220
+ file : None ,
1221
+ column : None ,
1222
+ line : None ,
1223
+ } ,
1224
+ status : CheckStatus :: Success ,
1225
+ reach : None ,
1226
+ trace : None ,
1227
+ } ;
1228
+ assert_eq ! (
1229
+ dummy_prop. property_name( ) . unwrap( ) ,
1230
+ prop_id_string[ 1 ..prop_id_string. len( ) - 1 ]
1231
+ ) ;
1232
+ }
1233
+
1234
+ #[ test]
1235
+ fn check_property_id_deserialization_special ( ) {
1236
+ let prop_id_string = "\" .recursion\" " ;
1237
+ let prop_id_result: Result < PropertyId , serde_json:: Error > =
1238
+ serde_json:: from_str ( prop_id_string) ;
1239
+ let prop_id = prop_id_result. unwrap ( ) ;
1240
+ assert_eq ! ( prop_id. fn_name, None ) ;
1241
+ assert_eq ! ( prop_id. class, String :: from( "recursion" ) ) ;
1242
+ assert_eq ! ( prop_id. id, 1 ) ;
1243
+
1244
+ let dummy_prop = Property {
1245
+ description : "" . to_string ( ) ,
1246
+ property_id : prop_id,
1247
+ source_location : SourceLocation {
1248
+ function : None ,
1249
+ file : None ,
1250
+ column : None ,
1251
+ line : None ,
1252
+ } ,
1253
+ status : CheckStatus :: Success ,
1254
+ reach : None ,
1255
+ trace : None ,
1256
+ } ;
1257
+ assert_eq ! ( dummy_prop. property_name( ) . unwrap( ) , "recursion.1" ) ;
1258
+ }
1259
+
1260
+ #[ test]
1261
+ #[ should_panic]
1262
+ fn check_property_id_deserialization_panics ( ) {
1263
+ let prop_id_string = "\" not_a_property_ID\" " ;
1264
+ let prop_id_result: Result < PropertyId , serde_json:: Error > =
1265
+ serde_json:: from_str ( prop_id_string) ;
1266
+ let _prop_id = prop_id_result. unwrap ( ) ;
1267
+ }
1268
+ }
0 commit comments