[pulse][1/2] new kind of latent issues to remove some FNs

Summary:
This first commit introduces test cases and the new summary type, in
particular how it is propagated during function calls. We don't yet
actually generate these summary types, this is for the next diff.

The goal is to catch this pattern:

```
foo(p) {
  if(p) {}
  *p = 42;
}

goo() { foo(NULL); }
```

We went foo(p) to be a latent error when p=0. Right now we detect a
contradiction p|->- * p=0 |- false. The next diff will fix it.

Reviewed By: skcho

Differential Revision: D26918552

fbshipit-source-id: 6614db17b
master
Jules Villard 4 years ago committed by Facebook GitHub Bot
parent d0cf7e3135
commit 8a1213962e

@ -162,7 +162,10 @@ let extract_impurity tenv pname formals (exec_state : ExecutionDomain.t) : Impur
((astate :> AbductiveDomain.t), true) ((astate :> AbductiveDomain.t), true)
| ContinueProgram astate -> | ContinueProgram astate ->
(astate, false) (astate, false)
| AbortProgram astate | LatentAbortProgram {astate} | ISLLatentMemoryError astate -> | AbortProgram astate
| LatentAbortProgram {astate}
| LatentInvalidAccess {astate}
| ISLLatentMemoryError astate ->
((astate :> AbductiveDomain.t), false) ((astate :> AbductiveDomain.t), false)
in in
let pre_heap = (AbductiveDomain.get_pre astate).BaseDomain.heap in let pre_heap = (AbductiveDomain.get_pre astate).BaseDomain.heap in

