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.

205 lines
8.0 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
open AbstractDomain.Types
module F = Format
module L = Logging
module ModifiedVarSet = PrettyPrintable.MakePPSet (Var)
module InstrCFG = ProcCfg.NormalOneInstrPerNode
let debug fmt = L.debug Analysis Verbose fmt
(* A simple purity checker *)
type analysis_data =
{ tenv: Tenv.t
; inferbo_invariant_map: BufferOverrunAnalysis.invariant_map
; formals: Var.t list
; get_callee_summary: Procname.t -> PurityDomain.summary option }
module TransferFunctions = struct
module CFG = ProcCfg.Normal
module Domain = PurityDomain
type nonrec analysis_data = analysis_data
let get_alias_set inferbo_mem var =
let default = ModifiedVarSet.empty in
let alias_v = BufferOverrunDomain.Mem.find (AbsLoc.Loc.of_var var) inferbo_mem in
let pow_locs = BufferOverrunDomain.Val.get_all_locs alias_v in
AbsLoc.PowLoc.fold
(fun loc modified_acc ->
AbsLoc.Loc.get_path loc
|> Option.value_map ~default:modified_acc ~f:(fun path ->
Symb.SymbolPath.get_pvar path
|> Option.value_map ~default:modified_acc ~f:(fun pvar ->
debug "Add alias of %a -> %a " Var.pp var (Pvar.pp Pp.text) pvar ;
ModifiedVarSet.add (Var.of_pvar pvar) modified_acc ) ) )
pow_locs default
let get_modified_params formals ~f =
List.foldi ~init:Domain.ModifiedParamIndices.empty
~f:(fun i modified_acc var ->
if f var then Domain.ModifiedParamIndices.add i modified_acc else modified_acc )
formals
(* given a heap access to ae, find which parameter indices of pdesc
it modifies *)
let track_modified_params inferbo_mem formals ae =
let base_var, _ = HilExp.AccessExpression.get_base ae in
(* treat writes to global (static) variables separately since they
are not considered to be explicit parameters. *)
if Var.is_global base_var then Domain.impure_global
else
let alias_set = lazy (get_alias_set inferbo_mem base_var) in
get_modified_params formals ~f:(fun var ->
Var.equal var base_var || ModifiedVarSet.mem var (Lazy.force alias_set) )
|> Domain.impure_params
let rec is_heap_access ae =
match (ae : HilExp.AccessExpression.t) with
| FieldOffset _ | ArrayOffset _ ->
true
| Dereference ae | AddressOf ae ->
is_heap_access ae
| Base _ ->
false
exception Modified_Global
(* find all the variables occurring in the given modified argument exp,
unless the variable is a global (which makes all other function
calls impure since they may read from that modified global var ) *)
let get_modified_vars_unless_global inferbo_mem modified_acc modified_arg_exp =
debug "Argument %a is modified.\n" HilExp.pp modified_arg_exp ;
let ae_list = HilExp.get_access_exprs modified_arg_exp in
List.fold ae_list ~init:modified_acc ~f:(fun modified_acc ae ->
let base_var, typ = HilExp.AccessExpression.get_base ae in
let alias_set = get_alias_set inferbo_mem base_var in
debug "Alias set for %a : %a \n\n\n" Var.pp base_var ModifiedVarSet.pp alias_set ;
if Var.is_global base_var || ModifiedVarSet.exists Var.is_global alias_set then
raise Modified_Global
else
let modified_acc' =
if Typ.is_pointer typ then ModifiedVarSet.add base_var modified_acc else modified_acc
in
ModifiedVarSet.union modified_acc' alias_set )
(* given the modified parameters and the args of the callee, find
parameter indices of the current procedure that match, i.e have
been modified by the callee. Note that index counting starts from
0, reserved for the implicit parameter (formal) this .
E.g. : for the below call to 'impure_fun' in 'foo', we return 2
(i.e. index of a wrt. foo's formals).
void foo (int x, Object a, Object b){
for (...){
impure_fun(b, 10, a); // modifies only 3rd argument, i.e. a
}
}
*)
let find_params_matching_modified_args inferbo_mem formals callee_args callee_modified_params =
try
let vars_of_modified_args =
List.foldi ~init:ModifiedVarSet.empty
~f:(fun i acc arg_exp ->
if Domain.ModifiedParamIndices.mem i callee_modified_params then
get_modified_vars_unless_global inferbo_mem acc arg_exp
else acc )
callee_args
in
(* find the respective parameter of the caller, matching the modified vars *)
let caller_modified_params =
get_modified_params formals ~f:(fun formal_var ->
ModifiedVarSet.mem formal_var vars_of_modified_args )
in
Domain.impure_params caller_modified_params
with Modified_Global -> Domain.impure_global
(* if the callee is impure, find the parameters that have been modified by the callee *)
let find_modified_if_impure inferbo_mem formals args callee_summary =
match callee_summary with
| Top ->
Domain.impure_global
| NonTop callee_modified_params ->
Domain.(
debug "Callee modified params %a \n" ModifiedParamIndices.pp callee_modified_params ;
if ModifiedParamIndices.is_empty callee_modified_params then pure
else find_params_matching_modified_args inferbo_mem formals args callee_modified_params)
let modified_global ae = HilExp.AccessExpression.get_base ae |> fst |> Var.is_global
let exec_instr (astate : Domain.t) {tenv; inferbo_invariant_map; formals; get_callee_summary}
(node : CFG.Node.t) (instr : HilInstr.t) =
let (node_id : InstrCFG.Node.id) =
CFG.Node.underlying_node node |> InstrCFG.last_of_underlying_node |> InstrCFG.Node.id
in
let inferbo_mem =
Option.value_exn (BufferOverrunAnalysis.extract_post node_id inferbo_invariant_map)
in
match instr with
| Assign (ae, _, _)
when is_heap_access ae || (Language.curr_language_is Clang && modified_global ae) ->
track_modified_params inferbo_mem formals ae |> Domain.join astate
| Call (_, Direct called_pname, args, _, _) ->
Domain.join astate
( match PurityModels.ProcName.dispatch tenv called_pname with
| Some callee_summary ->
find_modified_if_impure inferbo_mem formals args callee_summary
| None -> (
match get_callee_summary called_pname with
| Some callee_summary ->
debug "Reading from %a \n" Procname.pp called_pname ;
find_modified_if_impure inferbo_mem formals args callee_summary
| None ->
if Procname.is_constructor called_pname then Domain.pure else Domain.impure_global )
)
| Call (_, Indirect _, _, _, _) ->
(* This should never happen in Java *)
debug "Unexpected indirect call %a" HilInstr.pp instr ;
Top
| _ ->
astate
let pp_session_name _node fmt = F.pp_print_string fmt "purity checker"
end
module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions)
let compute_summary {InterproceduralAnalysis.proc_desc; tenv; analyze_dependency}
inferbo_invariant_map =
let proc_name = Procdesc.get_proc_name proc_desc in
let formals =
Procdesc.get_formals proc_desc
|> List.map ~f:(fun (mname, _) -> Var.of_pvar (Pvar.mk mname proc_name))
in
let get_callee_summary callee_pname =
analyze_dependency callee_pname |> Option.bind ~f:(fun (_, (purity_opt, _)) -> purity_opt)
in
let analysis_data = {tenv; inferbo_invariant_map; formals; get_callee_summary} in
Analyzer.compute_post analysis_data ~initial:PurityDomain.pure proc_desc
let checker analysis_data =
let inferbo_invariant_map =
BufferOverrunAnalysis.cached_compute_invariant_map
(InterproceduralAnalysis.bind_payload ~f:snd analysis_data)
in
let astate_opt = compute_summary analysis_data inferbo_invariant_map in
Option.iter astate_opt ~f:(fun astate -> debug "Purity summary :%a \n" PurityDomain.pp astate) ;
astate_opt