[pulse] Distinguish exit state at top level

Summary:
This diff lifts the `PulseAbductiveDomain.t` in `PulseExecutionState` by tracking whether the program continues the analysis normally or exits unusually (e.g. by calling `exit` or `throw`):

```
type exec_state =
  | ContinueProgram of PulseAbductiveDomain.t  (** represents the state at the program point *)
  | ExitProgram of PulseAbductiveDomain.t
      (** represents the state originating at exit/divergence. *)

```

Now, Pulse's actual domain is tracked by `PulseExecutionState` and as soon as we try to analyze an instruction at `ExitProgram`, we simply return its state.

The aim is to recover the state at the time of the exit, rather than simply ignoring them (i.e. returning  empty disjuncts). This allows us to get rid of some FNs that we were not able to detect before. Moreover, it also allows the impurity analysis to be more precise since we will know how the state changed up to exit.

TODO:
- Impurity analysis needs to be improved to consider functions that simply exit as impure.
- The next goal is to handle error state similarly so that when pulse finds an error, we recover the state at the error location (and potentially continue to analyze?).

Disclaimer: currently, we handle throw statements like exit (as was the case before). However, this is not correct. Ideally, control flow from throw nodes follows catch nodes rather than exiting the program entirely.

Reviewed By: jvillard

Differential Revision: D20791747

fbshipit-source-id: df9e5445a
master
Ezgi Çiçek 5 years ago committed by Facebook GitHub Bot
parent f0afa18cbf
commit 5a2b285fff

@ -138,9 +138,12 @@ let is_modeled_pure tenv pname =
(** Given Pulse summary, extract impurity info, i.e. parameters and global variables that are (** Given Pulse summary, extract impurity info, i.e. parameters and global variables that are
modified by the function and skipped functions. *) modified by the function and skipped functions. *)
let extract_impurity tenv pdesc pre_post : ImpurityDomain.t = let extract_impurity tenv pdesc (exec_state : PulseExecutionState.t) : ImpurityDomain.t =
let pre_heap = (AbductiveDomain.PrePost.get_pre pre_post).BaseDomain.heap in match exec_state with
let post = AbductiveDomain.PrePost.get_post pre_post in | ExitProgram astate | ContinueProgram astate ->
(* TODO: consider impure even though the program only exits with pre=post *)
let pre_heap = (PulseAbductiveDomain.get_pre astate).BaseDomain.heap in
let post = PulseAbductiveDomain.get_post astate in
let post_stack = post.BaseDomain.stack in let post_stack = post.BaseDomain.stack in
let pname = Procdesc.get_proc_name pdesc in let pname = Procdesc.get_proc_name pdesc in
let modified_params = let modified_params =
@ -148,7 +151,7 @@ let extract_impurity tenv pdesc pre_post : ImpurityDomain.t =
in in
let modified_globals = get_modified_globals pre_heap post post_stack in let modified_globals = get_modified_globals pre_heap post post_stack in
let skipped_calls = let skipped_calls =
AbductiveDomain.PrePost.get_skipped_calls pre_post PulseAbductiveDomain.get_skipped_calls astate
|> PulseAbductiveDomain.SkippedCalls.filter (fun proc_name _ -> |> PulseAbductiveDomain.SkippedCalls.filter (fun proc_name _ ->
Purity.should_report proc_name && not (is_modeled_pure tenv proc_name) ) Purity.should_report proc_name && not (is_modeled_pure tenv proc_name) )
in in
@ -177,8 +180,8 @@ let checker {exe_env; Callbacks.summary} : Summary.t =
impure_fun_desc impure_fun_desc
| Some pre_posts -> | Some pre_posts ->
let (ImpurityDomain.{modified_globals; modified_params; skipped_calls} as impurity_astate) = let (ImpurityDomain.{modified_globals; modified_params; skipped_calls} as impurity_astate) =
List.fold pre_posts ~init:ImpurityDomain.pure ~f:(fun acc pre_post -> List.fold pre_posts ~init:ImpurityDomain.pure ~f:(fun acc exec_state ->
let modified = extract_impurity tenv pdesc pre_post in let modified = extract_impurity tenv pdesc exec_state in
ImpurityDomain.join acc modified ) ImpurityDomain.join acc modified )
in in
if Purity.should_report proc_name && not (ImpurityDomain.is_pure impurity_astate) then if Purity.should_report proc_name && not (ImpurityDomain.is_pure impurity_astate) then

