Skip to content

Commit 4fec6b2

Browse files
ArjanSeijsmeta-codesync[bot]
authored andcommitted
[rust] [2/2] Support the elaborated box reprenstation for precise-drops (#2036)
Summary: and some partial support for modeling drops when retrieving drop glue with --desugar-drops. We do some abstraction over Box, and NonNull since the compiler has some special handling for how it treats Box and NonNull and their ABI are equivalent. Merge after #2035 Pull Request resolved: #2036 Reviewed By: martintrojer Differential Revision: D105306414 Pulled By: dulmarod fbshipit-source-id: 053875db8b4e14478e7af10efde68b783bb95e22
1 parent dc6fa54 commit 4fec6b2

7 files changed

Lines changed: 219 additions & 37 deletions

File tree

infer/lib/rust/models.sil

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@
3535
// For example the following code:
3636
// let b = Box::new(10);
3737
// let value = *b;
38-
// By default boxes are handled as pointers by the use of the Deref Trait. ULLBC:
38+
// (1) By default boxes are handled as pointers by the use of the Deref Trait. ULLBC:
3939
// `value = copy (*(b@1))`;
40-
// When using --mir-elaborated boxes are treated as a pointer to Unqiue and then derefenced :
40+
// (2) When using --mir-elaborated or --precise-drops boxes are treated as a pointer to Unqiue and then derefenced :
4141
// @3 := transmute<NonNull<i32>, *const i32>(copy ((*(b@1)).0));
4242
// value@2 := copy (*(@3));
43-
// When using --mir-elaborated --raw-boxes boxes are handled as:
43+
// (3) When using --mir-elaborated --raw-boxes boxes are handled as:
4444
// @3 := transmute<NonNull<i32>, *const i32>(copy (((b@1).0).0));
4545
// value@2 := copy (*(@3));
46-
// At some point we want to move to model the --mir-elaborated version since this allows us the get drop-elaboration to
47-
// get precise information on when a a value is freed.
46+
// With the default charon arguments defined Rust.ml we model version 2.
47+
// We do special handling for Box, Unqiue, and NonNull in RustMir2Textual.ml to treat them as pointers.
4848
define __sil_boxnew(o : int): *int {
4949
local ptr : *int
5050
#entry:
@@ -59,4 +59,15 @@ define __sil_boxnew(o : int): *int {
5959
#invalid:
6060
unreachable
6161

62+
}
63+
64+
// The function for dropping a box containg a primitive type
65+
// when using the --desugar-drops.
66+
// This is a model for the function:
67+
// alloc::boxed::Box::<i32>::drop_in_place(&Box)
68+
//
69+
define __sil_boxdrop(o : **int): void {
70+
#entry:
71+
n1 = __sil_free([[&o:**int]:*int])
72+
ret null
6273
}

infer/src/rust/RustMir2Textual.ml

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,28 @@ let mk_typename_from_type_decl crate (type_decl : Charon.Generated_Types.type_de
108108
Textual.TypeName.of_string name
109109

110110

111+
(* Model for box drops when using --desugar-drops *)
112+
let is_box_primitive_drop_in_place crate fun_decl_id =
113+
let decl = fun_map_find_id crate fun_decl_id in
114+
match (decl.item_meta.name, decl.signature.inputs) with
115+
| ( PeIdent ("alloc", _)
116+
:: PeIdent ("boxed", _)
117+
:: PeIdent ("Box", _)
118+
:: _
119+
:: PeIdent ("drop_in_place", _)
120+
:: _
121+
, TRawPtr (TAdt {id= TBuiltin TBox; generics= {types= TLiteral _ :: _}}, _) :: _ ) ->
122+
true
123+
| _, _ ->
124+
false
125+
126+
111127
let fun_name_from_fun_operand (crate : Charon.UllbcAst.crate)
112128
(operand : Charon.Generated_GAst.fn_operand) : Textual.QualifiedProcName.t =
113129
match operand with
130+
| FnOpRegular {kind= FunId (FRegular fun_decl_id)}
131+
when is_box_primitive_drop_in_place crate fun_decl_id ->
132+
Textual.ProcDecl.boxdrop_name
114133
| FnOpRegular {kind= FunId (FRegular fun_decl_id)} ->
115134
let decl = fun_map_find_id crate fun_decl_id in
116135
mk_qualified_proc_name crate decl.item_meta
@@ -225,6 +244,33 @@ let proc_name_from_binop (op : Charon.Generated_Expressions.binop) (typ : Textua
225244
(Textual.ProcDecl.of_binop bin_op, typ)
226245

227246

247+
(* Model Unqiue<T> and NonNull<T> as *T *)
248+
let is_pointer_type crate (name : Charon.Generated_Types.name) =
249+
let name = name |> List.map ~f:(name_of_path_element crate) in
250+
match name with
251+
| "core" :: "ptr" :: "non_null" :: "NonNull" :: _ ->
252+
true
253+
| "core" :: "ptr" :: "unique" :: "Unique" :: _ ->
254+
true
255+
| _ ->
256+
false
257+
258+
259+
let is_ty_decl_pointer_type crate type_decl_id =
260+
let type_decl = type_decl_map_find_id crate type_decl_id in
261+
is_pointer_type crate type_decl.item_meta.name
262+
263+
264+
let is_ty_pointer_type crate (ty : Charon.Generated_Types.ty) =
265+
match ty with
266+
| TAdt {id= TAdtId type_decl_id} ->
267+
is_ty_decl_pointer_type crate type_decl_id
268+
| TAdt {id= TBuiltin TBox} ->
269+
true
270+
| _ ->
271+
false
272+
273+
228274
let rec mk_struct_args crate (types : Charon.Generated_Types.ty list) =
229275
List.map types ~f:(fun typ ->
230276
Textual.TypeName.of_string (Format.asprintf "%a" Textual.Typ.pp (ty_to_textual_typ crate typ)) )
@@ -239,13 +285,25 @@ and mk_tuple_struct_typ crate type_decl_ref =
239285
Textual.Typ.Struct (mk_tuple_type_name crate type_decl_ref)
240286

241287

288+
and mk_ptr_from_generics_types crate (types : Charon.Generated_Types.ty list) =
289+
match types with
290+
| typ :: _ ->
291+
Textual.Typ.mk_ptr (ty_to_textual_typ crate typ)
292+
| _ ->
293+
Textual.Typ.mk_ptr Textual.Typ.Void
294+
295+
242296
and adt_ty_to_textual_typ crate (type_decl_ref : Charon.Generated_Types.type_decl_ref) :
243297
Textual.Typ.t =
244298
(* TODO: Implement other adt types *)
245299
match type_decl_ref.id with
246300
| TTuple ->
247301
if List.is_empty type_decl_ref.generics.types then Textual.Typ.Void
248302
else mk_tuple_struct_typ crate type_decl_ref.generics.types
303+
| TAdtId type_decl_id
304+
when let type_decl = type_decl_map_find_id crate type_decl_id in
305+
is_pointer_type crate type_decl.item_meta.name ->
306+
mk_ptr_from_generics_types crate type_decl_ref.generics.types
249307
| TAdtId type_decl_id -> (
250308
let type_decl = type_decl_map_find_id crate type_decl_id in
251309
match type_decl.kind with
@@ -257,12 +315,8 @@ and adt_ty_to_textual_typ crate (type_decl_ref : Charon.Generated_Types.type_dec
257315
Textual.Typ.Struct type_name
258316
| _ ->
259317
Textual.Typ.Void )
260-
| TBuiltin TBox -> (
261-
match type_decl_ref.generics.types with
262-
| typ :: _ ->
263-
Textual.Typ.mk_ptr (ty_to_textual_typ crate typ)
264-
| _ ->
265-
Textual.Typ.mk_ptr Textual.Typ.Void )
318+
| TBuiltin TBox ->
319+
mk_ptr_from_generics_types crate type_decl_ref.generics.types
266320
| TBuiltin TStr ->
267321
Textual.Typ.Struct Textual.TypeName.sil_string
268322

@@ -377,10 +431,31 @@ let rec mk_exp_from_place ~loc (crate : Charon.UllbcAst.crate) (place_map : plac
377431
| PlaceLocal var_id ->
378432
let exp = Textual.Exp.Lvar (place_map_find_id place_map var_id) in
379433
exp
434+
(* This models the --precise-drop versions of Box, Unqiue and NonNull as pointers since the
435+
compiler has some special handling for how it treats Box and NonNull and their ABI are equivalent.
436+
For example:
437+
let b = Box::new(10);
438+
let value = *b;
439+
Is lowered in mir to
440+
b_1 = @BoxNew<i32>
441+
_3 := transmute<NonNull<i32>, *const i32>(copy (( *(b_1)).0));
442+
value_2 := copy ( *(_3));
443+
There are two abstractions here:
444+
(1) *b_1 is the derefence of the box struct, box that access the underlying unique.
445+
The .0 is then a regular field acces of the unqiue that gets The NonNull.
446+
(2) The transmute treats the NonNull struct the same is it's field.
447+
This skips the ( *(b_1)) step.
448+
*)
449+
| PlaceProjection (({ty= TAdt {id= TBuiltin TBox}} as projection_place), Deref)
450+
when is_ty_pointer_type crate place.ty ->
451+
mk_exp_from_place ~loc crate place_map projection_place
380452
| PlaceProjection (projection_place, Deref) ->
381453
let proj_typ = ty_to_textual_typ crate projection_place.ty in
382454
let exp = mk_exp_from_place ~loc crate place_map projection_place in
383455
Textual.Exp.Load {exp; typ= Some proj_typ}
456+
(* This skips the NonNull<T>.0 and Unqiue<T>.0 step *)
457+
| PlaceProjection (projection_place, Field _) when is_ty_pointer_type crate projection_place.ty ->
458+
mk_exp_from_place ~loc crate place_map projection_place
384459
| PlaceProjection (projection_place, Field (ProjAdt (type_decl_id, variant), field_id)) ->
385460
let exp = mk_exp_from_place ~loc crate place_map projection_place in
386461
let type_decl = type_decl_map_find_id crate type_decl_id in
@@ -630,21 +705,26 @@ let mk_terminator (crate : Charon.UllbcAst.crate) (idx : int) (place_map : place
630705
(* A drop frees the memory of a value once it goes out of scope.
631706
The following implementation is a simplified model for drops
632707
of boxes created by Box::new() of types using the global allocator.
708+
633709
https://doc.rust-lang.org/1.93.1/alloc/alloc/struct.Global.html
634710
https://rustc-dev-guide.rust-lang.org/mir/drop-elaboration.html
635711
https://doc.rust-lang.org/1.93.1/alloc/alloc/trait.GlobalAlloc.html#tymethod.dealloc
636712
*)
637-
| Drop (Precise, ({ty= TAdt {id= TBuiltin TBox}} as place), _trait_ref, target, on_unwind) ->
638-
let exp, _ = mk_exp_from_place_load ~loc crate place_map place in
639-
let qualified_free_name = Textual.ProcDecl.free_name in
640-
let free_call = Textual.Exp.call_non_virtual qualified_free_name [exp] in
641-
let free_instr = Textual.Instr.Let {id= None; exp= free_call; loc} in
642-
let on_unwind = mk_label (Charon.Generated_UllbcAst.BlockId.to_int on_unwind) in
643-
let target = mk_jump target in
644-
([], [free_instr], target, [on_unwind])
713+
| Drop (Precise, place, _trait_ref, target, on_unwind) -> (
714+
match place.ty with
715+
| TAdt {id= TBuiltin TBox; generics= {types= TLiteral _ :: _}} ->
716+
let exp, _ = mk_exp_from_place_load ~loc crate place_map place in
717+
let qualified_free_name = Textual.ProcDecl.free_name in
718+
let free_call = Textual.Exp.call_non_virtual qualified_free_name [exp] in
719+
let free_instr = Textual.Instr.Let {id= None; exp= free_call; loc} in
720+
let on_unwind = mk_label (Charon.Generated_UllbcAst.BlockId.to_int on_unwind) in
721+
let target = mk_jump target in
722+
([], [free_instr], target, [on_unwind])
723+
| ty ->
724+
L.die UserError "[ERROR] Unsupported drop for type: @. > %a @." Charon.Types.pp_ty ty )
645725
| _ ->
646-
L.die UserError "[ERROR] Unsupported terminator: %a " Charon.Generated_UllbcAst.pp_terminator
647-
terminator
726+
L.die UserError "[ERROR] Unsupported terminator: @. > %a @."
727+
Charon.Generated_UllbcAst.pp_terminator terminator
648728

649729

650730
let mk_field_store_instr_from_rvalue ~loc crate lexp enclosing_class place_map field_id
@@ -680,16 +760,7 @@ let mk_instr crate (place_map : place_map_ty) (statement : Charon.Generated_Ullb
680760
| Assign (lhs, Aggregate (AggregatedAdt ({id= TAdtId type_decl_id}, None, None), ops)) ->
681761
let lexp = mk_exp_from_place ~loc crate place_map lhs in
682762
let type_decl = type_decl_map_find_id crate type_decl_id in
683-
let fields =
684-
match type_decl.kind with
685-
| Struct fields ->
686-
fields
687-
| _ ->
688-
L.die UserError
689-
"[ERROR] Should not be reachable: Encountered none struct kind even tough variant \
690-
and field_id are None. @. > %a @."
691-
Charon.Generated_Types.pp_type_decl_kind type_decl.kind
692-
in
763+
let fields = Charon.TypesUtils.type_decl_get_fields type_decl None in
693764
let enclosing_class = mk_typename_from_type_decl crate type_decl None in
694765
mk_field_store_instrs_from_rvalues ~loc crate lexp enclosing_class place_map ops fields
695766
(* Enum Variant *)
@@ -749,6 +820,10 @@ let mk_instr crate (place_map : place_map_ty) (statement : Charon.Generated_Ullb
749820
[]
750821
| StorageLive _ ->
751822
[]
823+
| PlaceMention place ->
824+
let exp, _ = mk_exp_from_place_load ~loc crate place_map place in
825+
let instr = Textual.Instr.Let {id= None; exp; loc} in
826+
[instr]
752827
| s ->
753828
L.die UserError "[ERROR] Unsupported statement: @. > %a @."
754829
Charon.Generated_UllbcAst.pp_statement_kind s

infer/src/rust/unit/RustMir2TextualTestPointers.ml

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,6 @@ let%expect_test "box_memoryleak" =
436436
|}]
437437

438438

439-
(* TODO: Support for precise box drops will be added in the next commit *)
440439
let%expect_test "box_use_after_free" =
441440
let source =
442441
{|
@@ -458,4 +457,57 @@ let%expect_test "box_use_after_free" =
458457
--start-from=crate::main"
459458
source ;
460459
[%expect
461-
{| Test failed: expression [&x_2:*int] has type *int, while a pointer of struct type was expected |}]
460+
{|
461+
.source_language = "Rust"
462+
463+
type alloc::alloc::Global = {}
464+
465+
type core::marker::PhantomData::<i32> = {}
466+
467+
type core::ptr::non_null::NonNull::<i32> = {pointer: *int}
468+
469+
type core::ptr::unique::Unique::<i32> = {pointer: *void; _marker: core::marker::PhantomData::<i32>}
470+
471+
define dummy::main() : void {
472+
local var_0: void, ptr_1: *int, x_2: *int, var_3: *int, var_4: *int, ub_5: int, var_6: *int
473+
#node_0:
474+
store &var_0 <- null:void
475+
n0 = __sil_boxnew(50)
476+
store &x_2 <- n0:*int
477+
jmp node_2
478+
.handlers node_1
479+
480+
#node_1:
481+
throw "UnwindResume"
482+
483+
#node_2:
484+
n1:*void = load &x_2
485+
store &var_6 <- __sil_cast(<*int>, n1):*int
486+
n2:*int = load &var_6
487+
store &var_4 <- n2:*int
488+
n3:*int = load &var_4
489+
store &var_3 <- n3:*int
490+
n4:*int = load &var_3
491+
store &ptr_1 <- n4:*int
492+
n5:*int = load &x_2
493+
n6 = __sil_free(n5)
494+
jmp node_3
495+
.handlers node_4
496+
497+
#node_3:
498+
n7:*int = load &ptr_1
499+
n8:int = load n7
500+
store &ub_5 <- n8:int
501+
store &var_0 <- null:void
502+
n9:void = load &var_0
503+
ret n9
504+
505+
#node_4:
506+
throw "UnwindResume"
507+
508+
}
509+
510+
declare alloc::alloc::Global::{TraitImpl@1}::drop_in_place(*alloc::alloc::Global) : void
511+
512+
declare alloc::boxed::Box::{TraitImpl@2}::drop_in_place::<i32, alloc::alloc::Global>(**int) : void
513+
|}]

infer/src/textual/Textual.ml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,9 @@ let builtin_get_lazy_class = "__sil_get_lazy_class"
208208

209209
let builtin_instanceof = "__sil_instanceof"
210210

211-
let builin_boxnew = "__sil_boxnew"
211+
let builtin_boxnew = "__sil_boxnew"
212+
213+
let builtin_boxdrop = "__sil_boxdrop"
212214

213215
module BaseTypeName : sig
214216
include NAME
@@ -874,7 +876,9 @@ module ProcDecl = struct
874876

875877
let instanceof_name = make_toplevel_name builtin_instanceof Location.Unknown
876878

877-
let boxnew_name = make_toplevel_name builin_boxnew Location.Unknown
879+
let boxnew_name = make_toplevel_name builtin_boxnew Location.Unknown
880+
881+
let boxdrop_name = make_toplevel_name builtin_boxdrop Location.Unknown
878882

879883
let unop_table : (Unop.t * string) list =
880884
[(Neg, "__sil_neg"); (BNot, "__sil_bnot"); (LNot, "__sil_lnot")]
@@ -1075,7 +1079,7 @@ module ProcDecl = struct
10751079
[builtin_assert_fail; builtin_swift_alloc; builtin_objc_alloc] @ unop_builtins @ binop_builtins
10761080

10771081

1078-
let builtins_rust = [builtin_free; builtin_malloc; builin_boxnew]
1082+
let builtins_rust = [builtin_free; builtin_malloc; builtin_boxnew; builtin_boxdrop]
10791083

10801084
let is_builtin (proc : QualifiedProcName.t) lang =
10811085
match lang with

infer/src/textual/Textual.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ module ProcDecl : sig
431431

432432
val boxnew_name : QualifiedProcName.t
433433

434+
val boxdrop_name : QualifiedProcName.t
435+
434436
val is_allocate_array_builtin : QualifiedProcName.t -> bool
435437

436438
val is_get_lazy_class_builtin : QualifiedProcName.t -> bool

infer/tests/codetoanalyze/rust/pulse/issues.exp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
box_free.rs, box_free::main, 10, USE_AFTER_FREE, no_bucket, ERROR, [invalidation part of the trace starts here,in call to `__sil_boxnew`,allocated by call to `malloc` (modelled),returned,return from call to `__sil_boxnew`,assigned,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,in call to `__sil_boxnew`,allocated by call to `malloc` (modelled),returned,return from call to `__sil_boxnew`,assigned,assigned,assigned,assigned,assigned,invalid access occurs here]
12
box_leak.rs, box_leak::main, 4, MEMORY_LEAK_C, no_bucket, ERROR, [allocation part of the trace starts here,when calling `__sil_boxnew` here,allocated by `malloc` here,memory becomes unreachable here]
23
dangling.rs, dangling::create_evil_pointer, 7, STACK_VARIABLE_ADDRESS_ESCAPE, no_bucket, ERROR, [variable `x_1` accessed here,assigned,assigned,assigned,returned here]
34
dangling.rs, dangling::main, 11, USE_AFTER_LIFETIME, no_bucket, ERROR, [invalidation part of the trace starts here,when calling `dangling::create_evil_pointer` here,variable `x_1` accessed here,is the address of a stack variable `x_1` whose lifetime has ended,use-after-lifetime part of the trace starts here,in call to `dangling::create_evil_pointer`,variable `x_1` accessed here,assigned,assigned,assigned,returned,return from call to `dangling::create_evil_pointer`,assigned,invalid access occurs here]

infer/tests/codetoanalyze/rust/sil/box_free.rs.ullbc.sil

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,42 @@
88

99
type alloc::alloc::Global = {}
1010

11-
declare box_free::main() : void
11+
define box_free::main() : void {
12+
local var_0: void, ptr_1: *int, x_2: *int, var_3: *int, var_4: *int, ub_5: int, var_6: *int
13+
#node_0:
14+
store &var_0 <- null:void
15+
n0 = __sil_boxnew(50)
16+
store &x_2 <- n0:*int
17+
jmp node_2
18+
.handlers node_1
19+
20+
#node_1:
21+
throw "UnwindResume"
22+
23+
#node_2:
24+
n1:*int = load &x_2
25+
store &var_6 <- __sil_cast(<*int>, n1):*int
26+
n2:*int = load &var_6
27+
store &var_4 <- n2:*int
28+
n3:*int = load &var_4
29+
store &var_3 <- n3:*int
30+
n4:*int = load &var_3
31+
store &ptr_1 <- n4:*int
32+
n5:*int = load &x_2
33+
n6 = __sil_free(n5)
34+
jmp node_3
35+
.handlers node_4
36+
37+
#node_3:
38+
n7:*int = load &ptr_1
39+
n8:int = load n7
40+
store &ub_5 <- n8:int
41+
store &var_0 <- null:void
42+
n9:void = load &var_0
43+
ret n9
44+
45+
#node_4:
46+
throw "UnwindResume"
47+
48+
}
1249

0 commit comments

Comments
 (0)