@ -86,7 +86,11 @@ module PulseTransferFunctions = struct
(* invalidate [&x] *) (* 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
>>| ExecutionDomain.continue >>| ExecutionDomain.continue
| ISLLatentMemoryError _ | AbortProgram _ | ExitProgram _ | LatentAbortProgram _ -> | ISLLatentMemoryError _
| AbortProgram _
| ExitProgram _
| LatentAbortProgram _
| LatentInvalidAccess _ ->
Ok exec_state Ok exec_state
@ -104,7 +108,11 @@ module PulseTransferFunctions = struct
match exec_state with match exec_state with
| ContinueProgram astate -> | ContinueProgram astate ->
ContinueProgram (do_astate astate) ContinueProgram (do_astate astate)
| ISLLatentMemoryError _ | AbortProgram _ | LatentAbortProgram _ | ExitProgram _ -> | ISLLatentMemoryError _
| AbortProgram _
| LatentAbortProgram _
| ExitProgram _
| LatentInvalidAccess _ ->
exec_state exec_state
in in
List.map ~f:(Result.map ~f:do_one_exec_state) exec_state_res List.map ~f:(Result.map ~f:do_one_exec_state) exec_state_res
@ -240,7 +248,11 @@ module PulseTransferFunctions = struct
call_instr ; call_instr ;
List.concat_map astate_list ~f:(fun (astate : Domain.t) -> List.concat_map astate_list ~f:(fun (astate : Domain.t) ->
match astate with match astate with
| ISLLatentMemoryError _ | AbortProgram _ | ExitProgram _ | LatentAbortProgram _ -> | ISLLatentMemoryError _
| AbortProgram _
| ExitProgram _
| LatentAbortProgram _
| LatentInvalidAccess _ ->
[astate] [astate]
| ContinueProgram astate -> | ContinueProgram astate ->
dispatch_call analysis_data ret call_exp actuals location call_flags astate dispatch_call analysis_data ret call_exp actuals location call_flags astate
@ -262,9 +274,7 @@ module PulseTransferFunctions = struct
({InterproceduralAnalysis.tenv; proc_desc; err_log} as analysis_data) _cfg_node ({InterproceduralAnalysis.tenv; proc_desc; err_log} as analysis_data) _cfg_node
(instr : Sil.instr) : Domain.t list = (instr : Sil.instr) : Domain.t list =
match astate with match astate with
| ISLLatentMemoryError _ | AbortProgram _ | LatentAbortProgram _ -> | AbortProgram _ | ISLLatentMemoryError _ | LatentAbortProgram _ | LatentInvalidAccess _ ->
(* We can also continue the analysis with the error state here
but there might be a risk we would get nonsense. *)
[astate] [astate]
| ExitProgram _ -> | ExitProgram _ ->
(* program already exited, simply propagate the exited state upwards *) (* program already exited, simply propagate the exited state upwards *)
@ -344,7 +354,11 @@ module PulseTransferFunctions = struct
let remove_vars vars astates = let remove_vars vars astates =
List.concat_map astates ~f:(fun (astate : Domain.t) -> List.concat_map astates ~f:(fun (astate : Domain.t) ->
match astate with match astate with
| ISLLatentMemoryError _ | AbortProgram _ | ExitProgram _ | LatentAbortProgram _ -> | ISLLatentMemoryError _
| AbortProgram _
| ExitProgram _
| LatentAbortProgram _
| LatentInvalidAccess _ ->
[astate] [astate]
| ContinueProgram astate -> | ContinueProgram astate ->
PulseOperations.remove_vars tenv vars location astate PulseOperations.remove_vars tenv vars location astate

@ -20,6 +20,11 @@ type 'abductive_domain_t base_t =
| ExitProgram of AbductiveDomain.summary | ExitProgram of AbductiveDomain.summary
| AbortProgram of AbductiveDomain.summary | AbortProgram of AbductiveDomain.summary
| LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t} | LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t}
| LatentInvalidAccess of
{ astate: AbductiveDomain.summary
; address: AbstractValue.t
; must_be_valid: (Trace.t[@yojson.opaque])
; calling_context: ((CallEvent.t * Location.t) list[@yojson.opaque]) }
| ISLLatentMemoryError of AbductiveDomain.summary | ISLLatentMemoryError of AbductiveDomain.summary
[@@deriving equal, compare, yojson_of] [@@deriving equal, compare, yojson_of]
@ -43,19 +48,23 @@ let leq ~lhs ~rhs =
, LatentAbortProgram {astate= astate2; latent_issue= issue2} ) -> , LatentAbortProgram {astate= astate2; latent_issue= issue2} ) ->
LatentIssue.equal issue1 issue2 LatentIssue.equal issue1 issue2
&& AbductiveDomain.leq ~lhs:(astate1 :> AbductiveDomain.t) ~rhs:(astate2 :> AbductiveDomain.t) && AbductiveDomain.leq ~lhs:(astate1 :> AbductiveDomain.t) ~rhs:(astate2 :> AbductiveDomain.t)
| ( LatentInvalidAccess {astate= astate1; address= v1; must_be_valid= _}
, LatentInvalidAccess {astate= astate2; address= v2; must_be_valid= _} ) ->
AbstractValue.equal v1 v2
&& AbductiveDomain.leq ~lhs:(astate1 :> AbductiveDomain.t) ~rhs:(astate2 :> AbductiveDomain.t)
| _ -> | _ ->
false false
let pp fmt = function let pp fmt = function
| AbortProgram astate ->
F.fprintf fmt "{AbortProgram %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t)
| ContinueProgram astate -> | ContinueProgram astate ->
AbductiveDomain.pp fmt astate AbductiveDomain.pp fmt astate
| ISLLatentMemoryError astate ->
F.fprintf fmt "{ISLLatentMemoryError %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t)
| ExitProgram astate -> | ExitProgram astate ->
F.fprintf fmt "{ExitProgram %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t) F.fprintf fmt "{ExitProgram %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t)
| AbortProgram astate -> | ISLLatentMemoryError astate ->
F.fprintf fmt "{AbortProgram %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t) F.fprintf fmt "{ISLLatentMemoryError %a}" AbductiveDomain.pp (astate :> AbductiveDomain.t)
| LatentAbortProgram {astate; latent_issue} -> | LatentAbortProgram {astate; latent_issue} ->
let diagnostic = LatentIssue.to_diagnostic latent_issue in let diagnostic = LatentIssue.to_diagnostic latent_issue in
let message = Diagnostic.get_message diagnostic in let message = Diagnostic.get_message diagnostic in
@ -63,6 +72,9 @@ let pp fmt = function
F.fprintf fmt "{LatentAbortProgram(%a: %s) %a}" Location.pp location message F.fprintf fmt "{LatentAbortProgram(%a: %s) %a}" Location.pp location message
AbductiveDomain.pp AbductiveDomain.pp
(astate :> AbductiveDomain.t) (astate :> AbductiveDomain.t)
| LatentInvalidAccess {astate; address; must_be_valid= _} ->
F.fprintf fmt "{LatentInvalidAccess(%a) %a}" AbstractValue.pp address AbductiveDomain.pp
(astate :> AbductiveDomain.t)
(* do not export this function as there lies wickedness: clients should generally care about what (* do not export this function as there lies wickedness: clients should generally care about what
@ -73,6 +85,7 @@ let get_astate : t -> AbductiveDomain.t = function
| ExitProgram astate | ExitProgram astate
| AbortProgram astate | AbortProgram astate
| LatentAbortProgram {astate} | LatentAbortProgram {astate}
| LatentInvalidAccess {astate}
| ISLLatentMemoryError astate -> | ISLLatentMemoryError astate ->
(astate :> AbductiveDomain.t) (astate :> AbductiveDomain.t)

@ -6,6 +6,7 @@
*) *)
open! IStd open! IStd
open PulseBasicInterface
module AbductiveDomain = PulseAbductiveDomain module AbductiveDomain = PulseAbductiveDomain
module LatentIssue = PulseLatentIssue module LatentIssue = PulseLatentIssue
@ -17,6 +18,13 @@ type 'abductive_domain_t base_t =
(** represents the state at the program point that caused an error *) (** represents the state at the program point that caused an error *)
| LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t} | LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t}
(** this path leads to an error but we don't have conclusive enough data to report it yet *) (** this path leads to an error but we don't have conclusive enough data to report it yet *)
| LatentInvalidAccess of
{ astate: AbductiveDomain.summary
; address: AbstractValue.t
; must_be_valid: Trace.t
; calling_context: (CallEvent.t * Location.t) list }
(** if [address] is ever observed to be invalid then there is an invalid access because it
[must_be_valid] *)
| ISLLatentMemoryError of AbductiveDomain.summary | ISLLatentMemoryError of AbductiveDomain.summary
(** represents the state at the program point that might cause an error; used for (** represents the state at the program point that might cause an error; used for
{!Config.pulse_isl} *) {!Config.pulse_isl} *)