@ -27,6 +27,10 @@ let check_error summary = function
raise_notrace AbstractDomain.Stop_analysis raise_notrace AbstractDomain.Stop_analysis
let check_error_continue summary result =
PulseExecutionState.ContinueProgram (check_error summary result)
let proc_name_of_call call_exp = let proc_name_of_call call_exp =
match (call_exp : Exp.t) with match (call_exp : Exp.t) with
| Const (Cfun proc_name) | Closure {name= proc_name} -> | Const (Cfun proc_name) | Closure {name= proc_name} ->
@ -39,7 +43,7 @@ type get_formals = Procname.t -> (Pvar.t * Typ.t) list option
module PulseTransferFunctions = struct module PulseTransferFunctions = struct
module CFG = ProcCfg.Normal module CFG = ProcCfg.Normal
module Domain = PulseAbductiveDomain module Domain = PulseExecutionState
type extras = get_formals type extras = get_formals
@ -50,9 +54,9 @@ module PulseTransferFunctions = struct
PulseOperations.call ~caller_summary call_loc callee_pname ~ret ~actuals ~formals_opt astate PulseOperations.call ~caller_summary call_loc callee_pname ~ret ~actuals ~formals_opt astate
| _ -> | _ ->
L.d_printfln "Skipping indirect call %a@\n" Exp.pp call_exp ; L.d_printfln "Skipping indirect call %a@\n" Exp.pp call_exp ;
Ok PulseOperations.unknown_call call_loc (SkippedUnknownCall call_exp) ~ret ~actuals
[ PulseOperations.unknown_call call_loc (SkippedUnknownCall call_exp) ~ret ~actuals ~formals_opt:None astate
~formals_opt:None astate ] |> PulseOperations.ok_continue
(** has an object just gone out of scope? *) (** has an object just gone out of scope? *)
@ -70,11 +74,16 @@ module PulseTransferFunctions = struct
(** [out_of_scope_access_expr] has just gone out of scope and in now invalid *) (** [out_of_scope_access_expr] has just gone out of scope and in now invalid *)
let exec_object_out_of_scope call_loc (pvar, typ) astate = let exec_object_out_of_scope call_loc (pvar, typ) exec_state =
match exec_state with
| PulseExecutionState.ContinueProgram astate ->
let gone_out_of_scope = Invalidation.GoneOutOfScope (pvar, typ) in let gone_out_of_scope = Invalidation.GoneOutOfScope (pvar, typ) in
(* invalidate [&x] *)
let* astate, out_of_scope_base = PulseOperations.eval call_loc (Exp.Lvar pvar) astate in let* astate, out_of_scope_base = PulseOperations.eval call_loc (Exp.Lvar pvar) astate in
(* invalidate [&x] *)
PulseOperations.invalidate call_loc gone_out_of_scope out_of_scope_base astate PulseOperations.invalidate call_loc gone_out_of_scope out_of_scope_base astate
>>| PulseExecutionState.continue
| PulseExecutionState.ExitProgram _ ->
Ok exec_state
let dispatch_call tenv summary ret call_exp actuals call_loc flags get_formals astate = let dispatch_call tenv summary ret call_exp actuals call_loc flags get_formals astate =
@ -99,7 +108,7 @@ module PulseTransferFunctions = struct
None None
in in
(* do interprocedural call then destroy objects going out of scope *) (* do interprocedural call then destroy objects going out of scope *)
let posts = let exec_state_res =
match model with match model with
| Some (model, callee_procname) -> | Some (model, callee_procname) ->
L.d_printfln "Found model for call@\n" ; L.d_printfln "Found model for call@\n" ;
@ -121,15 +130,21 @@ module PulseTransferFunctions = struct
match get_out_of_scope_object call_exp actuals flags with match get_out_of_scope_object call_exp actuals flags with
| Some pvar_typ -> | Some pvar_typ ->
L.d_printfln "%a is going out of scope" Pvar.pp_value (fst pvar_typ) ; L.d_printfln "%a is going out of scope" Pvar.pp_value (fst pvar_typ) ;
let* posts = posts in let* exec_states = exec_state_res in
List.map posts ~f:(fun astate -> exec_object_out_of_scope call_loc pvar_typ astate) List.map exec_states ~f:(fun exec_state ->
exec_object_out_of_scope call_loc pvar_typ exec_state )
|> Result.all |> Result.all
| None -> | None ->
posts exec_state_res
let exec_instr (astate : Domain.t) {tenv; ProcData.summary; extras= get_formals} _cfg_node let exec_instr (astate : Domain.t) {tenv; ProcData.summary; extras= get_formals} _cfg_node
(instr : Sil.instr) = (instr : Sil.instr) : Domain.t list =
match astate with
| ExitProgram _ ->
(* program already exited, simply propagate the exited state upwards *)
[astate]
| ContinueProgram astate -> (
match instr with match instr with
| Load {id= lhs_id; e= rhs_exp; loc} -> | Load {id= lhs_id; e= rhs_exp; loc} ->
(* [lhs_id := *rhs_exp] *) (* [lhs_id := *rhs_exp] *)
@ -137,7 +152,7 @@ module PulseTransferFunctions = struct
let+ astate, rhs_addr_hist = PulseOperations.eval_deref loc rhs_exp astate in let+ astate, rhs_addr_hist = PulseOperations.eval_deref loc rhs_exp astate in
PulseOperations.write_id lhs_id rhs_addr_hist astate PulseOperations.write_id lhs_id rhs_addr_hist astate
in in
[check_error summary result] [check_error_continue summary result]
| Store {e1= lhs_exp; e2= rhs_exp; loc} -> | Store {e1= lhs_exp; e2= rhs_exp; loc} ->
(* [*lhs_exp := rhs_exp] *) (* [*lhs_exp := rhs_exp] *)
let event = ValueHistory.Assignment loc in let event = ValueHistory.Assignment loc in
@ -156,24 +171,26 @@ module PulseTransferFunctions = struct
| _ -> | _ ->
Ok astate Ok astate
in in
[check_error summary result] [check_error_continue summary result]
| Prune (condition, loc, is_then_branch, if_kind) -> | Prune (condition, loc, is_then_branch, if_kind) ->
let post, cond_satisfiable = let exec_state, cond_satisfiable =
PulseOperations.prune ~is_then_branch if_kind loc ~condition astate |> check_error summary PulseOperations.prune ~is_then_branch if_kind loc ~condition astate
|> check_error summary
in in
if cond_satisfiable then (* [condition] is true or unknown value: go into the branch *) if cond_satisfiable then
[post] (* [condition] is true or unknown value: go into the branch *)
[Domain.continue exec_state]
else (* [condition] is known to be unsatisfiable: prune path *) [] else (* [condition] is known to be unsatisfiable: prune path *) []
| Call (ret, call_exp, actuals, loc, call_flags) -> | Call (ret, call_exp, actuals, loc, call_flags) ->
dispatch_call tenv summary ret call_exp actuals loc call_flags get_formals astate dispatch_call tenv summary ret call_exp actuals loc call_flags get_formals astate
|> check_error summary |> check_error summary
| Metadata (ExitScope (vars, location)) -> | Metadata (ExitScope (vars, location)) ->
let astate = PulseOperations.remove_vars vars location astate in let astate = PulseOperations.remove_vars vars location astate in
[check_error summary astate] [check_error_continue summary astate]
| Metadata (VariableLifetimeBegins (pvar, _, location)) -> | Metadata (VariableLifetimeBegins (pvar, _, location)) ->
[PulseOperations.realloc_pvar pvar location astate] [PulseOperations.realloc_pvar pvar location astate |> Domain.continue]
| Metadata (Abstract _ | Nullify _ | Skip) -> | Metadata (Abstract _ | Nullify _ | Skip) ->
[astate] [Domain.ContinueProgram astate] )
let pp_session_name _node fmt = F.pp_print_string fmt "Pulse" let pp_session_name _node fmt = F.pp_print_string fmt "Pulse"
@ -197,7 +214,7 @@ let checker {Callbacks.exe_env; summary} =
AbstractValue.init () ; AbstractValue.init () ;
let pdesc = Summary.get_proc_desc summary in let pdesc = Summary.get_proc_desc summary in
let initial = let initial =
DisjunctiveTransferFunctions.Disjuncts.singleton (PulseAbductiveDomain.mk_initial pdesc) DisjunctiveTransferFunctions.Disjuncts.singleton (PulseExecutionState.mk_initial pdesc)
in in
let get_formals callee_pname = let get_formals callee_pname =
Ondemand.get_proc_desc callee_pname |> Option.map ~f:Procdesc.get_pvar_formals Ondemand.get_proc_desc callee_pname |> Option.map ~f:Procdesc.get_pvar_formals

