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

303 lines
13 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open! IStd
module 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 path call_loc (reason : CallEvent.t) ~ret ~actuals ~formals_opt astate =
let event = ValueHistory.Call {f= reason; location= call_loc; in_call= []} in
let ret_val = AbstractValue.mk_fresh () in
let astate = PulseOperations.write_id (fst ret) (ret_val, [event]) astate in
(* set to [false] if we think the procedure called does not behave "functionally", i.e. return the
same value for the same inputs *)
let is_functional = ref true 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) ->
is_functional := false ;
(* 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 =
let* astate, f =
match reason with
| Call _ | Model _ ->
Ok (astate, None)
| SkippedKnownCall proc_name ->
Ok (astate, Some (PulseFormula.Procname proc_name))
| SkippedUnknownCall e ->
let+ astate, (v, _) = PulseOperations.eval path Read call_loc e astate in
(astate, Some (PulseFormula.Unknown v))
in
let+ astate =
match f with
| None ->
Ok astate
| Some f ->
PulseArithmetic.and_equal (AbstractValueOperand ret_val)
(FunctionApplicationOperand
{f; actuals= List.map ~f:(fun ((actual_val, _hist), _typ) -> actual_val) actuals})
astate
in
match reason with
| SkippedKnownCall proc_name ->
AbductiveDomain.add_skipped_call proc_name
(Trace.Immediate {location= call_loc; history= []})
astate
| _ ->
astate
in
let havoc_actuals_without_typ_info astate =
List.fold actuals ~init:astate ~f:(fun astate actual_typ ->
havoc_actual_if_ptr actual_typ None astate )
in
L.d_printfln "skipping unknown procedure" ;
( match formals_opt with
| None ->
havoc_actuals_without_typ_info astate
| Some formals -> (
match
List.fold2 actuals formals ~init:astate ~f:(fun astate actual_typ (_, formal_typ) ->
havoc_actual_if_ptr actual_typ (Some formal_typ) astate )
with
| Unequal_lengths ->
L.d_printfln "ERROR: formals have length %d but actuals have length %d"
(List.length formals) (List.length actuals) ;
havoc_actuals_without_typ_info astate
| Ok result ->
result ) )
|> add_skipped_proc
let apply_callee tenv path ~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 path ~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 post =
match return_val_opt with
| Some return_val_hist ->
PulseOperations.write_id (fst ret) return_val_hist post
| None ->
PulseOperations.havoc_id (fst ret)
[ValueHistory.Call {f= Call callee_pname; location= call_loc; in_call= []}]
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 call_loc astate
>>| AccessResult.ignore_memory_leaks >>| AccessResult.of_abductive_result
:> (AbductiveDomain.summary, AbductiveDomain.t AccessResult.error) result SatUnsat.t
)
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 (ReportableErrorSummary {diagnostic; astate= astate_summary}))
| `ISLDelay astate ->
Sat (Error (ISLError astate)) )
| 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
(ReportableErrorSummary
{ diagnostic=
AccessToInvalidAddress
{ calling_context
; invalidation
; invalidation_trace
; access_trace= fst must_be_valid
; must_be_valid_reason= snd must_be_valid }
; astate= astate_summary })) ) ) ) )
| 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 call_loc astate
>>| AccessResult.ignore_memory_leaks >>| AccessResult.of_abductive_result
:> (AbductiveDomain.summary, AbductiveDomain.t AccessResult.error) result SatUnsat.t )
>>| 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 (Caml.List.to_seq arg_values) (post :> BaseDomain.t)
in
AbstractValue.Set.fold AbductiveDomain.initialize reachable_values astate
let call_aux tenv path caller_proc_desc call_loc callee_pname ret actuals callee_proc_desc
exec_states (astate : AbductiveDomain.t) =
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; typ} ->
let pvar = Pvar.mk name callee_pname in
(Var.of_pvar pvar, capture_mode, typ) )
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 path 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 ~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 path ~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 )
let call tenv path ~caller_proc_desc ~(callee_data : (Procdesc.t * PulseSummary.t) option) call_loc
callee_pname ~ret ~actuals ~formals_opt (astate : AbductiveDomain.t) =
(* a special case for objc nil messaging *)
let unknown_objc_nil_messaging astate_unknown procdesc =
let result_unknown =
let<+> astate_unknown =
PulseObjectiveCSummary.append_objc_actual_self_positive procdesc (List.hd actuals)
astate_unknown
in
astate_unknown
in
let result_unknown_nil =
PulseObjectiveCSummary.mk_objc_method_nil_summary tenv procdesc
(ExecutionDomain.mk_initial tenv procdesc)
|> Option.value_map ~default:[] ~f:(fun nil_summary ->
let<*> nil_astate = nil_summary in
call_aux tenv path caller_proc_desc call_loc callee_pname ret actuals procdesc
([ContinueProgram nil_astate] :> ExecutionDomain.t list)
astate )
in
result_unknown @ result_unknown_nil
in
match callee_data with
| Some (callee_proc_desc, exec_states) ->
call_aux tenv path caller_proc_desc call_loc callee_pname ret actuals callee_proc_desc
(exec_states :> ExecutionDomain.t list)
astate
| None ->
(* no spec found for some reason (unknown function, ...) *)
L.d_printfln "No spec found for %a@\n" Procname.pp callee_pname ;
let arg_values = List.map actuals ~f:(fun ((value, _), _) -> value) in
let<*> astate_unknown =
conservatively_initialize_args arg_values astate
|> unknown_call tenv path call_loc (SkippedKnownCall callee_pname) ~ret ~actuals
~formals_opt
in
let callee_procdesc_opt = AnalysisCallbacks.get_proc_desc callee_pname in
Option.value_map callee_procdesc_opt
~default:[Ok (ContinueProgram astate_unknown)]
~f:(unknown_objc_nil_messaging astate_unknown)