Summary: PulseAbductiveDomain.ml can be split into two distinct parts: 1. The definition of the "abductive domain" itself. This remains in that file. 2. How to apply a given pre/post pair to the current state (during a function call). This is about the same size as 1. in terms of lines of code(!) and is now in PulseInterproc.ml. Reviewed By: ezgicicek Differential Revision: D21022921 fbshipit-source-id: 431fe061emaster
parent
9ed10d435b
commit
c00de7ad27
@ -0,0 +1,635 @@
|
||||
(*
|
||||
* 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.
|
||||
*)
|
||||
|
||||
[@@@warning "+9"]
|
||||
|
||||
open! IStd
|
||||
module F = Format
|
||||
module L = Logging
|
||||
open PulseBasicInterface
|
||||
open PulseDomainInterface
|
||||
|
||||
(* {2 machinery to apply a pre/post pair corresponding to a function's summary in a function call
|
||||
to the current state} *)
|
||||
|
||||
module AddressSet = AbstractValue.Set
|
||||
module AddressMap = AbstractValue.Map
|
||||
|
||||
(** stuff we carry around when computing the result of applying one pre/post pair *)
|
||||
type call_state =
|
||||
{ astate: AbductiveDomain.t (** caller's abstract state computed so far *)
|
||||
; subst: (AbstractValue.t * ValueHistory.t) AddressMap.t
|
||||
(** translation from callee addresses to caller addresses and their caller histories *)
|
||||
; rev_subst: AbstractValue.t AddressMap.t
|
||||
(** the inverse translation from [subst] from caller addresses to callee addresses *)
|
||||
; visited: AddressSet.t
|
||||
(** set of callee addresses that have been visited already
|
||||
|
||||
NOTE: this is not always equal to the domain of [rev_subst]: when applying the post we
|
||||
visit each subgraph from each formal independently so we reset [visited] between the
|
||||
visit of each formal *) }
|
||||
|
||||
let pp_call_state fmt {astate; subst; rev_subst; visited} =
|
||||
F.fprintf fmt
|
||||
"@[<v>{ astate=@[<hv2>%a@];@,\
|
||||
\ subst=@[<hv2>%a@];@,\
|
||||
\ rev_subst=@[<hv2>%a@];@,\
|
||||
\ visited=@[<hv2>%a@]@,\
|
||||
\ }@]" AbductiveDomain.pp astate
|
||||
(AddressMap.pp ~pp_value:(fun fmt (addr, _) -> AbstractValue.pp fmt addr))
|
||||
subst
|
||||
(AddressMap.pp ~pp_value:AbstractValue.pp)
|
||||
rev_subst AddressSet.pp visited
|
||||
|
||||
|
||||
type contradiction =
|
||||
| Aliasing of
|
||||
{ addr_caller: AbstractValue.t
|
||||
; addr_callee: AbstractValue.t
|
||||
; addr_callee': AbstractValue.t
|
||||
; call_state: call_state }
|
||||
(** 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. *)
|
||||
| CItv of
|
||||
{ addr_caller: AbstractValue.t
|
||||
; addr_callee: AbstractValue.t
|
||||
; arith_caller: CItv.t option
|
||||
; arith_callee: CItv.t option
|
||||
; call_state: call_state }
|
||||
(** raised when the pre asserts arithmetic facts that are demonstrably false in the caller
|
||||
state *)
|
||||
| ArithmeticBo of
|
||||
{ addr_caller: AbstractValue.t
|
||||
; addr_callee: AbstractValue.t
|
||||
; arith_callee: Itv.ItvPure.t
|
||||
; call_state: call_state }
|
||||
(** raised when the pre asserts arithmetic facts that are demonstrably false in the caller
|
||||
state *)
|
||||
| FormalActualLength of
|
||||
{formals: Var.t list; actuals: ((AbstractValue.t * ValueHistory.t) * Typ.t) list}
|
||||
|
||||
let pp_contradiction fmt = function
|
||||
| Aliasing {addr_caller; addr_callee; addr_callee'; call_state} ->
|
||||
F.fprintf fmt
|
||||
"address %a in caller already bound to %a, not %a@\nnote: current call state was %a"
|
||||
AbstractValue.pp addr_caller AbstractValue.pp addr_callee' AbstractValue.pp addr_callee
|
||||
pp_call_state call_state
|
||||
| CItv {addr_caller; addr_callee; arith_caller; arith_callee; call_state} ->
|
||||
F.fprintf fmt
|
||||
"caller addr %a%a but callee addr %a%a; %a=%a is unsatisfiable@\n\
|
||||
note: current call state was %a" AbstractValue.pp addr_caller (Pp.option CItv.pp)
|
||||
arith_caller AbstractValue.pp addr_callee (Pp.option CItv.pp) arith_callee AbstractValue.pp
|
||||
addr_caller AbstractValue.pp addr_callee pp_call_state call_state
|
||||
| ArithmeticBo {addr_caller; addr_callee; arith_callee; call_state} ->
|
||||
F.fprintf fmt
|
||||
"callee addr %a%a is incompatible with caller addr %a's arithmetic constraints@\n\
|
||||
note: current call state was %a" AbstractValue.pp addr_callee Itv.ItvPure.pp arith_callee
|
||||
AbstractValue.pp addr_caller pp_call_state call_state
|
||||
| FormalActualLength {formals; actuals} ->
|
||||
F.fprintf fmt "formals have length %d but actuals have length %d" (List.length formals)
|
||||
(List.length actuals)
|
||||
|
||||
|
||||
exception Contradiction of contradiction
|
||||
|
||||
let fold_globals_of_stack call_loc stack call_state ~f =
|
||||
Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:BaseStack.fold)
|
||||
stack ~init:call_state ~f:(fun call_state (var, stack_value) ->
|
||||
match var with
|
||||
| Var.ProgramVar pvar when Pvar.is_global pvar ->
|
||||
let call_state, addr_hist_caller =
|
||||
let astate, var_value =
|
||||
Stack.eval [ValueHistory.VariableAccessed (pvar, call_loc)] var call_state.astate
|
||||
in
|
||||
if phys_equal astate call_state.astate then (call_state, var_value)
|
||||
else ({call_state with astate}, var_value)
|
||||
in
|
||||
f pvar ~stack_value ~addr_hist_caller call_state
|
||||
| _ ->
|
||||
Ok call_state )
|
||||
|
||||
|
||||
let visit call_state ~addr_callee ~addr_hist_caller =
|
||||
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') ->
|
||||
raise (Contradiction (Aliasing {addr_caller; addr_callee; addr_callee'; call_state}))
|
||||
| _ ->
|
||||
() ) ;
|
||||
if AddressSet.mem addr_callee call_state.visited then (`AlreadyVisited, call_state)
|
||||
else
|
||||
( `NotAlreadyVisited
|
||||
, { call_state with
|
||||
visited= AddressSet.add addr_callee call_state.visited
|
||||
; subst= AddressMap.add addr_callee addr_hist_caller call_state.subst
|
||||
; rev_subst= AddressMap.add addr_caller addr_callee call_state.rev_subst } )
|
||||
|
||||
|
||||
let pp f {AbductiveDomain.pre; post; skipped_calls} =
|
||||
F.fprintf f "PRE:@\n @[%a@]@\n" BaseDomain.pp (pre :> BaseDomain.t) ;
|
||||
F.fprintf f "POST:@\n @[%a@]@\n" BaseDomain.pp (post :> BaseDomain.t) ;
|
||||
F.fprintf f "SKIPPED_CALLS:@ @[%a@]@\n" AbductiveDomain.SkippedCalls.pp skipped_calls
|
||||
|
||||
|
||||
(* {3 reading the pre from the current state} *)
|
||||
|
||||
let add_call_to_trace proc_name call_location caller_history in_call =
|
||||
Trace.ViaCall {f= Call proc_name; location= call_location; history= caller_history; in_call}
|
||||
|
||||
|
||||
(** 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. *)
|
||||
let rec materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre ~addr_hist_caller
|
||||
call_state =
|
||||
match visit call_state ~addr_callee:addr_pre ~addr_hist_caller with
|
||||
| `AlreadyVisited, call_state ->
|
||||
Ok call_state
|
||||
| `NotAlreadyVisited, call_state -> (
|
||||
match BaseMemory.find_opt addr_pre pre.BaseDomain.heap with
|
||||
| None ->
|
||||
Ok call_state
|
||||
| Some edges_pre ->
|
||||
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, _)) ->
|
||||
let astate, addr_hist_dest_caller =
|
||||
Memory.eval_edge addr_hist_caller access call_state.astate
|
||||
in
|
||||
let call_state = {call_state with astate} in
|
||||
materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre:addr_pre_dest
|
||||
~addr_hist_caller:addr_hist_dest_caller call_state ) )
|
||||
|
||||
|
||||
(** materialize subgraph of [pre] rooted at the address represented by a [formal] parameter that has
|
||||
been instantiated with the corresponding [actual] into the current state [call_state.astate] *)
|
||||
let materialize_pre_from_actual callee_proc_name call_location ~pre ~formal ~actual call_state =
|
||||
L.d_printfln "Materializing PRE from [%a <- %a]" Var.pp formal AbstractValue.pp (fst actual) ;
|
||||
(let open IOption.Let_syntax in
|
||||
let* addr_formal_pre, _ = BaseStack.find_opt formal pre.BaseDomain.stack in
|
||||
let+ formal_pre, _ = BaseMemory.find_edge_opt addr_formal_pre Dereference pre.BaseDomain.heap in
|
||||
materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre:formal_pre
|
||||
~addr_hist_caller:actual call_state)
|
||||
|> function Some result -> result | None -> Ok call_state
|
||||
|
||||
|
||||
let is_cell_read_only ~edges_pre_opt ~cell_post:(edges_post, attrs_post) =
|
||||
match edges_pre_opt with
|
||||
| None ->
|
||||
false
|
||||
| Some edges_pre when not (Attributes.is_modified attrs_post) ->
|
||||
let are_edges_equal =
|
||||
BaseMemory.Edges.equal
|
||||
(fun (addr_dest_pre, _) (addr_dest_post, _) ->
|
||||
(* NOTE: ignores traces
|
||||
|
||||
TODO: can the traces be leveraged here? maybe easy to detect writes by looking at
|
||||
the post trace *)
|
||||
AbstractValue.equal addr_dest_pre addr_dest_post )
|
||||
edges_pre edges_post
|
||||
in
|
||||
if CommandLineOption.strict_mode then assert are_edges_equal ;
|
||||
are_edges_equal
|
||||
| _ ->
|
||||
false
|
||||
|
||||
|
||||
let materialize_pre_for_parameters callee_proc_name call_location pre_post ~formals ~actuals
|
||||
call_state =
|
||||
(* For each [(formal, actual)] pair, resolve them to addresses in their respective states then
|
||||
call [materialize_pre_from] on them. Give up if calling the function introduces aliasing.
|
||||
*)
|
||||
match
|
||||
IList.fold2_result formals actuals ~init:call_state ~f:(fun call_state formal (actual, _) ->
|
||||
materialize_pre_from_actual callee_proc_name call_location
|
||||
~pre:(pre_post.AbductiveDomain.pre :> BaseDomain.t)
|
||||
~formal ~actual call_state )
|
||||
with
|
||||
| Unequal_lengths ->
|
||||
raise (Contradiction (FormalActualLength {formals; actuals}))
|
||||
| Ok result ->
|
||||
result
|
||||
|
||||
|
||||
let materialize_pre_for_globals callee_proc_name call_location pre_post call_state =
|
||||
fold_globals_of_stack call_location (pre_post.AbductiveDomain.pre :> BaseDomain.t).stack
|
||||
call_state ~f:(fun _var ~stack_value:(addr_pre, _) ~addr_hist_caller call_state ->
|
||||
materialize_pre_from_address callee_proc_name call_location
|
||||
~pre:(pre_post.AbductiveDomain.pre :> BaseDomain.t)
|
||||
~addr_pre ~addr_hist_caller call_state )
|
||||
|
||||
|
||||
let eval_sym_of_subst astate subst s bound_end =
|
||||
let v = Symb.Symbol.get_pulse_value_exn s in
|
||||
match PulseAbstractValue.Map.find_opt v !subst with
|
||||
| Some (v', _) ->
|
||||
Itv.ItvPure.get_bound (AddressAttributes.get_bo_itv v' astate) bound_end
|
||||
| None ->
|
||||
let v' = PulseAbstractValue.mk_fresh () in
|
||||
subst := PulseAbstractValue.Map.add v (v', []) !subst ;
|
||||
Bounds.Bound.of_pulse_value v'
|
||||
|
||||
|
||||
let subst_attribute call_state subst_ref astate ~addr_caller attr ~addr_callee =
|
||||
match (attr : Attribute.t) with
|
||||
| CItv (arith_callee, hist) -> (
|
||||
let arith_caller_opt = AddressAttributes.get_citv addr_caller astate |> Option.map ~f:fst in
|
||||
match CItv.abduce_binop_is_true ~negated:false Eq arith_caller_opt (Some arith_callee) with
|
||||
| Unsatisfiable ->
|
||||
raise
|
||||
(Contradiction
|
||||
(CItv
|
||||
{ addr_caller
|
||||
; addr_callee
|
||||
; arith_caller= arith_caller_opt
|
||||
; arith_callee= Some arith_callee
|
||||
; call_state }))
|
||||
| Satisfiable (Some abduce_caller, _abduce_callee) ->
|
||||
Attribute.CItv (abduce_caller, hist)
|
||||
| Satisfiable (None, _) ->
|
||||
attr )
|
||||
| BoItv itv -> (
|
||||
match
|
||||
Itv.ItvPure.subst itv (fun symb bound ->
|
||||
AbstractDomain.Types.NonBottom (eval_sym_of_subst astate subst_ref symb bound) )
|
||||
with
|
||||
| NonBottom itv' ->
|
||||
Attribute.BoItv itv'
|
||||
| Bottom ->
|
||||
raise
|
||||
(Contradiction (ArithmeticBo {addr_callee; addr_caller; arith_callee= itv; call_state})) )
|
||||
| AddressOfCppTemporary _
|
||||
| AddressOfStackVariable _
|
||||
| Allocated _
|
||||
| Closure _
|
||||
| Invalid _
|
||||
| MustBeValid _
|
||||
| StdVectorReserve
|
||||
| WrittenTo _ ->
|
||||
(* non-relational attributes *)
|
||||
attr
|
||||
|
||||
|
||||
let add_call_to_attributes proc_name call_location ~addr_callee ~addr_caller caller_history attrs
|
||||
call_state =
|
||||
let subst_ref = ref call_state.subst in
|
||||
let attrs =
|
||||
Attributes.map attrs ~f:(fun attr ->
|
||||
subst_attribute call_state subst_ref call_state.astate ~addr_callee ~addr_caller attr
|
||||
|> PulseAttribute.map_trace ~f:(fun trace ->
|
||||
add_call_to_trace proc_name call_location caller_history trace ) )
|
||||
in
|
||||
(!subst_ref, attrs)
|
||||
|
||||
|
||||
let apply_arithmetic_constraints callee_proc_name call_location pre_post call_state =
|
||||
let one_address_sat addr_callee callee_attrs (addr_caller, caller_history) call_state =
|
||||
let subst, attrs_caller =
|
||||
add_call_to_attributes callee_proc_name call_location ~addr_callee ~addr_caller caller_history
|
||||
callee_attrs call_state
|
||||
in
|
||||
let astate = AddressAttributes.abduce_and_add addr_caller attrs_caller call_state.astate in
|
||||
if phys_equal subst call_state.subst && phys_equal astate call_state.astate then call_state
|
||||
else {call_state with subst; astate}
|
||||
in
|
||||
(* check all callee addresses that make sense for the caller, i.e. the domain of [call_state.subst] *)
|
||||
AddressMap.fold
|
||||
(fun addr_callee addr_hist_caller call_state ->
|
||||
match
|
||||
BaseAddressAttributes.find_opt addr_callee
|
||||
(pre_post.AbductiveDomain.pre :> BaseDomain.t).attrs
|
||||
with
|
||||
| None ->
|
||||
call_state
|
||||
| Some callee_attrs ->
|
||||
one_address_sat addr_callee callee_attrs addr_hist_caller call_state )
|
||||
call_state.subst call_state
|
||||
|
||||
|
||||
let materialize_pre callee_proc_name call_location pre_post ~formals ~actuals call_state =
|
||||
PerfEvent.(log (fun logger -> log_begin_event logger ~name:"pulse call pre" ())) ;
|
||||
let r =
|
||||
let open IResult.Let_syntax in
|
||||
(* first make as large a mapping as we can between callee values and caller values... *)
|
||||
materialize_pre_for_parameters callee_proc_name call_location pre_post ~formals ~actuals
|
||||
call_state
|
||||
>>= materialize_pre_for_globals callee_proc_name call_location pre_post
|
||||
>>| (* ...then relational arithmetic constraints in the callee's attributes will make sense in
|
||||
terms of the caller's values *)
|
||||
apply_arithmetic_constraints callee_proc_name call_location pre_post
|
||||
in
|
||||
PerfEvent.(log (fun logger -> log_end_event logger ())) ;
|
||||
r
|
||||
|
||||
|
||||
(* {3 applying the post to the current state} *)
|
||||
|
||||
let subst_find_or_new subst addr_callee ~default_hist_caller =
|
||||
match AddressMap.find_opt addr_callee subst with
|
||||
| None ->
|
||||
let addr_hist_fresh = (AbstractValue.mk_fresh (), default_hist_caller) in
|
||||
(AddressMap.add addr_callee addr_hist_fresh subst, addr_hist_fresh)
|
||||
| Some addr_hist_caller ->
|
||||
(subst, addr_hist_caller)
|
||||
|
||||
|
||||
let call_state_subst_find_or_new call_state addr_callee ~default_hist_caller =
|
||||
let new_subst, addr_hist_caller =
|
||||
subst_find_or_new call_state.subst addr_callee ~default_hist_caller
|
||||
in
|
||||
if phys_equal new_subst call_state.subst then (call_state, addr_hist_caller)
|
||||
else ({call_state with subst= new_subst}, addr_hist_caller)
|
||||
|
||||
|
||||
let delete_edges_in_callee_pre_from_caller ~addr_callee:_ ~edges_pre_opt ~addr_caller call_state =
|
||||
match
|
||||
BaseMemory.find_opt addr_caller (call_state.astate.AbductiveDomain.post :> BaseDomain.t).heap
|
||||
with
|
||||
| None ->
|
||||
BaseMemory.Edges.empty
|
||||
| Some old_post_edges -> (
|
||||
match edges_pre_opt with
|
||||
| None ->
|
||||
old_post_edges
|
||||
| Some edges_pre ->
|
||||
BaseMemory.Edges.merge
|
||||
(fun _access old_opt pre_opt ->
|
||||
(* TODO: should apply [call_state.subst] to [_access]! Actually, should rewrite the
|
||||
whole [cell_pre] beforehand so that [Edges.merge] makes sense. *)
|
||||
if Option.is_some pre_opt then
|
||||
(* delete edge if some edge for the same access exists in the pre *)
|
||||
None
|
||||
else (* keep old edge if it exists *) old_opt )
|
||||
old_post_edges edges_pre )
|
||||
|
||||
|
||||
let record_post_cell callee_proc_name call_loc ~addr_callee ~edges_pre_opt
|
||||
~cell_callee_post:(edges_callee_post, attrs_callee_post)
|
||||
~addr_hist_caller:(addr_caller, hist_caller) call_state =
|
||||
let call_state =
|
||||
let subst, attrs_post_caller =
|
||||
add_call_to_attributes ~addr_callee ~addr_caller callee_proc_name call_loc hist_caller
|
||||
attrs_callee_post call_state
|
||||
in
|
||||
let astate = AddressAttributes.abduce_and_add addr_caller attrs_post_caller call_state.astate in
|
||||
{call_state with subst; astate}
|
||||
in
|
||||
let subst, translated_post_edges =
|
||||
BaseMemory.Edges.fold_map ~init:call_state.subst edges_callee_post
|
||||
~f:(fun subst (addr_callee, trace_post) ->
|
||||
let subst, (addr_curr, hist_curr) =
|
||||
subst_find_or_new subst addr_callee ~default_hist_caller:hist_caller
|
||||
in
|
||||
( subst
|
||||
, ( addr_curr
|
||||
, ValueHistory.Call {f= Call callee_proc_name; location= call_loc; in_call= trace_post}
|
||||
:: hist_curr ) ) )
|
||||
in
|
||||
let astate =
|
||||
let post_edges_minus_pre =
|
||||
delete_edges_in_callee_pre_from_caller ~addr_callee ~edges_pre_opt ~addr_caller call_state
|
||||
in
|
||||
let edges_post_caller =
|
||||
BaseMemory.Edges.union
|
||||
(fun _ _ post_cell -> Some post_cell)
|
||||
post_edges_minus_pre translated_post_edges
|
||||
in
|
||||
AbductiveDomain.set_post_edges addr_caller edges_post_caller call_state.astate
|
||||
in
|
||||
{call_state with subst; astate}
|
||||
|
||||
|
||||
let rec record_post_for_address callee_proc_name call_loc ({AbductiveDomain.pre; _} as pre_post)
|
||||
~addr_callee ~addr_hist_caller call_state =
|
||||
L.d_printfln "%a<->%a" AbstractValue.pp addr_callee AbstractValue.pp (fst addr_hist_caller) ;
|
||||
match visit call_state ~addr_callee ~addr_hist_caller with
|
||||
| `AlreadyVisited, call_state ->
|
||||
call_state
|
||||
| `NotAlreadyVisited, call_state -> (
|
||||
match AbductiveDomain.find_post_cell_opt addr_callee pre_post with
|
||||
| None ->
|
||||
call_state
|
||||
| Some ((edges_post, _attrs_post) as cell_callee_post) ->
|
||||
let edges_pre_opt = BaseMemory.find_opt addr_callee (pre :> BaseDomain.t).BaseDomain.heap in
|
||||
let call_state_after_post =
|
||||
if is_cell_read_only ~edges_pre_opt ~cell_post:cell_callee_post then call_state
|
||||
else
|
||||
record_post_cell callee_proc_name call_loc ~addr_callee ~edges_pre_opt ~addr_hist_caller
|
||||
~cell_callee_post call_state
|
||||
in
|
||||
IContainer.fold_of_pervasives_map_fold ~fold:Memory.Edges.fold ~init:call_state_after_post
|
||||
edges_post ~f:(fun call_state (_access, (addr_callee_dest, _)) ->
|
||||
let call_state, addr_hist_curr_dest =
|
||||
call_state_subst_find_or_new call_state addr_callee_dest
|
||||
~default_hist_caller:(snd addr_hist_caller)
|
||||
in
|
||||
record_post_for_address callee_proc_name call_loc pre_post ~addr_callee:addr_callee_dest
|
||||
~addr_hist_caller:addr_hist_curr_dest call_state ) )
|
||||
|
||||
|
||||
let record_post_for_actual callee_proc_name call_loc pre_post ~formal ~actual call_state =
|
||||
L.d_printfln_escaped "Recording POST from [%a] <-> %a" Var.pp formal AbstractValue.pp (fst actual) ;
|
||||
match
|
||||
let open IOption.Let_syntax in
|
||||
let* addr_formal_pre, _ =
|
||||
BaseStack.find_opt formal (pre_post.AbductiveDomain.pre :> BaseDomain.t).BaseDomain.stack
|
||||
in
|
||||
let+ formal_pre, _ =
|
||||
BaseMemory.find_edge_opt addr_formal_pre Dereference
|
||||
(pre_post.AbductiveDomain.pre :> BaseDomain.t).BaseDomain.heap
|
||||
in
|
||||
record_post_for_address callee_proc_name call_loc pre_post ~addr_callee:formal_pre
|
||||
~addr_hist_caller:actual call_state
|
||||
with
|
||||
| Some call_state ->
|
||||
call_state
|
||||
| None ->
|
||||
call_state
|
||||
|
||||
|
||||
let record_post_for_return callee_proc_name call_loc pre_post call_state =
|
||||
let return_var = Var.of_pvar (Pvar.get_ret_pvar callee_proc_name) in
|
||||
match BaseStack.find_opt return_var (pre_post.AbductiveDomain.post :> BaseDomain.t).stack with
|
||||
| None ->
|
||||
(call_state, None)
|
||||
| Some (addr_return, _) -> (
|
||||
match
|
||||
BaseMemory.find_edge_opt addr_return Dereference
|
||||
(pre_post.AbductiveDomain.post :> BaseDomain.t).BaseDomain.heap
|
||||
with
|
||||
| None ->
|
||||
(call_state, None)
|
||||
| Some (return_callee, _) ->
|
||||
let return_caller_addr_hist =
|
||||
match AddressMap.find_opt return_callee call_state.subst with
|
||||
| Some return_caller_hist ->
|
||||
return_caller_hist
|
||||
| None ->
|
||||
( AbstractValue.mk_fresh ()
|
||||
, [ (* this could maybe include an event like "returned here" *) ] )
|
||||
in
|
||||
let call_state =
|
||||
record_post_for_address callee_proc_name call_loc pre_post ~addr_callee:return_callee
|
||||
~addr_hist_caller:return_caller_addr_hist call_state
|
||||
in
|
||||
(call_state, Some return_caller_addr_hist) )
|
||||
|
||||
|
||||
let apply_post_for_parameters callee_proc_name call_location pre_post ~formals ~actuals call_state =
|
||||
(* for each [(formal_i, actual_i)] pair, do [post_i = post union subst(graph reachable from
|
||||
formal_i in post)], deleting previous info when comparing pre and post shows a difference
|
||||
(TODO: record in the pre when a location is written to instead of just comparing values
|
||||
between pre and post since it's unreliable, eg replace value read in pre with same value in
|
||||
post but nuke other fields in the meantime? is that possible?). *)
|
||||
match
|
||||
List.fold2 formals actuals ~init:call_state ~f:(fun call_state formal (actual, _) ->
|
||||
record_post_for_actual callee_proc_name call_location pre_post ~formal ~actual call_state )
|
||||
with
|
||||
| Unequal_lengths ->
|
||||
(* should have been checked before by [materialize_pre] *)
|
||||
assert false
|
||||
| Ok call_state ->
|
||||
call_state
|
||||
|
||||
|
||||
let apply_post_for_globals callee_proc_name call_location pre_post call_state =
|
||||
match
|
||||
fold_globals_of_stack call_location (pre_post.AbductiveDomain.pre :> BaseDomain.t).stack
|
||||
call_state ~f:(fun _var ~stack_value:(addr_callee, _) ~addr_hist_caller call_state ->
|
||||
Ok
|
||||
(record_post_for_address callee_proc_name call_location pre_post ~addr_callee
|
||||
~addr_hist_caller call_state) )
|
||||
with
|
||||
| Error _ ->
|
||||
(* always return [Ok _] above *) assert false
|
||||
| Ok call_state ->
|
||||
call_state
|
||||
|
||||
|
||||
let record_post_remaining_attributes callee_proc_name call_loc pre_post call_state =
|
||||
BaseAddressAttributes.fold
|
||||
(fun addr_callee attrs call_state ->
|
||||
if AddressSet.mem addr_callee call_state.visited then
|
||||
(* already recorded the attributes when we were walking the edges map *)
|
||||
call_state
|
||||
else
|
||||
match AddressMap.find_opt addr_callee call_state.subst with
|
||||
| None ->
|
||||
(* callee address has no meaning for the caller *) call_state
|
||||
| Some (addr_caller, history) ->
|
||||
let subst, attrs' =
|
||||
add_call_to_attributes callee_proc_name call_loc ~addr_callee ~addr_caller history
|
||||
attrs call_state
|
||||
in
|
||||
let astate = AddressAttributes.abduce_and_add addr_caller attrs' call_state.astate in
|
||||
{call_state with subst; astate} )
|
||||
(pre_post.AbductiveDomain.post :> BaseDomain.t).attrs call_state
|
||||
|
||||
|
||||
let record_skipped_calls callee_proc_name call_loc pre_post call_state =
|
||||
let callee_skipped_map =
|
||||
pre_post.AbductiveDomain.skipped_calls
|
||||
|> AbductiveDomain.SkippedCalls.map (fun trace ->
|
||||
add_call_to_trace callee_proc_name call_loc [] trace )
|
||||
in
|
||||
let astate = AbductiveDomain.add_skipped_calls callee_skipped_map call_state.astate in
|
||||
{call_state with astate}
|
||||
|
||||
|
||||
let apply_post callee_proc_name call_location pre_post ~formals ~actuals call_state =
|
||||
PerfEvent.(log (fun logger -> log_begin_event logger ~name:"pulse call post" ())) ;
|
||||
let r =
|
||||
apply_post_for_parameters callee_proc_name call_location pre_post ~formals ~actuals call_state
|
||||
|> apply_post_for_globals callee_proc_name call_location pre_post
|
||||
|> record_post_for_return callee_proc_name call_location pre_post
|
||||
|> fun (call_state, return_caller) ->
|
||||
( record_post_remaining_attributes callee_proc_name call_location pre_post call_state
|
||||
|> record_skipped_calls callee_proc_name call_location pre_post
|
||||
, return_caller )
|
||||
|> fun ({astate; _}, return_caller) -> (astate, return_caller)
|
||||
in
|
||||
PerfEvent.(log (fun logger -> log_end_event logger ())) ;
|
||||
r
|
||||
|
||||
|
||||
let check_all_valid callee_proc_name call_location {AbductiveDomain.pre; _} call_state =
|
||||
AddressMap.fold
|
||||
(fun addr_pre (addr_caller, hist_caller) astate_result ->
|
||||
match astate_result with
|
||||
| Error _ ->
|
||||
astate_result
|
||||
| Ok astate -> (
|
||||
match BaseAddressAttributes.get_must_be_valid addr_pre (pre :> BaseDomain.t).attrs with
|
||||
| None ->
|
||||
astate_result
|
||||
| Some callee_access_trace ->
|
||||
let access_trace =
|
||||
Trace.ViaCall
|
||||
{ in_call= callee_access_trace
|
||||
; f= Call callee_proc_name
|
||||
; location= call_location
|
||||
; history= hist_caller }
|
||||
in
|
||||
AddressAttributes.check_valid access_trace addr_caller astate
|
||||
|> Result.map_error ~f:(fun (invalidation, invalidation_trace) ->
|
||||
L.d_printfln "ERROR: caller's %a invalid!" AbstractValue.pp addr_caller ;
|
||||
( Diagnostic.AccessToInvalidAddress
|
||||
{invalidation; invalidation_trace; access_trace}
|
||||
, astate ) ) ) )
|
||||
call_state.subst (Ok call_state.astate)
|
||||
|
||||
|
||||
(* - read all the pre, assert validity of addresses and materializes *everything* (to throw stuff
|
||||
in the *current* pre as appropriate so that callers of the current procedure will also know
|
||||
about the deeper reads)
|
||||
|
||||
- for each actual, write the post for that actual
|
||||
|
||||
- if aliasing is introduced at any time then give up
|
||||
|
||||
questions:
|
||||
|
||||
- what if some preconditions raise lifetime issues but others don't? Have to be careful with
|
||||
the noise that this will introduce since we don't care about values. For instance, if the pre
|
||||
is for a path where [formal != 0] and we pass [0] then it will be an FP. Maybe the solution is
|
||||
to bake in some value analysis. *)
|
||||
let apply_prepost callee_proc_name call_location ~callee_prepost:pre_post ~formals ~actuals astate =
|
||||
L.d_printfln "Applying pre/post for %a(%a):@\n%a" Procname.pp callee_proc_name
|
||||
(Pp.seq ~sep:"," Var.pp) formals pp pre_post ;
|
||||
let empty_call_state =
|
||||
{astate; subst= AddressMap.empty; rev_subst= AddressMap.empty; visited= AddressSet.empty}
|
||||
in
|
||||
(* read the precondition *)
|
||||
match
|
||||
materialize_pre callee_proc_name call_location pre_post ~formals ~actuals empty_call_state
|
||||
with
|
||||
| exception Contradiction 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_contradiction reason ;
|
||||
Ok None
|
||||
| Error _ as error ->
|
||||
(* error: the function call requires to read some state known to be invalid *)
|
||||
error
|
||||
| Ok call_state -> (
|
||||
L.d_printfln "Pre applied successfully. call_state=%a" pp_call_state call_state ;
|
||||
match
|
||||
let open IResult.Let_syntax in
|
||||
let+ astate = check_all_valid callee_proc_name call_location pre_post call_state in
|
||||
(* reset [visited] *)
|
||||
let call_state = {call_state with astate; visited= AddressSet.empty} in
|
||||
(* apply the postcondition *)
|
||||
apply_post callee_proc_name call_location pre_post ~formals ~actuals call_state
|
||||
with
|
||||
| Ok post ->
|
||||
Ok (Some post)
|
||||
| exception Contradiction reason ->
|
||||
L.d_printfln "Cannot apply post-condition: %a" pp_contradiction reason ;
|
||||
Ok None
|
||||
| Error _ as error ->
|
||||
error )
|
@ -0,0 +1,22 @@
|
||||
(*
|
||||
* 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
|
||||
open PulseBasicInterface
|
||||
|
||||
val apply_prepost :
|
||||
Procname.t
|
||||
-> Location.t
|
||||
-> callee_prepost:PulseAbductiveDomain.t
|
||||
-> formals:Var.t list
|
||||
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
|
||||
-> PulseAbductiveDomain.t
|
||||
-> ( (PulseAbductiveDomain.t * (AbstractValue.t * ValueHistory.t) option) option
|
||||
, Diagnostic.t * PulseAbductiveDomain.t )
|
||||
result
|
||||
(** return the abstract state after the call along with an optional return value, or [None] if the
|
||||
precondition could not be satisfied (e.g. some aliasing constraints were not satisfied) *)
|
Loading…
Reference in new issue