@ -362,11 +362,6 @@ let set_post_cell (addr, history) (edges_map, attr_set) location astate =
|> BaseAddressAttributes.add addr attr_set ) |> BaseAddressAttributes.add addr attr_set )
module PrePost = struct
type domain_t = t
type t = domain_t
let filter_for_summary astate = let filter_for_summary astate =
let post_stack = let post_stack =
BaseStack.filter BaseStack.filter
@ -430,7 +425,7 @@ module PrePost = struct
(** stuff we carry around when computing the result of applying one pre/post pair *) (** stuff we carry around when computing the result of applying one pre/post pair *)
type call_state = type call_state =
{ astate: domain_t (** caller's abstract state computed so far *) { astate: t (** caller's abstract state computed so far *)
; subst: (AbstractValue.t * ValueHistory.t) AddressMap.t ; subst: (AbstractValue.t * ValueHistory.t) AddressMap.t
(** translation from callee addresses to caller addresses and their caller histories *) (** translation from callee addresses to caller addresses and their caller histories *)
; rev_subst: AbstractValue.t AddressMap.t ; rev_subst: AbstractValue.t AddressMap.t
@ -463,8 +458,8 @@ module PrePost = struct
; call_state: call_state } ; call_state: call_state }
(** raised when the precondition and the current state disagree on the aliasing, i.e. some (** 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 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 single address [caller_addr] in the caller's current state. Typically raised when calling
calling [foo(z,z)] where the spec for [foo(x,y)] says that [x] and [y] are disjoint. *) [foo(z,z)] where the spec for [foo(x,y)] says that [x] and [y] are disjoint. *)
| CItv of | CItv of
{ addr_caller: AbstractValue.t { addr_caller: AbstractValue.t
; addr_callee: AbstractValue.t ; addr_callee: AbstractValue.t
@ -493,8 +488,8 @@ module PrePost = struct
F.fprintf fmt F.fprintf fmt
"caller addr %a%a but callee addr %a%a; %a=%a is unsatisfiable@\n\ "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) 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 arith_caller AbstractValue.pp addr_callee (Pp.option CItv.pp) arith_callee AbstractValue.pp
AbstractValue.pp addr_caller AbstractValue.pp addr_callee pp_call_state call_state addr_caller AbstractValue.pp addr_callee pp_call_state call_state
| ArithmeticBo {addr_caller; addr_callee; arith_callee; call_state} -> | ArithmeticBo {addr_caller; addr_callee; arith_callee; call_state} ->
F.fprintf fmt F.fprintf fmt
"callee addr %a%a is incompatible with caller addr %a's arithmetic constraints@\n\ "callee addr %a%a is incompatible with caller addr %a's arithmetic constraints@\n\
@ -555,8 +550,8 @@ module PrePost = struct
(** Materialize the (abstract memory) subgraph of [pre] reachable from [addr_pre] in (** 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 [call_state.astate] starting from address [addr_caller]. Report an error if some invalid
addresses are traversed in the process. *) addresses are traversed in the process. *)
let rec materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre let rec materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre ~addr_hist_caller
~addr_hist_caller call_state = call_state =
match visit call_state ~addr_callee:addr_pre ~addr_hist_caller with match visit call_state ~addr_callee:addr_pre ~addr_hist_caller with
| `AlreadyVisited, call_state -> | `AlreadyVisited, call_state ->
Ok call_state Ok call_state
@ -565,20 +560,18 @@ module PrePost = struct
| None -> | None ->
Ok call_state Ok call_state
| Some edges_pre -> | Some edges_pre ->
Container.fold_result Container.fold_result ~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Memory.Edges.fold)
~fold:(IContainer.fold_of_pervasives_map_fold ~fold:Memory.Edges.fold)
~init:call_state edges_pre ~f:(fun call_state (access, (addr_pre_dest, _)) -> ~init:call_state edges_pre ~f:(fun call_state (access, (addr_pre_dest, _)) ->
let astate, addr_hist_dest_caller = let astate, addr_hist_dest_caller =
Memory.eval_edge addr_hist_caller access call_state.astate Memory.eval_edge addr_hist_caller access call_state.astate
in in
let call_state = {call_state with astate} in let call_state = {call_state with astate} in
materialize_pre_from_address callee_proc_name call_location ~pre materialize_pre_from_address callee_proc_name call_location ~pre ~addr_pre:addr_pre_dest
~addr_pre:addr_pre_dest ~addr_hist_caller:addr_hist_dest_caller call_state ) ) ~addr_hist_caller:addr_hist_dest_caller call_state ) )
(** materialize subgraph of [pre] rooted at the address represented by a [formal] parameter that (** materialize subgraph of [pre] rooted at the address represented by a [formal] parameter that has
has been instantiated with the corresponding [actual] into the current state been instantiated with the corresponding [actual] into the current state [call_state.astate] *)
[call_state.astate] *)
let materialize_pre_from_actual callee_proc_name call_location ~pre ~formal ~actual call_state = 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) ; L.d_printfln "Materializing PRE from [%a <- %a]" Var.pp formal AbstractValue.pp (fst actual) ;
(let open IOption.Let_syntax in (let open IOption.Let_syntax in
@ -673,8 +666,7 @@ module PrePost = struct
Attribute.BoItv itv' Attribute.BoItv itv'
| Bottom -> | Bottom ->
raise raise
(Contradiction (ArithmeticBo {addr_callee; addr_caller; arith_callee= itv; call_state})) (Contradiction (ArithmeticBo {addr_callee; addr_caller; arith_callee= itv; call_state})) )
)
| AddressOfCppTemporary _ | AddressOfCppTemporary _
| AddressOfStackVariable _ | AddressOfStackVariable _
| Allocated _ | Allocated _
@ -722,8 +714,8 @@ module PrePost = struct
let apply_arithmetic_constraints callee_proc_name call_location pre_post call_state = 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 one_address_sat addr_callee callee_attrs (addr_caller, caller_history) call_state =
let subst, attrs_caller = let subst, attrs_caller =
add_call_to_attributes callee_proc_name call_location ~addr_callee ~addr_caller add_call_to_attributes callee_proc_name call_location ~addr_callee ~addr_caller caller_history
caller_history callee_attrs call_state callee_attrs call_state
in in
let astate = AddressAttributes.abduce_and_add addr_caller attrs_caller call_state.astate 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 if phys_equal subst call_state.subst && phys_equal astate call_state.astate then call_state
@ -805,9 +797,7 @@ module PrePost = struct
add_call_to_attributes ~addr_callee ~addr_caller callee_proc_name call_loc hist_caller add_call_to_attributes ~addr_callee ~addr_caller callee_proc_name call_loc hist_caller
attrs_post call_state attrs_post call_state
in in
let astate = let astate = AddressAttributes.abduce_and_add addr_caller attrs_post_caller call_state.astate in
AddressAttributes.abduce_and_add addr_caller attrs_post_caller call_state.astate
in
{call_state with subst; astate} {call_state with subst; astate}
in in
let heap = (call_state.astate.post :> base_domain).heap in let heap = (call_state.astate.post :> base_domain).heap in
@ -866,14 +856,12 @@ module PrePost = struct
| None -> | None ->
call_state call_state
| Some ((edges_post, _attrs_post) as cell_post) -> | Some ((edges_post, _attrs_post) as cell_post) ->
let edges_pre_opt = let edges_pre_opt = BaseMemory.find_opt addr_callee (pre :> BaseDomain.t).BaseDomain.heap in
BaseMemory.find_opt addr_callee (pre :> BaseDomain.t).BaseDomain.heap
in
let call_state_after_post = let call_state_after_post =
if is_cell_read_only ~edges_pre_opt ~cell_post then call_state if is_cell_read_only ~edges_pre_opt ~cell_post then call_state
else else
record_post_cell callee_proc_name call_loc ~addr_callee ~edges_pre_opt record_post_cell callee_proc_name call_loc ~addr_callee ~edges_pre_opt ~addr_hist_caller
~addr_hist_caller ~cell_post call_state ~cell_post call_state
in in
IContainer.fold_of_pervasives_map_fold ~fold:Memory.Edges.fold ~init:call_state_after_post 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, _)) -> edges_post ~f:(fun call_state (_access, (addr_callee_dest, _)) ->
@ -881,13 +869,12 @@ module PrePost = struct
call_state_subst_find_or_new call_state addr_callee_dest call_state_subst_find_or_new call_state addr_callee_dest
~default_hist_caller:(snd addr_hist_caller) ~default_hist_caller:(snd addr_hist_caller)
in in
record_post_for_address callee_proc_name call_loc pre_post record_post_for_address callee_proc_name call_loc pre_post ~addr_callee:addr_callee_dest
~addr_callee:addr_callee_dest ~addr_hist_caller:addr_hist_curr_dest call_state ) ) ~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 = 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 L.d_printfln_escaped "Recording POST from [%a] <-> %a" Var.pp formal AbstractValue.pp (fst actual) ;
(fst actual) ;
match match
let open IOption.Let_syntax in let open IOption.Let_syntax in
let* addr_formal_pre, _ = let* addr_formal_pre, _ =
@ -934,8 +921,7 @@ module PrePost = struct
(call_state, Some return_caller_addr_hist) ) (call_state, Some return_caller_addr_hist) )
let apply_post_for_parameters callee_proc_name call_location pre_post ~formals ~actuals call_state 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 (* 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 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 (TODO: record in the pre when a location is written to instead of just comparing values
@ -943,8 +929,7 @@ module PrePost = struct
post but nuke other fields in the meantime? is that possible?). *) post but nuke other fields in the meantime? is that possible?). *)
match match
List.fold2 formals actuals ~init:call_state ~f:(fun call_state formal (actual, _) -> 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 record_post_for_actual callee_proc_name call_location pre_post ~formal ~actual call_state )
)
with with
| Unequal_lengths -> | Unequal_lengths ->
(* should have been checked before by [materialize_pre] *) (* should have been checked before by [materialize_pre] *)
@ -1038,8 +1023,8 @@ module PrePost = struct
AddressAttributes.check_valid access_trace addr_caller astate AddressAttributes.check_valid access_trace addr_caller astate
|> Result.map_error ~f:(fun (invalidation, invalidation_trace) -> |> Result.map_error ~f:(fun (invalidation, invalidation_trace) ->
L.d_printfln "ERROR: caller's %a invalid!" AbstractValue.pp addr_caller ; L.d_printfln "ERROR: caller's %a invalid!" AbstractValue.pp addr_caller ;
Diagnostic.AccessToInvalidAddress Diagnostic.AccessToInvalidAddress {invalidation; invalidation_trace; access_trace}
{invalidation; invalidation_trace; access_trace} ) ) ) ) ) )
call_state.subst (Ok call_state.astate) call_state.subst (Ok call_state.astate)
@ -1099,4 +1084,3 @@ module PrePost = struct
let get_post {post} = (post :> BaseDomain.t) let get_post {post} = (post :> BaseDomain.t)
let get_skipped_calls {skipped_calls} = skipped_calls let get_skipped_calls {skipped_calls} = skipped_calls
end