@ -745,10 +745,10 @@ let apply_prepost ~is_isl_error_prepost callee_proc_name call_location ~callee_p
astate astate
else Ok astate else Ok astate
in in
(astate, return_caller) (astate, return_caller, call_state.subst)
with with
| post -> | result ->
Sat post Sat result
| exception Contradiction reason -> | exception Contradiction reason ->
L.d_printfln "Cannot apply post-condition: %a" pp_contradiction reason ; L.d_printfln "Cannot apply post-condition: %a" pp_contradiction reason ;
Unsat ) Unsat )

@ -18,4 +18,15 @@ val apply_prepost :
-> formals:Var.t list -> formals:Var.t list
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list -> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
-> AbductiveDomain.t -> AbductiveDomain.t
-> (AbductiveDomain.t * (AbstractValue.t * ValueHistory.t) option) AccessResult.t SatUnsat.t -> ( AbductiveDomain.t
* (AbstractValue.t * ValueHistory.t) option
* (AbstractValue.t * ValueHistory.t) AbstractValue.Map.t )
AccessResult.t
SatUnsat.t
(** result of applying one pre/post pair of a callee's summary:
- {!SatUnsat.Unsat} if that path in the callee is infeasible
- otherwise, there can be an error detected
- otherwise, the result is a new abstract state, an optional return value, and a substitution
[callee_abstract_value -> caller_abstract_value] mapping callee's abstract values to what they
became in the new (caller) state *)

