[pulse] abduce arithmetic facts

Summary:
This does several things because it was hard to split it more:
1. Split most of the arithmetic reasoning to PulseArithmetic.ml. This
doesn't need to be reviewed thoroughly because an upcoming diff
changes the domain from just `EqualTo of Const.t` to an interval domain!
2. When going through a prune node intra-procedurally, abduce arithmetic
facts to the pre (instead of just propagating them). This is the "assume
as assert" trick used by biabduction 1.0 too and allows to propagate
arithmetic constraints to callers.
3. Use 2 when applying summaries by pruning specs whose preconditions
have un-satisfiable arithmetic constraints.

This changes one of the tests! Pulse now does a bit more work to find
the false positive, as can be seen in the longer trace.

Reviewed By: skcho

Differential Revision: D18117160

fbshipit-source-id: af3b2c8c0
master
Jules Villard 5 years ago committed by Facebook Github Bot
parent 702602dcec
commit b20c22a5ee

@ -214,7 +214,7 @@ module Memory = struct
BaseMemory.get_closure_proc_name addr (astate.post :> base_domain).heap
let get_constant addr astate = BaseMemory.get_constant addr (astate.post :> base_domain).heap
let get_arithmetic addr astate = BaseMemory.get_arithmetic addr (astate.post :> base_domain).heap
let std_vector_reserve addr astate =
map_post_heap astate ~f:(fun heap -> BaseMemory.std_vector_reserve addr heap)
@ -347,10 +347,35 @@ module PrePost = struct
module AddressSet = AbstractValue.Set
module AddressMap = AbstractValue.Map
(** raised when the pre/post pair and the current state disagree on the aliasing, i.e. some
addresses that are distinct in the pre/post are aliased in the current state. Typically raised
when calling [foo(z,z)] where the spec for [foo(x,y)] says that [x] and [y] are disjoint. *)
exception Aliasing
type cannot_apply_pre =
| Aliasing of
{ addr_caller: AbstractValue.t
; addr_callee: AbstractValue.t
; addr_callee': AbstractValue.t }
(** raised when the precondition and the current state disagree on the aliasing, i.e. some
addresses [callee_addr] and [callee_addr'] that are distinct in the pre are aliased to
a single address [caller_addr] in the caller's current state. Typically raised when
calling [foo(z,z)] where the spec for [foo(x,y)] says that [x] and [y] are disjoint. *)
| Arithmetic of
{ addr_caller: AbstractValue.t
; addr_callee: AbstractValue.t
; arith_caller: Arithmetic.t option
; arith_callee: Arithmetic.t option }
(** raised when the pre asserts arithmetic facts that are demonstrably false in the caller
state *)
let pp_cannot_apply_pre fmt = function
| Aliasing {addr_caller; addr_callee; addr_callee'} ->
F.fprintf fmt "address %a in caller already bound to %a, not %a" AbstractValue.pp
addr_caller AbstractValue.pp addr_callee' AbstractValue.pp addr_callee
| Arithmetic {addr_caller; addr_callee; arith_caller; arith_callee} ->
F.fprintf fmt "caller addr %a%a but callee addr %a%a; %a=%a is unsatisfiable"
AbstractValue.pp addr_caller (Pp.option Arithmetic.pp) arith_caller AbstractValue.pp
addr_callee (Pp.option Arithmetic.pp) arith_callee AbstractValue.pp addr_caller
AbstractValue.pp addr_callee
exception CannotApplyPre of cannot_apply_pre
(** stuff we carry around when computing the result of applying one pre/post pair *)
type call_state =
@ -399,10 +424,7 @@ module PrePost = struct
let addr_caller = fst addr_hist_caller in
( match AddressMap.find_opt addr_caller call_state.rev_subst with
| Some addr_callee' when not (AbstractValue.equal addr_callee addr_callee') ->
L.d_printfln "Huho, address %a in post already bound to %a, not %a@\nState=%a"
AbstractValue.pp addr_caller AbstractValue.pp addr_callee' AbstractValue.pp addr_callee
pp_call_state call_state ;
raise Aliasing
raise (CannotApplyPre (Aliasing {addr_caller; addr_callee; addr_callee'}))
| _ ->
() ) ;
if AddressSet.mem addr_callee call_state.visited then (`AlreadyVisited, call_state)
@ -421,6 +443,32 @@ module PrePost = struct
(* {3 reading the pre from the current state} *)
let solve_arithmetic_constraints ~addr_pre ~attrs_pre ~addr_hist_caller call_state =
match Attributes.get_arithmetic attrs_pre with
| None ->
call_state
| Some _ as arith_callee ->
let addr_caller = fst addr_hist_caller in
let astate = call_state.astate in
let arith_caller = Memory.get_arithmetic addr_caller astate in
(* TODO: we don't use [abduced_callee] but we could probably use it to refine the attributes
for that address in the post since abstract values are immutable *)
let satisfiable, abduce_caller, _abduce_callee =
Arithmetic.abduce_binop_is_true ~negated:false Eq arith_caller arith_callee
in
if not satisfiable then
raise
(CannotApplyPre
(Arithmetic {addr_caller; addr_callee= addr_pre; arith_caller; arith_callee})) ;
let new_astate =
Option.fold abduce_caller ~init:astate ~f:(fun astate abduce_caller ->
let attribute = Attribute.Arithmetic abduce_caller in
Memory.abduce_attribute addr_caller attribute astate
|> Memory.add_attribute addr_caller attribute )
in
{call_state with astate= new_astate}
(** Materialize the (abstract memory) subgraph of [pre] reachable from [addr_pre] in
[call_state.astate] starting from address [addr_caller]. Report an error if some invalid
addresses are traversed in the process. *)
@ -433,7 +481,10 @@ module PrePost = struct
match BaseMemory.find_opt addr_pre pre.BaseDomain.heap with
| None ->
Ok call_state
| Some (edges_pre, _attrs_pre) ->
| Some (edges_pre, attrs_pre) ->
let call_state =
solve_arithmetic_constraints ~addr_pre ~attrs_pre ~addr_hist_caller call_state
in
Container.fold_result
~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Memory.Edges.fold)
~init:call_state edges_pre ~f:(fun call_state (access, (addr_pre_dest, _)) ->
@ -572,8 +623,8 @@ module PrePost = struct
; in_call= invalidation })
| AddressOfCppTemporary (_, _)
| AddressOfStackVariable (_, _, _)
| Arithmetic _
| Closure _
| Constant _
| MustBeValid _
| StdVectorReserve
| WrittenTo _ ->
@ -839,9 +890,10 @@ module PrePost = struct
match
materialize_pre callee_proc_name call_location pre_post ~formals ~actuals empty_call_state
with
| exception Aliasing ->
| exception CannotApplyPre reason ->
(* can't make sense of the pre-condition in the current context: give up on that particular
pre/post pair *)
L.d_printfln "Cannot apply precondition: %a" pp_cannot_apply_pre reason ;
Ok None
| None ->
(* couldn't apply the pre for some technical reason (as in: not by the fault of the

@ -45,7 +45,11 @@ module Memory : sig
module Access = BaseMemory.Access
module Edges = BaseMemory.Edges
val abduce_attribute : AbstractValue.t -> Attribute.t -> t -> t
(** add the attribute to the pre, if meaningful (does not modify the post) *)
val add_attribute : AbstractValue.t -> Attribute.t -> t -> t
(** add the attribute only to the post *)
val add_edge :
AbstractValue.t * ValueHistory.t
@ -76,7 +80,7 @@ module Memory : sig
(** [eval_edge (addr,hist) access astate] follows the edge [addr --access--> .] in memory and
returns what it points to or creates a fresh value if that edge didn't exist. *)
val get_constant : AbstractValue.t -> t -> Const.t option
val get_arithmetic : AbstractValue.t -> t -> Arithmetic.t option
end
val is_local : Var.t -> t -> bool

@ -0,0 +1,68 @@
(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module F = Format
type t = EqualTo of Const.t [@@deriving compare]
let pp fmt = function EqualTo c -> F.fprintf fmt "=%a" (Const.pp Pp.text) c
(** booleans with \top *)
module TBool = struct
type t = True | False | Top
end
let flip_abduced (tbool, c1, c2) = (tbool, c2, c1)
let rec abduce_eq a1 a2 =
match (a1, a2) with
| Some (EqualTo c1), Some (EqualTo c2) when Const.equal c1 c2 ->
(TBool.True, None, None)
| Some (EqualTo _c1), Some (EqualTo _c2) (* c1≠c2 *) ->
(TBool.False, None, None)
| None, Some _ ->
abduce_eq a2 a1 |> flip_abduced
| Some (EqualTo _c), None ->
(TBool.True, None, a1)
| None, None ->
(TBool.Top, None, None)
let abduce_ne a1 a2 =
match (a1, a2) with
| Some (EqualTo c1), Some (EqualTo c2) when Const.equal c1 c2 ->
(TBool.False, None, None)
| Some (EqualTo _c1), Some (EqualTo _c2) (* c1≠c2 *) ->
(TBool.True, None, None)
| None, Some _ | Some _, None ->
(* cannot express ≠c so go to Top *)
(TBool.Top, None, None)
| None, None ->
(TBool.Top, None, None)
let abduce_binop_constraints ~negated (bop : Binop.t) a1 a2 =
let open Binop in
match (bop, negated) with
| Eq, false | Ne, true ->
abduce_eq a1 a2
| Eq, true | Ne, false ->
abduce_ne a1 a2
| _ ->
(TBool.Top, None, None)
let abduce_binop_is_true_aux ~negated bop a1_opt a2_opt =
Logging.d_printfln "abduce_binop_is_true ~negated:%b %s (%a) (%a)" negated
(Binop.str Pp.text bop) (Pp.option pp) a1_opt (Pp.option pp) a2_opt ;
abduce_binop_constraints ~negated bop a1_opt a2_opt
let abduce_binop_is_true ~negated bop v1 v2 =
let result, abduced1, abduced2 = abduce_binop_is_true_aux ~negated bop v1 v2 in
let can_go_through = match result with Top | True -> true | False -> false in
(can_go_through, abduced1, abduced2)

@ -0,0 +1,25 @@
(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module F = Format
type t = EqualTo of Const.t [@@deriving compare]
val pp : F.formatter -> t -> unit
val abduce_binop_is_true :
negated:bool -> Binop.t -> t option -> t option -> bool * t option * t option
(** given [arith_lhs_opt bop arith_rhs_opt], return a triple
[(satisfiable,abduced_lhs_opt,abduced_rhs_opt)] such that (taking lhs=true if lhs_opt is [None],
same for rhs):
- [satisfiable] iff lhs bop rhs
- [abduced_lhs_opt=Some alhs] if [satisfiable] and (lhs bop rhs => alhslhs) (and similarly for rhs)
- likewise if [negated] with (lhs bop rhs = ) in the two points above
*)

@ -6,6 +6,7 @@
*)
open! IStd
module F = Format
module Arithmetic = PulseArithmetic
module Invalidation = PulseInvalidation
module Trace = PulseTrace
module ValueHistory = PulseValueHistory
@ -28,8 +29,8 @@ module Attribute = struct
type t =
| AddressOfCppTemporary of Var.t * ValueHistory.t
| AddressOfStackVariable of Var.t * Location.t * ValueHistory.t
| Arithmetic of Arithmetic.t
| Closure of Typ.Procname.t
| Constant of Const.t
| Invalid of Invalidation.t Trace.t
| MustBeValid of unit Trace.t
| StdVectorReserve
@ -59,7 +60,7 @@ module Attribute = struct
let std_vector_reserve_rank = Variants.to_rank StdVectorReserve
let const_rank = Variants.to_rank (Constant (Const.Cint IntLit.zero))
let const_rank = Variants.to_rank (Arithmetic (Arithmetic.EqualTo (Cint IntLit.zero)))
let pp f attribute =
let pp_string_if_debug string fmt () =
@ -72,8 +73,8 @@ module Attribute = struct
F.fprintf f "s&%a (%a) at %a" Var.pp var ValueHistory.pp history Location.pp location
| Closure pname ->
Typ.Procname.pp f pname
| Constant c ->
F.fprintf f "=%a" (Const.pp Pp.text) c
| Arithmetic phi ->
Arithmetic.pp f phi
| Invalid invalidation ->
F.fprintf f "Invalid %a" (Trace.pp Invalidation.pp) invalidation
| MustBeValid action ->
@ -131,11 +132,11 @@ module Attributes = struct
|| Option.is_some (Set.find_rank attrs Attribute.invalid_rank)
let get_constant attrs =
let get_arithmetic attrs =
Set.find_rank attrs Attribute.const_rank
|> Option.map ~f:(fun attr ->
let[@warning "-8"] (Attribute.Constant c) = attr in
c )
let[@warning "-8"] (Attribute.Arithmetic a) = attr in
a )
include Set

@ -6,6 +6,7 @@
*)
open! IStd
module F = Format
module Arithmetic = PulseArithmetic
module Invalidation = PulseInvalidation
module Trace = PulseTrace
module ValueHistory = PulseValueHistory
@ -13,8 +14,8 @@ module ValueHistory = PulseValueHistory
type t =
| AddressOfCppTemporary of Var.t * ValueHistory.t
| AddressOfStackVariable of Var.t * Location.t * ValueHistory.t
| Arithmetic of Arithmetic.t
| Closure of Typ.Procname.t
| Constant of Const.t
| Invalid of Invalidation.t Trace.t
| MustBeValid of unit Trace.t
| StdVectorReserve
@ -30,7 +31,7 @@ module Attributes : sig
val get_closure_proc_name : t -> Typ.Procname.t option
val get_constant : t -> Const.t option
val get_arithmetic : t -> Arithmetic.t option
val get_invalid : t -> Invalidation.t Trace.t option

@ -89,7 +89,7 @@ let get_attribute getter address memory =
let get_closure_proc_name = get_attribute Attributes.get_closure_proc_name
let get_constant = get_attribute Attributes.get_constant
let get_arithmetic = get_attribute Attributes.get_arithmetic
let get_must_be_valid = get_attribute Attributes.get_must_be_valid

@ -60,7 +60,7 @@ val check_valid : AbstractValue.t -> t -> (unit, Invalidation.t Trace.t) result
val get_closure_proc_name : AbstractValue.t -> t -> Typ.Procname.t option
val get_constant : AbstractValue.t -> t -> Const.t option
val get_arithmetic : AbstractValue.t -> t -> Arithmetic.t option
val get_must_be_valid : AbstractValue.t -> t -> unit Trace.t option

@ -9,6 +9,7 @@ open! IStd
(** Basic Pulse modules that are safe to use in any module *)
module AbstractValue = PulseAbstractValue
module Arithmetic = PulseArithmetic
module Attribute = PulseAttribute
module Attributes = PulseAttribute.Attributes
module CallEvent = PulseCallEvent

@ -134,7 +134,7 @@ let eval location exp0 astate =
| Const c ->
(* TODO: make identical const the same address *)
let addr = AbstractValue.mk_fresh () in
let astate = Memory.add_attribute addr (Constant c) astate in
let astate = Memory.add_attribute addr (Arithmetic (Arithmetic.EqualTo c)) astate in
Ok (astate, (addr, []))
| Sizeof _ | UnOp _ | BinOp _ | Exn _ ->
Ok (astate, (AbstractValue.mk_fresh (), (* TODO history *) []))
@ -142,57 +142,39 @@ let eval location exp0 astate =
eval exp0 astate
type eval_result = EvalConst of Const.t | EvalAddr of AbstractValue.t
let eval_to_const location exp astate =
let eval_arith location exp astate =
match (exp : Exp.t) with
| Const c ->
Ok (astate, EvalConst c)
Ok (astate, None, Some (Arithmetic.EqualTo c))
| exp -> (
eval location exp astate
>>| fun (astate, (addr, _)) ->
match Memory.get_constant addr astate with
| Some c ->
(astate, EvalConst c)
match Memory.get_arithmetic addr astate with
| Some a ->
(astate, Some addr, Some a)
| None ->
(astate, EvalAddr addr) )
(astate, Some addr, None) )
let eval_binop ~negated (bop : Binop.t) c1 c2 =
match (bop, negated) with
| Eq, false | Ne, true ->
Const.equal c1 c2
| Eq, true | Ne, false ->
not (Const.equal c1 c2)
| _ ->
true
let record_abduced addr_opt arith_opt astate =
match Option.both addr_opt arith_opt with
| None ->
astate
| Some (addr, arith) ->
let attribute = Attribute.Arithmetic arith in
Memory.abduce_attribute addr attribute astate |> Memory.add_attribute addr attribute
module TBool = struct
(** booleans with \top *)
type t = True | False | Top
let of_bool b = if b then True else False
end
let rec eval_cond ~negated location exp astate =
match (exp : Exp.t) with
| BinOp (bop, e1, e2) -> (
eval_to_const location e1 astate
>>= fun (astate, eval1) ->
eval_to_const location e2 astate
>>| fun (astate, eval2) ->
match (eval1, eval2) with
| EvalConst c1, EvalConst c2 ->
(astate, eval_binop ~negated bop c1 c2 |> TBool.of_bool)
| EvalAddr _, EvalAddr _ ->
(astate, TBool.Top)
| EvalAddr v, EvalConst c | EvalConst c, EvalAddr v -> (
match (bop, negated) with
| Eq, false | Ne, true ->
(Memory.add_attribute v (Constant c) astate, TBool.True)
| _ ->
(astate, TBool.Top) ) )
| BinOp (bop, e1, e2) ->
eval_arith location e1 astate
>>= fun (astate, addr1, eval1) ->
eval_arith location e2 astate
>>| fun (astate, addr2, eval2) ->
let result, abduced1, abduced2 = Arithmetic.abduce_binop_is_true ~negated bop eval1 eval2 in
let astate = record_abduced addr1 abduced1 astate |> record_abduced addr2 abduced2 in
(astate, result)
| UnOp (LNot, exp', _) ->
eval_cond ~negated:(not negated) location exp' astate
| exp ->
@ -200,12 +182,7 @@ let rec eval_cond ~negated location exp astate =
eval_cond ~negated location (Exp.BinOp (Ne, exp, zero)) astate
let assert_is_true location ~condition astate =
eval_cond ~negated:false location condition astate
>>| fun (astate, result) ->
let can_go_through = match (result : TBool.t) with Top | True -> true | False -> false in
(astate, can_go_through)
let assert_is_true location ~condition astate = eval_cond ~negated:false location condition astate
let eval_deref location exp astate =
eval location exp astate

@ -11,7 +11,7 @@ codetoanalyze/cpp/pulse/deduplication.cpp, deduplication::SomeTemplatedClass<int
codetoanalyze/cpp/pulse/deduplication.cpp, deduplication::SomeTemplatedClass<int>::lifetime_error_bad, 2, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,parameter `a` of deduplication::SomeTemplatedClass<int>::lifetime_error_bad,when calling `deduplication::SomeTemplatedClass<int>::templated_wrapper_delete_ok` here,parameter `a` of deduplication::SomeTemplatedClass<int>::templated_wrapper_delete_ok,was invalidated by `delete`,use-after-lifetime part of the trace starts here,parameter `a` of deduplication::SomeTemplatedClass<int>::lifetime_error_bad,when calling `deduplication::SomeTemplatedClass<int>::templated_wrapper_access_ok` here,parameter `a` of deduplication::SomeTemplatedClass<int>::templated_wrapper_access_ok,invalid access occurs here]
codetoanalyze/cpp/pulse/deduplication.cpp, deduplication::templated_function_bad<_Bool>, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,assigned,when calling `deduplication::templated_delete_function<_Bool>` here,parameter `a` of deduplication::templated_delete_function<_Bool>,was invalidated by `delete`,use-after-lifetime part of the trace starts here,assigned,when calling `deduplication::templated_access_function<_Bool>` here,parameter `a` of deduplication::templated_access_function<_Bool>,invalid access occurs here]
codetoanalyze/cpp/pulse/deduplication.cpp, deduplication::templated_function_bad<int>, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,assigned,when calling `deduplication::templated_delete_function<int>` here,parameter `a` of deduplication::templated_delete_function<int>,was invalidated by `delete`,use-after-lifetime part of the trace starts here,assigned,when calling `deduplication::templated_access_function<int>` here,parameter `a` of deduplication::templated_access_function<int>,invalid access occurs here]
codetoanalyze/cpp/pulse/fbstring.cpp, FP_pass_to_copy_ok, 6, USE_AFTER_FREE, no_bucket, ERROR, [invalidation part of the trace starts here,variable `s` declared here,when calling `copy_fbstring` here,parameter `s` of copy_fbstring,passed as argument to `LikeFBString::LikeFBString`,parameter `src` of LikeFBString::LikeFBString,passed as argument to `LikeFBString::copyLarge`,parameter `src` of LikeFBString::copyLarge,assigned,return from call to `LikeFBString::copyLarge`,return from call to `LikeFBString::LikeFBString`,when calling `LikeFBString::~LikeFBString` here,parameter `this` of LikeFBString::~LikeFBString,when calling `LikeFBString::__infer_inner_destructor_~LikeFBString` here,parameter `this` of LikeFBString::__infer_inner_destructor_~LikeFBString,when calling `LikeFBString::decr_ref_count` here,parameter `this` of LikeFBString::decr_ref_count,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,variable `s` declared here,when calling `LikeFBString::~LikeFBString` here,parameter `this` of LikeFBString::~LikeFBString,when calling `LikeFBString::__infer_inner_destructor_~LikeFBString` here,parameter `this` of LikeFBString::__infer_inner_destructor_~LikeFBString,invalid access occurs here]
codetoanalyze/cpp/pulse/fbstring.cpp, FP_pass_to_copy_ok, 6, USE_AFTER_FREE, no_bucket, ERROR, [invalidation part of the trace starts here,variable `s` declared here,when calling `copy_fbstring` here,parameter `s` of copy_fbstring,passed as argument to `LikeFBString::LikeFBString`,parameter `src` of LikeFBString::LikeFBString,passed as argument to `LikeFBString::copyLarge`,parameter `src` of LikeFBString::copyLarge,assigned,return from call to `LikeFBString::copyLarge`,return from call to `LikeFBString::LikeFBString`,when calling `LikeFBString::~LikeFBString` here,parameter `this` of LikeFBString::~LikeFBString,when calling `LikeFBString::__infer_inner_destructor_~LikeFBString` here,parameter `this` of LikeFBString::__infer_inner_destructor_~LikeFBString,when calling `LikeFBString::decr_ref_count` here,parameter `this` of LikeFBString::decr_ref_count,was invalidated by call to `free()`,use-after-lifetime part of the trace starts here,variable `s` declared here,when calling `LikeFBString::~LikeFBString` here,parameter `this` of LikeFBString::~LikeFBString,when calling `LikeFBString::__infer_inner_destructor_~LikeFBString` here,parameter `this` of LikeFBString::__infer_inner_destructor_~LikeFBString,when calling `LikeFBString::decr_ref_count` here,parameter `this` of LikeFBString::decr_ref_count,invalid access occurs here]
codetoanalyze/cpp/pulse/folly_DestructorGuard.cpp, UsingDelayedDestruction::double_delete_bad, 2, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,parameter `this` of UsingDelayedDestruction::double_delete_bad,was invalidated by `delete`,use-after-lifetime part of the trace starts here,parameter `this` of UsingDelayedDestruction::double_delete_bad,invalid access occurs here]
codetoanalyze/cpp/pulse/frontend.cpp, deref_null_namespace_alias_ptr_bad, 4, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,when calling `some::thing::bad_ptr` here,assigned,was invalidated by `delete`,use-after-lifetime part of the trace starts here,passed as argument to `some::thing::bad_ptr`,return from call to `some::thing::bad_ptr`,assigned,invalid access occurs here]
codetoanalyze/cpp/pulse/interprocedural.cpp, access_to_invalidated_alias2_bad, 3, USE_AFTER_DELETE, no_bucket, ERROR, [invalidation part of the trace starts here,parameter `x` of access_to_invalidated_alias2_bad,assigned,when calling `invalidate_and_set_to_null` here,parameter `x_ptr` of invalidate_and_set_to_null,was invalidated by `delete`,use-after-lifetime part of the trace starts here,parameter `x` of access_to_invalidated_alias2_bad,when calling `wraps_read` here,parameter `x` of wraps_read,when calling `wraps_read_inner` here,parameter `x` of wraps_read_inner,invalid access occurs here]

Loading…
Cancel
Save