@ -110,14 +110,17 @@ val discard_unreachable : t -> t * BaseAddressAttributes.t
val add_skipped_calls : Procname.t -> PulseTrace.t -> t -> t val add_skipped_calls : Procname.t -> PulseTrace.t -> t -> t
module PrePost : sig val leq : lhs:t -> rhs:t -> bool
type domain_t = t
type t = private domain_t
val pp : Format.formatter -> t -> unit val pp : Format.formatter -> t -> unit
val of_post : Procdesc.t -> domain_t -> t val of_post : Procdesc.t -> t -> t
val get_pre : t -> BaseDomain.t
val get_post : t -> BaseDomain.t
val get_skipped_calls : t -> SkippedCalls.t
val apply : val apply :
Procname.t Procname.t
@ -125,14 +128,7 @@ module PrePost : sig
-> t -> t
-> formals:Var.t list -> formals:Var.t list
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list -> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
-> domain_t -> t
-> ((domain_t * (AbstractValue.t * ValueHistory.t) option) option, Diagnostic.t) result -> ((t * (AbstractValue.t * ValueHistory.t) option) option, Diagnostic.t) result
(** return the abstract state after the call along with an optional return value, or [None] if the (** 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) *) precondition could not be satisfied (e.g. some aliasing constraints were not satisfied) *)
val get_pre : t -> BaseDomain.t
val get_post : t -> BaseDomain.t
val get_skipped_calls : t -> SkippedCalls.t
end