@ -41,6 +41,7 @@ let mk_objc_method_nil_summary {InterproceduralAnalysis.tenv; proc_desc; err_log
| ExitProgram _, _ | ExitProgram _, _
| AbortProgram _, _ | AbortProgram _, _
| LatentAbortProgram _, _ | LatentAbortProgram _, _
| LatentInvalidAccess _, _
| ISLLatentMemoryError _, _ -> | ISLLatentMemoryError _, _ ->
None None
@ -55,7 +56,11 @@ let append_objc_self_positive {InterproceduralAnalysis.tenv; proc_desc; err_log}
PulseArithmetic.prune_positive (fst value) astate PulseArithmetic.prune_positive (fst value) astate
in in
PulseReport.report_result tenv proc_desc err_log result PulseReport.report_result tenv proc_desc err_log result
| ExitProgram _ | AbortProgram _ | LatentAbortProgram _ | ISLLatentMemoryError _ -> | ExitProgram _
| AbortProgram _
| LatentAbortProgram _
| LatentInvalidAccess _
| ISLLatentMemoryError _ ->
[astate] [astate]

@ -20,6 +20,11 @@ module Import = struct
| ExitProgram of AbductiveDomain.summary | ExitProgram of AbductiveDomain.summary
| AbortProgram of AbductiveDomain.summary | AbortProgram of AbductiveDomain.summary
| LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t} | LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t}
| LatentInvalidAccess of
{ astate: AbductiveDomain.summary
; address: AbstractValue.t
; must_be_valid: Trace.t
; calling_context: (CallEvent.t * Location.t) list }
| ISLLatentMemoryError of AbductiveDomain.summary | ISLLatentMemoryError of AbductiveDomain.summary
type 'astate base_error = 'astate AccessResult.error = type 'astate base_error = 'astate AccessResult.error =
@ -622,7 +627,7 @@ let apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state
with with
| (Sat (Error _) | Unsat) as path_result -> | (Sat (Error _) | Unsat) as path_result ->
path_result path_result
| Sat (Ok (post, return_val_opt)) -> | Sat (Ok (post, return_val_opt, subst)) ->
let event = ValueHistory.Call {f= Call callee_pname; location= call_loc; in_call= []} in let event = 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
@ -631,28 +636,31 @@ let apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state
| None -> | None ->
havoc_id (fst ret) [event] post havoc_id (fst ret) [event] post
in in
f post f subst post
in in
let open ExecutionDomain in let open ExecutionDomain in
let open SatUnsat.Import in let open SatUnsat.Import in
match callee_exec_state with match callee_exec_state with
| ContinueProgram astate -> | ContinueProgram astate ->
map_call_result ~is_isl_error_prepost:false astate ~f:(fun astate -> map_call_result ~is_isl_error_prepost:false astate ~f:(fun _subst astate ->
Sat (Ok (ContinueProgram astate)) ) Sat (Ok (ContinueProgram astate)) )
| AbortProgram astate | ExitProgram astate | LatentAbortProgram {astate} -> | AbortProgram astate
| ExitProgram astate
| LatentAbortProgram {astate}
| LatentInvalidAccess {astate} ->
map_call_result ~is_isl_error_prepost:false map_call_result ~is_isl_error_prepost:false
(astate :> AbductiveDomain.t) (astate :> AbductiveDomain.t)
~f:(fun astate -> ~f:(fun subst astate ->
let+ (astate_summary : AbductiveDomain.summary) = let* (astate_summary : AbductiveDomain.summary) =
AbductiveDomain.summary_of_post tenv caller_proc_desc astate AbductiveDomain.summary_of_post tenv caller_proc_desc astate
in in
match callee_exec_state with match callee_exec_state with
| ContinueProgram _ | ISLLatentMemoryError _ -> | ContinueProgram _ | ISLLatentMemoryError _ ->
assert false assert false
| AbortProgram _ -> | AbortProgram _ ->
Ok (AbortProgram astate_summary) Sat (Ok (AbortProgram astate_summary))
| ExitProgram _ -> | ExitProgram _ ->
Ok (ExitProgram astate_summary) Sat (Ok (ExitProgram astate_summary))
| LatentAbortProgram {latent_issue} -> ( | LatentAbortProgram {latent_issue} -> (
let latent_issue = let latent_issue =
LatentIssue.add_call (CallEvent.Call callee_pname, call_loc) latent_issue LatentIssue.add_call (CallEvent.Call callee_pname, call_loc) latent_issue
@ -660,15 +668,43 @@ let apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state
let diagnostic = LatentIssue.to_diagnostic latent_issue in let diagnostic = LatentIssue.to_diagnostic latent_issue in
match LatentIssue.should_report astate_summary diagnostic with match LatentIssue.should_report astate_summary diagnostic with
| `DelayReport latent_issue -> | `DelayReport latent_issue ->
Ok (LatentAbortProgram {astate= astate_summary; latent_issue}) Sat (Ok (LatentAbortProgram {astate= astate_summary; latent_issue}))
| `ReportNow -> | `ReportNow ->
Error Sat
(ReportableError {diagnostic; astate= (astate_summary :> AbductiveDomain.t)}) ) (Error
(ReportableError {diagnostic; astate= (astate_summary :> AbductiveDomain.t)}))
) )
| LatentInvalidAccess {address= address_callee; must_be_valid; calling_context} -> (
match AbstractValue.Map.find_opt address_callee subst with
| None ->
(* the address became unreachable so the bug can never be reached; drop it *) Unsat
| Some (address, _history) -> (
let calling_context = (CallEvent.Call callee_pname, call_loc) :: calling_context in
match
AbductiveDomain.find_post_cell_opt address (astate_summary :> AbductiveDomain.t)
|> Option.bind ~f:(fun (_, attrs) -> Attributes.get_invalid attrs)
with
| None ->
(* still no proof that the address is invalid *)
Sat
(Ok
(LatentInvalidAccess
{astate= astate_summary; address; must_be_valid; calling_context}))
| Some (invalidation, invalidation_trace) ->
Sat
(Error
(ReportableError
{ diagnostic=
AccessToInvalidAddress
{ calling_context
; invalidation
; invalidation_trace
; access_trace= must_be_valid }
; astate= (astate_summary :> AbductiveDomain.t) })) ) ) )
| ISLLatentMemoryError astate -> | ISLLatentMemoryError astate ->
map_call_result ~is_isl_error_prepost:true map_call_result ~is_isl_error_prepost:true
(astate :> AbductiveDomain.t) (astate :> AbductiveDomain.t)
~f:(fun astate -> ~f:(fun _subst astate ->
let+ astate_summary = AbductiveDomain.summary_of_post tenv caller_proc_desc astate in let+ astate_summary = AbductiveDomain.summary_of_post tenv caller_proc_desc astate in
Ok (ISLLatentMemoryError astate_summary) ) Ok (ISLLatentMemoryError astate_summary) )

