You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

438 lines
17 KiB

(*
* 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
module L = Logging
open PulseBasicInterface
module BaseDomain = PulseBaseDomain
module BaseStack = PulseBaseStack
module BaseMemory = PulseBaseMemory
module BaseAddressAttributes = PulseBaseAddressAttributes
(** signature common to the "normal" [Domain], representing the post at the current program point,
and the inverted [PreDomain], representing the inferred pre-condition*)
module type BaseDomainSig = sig
(* private because the lattice is not the same for preconditions and postconditions so we don't
want to confuse them *)
type t = private BaseDomain.t
val empty : t
val update : ?stack:BaseStack.t -> ?heap:BaseMemory.t -> ?attrs:BaseAddressAttributes.t -> t -> t
val filter_addr : f:(AbstractValue.t -> bool) -> t -> t
(** filter both heap and attrs *)
val filter_addr_with_discarded_addrs :
f:(AbstractValue.t -> bool) -> t -> t * AbstractValue.t list
(** compute new state containing only reachable addresses in its heap and attributes, as well as
the list of discarded unreachable addresses *)
val pp : F.formatter -> t -> unit
end
(* just to expose record field names without having to type
[BaseDomain.heap] *)
type base_domain = BaseDomain.t =
{heap: BaseMemory.t; stack: BaseStack.t; attrs: BaseAddressAttributes.t}
(** operations common to [Domain] and [PreDomain], see also the [BaseDomain] signature *)
module BaseDomainCommon = struct
let update ?stack ?heap ?attrs foot =
let new_stack, new_heap, new_attrs =
( Option.value ~default:foot.stack stack
, Option.value ~default:foot.heap heap
, Option.value ~default:foot.attrs attrs )
in
if
phys_equal new_stack foot.stack && phys_equal new_heap foot.heap
&& phys_equal new_attrs foot.attrs
then foot
else {stack= new_stack; heap= new_heap; attrs= new_attrs}
let filter_addr ~f foot =
let heap' = BaseMemory.filter (fun address _ -> f address) foot.heap in
let attrs' = BaseAddressAttributes.filter (fun address _ -> f address) foot.attrs in
update ~heap:heap' ~attrs:attrs' foot
let filter_addr_with_discarded_addrs ~f foot =
let heap' = BaseMemory.filter (fun address _ -> f address) foot.heap in
let attrs', discarded_addresses =
BaseAddressAttributes.filter_with_discarded_addrs (fun address _ -> f address) foot.attrs
in
(update ~heap:heap' ~attrs:attrs' foot, discarded_addresses)
end
(** represents the post abstract state at each program point *)
module PostDomain : BaseDomainSig = struct
include BaseDomainCommon
include BaseDomain
end
(* NOTE: [PreDomain] and [Domain] theoretically differ in that [PreDomain] should be the inverted lattice of [Domain], but since we never actually join states or check implication the two collapse into one. *)
(** represents the inferred pre-condition at each program point, biabduction style *)
module PreDomain : BaseDomainSig = PostDomain
(** biabduction-style pre/post state + skipped calls *)
type t =
{ post: PostDomain.t (** state at the current program point*)
; pre: PreDomain.t (** inferred pre at the current program point *)
; skipped_calls: SkippedCalls.t (** set of skipped calls *)
; path_condition: PathCondition.t }
let pp f {post; pre; path_condition; skipped_calls} =
F.fprintf f "@[<v>%a@;%a@;PRE=[%a]@;skipped_calls=%a@]" PathCondition.pp path_condition
PostDomain.pp post PreDomain.pp pre SkippedCalls.pp skipped_calls
let set_path_condition path_condition astate = {astate with path_condition}
let leq ~lhs ~rhs =
SkippedCalls.leq ~lhs:lhs.skipped_calls ~rhs:rhs.skipped_calls
&&
match
BaseDomain.isograph_map BaseDomain.empty_mapping
~lhs:(rhs.pre :> BaseDomain.t)
~rhs:(lhs.pre :> BaseDomain.t)
with
| NotIsomorphic ->
false
| IsomorphicUpTo foot_mapping ->
BaseDomain.is_isograph foot_mapping
~lhs:(lhs.post :> BaseDomain.t)
~rhs:(rhs.post :> BaseDomain.t)
module Stack = struct
let is_abducible astate var =
(* HACK: formals are pre-registered in the initial state *)
BaseStack.mem var (astate.pre :> base_domain).stack || Var.is_global var
(** [astate] with [astate.post.stack = f astate.post.stack] *)
let map_post_stack ~f astate =
let new_post = PostDomain.update astate.post ~stack:(f (astate.post :> base_domain).stack) in
if phys_equal new_post astate.post then astate else {astate with post= new_post}
let eval origin var astate =
match BaseStack.find_opt var (astate.post :> base_domain).stack with
| Some addr_hist ->
(astate, addr_hist)
| None ->
let addr = AbstractValue.mk_fresh () in
let addr_hist = (addr, origin) in
let post_stack = BaseStack.add var addr_hist (astate.post :> base_domain).stack in
let pre =
(* do not overwrite values of variables already in the pre *)
if (not (BaseStack.mem var (astate.pre :> base_domain).stack)) && is_abducible astate var
then
(* HACK: do not record the history of values in the pre as they are unused *)
let foot_stack = BaseStack.add var (addr, []) (astate.pre :> base_domain).stack in
let foot_heap = BaseMemory.register_address addr (astate.pre :> base_domain).heap in
PreDomain.update ~stack:foot_stack ~heap:foot_heap astate.pre
else astate.pre
in
( { post= PostDomain.update astate.post ~stack:post_stack
; pre
; skipped_calls= astate.skipped_calls
; path_condition= astate.path_condition }
, addr_hist )
let add var addr_loc_opt astate =
map_post_stack astate ~f:(fun stack -> BaseStack.add var addr_loc_opt stack)
let remove_vars vars astate =
let vars_to_remove =
let is_in_pre var astate = BaseStack.mem var (astate.pre :> base_domain).stack in
List.filter vars ~f:(fun var -> not (is_in_pre var astate))
in
map_post_stack astate ~f:(fun stack ->
BaseStack.filter (fun var _ -> not (List.mem ~equal:Var.equal vars_to_remove var)) stack )
let fold f astate accum = BaseStack.fold f (astate.post :> base_domain).stack accum
let find_opt var astate = BaseStack.find_opt var (astate.post :> base_domain).stack
let mem var astate = BaseStack.mem var (astate.post :> base_domain).stack
let exists f astate = BaseStack.exists f (astate.post :> base_domain).stack
let keys astate =
BaseStack.fold (fun key _ keys -> key :: keys) (astate.post :> base_domain).stack []
end
module AddressAttributes = struct
open IResult.Let_syntax
(** if [address] is in [pre] then add the attribute [attr] *)
let abduce_attribute address attribute astate =
L.d_printfln "Abducing %a:%a" AbstractValue.pp address Attribute.pp attribute ;
let new_pre =
if BaseMemory.mem address (astate.pre :> base_domain).heap then
PreDomain.update astate.pre
~attrs:(BaseAddressAttributes.add_one address attribute (astate.pre :> base_domain).attrs)
else astate.pre
in
if phys_equal new_pre astate.pre then astate else {astate with pre= new_pre}
let check_valid access_trace addr astate =
let+ () = BaseAddressAttributes.check_valid addr (astate.post :> base_domain).attrs in
(* if [address] is in [pre] and it should be valid then that fact goes in the precondition *)
abduce_attribute addr (MustBeValid access_trace) astate
(** [astate] with [astate.post.attrs = f astate.post.attrs] *)
let map_post_attrs ~f astate =
let new_post = PostDomain.update astate.post ~attrs:(f (astate.post :> base_domain).attrs) in
if phys_equal new_post astate.post then astate else {astate with post= new_post}
let invalidate address invalidation location astate =
map_post_attrs astate ~f:(BaseAddressAttributes.invalidate address invalidation location)
let allocate procname address location astate =
map_post_attrs astate ~f:(BaseAddressAttributes.allocate procname address location)
let add_dynamic_type typ address astate =
map_post_attrs astate ~f:(BaseAddressAttributes.add_dynamic_type typ address)
let remove_allocation_attr address astate =
map_post_attrs astate ~f:(BaseAddressAttributes.remove_allocation_attr address)
let add_one address attributes astate =
map_post_attrs astate ~f:(BaseAddressAttributes.add_one address attributes)
let get_closure_proc_name addr astate =
BaseAddressAttributes.get_closure_proc_name addr (astate.post :> base_domain).attrs
let std_vector_reserve addr astate =
map_post_attrs astate ~f:(BaseAddressAttributes.std_vector_reserve addr)
let is_std_vector_reserved addr astate =
BaseAddressAttributes.is_std_vector_reserved addr (astate.post :> base_domain).attrs
let is_end_iterator addr astate =
BaseAddressAttributes.is_end_iterator addr (astate.post :> base_domain).attrs
let abduce_and_add value attrs astate =
Attributes.fold attrs ~init:astate ~f:(fun astate attr ->
let astate =
if Attribute.is_suitable_for_pre attr then abduce_attribute value attr astate else astate
in
add_one value attr astate )
let find_opt address astate =
BaseAddressAttributes.find_opt address (astate.post :> base_domain).attrs
end
module Memory = struct
module Access = BaseMemory.Access
module Edges = BaseMemory.Edges
(** [astate] with [astate.post.heap = f astate.post.heap] *)
let map_post_heap ~f astate =
let new_post = PostDomain.update astate.post ~heap:(f (astate.post :> base_domain).heap) in
if phys_equal new_post astate.post then astate else {astate with post= new_post}
let add_edge (addr, history) access new_addr_hist location astate =
map_post_heap astate ~f:(BaseMemory.add_edge addr access new_addr_hist)
|> AddressAttributes.map_post_attrs
~f:(BaseAddressAttributes.add_one addr (WrittenTo (Trace.Immediate {location; history})))
let find_edge_opt address access astate =
BaseMemory.find_edge_opt address access (astate.post :> base_domain).heap
let eval_edge (addr_src, hist_src) access astate =
match find_edge_opt addr_src access astate with
| Some addr_hist_dst ->
(astate, addr_hist_dst)
| None ->
let addr_dst = AbstractValue.mk_fresh () in
let addr_hist_dst = (addr_dst, hist_src) in
let post_heap =
BaseMemory.add_edge addr_src access addr_hist_dst (astate.post :> base_domain).heap
in
let foot_heap =
if BaseMemory.mem addr_src (astate.pre :> base_domain).heap then
(* HACK: do not record the history of values in the pre as they are unused *)
BaseMemory.add_edge addr_src access (addr_dst, []) (astate.pre :> base_domain).heap
|> BaseMemory.register_address addr_dst
else (astate.pre :> base_domain).heap
in
( { post= PostDomain.update astate.post ~heap:post_heap
; pre= PreDomain.update astate.pre ~heap:foot_heap
; skipped_calls= astate.skipped_calls
; path_condition= astate.path_condition }
, addr_hist_dst )
let find_opt address astate = BaseMemory.find_opt address (astate.post :> base_domain).heap
end
let mk_initial proc_desc =
(* HACK: save the formals in the stacks of the pre and the post to remember which local variables
correspond to formals *)
let formals =
let proc_name = Procdesc.get_proc_name proc_desc in
let location = Procdesc.get_loc proc_desc in
Procdesc.get_formals proc_desc
|> List.map ~f:(fun (mangled, _) ->
let pvar = Pvar.mk mangled proc_name in
( Var.of_pvar pvar
, (AbstractValue.mk_fresh (), [ValueHistory.FormalDeclared (pvar, location)]) ) )
in
let initial_stack =
List.fold formals ~init:(PreDomain.empty :> BaseDomain.t).stack
~f:(fun stack (formal, addr_loc) -> BaseStack.add formal addr_loc stack)
in
let pre =
let initial_heap =
List.fold formals ~init:(PreDomain.empty :> base_domain).heap ~f:(fun heap (_, (addr, _)) ->
BaseMemory.register_address addr heap )
in
PreDomain.update ~stack:initial_stack ~heap:initial_heap PreDomain.empty
in
let post = PostDomain.update ~stack:initial_stack PostDomain.empty in
{pre; post; skipped_calls= SkippedCalls.empty; path_condition= PathCondition.true_}
let add_skipped_call pname trace astate =
let new_skipped_calls = SkippedCalls.add pname trace astate.skipped_calls in
if phys_equal new_skipped_calls astate.skipped_calls then astate
else {astate with skipped_calls= new_skipped_calls}
let add_skipped_calls new_skipped_calls astate =
(* favor calls we already knew about somewhat arbitrarily *)
let skipped_calls =
SkippedCalls.union
(fun _ orig_call _new_call -> Some orig_call)
astate.skipped_calls new_skipped_calls
in
if phys_equal skipped_calls astate.skipped_calls then astate else {astate with skipped_calls}
let discard_unreachable ({pre; post} as astate) =
let pre_addresses = BaseDomain.reachable_addresses (pre :> BaseDomain.t) in
let pre_new =
PreDomain.filter_addr ~f:(fun address -> AbstractValue.Set.mem address pre_addresses) pre
in
let post_addresses = BaseDomain.reachable_addresses (post :> BaseDomain.t) in
let live_addresses = AbstractValue.Set.union pre_addresses post_addresses in
let post_new, discard_addresses =
PostDomain.filter_addr_with_discarded_addrs
~f:(fun address -> AbstractValue.Set.mem address live_addresses)
post
in
(* note: we don't call {!PulsePathCondition.simplify} *)
let astate =
if phys_equal pre_new pre && phys_equal post_new post then astate
else {astate with pre= pre_new; post= post_new}
in
(astate, live_addresses, discard_addresses)
let is_local var astate = not (Var.is_return var || Stack.is_abducible astate var)
let set_post_edges addr edges astate = Memory.map_post_heap astate ~f:(BaseMemory.add addr edges)
(* {3 Helper functions to traverse the two maps at once } *)
let find_post_cell_opt addr {post} = BaseDomain.find_cell_opt addr (post :> BaseDomain.t)
let set_post_cell (addr, history) (edges, attr_set) location astate =
set_post_edges addr edges astate
|> AddressAttributes.map_post_attrs ~f:(fun attrs ->
BaseAddressAttributes.add_one addr (WrittenTo (Trace.Immediate {location; history})) attrs
|> BaseAddressAttributes.add addr attr_set )
let filter_for_summary astate =
let post_stack =
BaseStack.filter
(fun var _ -> Var.appears_in_source_code var && not (is_local var astate))
(astate.post :> BaseDomain.t).stack
in
(* deregister empty edges *)
let deregister_empty heap =
BaseMemory.filter (fun _addr edges -> not (BaseMemory.Edges.is_empty edges)) heap
in
let pre_heap = deregister_empty (astate.pre :> base_domain).heap in
let post_heap = deregister_empty (astate.post :> base_domain).heap in
{ astate with
pre= PreDomain.update astate.pre ~heap:pre_heap
; post= PostDomain.update ~stack:post_stack ~heap:post_heap astate.post }
let add_out_of_scope_attribute addr pvar location history heap typ =
BaseAddressAttributes.add_one addr
(Invalid (GoneOutOfScope (pvar, typ), Immediate {location; history}))
heap
(** invalidate local variables going out of scope *)
let invalidate_locals pdesc astate : t =
let attrs : BaseAddressAttributes.t = (astate.post :> BaseDomain.t).attrs in
let attrs' =
BaseAddressAttributes.fold
(fun addr attrs acc ->
Attributes.get_address_of_stack_variable attrs
|> Option.value_map ~default:acc ~f:(fun (var, location, history) ->
let get_local_typ_opt pvar =
Procdesc.get_locals pdesc
|> List.find_map ~f:(fun ProcAttributes.{name; typ} ->
if Mangled.equal name (Pvar.get_name pvar) then Some typ else None )
in
match var with
| Var.ProgramVar pvar ->
get_local_typ_opt pvar
|> Option.value_map ~default:acc
~f:(add_out_of_scope_attribute addr pvar location history acc)
| _ ->
acc ) )
attrs attrs
in
if phys_equal attrs attrs' then astate
else {astate with post= PostDomain.update astate.post ~attrs:attrs'}
let of_post pdesc astate =
let astate = filter_for_summary astate in
let astate, live_addresses, _ = discard_unreachable astate in
let astate =
(* this also forces the lazy path condition for safe serialization *)
{astate with path_condition= PathCondition.simplify ~keep:live_addresses astate.path_condition}
in
invalidate_locals pdesc astate
let get_pre {pre} = (pre :> BaseDomain.t)
let get_post {post} = (post :> BaseDomain.t)