@ -0,0 +1,43 @@
(*
* 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 exec_state =
| ContinueProgram of PulseAbductiveDomain.t
| ExitProgram of PulseAbductiveDomain.t
type t = exec_state
let continue astate = ContinueProgram astate
let mk_initial pdesc = ContinueProgram (PulseAbductiveDomain.mk_initial pdesc)
let leq ~lhs ~rhs =
match (lhs, rhs) with
| ContinueProgram astate1, ContinueProgram astate2 | ExitProgram astate1, ExitProgram astate2 ->
PulseAbductiveDomain.leq ~lhs:astate1 ~rhs:astate2
| ExitProgram _, ContinueProgram _ | ContinueProgram _, ExitProgram _ ->
false
let pp fmt = function
| ContinueProgram astate ->
PulseAbductiveDomain.pp fmt astate
| ExitProgram astate ->
F.fprintf fmt "{ExitProgram %a}" PulseAbductiveDomain.pp astate
let map ~f exec_state =
match exec_state with
| ContinueProgram astate ->
ContinueProgram (f astate)
| ExitProgram astate ->
ExitProgram (f astate)
let of_post pdesc = map ~f:(PulseAbductiveDomain.of_post pdesc)

@ -0,0 +1,20 @@
(*
* 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
type exec_state =
| ContinueProgram of PulseAbductiveDomain.t (** represents the state at the program point *)
| ExitProgram of PulseAbductiveDomain.t
(** represents the state originating at exit/divergence. *)
include AbstractDomain.NoJoin with type t = exec_state
val continue : PulseAbductiveDomain.t -> t
val of_post : Procdesc.t -> t -> t
val mk_initial : Procdesc.t -> t

@ -17,7 +17,7 @@ type model =
-> Location.t -> Location.t
-> ret:Ident.t * Typ.t -> ret:Ident.t * Typ.t
-> PulseAbductiveDomain.t -> PulseAbductiveDomain.t
-> PulseAbductiveDomain.t list PulseOperations.access_result -> PulseExecutionState.t list PulseOperations.access_result
module Misc = struct module Misc = struct
let shallow_copy model_desc dest_pointer_hist src_pointer_hist : model = let shallow_copy model_desc dest_pointer_hist src_pointer_hist : model =
@ -30,10 +30,14 @@ module Misc = struct
~obj:(fst obj_copy, event :: snd obj_copy) ~obj:(fst obj_copy, event :: snd obj_copy)
astate astate
in in
[PulseOperations.havoc_id ret_id [event] astate] let astate = PulseOperations.havoc_id ret_id [event] astate in
[PulseExecutionState.ContinueProgram astate]
let early_exit : model =
fun ~caller_summary:_ ~callee_procname:_ _ ~ret:_ astate ->
Ok [PulseExecutionState.ExitProgram astate]
let early_exit : model = fun ~caller_summary:_ ~callee_procname:_ _ ~ret:_ _ -> Ok []
let return_int : Int64.t -> model = let return_int : Int64.t -> model =
fun i64 ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate -> fun i64 ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate ->
@ -44,7 +48,7 @@ module Misc = struct
|> AddressAttributes.add_one ret_addr |> AddressAttributes.add_one ret_addr
(CItv (CItv.equal_to i, Immediate {location; history= []})) (CItv (CItv.equal_to i, Immediate {location; history= []}))
in in
Ok [PulseOperations.write_id ret_id (ret_addr, []) astate] PulseOperations.write_id ret_id (ret_addr, []) astate |> PulseOperations.ok_continue
let return_unknown_size : model = let return_unknown_size : model =
@ -55,20 +59,22 @@ module Misc = struct
|> AddressAttributes.add_one ret_addr |> AddressAttributes.add_one ret_addr
(CItv (CItv.zero_inf, Immediate {location; history= []})) (CItv (CItv.zero_inf, Immediate {location; history= []}))
in in
Ok [PulseOperations.write_id ret_id (ret_addr, []) astate] PulseOperations.write_id ret_id (ret_addr, []) astate |> PulseOperations.ok_continue
let skip : model =
fun ~caller_summary:_ ~callee_procname:_ _ ~ret:_ astate -> PulseOperations.ok_continue astate
let skip : model = fun ~caller_summary:_ ~callee_procname:_ _ ~ret:_ astate -> Ok [astate]
let nondet ~fn_name : model = let nondet ~fn_name : model =
fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate ->
let event = ValueHistory.Call {f= Model fn_name; location; in_call= []} in let event = ValueHistory.Call {f= Model fn_name; location; in_call= []} in
Ok [PulseOperations.havoc_id ret_id [event] astate] PulseOperations.havoc_id ret_id [event] astate |> PulseOperations.ok_continue
let id_first_arg arg_access_hist : model = let id_first_arg arg_access_hist : model =
fun ~caller_summary:_ ~callee_procname:_ _ ~ret astate -> fun ~caller_summary:_ ~callee_procname:_ _ ~ret astate ->
Ok [PulseOperations.write_id (fst ret) arg_access_hist astate] PulseOperations.write_id (fst ret) arg_access_hist astate |> PulseOperations.ok_continue
end end
module C = struct module C = struct
@ -82,10 +88,10 @@ module C = struct
|| Itv.ItvPure.is_zero (AddressAttributes.get_bo_itv (fst deleted_access) astate) || Itv.ItvPure.is_zero (AddressAttributes.get_bo_itv (fst deleted_access) astate)
in in
if is_known_zero then (* freeing 0 is a no-op *) if is_known_zero then (* freeing 0 is a no-op *)
Ok [astate] PulseOperations.ok_continue astate
else else
let+ astate = PulseOperations.invalidate location Invalidation.CFree deleted_access astate in let+ astate = PulseOperations.invalidate location Invalidation.CFree deleted_access astate in
[astate] [PulseExecutionState.ContinueProgram astate]
let malloc _ : model = let malloc _ : model =
@ -97,6 +103,7 @@ module C = struct
|> AddressAttributes.add_one ret_addr (BoItv Itv.ItvPure.pos) |> AddressAttributes.add_one ret_addr (BoItv Itv.ItvPure.pos)
|> AddressAttributes.add_one ret_addr |> AddressAttributes.add_one ret_addr
(CItv (CItv.ge_to IntLit.one, Immediate {location; history= []})) (CItv (CItv.ge_to IntLit.one, Immediate {location; history= []}))
|> PulseExecutionState.continue
in in
let+ astate_null = let+ astate_null =
AddressAttributes.add_one ret_addr (BoItv (Itv.ItvPure.of_int_lit IntLit.zero)) astate AddressAttributes.add_one ret_addr (BoItv (Itv.ItvPure.of_int_lit IntLit.zero)) astate
@ -106,23 +113,27 @@ module C = struct
|> PulseOperations.invalidate location (Invalidation.ConstantDereference IntLit.zero) |> PulseOperations.invalidate location (Invalidation.ConstantDereference IntLit.zero)
(ret_addr, []) (ret_addr, [])
in in
[astate_alloc; astate_null] [astate_alloc; PulseExecutionState.ContinueProgram astate_null]
end end
module Cplusplus = struct module Cplusplus = struct
let delete deleted_access : model = let delete deleted_access : model =
fun ~caller_summary:_ ~callee_procname:_ location ~ret:_ astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret:_ astate ->
PulseOperations.invalidate location Invalidation.CppDelete deleted_access astate >>| List.return let+ astate =
PulseOperations.invalidate location Invalidation.CppDelete deleted_access astate
in
[PulseExecutionState.ContinueProgram astate]
let placement_new actuals : model = let placement_new actuals : model =
fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate ->
let event = ValueHistory.Call {f= Model "<placement new>()"; location; in_call= []} in let event = ValueHistory.Call {f= Model "<placement new>()"; location; in_call= []} in
match List.rev actuals with ( match List.rev actuals with
| ProcnameDispatcher.Call.FuncArg.{arg_payload= address, hist} :: _ -> | ProcnameDispatcher.Call.FuncArg.{arg_payload= address, hist} :: _ ->
Ok [PulseOperations.write_id ret_id (address, event :: hist) astate] PulseOperations.write_id ret_id (address, event :: hist) astate
| _ -> | _ ->
Ok [PulseOperations.havoc_id ret_id [event] astate] PulseOperations.havoc_id ret_id [event] astate )
|> PulseOperations.ok_continue
end end
module StdAtomicInteger = struct module StdAtomicInteger = struct
@ -150,7 +161,7 @@ module StdAtomicInteger = struct
in in
let* astate = PulseOperations.write_deref location ~ref:int_field ~obj:init_value astate in let* astate = PulseOperations.write_deref location ~ref:int_field ~obj:init_value astate in
let+ astate = PulseOperations.write_deref location ~ref:this_address ~obj:this astate in let+ astate = PulseOperations.write_deref location ~ref:this_address ~obj:this astate in
[astate] [PulseExecutionState.ContinueProgram astate]
let arith_bop prepost location event ret_id bop this operand astate = let arith_bop prepost location event ret_id bop this operand astate =
@ -173,7 +184,7 @@ module StdAtomicInteger = struct
arith_bop `Post location event ret_id (PlusA None) this (AbstractValueOperand increment) arith_bop `Post location event ret_id (PlusA None) this (AbstractValueOperand increment)
astate astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let fetch_sub this (increment, _) _memory_ordering : model = let fetch_sub this (increment, _) _memory_ordering : model =
@ -183,7 +194,7 @@ module StdAtomicInteger = struct
arith_bop `Post location event ret_id (MinusA None) this (AbstractValueOperand increment) arith_bop `Post location event ret_id (MinusA None) this (AbstractValueOperand increment)
astate astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let operator_plus_plus_pre this : model = let operator_plus_plus_pre this : model =
@ -192,7 +203,7 @@ module StdAtomicInteger = struct
let+ astate = let+ astate =
arith_bop `Pre location event ret_id (PlusA None) this (LiteralOperand IntLit.one) astate arith_bop `Pre location event ret_id (PlusA None) this (LiteralOperand IntLit.one) astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let operator_plus_plus_post this _int : model = let operator_plus_plus_post this _int : model =
@ -203,7 +214,7 @@ module StdAtomicInteger = struct
let+ astate = let+ astate =
arith_bop `Post location event ret_id (PlusA None) this (LiteralOperand IntLit.one) astate arith_bop `Post location event ret_id (PlusA None) this (LiteralOperand IntLit.one) astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let operator_minus_minus_pre this : model = let operator_minus_minus_pre this : model =
@ -212,7 +223,7 @@ module StdAtomicInteger = struct
let+ astate = let+ astate =
arith_bop `Pre location event ret_id (MinusA None) this (LiteralOperand IntLit.one) astate arith_bop `Pre location event ret_id (MinusA None) this (LiteralOperand IntLit.one) astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let operator_minus_minus_post this _int : model = let operator_minus_minus_post this _int : model =
@ -223,14 +234,15 @@ module StdAtomicInteger = struct
let+ astate = let+ astate =
arith_bop `Post location event ret_id (MinusA None) this (LiteralOperand IntLit.one) astate arith_bop `Post location event ret_id (MinusA None) this (LiteralOperand IntLit.one) astate
in in
[astate] [PulseExecutionState.ContinueProgram astate]
let load_instr model_desc this _memory_ordering_opt : model = let load_instr model_desc this _memory_ordering_opt : model =
fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret:(ret_id, _) astate ->
let event = ValueHistory.Call {f= Model model_desc; location; in_call= []} in let event = ValueHistory.Call {f= Model model_desc; location; in_call= []} in
let+ astate, _int_addr, (int, hist) = load_backing_int location this astate in let+ astate, _int_addr, (int, hist) = load_backing_int location this astate in
[PulseOperations.write_id ret_id (int, event :: hist) astate] let astate = PulseOperations.write_id ret_id (int, event :: hist) astate in
[PulseExecutionState.ContinueProgram astate]
let load = load_instr "std::atomic<T>::load()" let load = load_instr "std::atomic<T>::load()"
@ -249,7 +261,7 @@ module StdAtomicInteger = struct
fun ~caller_summary:_ ~callee_procname:_ location ~ret:_ astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret:_ astate ->
let event = ValueHistory.Call {f= Model "std::atomic::store()"; location; in_call= []} in let event = ValueHistory.Call {f= Model "std::atomic::store()"; location; in_call= []} in
let+ astate = store_backing_int location this_address (new_value, event :: new_hist) astate in let+ astate = store_backing_int location this_address (new_value, event :: new_hist) astate in
[astate] [PulseExecutionState.ContinueProgram astate]
let exchange this_address (new_value, new_hist) _memory_ordering : model = let exchange this_address (new_value, new_hist) _memory_ordering : model =
@ -257,7 +269,8 @@ module StdAtomicInteger = struct
let event = ValueHistory.Call {f= Model "std::atomic::exchange()"; location; in_call= []} in let event = ValueHistory.Call {f= Model "std::atomic::exchange()"; location; in_call= []} in
let* astate, _int_addr, (old_int, old_hist) = load_backing_int location this_address astate in let* astate, _int_addr, (old_int, old_hist) = load_backing_int location this_address astate in
let+ astate = store_backing_int location this_address (new_value, event :: new_hist) astate in let+ astate = store_backing_int location this_address (new_value, event :: new_hist) astate in
[PulseOperations.write_id ret_id (old_int, event :: old_hist) astate] let astate = PulseOperations.write_id ret_id (old_int, event :: old_hist) astate in
[PulseExecutionState.ContinueProgram astate]
end end
module ObjectiveC = struct module ObjectiveC = struct
@ -268,7 +281,7 @@ module ObjectiveC = struct
in in
let ret_addr = AbstractValue.mk_fresh () in let ret_addr = AbstractValue.mk_fresh () in
let astate = PulseOperations.allocate callee_procname location (ret_addr, []) astate in let astate = PulseOperations.allocate callee_procname location (ret_addr, []) astate in
Ok [PulseOperations.write_id ret_id (ret_addr, hist) astate] PulseOperations.write_id ret_id (ret_addr, hist) astate |> PulseOperations.ok_continue
end end
module JavaObject = struct module JavaObject = struct
@ -278,7 +291,8 @@ module JavaObject = struct
let event = ValueHistory.Call {f= Model "Object.clone"; location; in_call= []} in let event = ValueHistory.Call {f= Model "Object.clone"; location; in_call= []} in
let* astate, obj = PulseOperations.eval_access location src_pointer_hist Dereference astate in let* astate, obj = PulseOperations.eval_access location src_pointer_hist Dereference astate in
let+ astate, obj_copy = PulseOperations.shallow_copy location obj astate in let+ astate, obj_copy = PulseOperations.shallow_copy location obj astate in
[PulseOperations.write_id ret_id (fst obj_copy, event :: snd obj_copy) astate] let astate = PulseOperations.write_id ret_id (fst obj_copy, event :: snd obj_copy) astate in
[PulseExecutionState.ContinueProgram astate]
end end
module StdBasicString = struct module StdBasicString = struct
@ -301,7 +315,8 @@ module StdBasicString = struct
let+ astate, (string, hist) = let+ astate, (string, hist) =
PulseOperations.eval_access location string_addr_hist Dereference astate PulseOperations.eval_access location string_addr_hist Dereference astate
in in
[PulseOperations.write_id ret_id (string, event :: hist) astate] let astate = PulseOperations.write_id ret_id (string, event :: hist) astate in
[PulseExecutionState.ContinueProgram astate]
let destructor this_hist : model = let destructor this_hist : model =
@ -312,7 +327,7 @@ module StdBasicString = struct
let string_addr_hist = (string_addr, call_event :: string_hist) in let string_addr_hist = (string_addr, call_event :: string_hist) in
let* astate = PulseOperations.invalidate_deref location CppDelete string_addr_hist astate in let* astate = PulseOperations.invalidate_deref location CppDelete string_addr_hist astate in
let+ astate = PulseOperations.invalidate location CppDelete string_addr_hist astate in let+ astate = PulseOperations.invalidate location CppDelete string_addr_hist astate in
[astate] [PulseExecutionState.ContinueProgram astate]
end end
module StdFunction = struct module StdFunction = struct
@ -328,7 +343,8 @@ module StdFunction = struct
let* astate = PulseOperations.Closures.check_captured_addresses location lambda astate in let* astate = PulseOperations.Closures.check_captured_addresses location lambda astate in
match AddressAttributes.get_closure_proc_name lambda astate with match AddressAttributes.get_closure_proc_name lambda astate with
| None -> | None ->
(* we don't know what proc name this lambda resolves to *) Ok (havoc_ret ret astate) (* we don't know what proc name this lambda resolves to *)
Ok (havoc_ret ret astate |> List.map ~f:PulseExecutionState.continue)
| Some callee_proc_name -> | Some callee_proc_name ->
let actuals = let actuals =
List.map actuals ~f:(fun ProcnameDispatcher.Call.FuncArg.{arg_payload; typ} -> List.map actuals ~f:(fun ProcnameDispatcher.Call.FuncArg.{arg_payload; typ} ->
@ -373,14 +389,16 @@ module StdVector = struct
; location ; location
; in_call= [] } ; in_call= [] }
in in
reallocate_internal_array [crumb] vector vector_f location astate >>| List.return reallocate_internal_array [crumb] vector vector_f location astate
>>| PulseExecutionState.continue >>| List.return
let at ~desc vector index : model = let at ~desc vector index : model =
fun ~caller_summary:_ ~callee_procname:_ location ~ret astate -> fun ~caller_summary:_ ~callee_procname:_ location ~ret astate ->
let event = ValueHistory.Call {f= Model desc; location; in_call= []} in let event = ValueHistory.Call {f= Model desc; location; in_call= []} in
let+ astate, (addr, hist) = element_of_internal_array location vector (fst index) astate in let+ astate, (addr, hist) = element_of_internal_array location vector (fst index) astate in
[PulseOperations.write_id (fst ret) (addr, event :: hist) astate] let astate = PulseOperations.write_id (fst ret) (addr, event :: hist) astate in
[PulseExecutionState.ContinueProgram astate]
let reserve vector : model = let reserve vector : model =
@ -388,7 +406,7 @@ module StdVector = struct
let crumb = ValueHistory.Call {f= Model "std::vector::reserve()"; location; in_call= []} in let crumb = ValueHistory.Call {f= Model "std::vector::reserve()"; location; in_call= []} in
reallocate_internal_array [crumb] vector Reserve location astate reallocate_internal_array [crumb] vector Reserve location astate
>>| AddressAttributes.std_vector_reserve (fst vector) >>| AddressAttributes.std_vector_reserve (fst vector)
>>| List.return >>| PulseExecutionState.continue >>| List.return
let push_back vector : model = let push_back vector : model =
@ -397,10 +415,11 @@ module StdVector = struct
if AddressAttributes.is_std_vector_reserved (fst vector) astate then if AddressAttributes.is_std_vector_reserved (fst vector) astate then
(* assume that any call to [push_back] is ok after one called [reserve] on the same vector (* assume that any call to [push_back] is ok after one called [reserve] on the same vector
(a perfect analysis would also make sure we don't exceed the reserved size) *) (a perfect analysis would also make sure we don't exceed the reserved size) *)
Ok [astate] PulseOperations.ok_continue astate
else else
(* simulate a re-allocation of the underlying array every time an element is added *) (* simulate a re-allocation of the underlying array every time an element is added *)
reallocate_internal_array [crumb] vector PushBack location astate >>| List.return reallocate_internal_array [crumb] vector PushBack location astate
>>| PulseExecutionState.continue >>| List.return
end end
module JavaCollection = struct module JavaCollection = struct
@ -416,7 +435,8 @@ module JavaCollection = struct
astate astate
>>= PulseOperations.invalidate_deref location (StdVector Assign) old_elem >>= PulseOperations.invalidate_deref location (StdVector Assign) old_elem
in in
[PulseOperations.write_id (fst ret) (old_addr, event :: old_hist) astate] let astate = PulseOperations.write_id (fst ret) (old_addr, event :: old_hist) astate in
[PulseExecutionState.ContinueProgram astate]
end end
module StringSet = Caml.Set.Make (String) module StringSet = Caml.Set.Make (String)

@ -13,7 +13,7 @@ type model =
-> Location.t -> Location.t
-> ret:Ident.t * Typ.t -> ret:Ident.t * Typ.t
-> PulseAbductiveDomain.t -> PulseAbductiveDomain.t
-> PulseAbductiveDomain.t list PulseOperations.access_result -> PulseExecutionState.t list PulseOperations.access_result
val dispatch : val dispatch :
Tenv.t Tenv.t

@ -14,6 +14,8 @@ type t = AbductiveDomain.t
type 'a access_result = ('a, Diagnostic.t) result type 'a access_result = ('a, Diagnostic.t) result
let ok_continue post = Ok [PulseExecutionState.ContinueProgram post]
(** Check that the [address] is not known to be invalid *) (** Check that the [address] is not known to be invalid *)
let check_addr_access location (address, history) astate = let check_addr_access location (address, history) astate =
let access_trace = Trace.Immediate {location; history} in let access_trace = Trace.Immediate {location; history} in
@ -570,24 +572,14 @@ let unknown_call call_loc reason ~ret ~actuals ~formals_opt astate =
|> havoc_ret ret |> add_skipped_proc |> havoc_ret ret |> add_skipped_proc
let call ~caller_summary call_loc callee_pname ~ret ~actuals ~formals_opt astate = let apply_callee callee_pname call_loc callee_exec_state ~ret ~formals ~actuals astate =
match PulsePayload.read_full ~caller_summary ~callee_pname with let apply callee_prepost ~f =
| Some (callee_proc_desc, preposts) -> PulseAbductiveDomain.apply callee_pname call_loc callee_prepost ~formals ~actuals astate
let formals =
Procdesc.get_formals callee_proc_desc
|> List.map ~f:(fun (mangled, _) -> Pvar.mk mangled callee_pname |> Var.of_pvar)
in
(* call {!AbductiveDomain.PrePost.apply} on each pre/post pair in the summary. *)
List.fold_result preposts ~init:[] ~f:(fun posts pre_post ->
(* apply all pre/post specs *)
AbductiveDomain.PrePost.apply callee_pname call_loc pre_post ~formals ~actuals astate
>>| function >>| function
| None -> | None ->
(* couldn't apply pre/post pair *) posts (* couldn't apply pre/post pair *) None
| Some (post, return_val_opt) -> | Some (post, return_val_opt) ->
let event = let event = ValueHistory.Call {f= Call callee_pname; location= call_loc; in_call= []} in
ValueHistory.Call {f= Call callee_pname; location= call_loc; in_call= []}
in
let post = let post =
match return_val_opt with match return_val_opt with
| Some (return_val, return_hist) -> | Some (return_val, return_hist) ->
@ -595,8 +587,32 @@ let call ~caller_summary call_loc callee_pname ~ret ~actuals ~formals_opt astate
| None -> | None ->
havoc_id (fst ret) [event] post havoc_id (fst ret) [event] post
in in
post :: posts ) Some (f post)
in
let open PulseExecutionState in
match callee_exec_state with
| ContinueProgram astate ->
apply astate ~f:(fun astate -> ContinueProgram astate)
| ExitProgram astate ->
apply astate ~f:(fun astate -> ExitProgram astate)
let call ~caller_summary call_loc callee_pname ~ret ~actuals ~formals_opt
(astate : PulseAbductiveDomain.t) : (PulseExecutionState.t list, Diagnostic.t) result =
match PulsePayload.read_full ~caller_summary ~callee_pname with
| Some (callee_proc_desc, exec_states) ->
let formals =
Procdesc.get_formals callee_proc_desc
|> List.map ~f:(fun (mangled, _) -> Pvar.mk mangled callee_pname |> Var.of_pvar)
in
(* call {!AbductiveDomain.PrePost.apply} on each pre/post pair in the summary. *)
List.fold_result exec_states ~init:[] ~f:(fun posts callee_exec_state ->
(* apply all pre/post specs *)
apply_callee callee_pname call_loc callee_exec_state ~formals ~actuals ~ret astate
>>| function
| None -> (* couldn't apply pre/post pair *) posts | Some post -> post :: posts )
| None -> | None ->
(* no spec found for some reason (unknown function, ...) *) (* no spec found for some reason (unknown function, ...) *)
L.d_printfln "No spec found for %a@\n" Procname.pp callee_pname ; L.d_printfln "No spec found for %a@\n" Procname.pp callee_pname ;
Ok [unknown_call call_loc (SkippedKnownCall callee_pname) ~ret ~actuals ~formals_opt astate] unknown_call call_loc (SkippedKnownCall callee_pname) ~ret ~actuals ~formals_opt astate
|> ok_continue

@ -13,6 +13,8 @@ type t = PulseAbductiveDomain.t
type 'a access_result = ('a, Diagnostic.t) result type 'a access_result = ('a, Diagnostic.t) result
val ok_continue : t -> (PulseExecutionState.exec_state list, 'a) result
module Closures : sig module Closures : sig
val check_captured_addresses : Location.t -> AbstractValue.t -> t -> (t, Diagnostic.t) result val check_captured_addresses : Location.t -> AbstractValue.t -> t -> (t, Diagnostic.t) result
(** assert the validity of the addresses captured by the lambda *) (** assert the validity of the addresses captured by the lambda *)
@ -112,7 +114,7 @@ val call :
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list -> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
-> formals_opt:(Pvar.t * Typ.t) list option -> formals_opt:(Pvar.t * Typ.t) list option
-> t -> t
-> t list access_result -> PulseExecutionState.t list access_result
(** perform an interprocedural call: apply the summary for the call proc name passed as argument if (** perform an interprocedural call: apply the summary for the call proc name passed as argument if
it exists *) it exists *)

@ -6,15 +6,14 @@
*) *)
open! IStd open! IStd
module F = Format module F = Format
open PulseDomainInterface
type t = AbductiveDomain.PrePost.t list type t = PulseExecutionState.t list
let of_posts pdesc posts = List.map posts ~f:(PulseAbductiveDomain.PrePost.of_post pdesc) let of_posts pdesc posts = List.map posts ~f:(PulseExecutionState.of_post pdesc)
let pp fmt summary = let pp fmt summary =
F.open_vbox 0 ; F.open_vbox 0 ;
F.fprintf fmt "%d pre/post(s)@;" (List.length summary) ; F.fprintf fmt "%d pre/post(s)@;" (List.length summary) ;
List.iteri summary ~f:(fun i pre_post -> List.iteri summary ~f:(fun i pre_post ->
F.fprintf fmt "#%d: @[%a@]@;" i PulseAbductiveDomain.PrePost.pp pre_post ) ; F.fprintf fmt "#%d: @[%a@]@;" i PulseExecutionState.pp pre_post ) ;
F.close_box () F.close_box ()