@ -27,6 +27,11 @@ module Import : sig
| ExitProgram of AbductiveDomain.summary | ExitProgram of AbductiveDomain.summary
| AbortProgram of AbductiveDomain.summary | AbortProgram of AbductiveDomain.summary
| LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t} | LatentAbortProgram of {astate: AbductiveDomain.summary; latent_issue: LatentIssue.t}
| LatentInvalidAccess of
{ astate: AbductiveDomain.summary
; address: AbstractValue.t
; must_be_valid: Trace.t
; calling_context: (CallEvent.t * Location.t) list }
| ISLLatentMemoryError of AbductiveDomain.summary | ISLLatentMemoryError of AbductiveDomain.summary
type 'astate base_error = 'astate AccessResult.error = type 'astate base_error = 'astate AccessResult.error =

@ -37,6 +37,8 @@ let exec_summary_of_post_common tenv ~continue_program proc_desc (exec_astate :
Some (ExitProgram astate) Some (ExitProgram astate)
| LatentAbortProgram {astate; latent_issue} -> | LatentAbortProgram {astate; latent_issue} ->
Some (LatentAbortProgram {astate; latent_issue}) Some (LatentAbortProgram {astate; latent_issue})
| LatentInvalidAccess {astate; address; must_be_valid; calling_context} ->
Some (LatentInvalidAccess {astate; address; must_be_valid; calling_context})
| ISLLatentMemoryError astate -> | ISLLatentMemoryError astate ->
Some (ISLLatentMemoryError astate) Some (ISLLatentMemoryError astate)

Loading…
Cancel
Save