Summary: To implement nil summaries for unknown calls I would like to reuse functionality from PulseObjectiveCSummary which already depends on PulseOperations causing circular dependencies. Reviewed By: jvillard Differential Revision: D27155092 fbshipit-source-id: 1c300ead0master
parent
55871dd285
commit
056c8abbff
@ -0,0 +1,243 @@
|
|||||||
|
(*
|
||||||
|
* 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 L = Logging
|
||||||
|
open PulseBasicInterface
|
||||||
|
open PulseDomainInterface
|
||||||
|
open PulseOperations.Import
|
||||||
|
|
||||||
|
type t = AbductiveDomain.t
|
||||||
|
|
||||||
|
let is_ptr_to_const formal_typ_opt =
|
||||||
|
Option.value_map formal_typ_opt ~default:false ~f:(fun (formal_typ : Typ.t) ->
|
||||||
|
match formal_typ.desc with Typ.Tptr (t, _) -> Typ.is_const t.quals | _ -> false )
|
||||||
|
|
||||||
|
|
||||||
|
let unknown_call tenv call_loc reason ~ret ~actuals ~formals_opt astate =
|
||||||
|
let event = ValueHistory.Call {f= reason; location= call_loc; in_call= []} in
|
||||||
|
let havoc_ret (ret, _) astate = PulseOperations.havoc_id ret [event] astate in
|
||||||
|
let rec havoc_fields ((_, history) as addr) typ astate =
|
||||||
|
match typ.Typ.desc with
|
||||||
|
| Tstruct struct_name -> (
|
||||||
|
match Tenv.lookup tenv struct_name with
|
||||||
|
| Some {fields} ->
|
||||||
|
List.fold fields ~init:astate ~f:(fun acc (field, field_typ, _) ->
|
||||||
|
let fresh_value = AbstractValue.mk_fresh () in
|
||||||
|
Memory.add_edge addr (FieldAccess field) (fresh_value, [event]) call_loc acc
|
||||||
|
|> havoc_fields (fresh_value, history) field_typ )
|
||||||
|
| None ->
|
||||||
|
astate )
|
||||||
|
| _ ->
|
||||||
|
astate
|
||||||
|
in
|
||||||
|
let havoc_actual_if_ptr (actual, actual_typ) formal_typ_opt astate =
|
||||||
|
(* We should not havoc when the corresponding formal is a
|
||||||
|
pointer to const *)
|
||||||
|
match actual_typ.Typ.desc with
|
||||||
|
| Tptr (typ, _)
|
||||||
|
when (not (Language.curr_language_is Java)) && not (is_ptr_to_const formal_typ_opt) ->
|
||||||
|
(* HACK: write through the pointer even if it is invalid (except in Java). This is to avoid raising issues when
|
||||||
|
havoc'ing pointer parameters (which normally causes a [check_valid] call. *)
|
||||||
|
let fresh_value = AbstractValue.mk_fresh () in
|
||||||
|
Memory.add_edge actual Dereference (fresh_value, [event]) call_loc astate
|
||||||
|
|> havoc_fields actual typ
|
||||||
|
| _ ->
|
||||||
|
astate
|
||||||
|
in
|
||||||
|
let add_skipped_proc astate =
|
||||||
|
match reason with
|
||||||
|
| CallEvent.SkippedKnownCall proc_name ->
|
||||||
|
AbductiveDomain.add_skipped_call proc_name
|
||||||
|
(Trace.Immediate {location= call_loc; history= []})
|
||||||
|
astate
|
||||||
|
| _ ->
|
||||||
|
astate
|
||||||
|
in
|
||||||
|
L.d_printfln "skipping unknown procedure@." ;
|
||||||
|
( match formals_opt with
|
||||||
|
| None ->
|
||||||
|
List.fold actuals
|
||||||
|
~f:(fun astate actual_typ -> havoc_actual_if_ptr actual_typ None astate)
|
||||||
|
~init:astate
|
||||||
|
| Some formals -> (
|
||||||
|
match
|
||||||
|
List.fold2 actuals formals
|
||||||
|
~f:(fun astate actual_typ (_, formal_typ) ->
|
||||||
|
havoc_actual_if_ptr actual_typ (Some formal_typ) astate )
|
||||||
|
~init:astate
|
||||||
|
with
|
||||||
|
| Unequal_lengths ->
|
||||||
|
L.d_printfln "ERROR: formals have length %d but actuals have length %d"
|
||||||
|
(List.length formals) (List.length actuals) ;
|
||||||
|
astate
|
||||||
|
| Ok result ->
|
||||||
|
result ) )
|
||||||
|
|> havoc_ret ret |> add_skipped_proc
|
||||||
|
|
||||||
|
|
||||||
|
let apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state ~ret
|
||||||
|
~captured_vars_with_actuals ~formals ~actuals astate =
|
||||||
|
let map_call_result ~is_isl_error_prepost callee_prepost ~f =
|
||||||
|
match
|
||||||
|
PulseInterproc.apply_prepost ~is_isl_error_prepost callee_pname call_loc ~callee_prepost
|
||||||
|
~captured_vars_with_actuals ~formals ~actuals astate
|
||||||
|
with
|
||||||
|
| (Sat (Error _) | Unsat) as path_result ->
|
||||||
|
path_result
|
||||||
|
| Sat (Ok (post, return_val_opt, subst)) ->
|
||||||
|
let event = ValueHistory.Call {f= Call callee_pname; location= call_loc; in_call= []} in
|
||||||
|
let post =
|
||||||
|
match return_val_opt with
|
||||||
|
| Some (return_val, return_hist) ->
|
||||||
|
PulseOperations.write_id (fst ret) (return_val, event :: return_hist) post
|
||||||
|
| None ->
|
||||||
|
PulseOperations.havoc_id (fst ret) [event] post
|
||||||
|
in
|
||||||
|
f subst post
|
||||||
|
in
|
||||||
|
let open ExecutionDomain in
|
||||||
|
let open SatUnsat.Import in
|
||||||
|
match callee_exec_state with
|
||||||
|
| ContinueProgram astate ->
|
||||||
|
map_call_result ~is_isl_error_prepost:false astate ~f:(fun _subst astate ->
|
||||||
|
Sat (Ok (ContinueProgram astate)) )
|
||||||
|
| AbortProgram astate
|
||||||
|
| ExitProgram astate
|
||||||
|
| LatentAbortProgram {astate}
|
||||||
|
| LatentInvalidAccess {astate} ->
|
||||||
|
map_call_result ~is_isl_error_prepost:false
|
||||||
|
(astate :> AbductiveDomain.t)
|
||||||
|
~f:(fun subst astate ->
|
||||||
|
let* astate_summary_result =
|
||||||
|
AbductiveDomain.summary_of_post tenv caller_proc_desc astate
|
||||||
|
>>| AccessResult.of_abductive_result
|
||||||
|
in
|
||||||
|
match astate_summary_result with
|
||||||
|
| Error _ as error ->
|
||||||
|
Sat error
|
||||||
|
| Ok (astate_summary : AbductiveDomain.summary) -> (
|
||||||
|
match callee_exec_state with
|
||||||
|
| ContinueProgram _ | ISLLatentMemoryError _ ->
|
||||||
|
assert false
|
||||||
|
| AbortProgram _ ->
|
||||||
|
Sat (Ok (AbortProgram astate_summary))
|
||||||
|
| ExitProgram _ ->
|
||||||
|
Sat (Ok (ExitProgram astate_summary))
|
||||||
|
| LatentAbortProgram {latent_issue} -> (
|
||||||
|
let latent_issue =
|
||||||
|
LatentIssue.add_call (CallEvent.Call callee_pname, call_loc) latent_issue
|
||||||
|
in
|
||||||
|
let diagnostic = LatentIssue.to_diagnostic latent_issue in
|
||||||
|
match LatentIssue.should_report astate_summary diagnostic with
|
||||||
|
| `DelayReport latent_issue ->
|
||||||
|
Sat (Ok (LatentAbortProgram {astate= astate_summary; latent_issue}))
|
||||||
|
| `ReportNow ->
|
||||||
|
Sat
|
||||||
|
(Error
|
||||||
|
(ReportableError {diagnostic; astate= (astate_summary :> AbductiveDomain.t)}))
|
||||||
|
| `ISLDelay astate ->
|
||||||
|
Sat (Error (ISLError (astate :> 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 ->
|
||||||
|
map_call_result ~is_isl_error_prepost:true
|
||||||
|
(astate :> AbductiveDomain.t)
|
||||||
|
~f:(fun _subst astate ->
|
||||||
|
AbductiveDomain.summary_of_post tenv caller_proc_desc astate
|
||||||
|
>>| AccessResult.of_abductive_result
|
||||||
|
>>| Result.map ~f:(fun astate_summary -> ISLLatentMemoryError astate_summary) )
|
||||||
|
|
||||||
|
|
||||||
|
let conservatively_initialize_args arg_values ({AbductiveDomain.post} as astate) =
|
||||||
|
let reachable_values = BaseDomain.reachable_addresses_from arg_values (post :> BaseDomain.t) in
|
||||||
|
AbstractValue.Set.fold AbductiveDomain.initialize reachable_values astate
|
||||||
|
|
||||||
|
|
||||||
|
let call tenv ~caller_proc_desc ~(callee_data : (Procdesc.t * PulseSummary.t) option) call_loc
|
||||||
|
callee_pname ~ret ~actuals ~formals_opt (astate : AbductiveDomain.t) =
|
||||||
|
let get_arg_values () = List.map actuals ~f:(fun ((value, _), _) -> value) in
|
||||||
|
match callee_data 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
|
||||||
|
let captured_vars =
|
||||||
|
Procdesc.get_captured callee_proc_desc
|
||||||
|
|> List.map ~f:(fun {CapturedVar.name; capture_mode} ->
|
||||||
|
let pvar = Pvar.mk name callee_pname in
|
||||||
|
(Var.of_pvar pvar, capture_mode) )
|
||||||
|
in
|
||||||
|
let<*> astate, captured_vars_with_actuals =
|
||||||
|
match actuals with
|
||||||
|
| (actual_closure, _) :: _
|
||||||
|
when not (Procname.is_objc_block callee_pname || List.is_empty captured_vars) ->
|
||||||
|
(* Assumption: the first parameter will be a closure *)
|
||||||
|
PulseOperations.get_captured_actuals call_loc ~captured_vars ~actual_closure astate
|
||||||
|
| _ ->
|
||||||
|
Ok (astate, [])
|
||||||
|
in
|
||||||
|
let should_keep_at_most_one_disjunct =
|
||||||
|
Option.exists Config.pulse_cut_to_one_path_procedures_pattern ~f:(fun regex ->
|
||||||
|
Str.string_match regex (Procname.to_string callee_pname) 0 )
|
||||||
|
in
|
||||||
|
if should_keep_at_most_one_disjunct then
|
||||||
|
L.d_printfln "Will keep at most one disjunct because %a is in blacklist" Procname.pp
|
||||||
|
callee_pname ;
|
||||||
|
(* call {!AbductiveDomain.PrePost.apply} on each pre/post pair in the summary. *)
|
||||||
|
List.fold ~init:[]
|
||||||
|
(exec_states :> ExecutionDomain.t list)
|
||||||
|
~f:(fun posts callee_exec_state ->
|
||||||
|
if should_keep_at_most_one_disjunct && not (List.is_empty posts) then posts
|
||||||
|
else
|
||||||
|
(* apply all pre/post specs *)
|
||||||
|
match
|
||||||
|
apply_callee tenv ~caller_proc_desc callee_pname call_loc callee_exec_state
|
||||||
|
~captured_vars_with_actuals ~formals ~actuals ~ret astate
|
||||||
|
with
|
||||||
|
| Unsat ->
|
||||||
|
(* couldn't apply pre/post pair *)
|
||||||
|
posts
|
||||||
|
| Sat post ->
|
||||||
|
post :: posts )
|
||||||
|
| None ->
|
||||||
|
(* no spec found for some reason (unknown function, ...) *)
|
||||||
|
L.d_printfln "No spec found for %a@\n" Procname.pp callee_pname ;
|
||||||
|
let astate =
|
||||||
|
conservatively_initialize_args (get_arg_values ()) astate
|
||||||
|
|> unknown_call tenv call_loc (SkippedKnownCall callee_pname) ~ret ~actuals ~formals_opt
|
||||||
|
in
|
||||||
|
[Ok (ContinueProgram astate)]
|
@ -0,0 +1,40 @@
|
|||||||
|
(*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*)
|
||||||
|
|
||||||
|
open! IStd
|
||||||
|
open PulseBasicInterface
|
||||||
|
open PulseDomainInterface
|
||||||
|
|
||||||
|
type t = AbductiveDomain.t
|
||||||
|
|
||||||
|
val call :
|
||||||
|
Tenv.t
|
||||||
|
-> caller_proc_desc:Procdesc.t
|
||||||
|
-> callee_data:(Procdesc.t * PulseSummary.t) option
|
||||||
|
-> Location.t
|
||||||
|
-> Procname.t
|
||||||
|
-> ret:Ident.t * Typ.t
|
||||||
|
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
|
||||||
|
-> formals_opt:(Pvar.t * Typ.t) list option
|
||||||
|
-> t
|
||||||
|
-> ExecutionDomain.t AccessResult.t list
|
||||||
|
(** perform an interprocedural call: apply the summary for the call proc name passed as argument if
|
||||||
|
it exists *)
|
||||||
|
|
||||||
|
val unknown_call :
|
||||||
|
Tenv.t
|
||||||
|
-> Location.t
|
||||||
|
-> CallEvent.t
|
||||||
|
-> ret:Ident.t * Typ.t
|
||||||
|
-> actuals:((AbstractValue.t * ValueHistory.t) * Typ.t) list
|
||||||
|
-> formals_opt:(Pvar.t * Typ.t) list option
|
||||||
|
-> t
|
||||||
|
-> t
|
||||||
|
(** performs a call to a function with no summary by optimistically havoc'ing the by-ref actuals and
|
||||||
|
the return value as appropriate *)
|
||||||
|
|
||||||
|
val conservatively_initialize_args : AbstractValue.t list -> t -> t
|
Loading…
Reference in new issue