@ -6,8 +6,8 @@
*) *)
open! IStd open! IStd
type t = PulseAbductiveDomain.PrePost.t list type t = PulseExecutionState.t list
val of_posts : Procdesc.t -> PulseAbductiveDomain.t list -> t val of_posts : Procdesc.t -> PulseExecutionState.t list -> t
val pp : Format.formatter -> t -> unit val pp : Format.formatter -> t -> unit

@ -0,0 +1,20 @@
/*
* 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.
*/
#include <stdlib.h>
int x;
void exit_positive_impure_FN(int a[10], int b) {
if (b > 0) {
exit(0);
}
}
void unreachable_impure_FN(int a[10], int b) {
exit_positive_impure_FN(a, 10);
x = 9;
}

@ -0,0 +1,29 @@
/*
* 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.
*/
#include <stdlib.h>
// we get two disjuncts one for each branch
void exit_positive(int a[10], int b) {
if (b < 1) {
exit(0);
}
}
void unreachable_double_free_ok(int a[10], int b) {
exit_positive(a, 0);
free(a);
free(a);
}
void store_exit(int* x, bool b) {
if (b) {
*x = 42;
exit(0);
}
}
void store_exit_null_bad(bool b) { store_exit(NULL, b); }

@ -12,6 +12,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::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<_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/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/exit_test.cpp, store_exit_null_bad, 0, NULLPTR_DEREFERENCE, no_bucket, ERROR, [invalidation part of the trace starts here,assigned,is the null pointer,use-after-lifetime part of the trace starts here,when calling `store_exit` here,parameter `x` of store_exit,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/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/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] 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]

@ -0,0 +1,35 @@
/*
* 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.
*/
#include <stdlib.h>
int double_free_in_catch_bad_FN(int a[10], int b) {
try {
if (b > 0) {
free(a);
throw(0);
}
} catch (...) {
free(a);
}
return 0;
}
void throw_positive(int a[10], int b) {
if (b > 0) {
free(a);
throw(1);
}
}
int double_free_interproc_bad_FN(int a[10], int b) {
try {
throw_positive(a, 2);
} catch (...) {
free(a);
}
return 0;
}

@ -89,8 +89,15 @@ class Test {
return System.nanoTime(); return System.nanoTime();
} }
// In pulse, we get 0 disjuncts as a summary, hence consider this as impure // In pulse, we get Exited summary where pre=post
void exit_impure() { // TODO: change impurity to track exit as impure
void exit_impure_FN() {
System.exit(1);
}
// In pulse, we get Exited summary where pre=post
void modify_exit_impure(int[] a) {
a[0] = 0;
System.exit(1); System.exit(1);
} }

@ -34,9 +34,9 @@ codetoanalyze/java/impurity/PurityModeled.java, PurityModeled.write_impure():voi
codetoanalyze/java/impurity/Test.java, Test.Test(int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.Test(int),global variable `Test` modified here] codetoanalyze/java/impurity/Test.java, Test.Test(int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.Test(int),global variable `Test` modified here]
codetoanalyze/java/impurity/Test.java, Test.alias_impure(int[],int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.alias_impure(int[],int,int),parameter `array` modified here] codetoanalyze/java/impurity/Test.java, Test.alias_impure(int[],int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.alias_impure(int[],int,int),parameter `array` modified here]
codetoanalyze/java/impurity/Test.java, Test.call_impure_impure(int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.call_impure_impure(int),when calling `void Test.set_impure(int,int)` here,parameter `this` modified here] codetoanalyze/java/impurity/Test.java, Test.call_impure_impure(int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.call_impure_impure(int),when calling `void Test.set_impure(int,int)` here,parameter `this` modified here]
codetoanalyze/java/impurity/Test.java, Test.exit_impure():void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.exit_impure() with empty pulse summary]
codetoanalyze/java/impurity/Test.java, Test.global_array_set_impure(int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.global_array_set_impure(int,int),global variable `Test` modified here] codetoanalyze/java/impurity/Test.java, Test.global_array_set_impure(int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.global_array_set_impure(int,int),global variable `Test` modified here]
codetoanalyze/java/impurity/Test.java, Test.local_field_write_impure(Test):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.local_field_write_impure(Test),parameter `x` modified here] codetoanalyze/java/impurity/Test.java, Test.local_field_write_impure(Test):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.local_field_write_impure(Test),parameter `x` modified here]
codetoanalyze/java/impurity/Test.java, Test.modify_exit_impure(int[]):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.modify_exit_impure(int[]),parameter `a` modified here]
codetoanalyze/java/impurity/Test.java, Test.parameter_field_write_impure(Test,boolean):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.parameter_field_write_impure(Test,boolean),parameter `test` modified here] codetoanalyze/java/impurity/Test.java, Test.parameter_field_write_impure(Test,boolean):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.parameter_field_write_impure(Test,boolean),parameter `test` modified here]
codetoanalyze/java/impurity/Test.java, Test.set_impure(int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.set_impure(int,int),parameter `this` modified here] codetoanalyze/java/impurity/Test.java, Test.set_impure(int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.set_impure(int,int),parameter `this` modified here]
codetoanalyze/java/impurity/Test.java, Test.swap_impure(int[],int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.swap_impure(int[],int,int),parameter `array` modified here] codetoanalyze/java/impurity/Test.java, Test.swap_impure(int[],int,int):void, 0, IMPURE_FUNCTION, no_bucket, ERROR, [Impure function void Test.swap_impure(int[],int,int),parameter `array` modified here]

Loading…
Cancel
Save