|
|
|
(*
|
|
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
|
|
* Copyright (c) 2013 - present Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*)
|
|
|
|
|
|
|
|
open! IStd
|
|
|
|
module L = Logging
|
|
|
|
module F = Format
|
|
|
|
|
|
|
|
(** data type for the control flow graph *)
|
|
|
|
type t = Procdesc.t Typ.Procname.Hash.t
|
|
|
|
|
|
|
|
let create () = Typ.Procname.Hash.create 16
|
|
|
|
|
|
|
|
let get_all_proc_descs cfg =
|
|
|
|
let procs = ref [] in
|
|
|
|
let f _ pdesc = procs := pdesc :: !procs in
|
|
|
|
Typ.Procname.Hash.iter f cfg ; !procs
|
|
|
|
|
|
|
|
|
|
|
|
let get_all_proc_names cfg =
|
|
|
|
let procs = ref [] in
|
|
|
|
let f pname _ = procs := pname :: !procs in
|
|
|
|
Typ.Procname.Hash.iter f cfg ; !procs
|
|
|
|
|
|
|
|
|
|
|
|
(** Create a new procdesc *)
|
|
|
|
let create_proc_desc cfg (proc_attributes: ProcAttributes.t) =
|
|
|
|
let pdesc = Procdesc.from_proc_attributes proc_attributes in
|
|
|
|
Typ.Procname.Hash.add cfg proc_attributes.proc_name pdesc ;
|
|
|
|
pdesc
|
|
|
|
|
|
|
|
|
|
|
|
(** Iterate over all the nodes in the cfg *)
|
|
|
|
let iter_all_nodes ?(sorted= false) f cfg =
|
|
|
|
let do_proc_desc _ (pdesc: Procdesc.t) =
|
|
|
|
List.iter ~f:(fun node -> f pdesc node) (Procdesc.get_nodes pdesc)
|
|
|
|
in
|
|
|
|
if not sorted then Typ.Procname.Hash.iter do_proc_desc cfg
|
|
|
|
else
|
|
|
|
Typ.Procname.Hash.fold
|
|
|
|
(fun _ pdesc desc_nodes ->
|
|
|
|
List.fold
|
|
|
|
~f:(fun desc_nodes node -> (pdesc, node) :: desc_nodes)
|
|
|
|
~init:desc_nodes (Procdesc.get_nodes pdesc) )
|
|
|
|
cfg []
|
|
|
|
|> List.sort ~cmp:[%compare : Procdesc.t * Procdesc.Node.t]
|
|
|
|
|> List.iter ~f:(fun (d, n) -> f d n)
|
|
|
|
|
|
|
|
|
|
|
|
(** checks whether a cfg is connected or not *)
|
|
|
|
let check_cfg_connectedness cfg =
|
|
|
|
let is_exit_node n =
|
|
|
|
match Procdesc.Node.get_kind n with Procdesc.Node.Exit_node _ -> true | _ -> false
|
|
|
|
in
|
|
|
|
let broken_node n =
|
|
|
|
let succs = Procdesc.Node.get_succs n in
|
|
|
|
let preds = Procdesc.Node.get_preds n in
|
|
|
|
match Procdesc.Node.get_kind n with
|
|
|
|
| Procdesc.Node.Start_node _ ->
|
|
|
|
Int.equal (List.length succs) 0 || List.length preds > 0
|
|
|
|
| Procdesc.Node.Exit_node _ ->
|
|
|
|
List.length succs > 0 || Int.equal (List.length preds) 0
|
|
|
|
| Procdesc.Node.Stmt_node _ | Procdesc.Node.Prune_node _ | Procdesc.Node.Skip_node _ ->
|
|
|
|
Int.equal (List.length succs) 0 || Int.equal (List.length preds) 0
|
|
|
|
| Procdesc.Node.Join_node ->
|
|
|
|
(* Join node has the exception that it may be without predecessors
|
|
|
|
and pointing to an exit node *)
|
|
|
|
(* if the if brances end with a return *)
|
|
|
|
match succs with [n'] when is_exit_node n' -> false | _ -> Int.equal (List.length preds) 0
|
|
|
|
in
|
|
|
|
let do_pdesc pname pd =
|
|
|
|
let nodes = Procdesc.get_nodes pd in
|
|
|
|
(* TODO (T20302015): also check the CFGs for the C-like procedures *)
|
|
|
|
if not Config.keep_going && Typ.Procname.is_java pname && List.exists ~f:broken_node nodes then
|
|
|
|
L.(die InternalError) "Broken CFG on %a" Typ.Procname.pp pname
|
|
|
|
in
|
|
|
|
Typ.Procname.Hash.iter do_pdesc cfg
|
|
|
|
|
|
|
|
|
|
|
|
let load_statement =
|
|
|
|
ResultsDatabase.register_statement "SELECT cfgs FROM source_files WHERE source_file = :k"
|
|
|
|
|
|
|
|
|
|
|
|
module SQLite = SqliteUtils.MarshalledData (struct
|
|
|
|
type nonrec t = t
|
|
|
|
end)
|
|
|
|
|
|
|
|
let load source =
|
|
|
|
ResultsDatabase.with_registered_statement load_statement ~f:(fun db load_stmt ->
|
|
|
|
SourceFile.SQLite.serialize source |> Sqlite3.bind load_stmt 1
|
|
|
|
|> SqliteUtils.check_sqlite_error db ~log:"load bind source file" ;
|
|
|
|
SqliteUtils.sqlite_result_step ~finalize:false ~log:"Cfg.load" db load_stmt
|
|
|
|
|> Option.map ~f:SQLite.deserialize )
|
|
|
|
|
|
|
|
|
|
|
|
(** Save the .attr files for the procedures in the cfg. *)
|
|
|
|
let save_attributes source_file cfg =
|
|
|
|
let save_proc _ pdesc =
|
|
|
|
let attributes = Procdesc.get_attributes pdesc in
|
|
|
|
let loc = attributes.loc in
|
|
|
|
let attributes' =
|
|
|
|
let loc' = if Location.equal loc Location.dummy then {loc with file= source_file} else loc in
|
|
|
|
{attributes with loc= loc'; source_file_captured= source_file}
|
|
|
|
in
|
|
|
|
Attributes.store attributes'
|
|
|
|
in
|
|
|
|
Typ.Procname.Hash.iter save_proc cfg
|
|
|
|
|
|
|
|
|
|
|
|
(** Inline a synthetic (access or bridge) method. *)
|
|
|
|
let inline_synthetic_method ret_id etl pdesc loc_call : Sil.instr option =
|
|
|
|
let modified = ref None in
|
|
|
|
let found instr instr' =
|
|
|
|
modified := Some instr' ;
|
|
|
|
L.(debug Analysis Verbose)
|
|
|
|
"XX inline_synthetic_method found instr: %a@." (Sil.pp_instr Pp.text) instr ;
|
|
|
|
L.(debug Analysis Verbose)
|
|
|
|
"XX inline_synthetic_method instr': %a@." (Sil.pp_instr Pp.text) instr'
|
|
|
|
in
|
|
|
|
let do_instr _ instr =
|
|
|
|
match (instr, ret_id, etl) with
|
|
|
|
| ( Sil.Load (_, Exp.Lfield (Exp.Var _, fn, ft), bt, _)
|
|
|
|
, Some (ret_id, _)
|
|
|
|
, [(* getter for fields *) (e1, _)] ) ->
|
|
|
|
let instr' = Sil.Load (ret_id, Exp.Lfield (e1, fn, ft), bt, loc_call) in
|
|
|
|
found instr instr'
|
|
|
|
| Sil.Load (_, Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, _), Some (ret_id, _), []
|
|
|
|
when Pvar.is_global pvar ->
|
|
|
|
(* getter for static fields *)
|
|
|
|
let instr' = Sil.Load (ret_id, Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, loc_call) in
|
|
|
|
found instr instr'
|
|
|
|
| Sil.Store (Exp.Lfield (_, fn, ft), bt, _, _), _, [(* setter for fields *) (e1, _); (e2, _)] ->
|
|
|
|
let instr' = Sil.Store (Exp.Lfield (e1, fn, ft), bt, e2, loc_call) in
|
|
|
|
found instr instr'
|
|
|
|
| Sil.Store (Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, _, _), _, [(e1, _)]
|
|
|
|
when Pvar.is_global pvar ->
|
|
|
|
(* setter for static fields *)
|
|
|
|
let instr' = Sil.Store (Exp.Lfield (Exp.Lvar pvar, fn, ft), bt, e1, loc_call) in
|
|
|
|
found instr instr'
|
|
|
|
| Sil.Call (ret_id', Exp.Const Const.Cfun pn, etl', _, cf), _, _
|
|
|
|
when Bool.equal (is_none ret_id) (is_none ret_id')
|
|
|
|
&& Int.equal (List.length etl') (List.length etl) ->
|
|
|
|
let instr' = Sil.Call (ret_id, Exp.Const (Const.Cfun pn), etl, loc_call, cf) in
|
|
|
|
found instr instr'
|
|
|
|
| Sil.Call (ret_id', Exp.Const Const.Cfun pn, etl', _, cf), _, _
|
|
|
|
when Bool.equal (is_none ret_id) (is_none ret_id')
|
|
|
|
&& Int.equal (List.length etl' + 1) (List.length etl) ->
|
|
|
|
let etl1 =
|
|
|
|
match List.rev etl with
|
|
|
|
(* remove last element *)
|
|
|
|
| _ :: l ->
|
|
|
|
List.rev l
|
|
|
|
| [] ->
|
|
|
|
assert false
|
|
|
|
in
|
|
|
|
let instr' = Sil.Call (ret_id, Exp.Const (Const.Cfun pn), etl1, loc_call, cf) in
|
|
|
|
found instr instr'
|
|
|
|
| _ ->
|
|
|
|
()
|
|
|
|
in
|
|
|
|
Procdesc.iter_instrs do_instr pdesc ;
|
|
|
|
!modified
|
|
|
|
|
|
|
|
|
|
|
|
(** Find synthetic (access or bridge) Java methods in the procedure and inline them in the cfg. *)
|
|
|
|
let proc_inline_synthetic_methods cfg pdesc : unit =
|
|
|
|
let instr_inline_synthetic_method = function
|
|
|
|
| Sil.Call (ret_id, Exp.Const Const.Cfun pn, etl, loc, _) -> (
|
|
|
|
match Typ.Procname.Hash.find cfg pn with
|
|
|
|
| pd ->
|
|
|
|
let is_access = Typ.Procname.java_is_access_method pn in
|
|
|
|
let attributes = Procdesc.get_attributes pd in
|
|
|
|
let is_synthetic = attributes.is_synthetic_method in
|
|
|
|
let is_bridge = attributes.is_bridge_method in
|
|
|
|
if is_access || is_bridge || is_synthetic then inline_synthetic_method ret_id etl pd loc
|
|
|
|
else None
|
|
|
|
| exception Not_found ->
|
|
|
|
None )
|
|
|
|
| _ ->
|
|
|
|
None
|
|
|
|
in
|
|
|
|
let node_inline_synthetic_methods node =
|
|
|
|
let modified = ref false in
|
|
|
|
let do_instr instr =
|
|
|
|
match instr_inline_synthetic_method instr with
|
|
|
|
| None ->
|
|
|
|
instr
|
|
|
|
| Some instr' ->
|
|
|
|
modified := true ;
|
|
|
|
instr'
|
|
|
|
in
|
|
|
|
let instrs = Procdesc.Node.get_instrs node in
|
|
|
|
let instrs' = List.map ~f:do_instr instrs in
|
|
|
|
if !modified then Procdesc.Node.replace_instrs node instrs'
|
|
|
|
in
|
|
|
|
Procdesc.iter_nodes node_inline_synthetic_methods pdesc
|
|
|
|
|
|
|
|
|
|
|
|
(** Inline the java synthetic methods in the cfg *)
|
|
|
|
let inline_java_synthetic_methods cfg =
|
|
|
|
let f pname pdesc = if Typ.Procname.is_java pname then proc_inline_synthetic_methods cfg pdesc in
|
|
|
|
Typ.Procname.Hash.iter f cfg
|
|
|
|
|
|
|
|
|
|
|
|
(** compute the list of procedures added or changed in [cfg_new] over [cfg_old] *)
|
|
|
|
let mark_unchanged_pdescs cfg_new cfg_old =
|
|
|
|
let pdescs_eq (pd1: Procdesc.t) (pd2: Procdesc.t) =
|
|
|
|
(* map of exp names in pd1 -> exp names in pd2 *)
|
|
|
|
let exp_map = ref Exp.Map.empty in
|
|
|
|
(* map of node id's in pd1 -> node id's in pd2 *)
|
|
|
|
let node_map = ref Procdesc.NodeMap.empty in
|
|
|
|
(* formals are the same if their types are the same *)
|
|
|
|
let formals_eq formals1 formals2 =
|
|
|
|
List.equal ~equal:(fun (_, typ1) (_, typ2) -> Typ.equal typ1 typ2) formals1 formals2
|
|
|
|
in
|
|
|
|
let nodes_eq n1s n2s =
|
|
|
|
(* nodes are the same if they have the same id, instructions, and succs/preds up to renaming
|
|
|
|
with [exp_map] and [id_map] *)
|
|
|
|
let node_eq (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) =
|
|
|
|
let compare_id (n1: Procdesc.Node.t) (n2: Procdesc.Node.t) =
|
|
|
|
try
|
|
|
|
let n1_mapping = Procdesc.NodeMap.find n1 !node_map in
|
|
|
|
Procdesc.Node.compare n1_mapping n2
|
|
|
|
with Not_found ->
|
|
|
|
(* assume id's are equal and enforce by adding to [id_map] *)
|
|
|
|
node_map := Procdesc.NodeMap.add n1 n2 !node_map ;
|
|
|
|
0
|
|
|
|
in
|
|
|
|
let instrs_eq instrs1 instrs2 =
|
|
|
|
List.equal
|
|
|
|
~equal:(fun i1 i2 ->
|
|
|
|
let n, exp_map' = Sil.compare_structural_instr i1 i2 !exp_map in
|
|
|
|
exp_map := exp_map' ;
|
|
|
|
Int.equal n 0 )
|
|
|
|
instrs1 instrs2
|
|
|
|
in
|
|
|
|
Int.equal (compare_id n1 n2) 0
|
|
|
|
&& List.equal ~equal:Procdesc.Node.equal (Procdesc.Node.get_succs n1)
|
|
|
|
(Procdesc.Node.get_succs n2)
|
|
|
|
&& List.equal ~equal:Procdesc.Node.equal (Procdesc.Node.get_preds n1)
|
|
|
|
(Procdesc.Node.get_preds n2)
|
|
|
|
&& instrs_eq (Procdesc.Node.get_instrs n1) (Procdesc.Node.get_instrs n2)
|
|
|
|
in
|
|
|
|
try List.for_all2_exn ~f:node_eq n1s n2s with Invalid_argument _ -> false
|
|
|
|
in
|
|
|
|
let att1 = Procdesc.get_attributes pd1 and att2 = Procdesc.get_attributes pd2 in
|
|
|
|
Bool.equal att1.is_defined att2.is_defined && Typ.equal att1.ret_type att2.ret_type
|
|
|
|
&& formals_eq att1.formals att2.formals
|
|
|
|
&& nodes_eq (Procdesc.get_nodes pd1) (Procdesc.get_nodes pd2)
|
|
|
|
in
|
|
|
|
let mark_pdesc_if_unchanged pname (new_pdesc: Procdesc.t) =
|
|
|
|
try
|
|
|
|
let old_pdesc = Typ.Procname.Hash.find cfg_old pname in
|
|
|
|
let changed =
|
|
|
|
(* in continue_capture mode keep the old changed bit *)
|
|
|
|
Config.continue_capture && (Procdesc.get_attributes old_pdesc).changed
|
|
|
|
|| not (pdescs_eq old_pdesc new_pdesc)
|
|
|
|
in
|
|
|
|
(Procdesc.get_attributes new_pdesc).changed <- changed
|
|
|
|
with Not_found -> ()
|
|
|
|
in
|
|
|
|
Typ.Procname.Hash.iter mark_pdesc_if_unchanged cfg_new
|
|
|
|
|
|
|
|
|
|
|
|
let store_statement =
|
|
|
|
ResultsDatabase.register_statement "INSERT OR REPLACE INTO source_files VALUES (:source, :cfgs)"
|
|
|
|
|
|
|
|
|
|
|
|
let store source_file cfg =
|
|
|
|
inline_java_synthetic_methods cfg ;
|
|
|
|
( if Config.incremental_procs then
|
|
|
|
match load source_file with Some old_cfg -> mark_unchanged_pdescs cfg old_cfg | None -> () ) ;
|
|
|
|
(* NOTE: it's important to write attribute files to disk before writing cfgs to disk.
|
|
|
|
OndemandCapture module relies on it - it uses existance of the cfg as a barrier to make
|
|
|
|
sure that all attributes were written to disk (but not necessarily flushed) *)
|
|
|
|
save_attributes source_file cfg ;
|
|
|
|
ResultsDatabase.with_registered_statement store_statement ~f:(fun db store_stmt ->
|
|
|
|
SourceFile.SQLite.serialize source_file |> Sqlite3.bind store_stmt 1
|
|
|
|
(* :source *)
|
|
|
|
|> SqliteUtils.check_sqlite_error db ~log:"store bind source file" ;
|
|
|
|
SQLite.serialize cfg |> Sqlite3.bind store_stmt 2
|
|
|
|
(* :cfg *)
|
|
|
|
|> SqliteUtils.check_sqlite_error db ~log:"store bind cfg" ;
|
|
|
|
SqliteUtils.sqlite_unit_step ~finalize:false ~log:"Cfg.store" db store_stmt )
|
|
|
|
|
|
|
|
|
|
|
|
let pp_proc_signatures fmt cfg =
|
|
|
|
F.fprintf fmt "METHOD SIGNATURES@\n@." ;
|
|
|
|
let sorted_procs = List.sort ~cmp:Procdesc.compare (get_all_proc_descs cfg) in
|
|
|
|
List.iter ~f:(fun pdesc -> F.fprintf fmt "%a@." Procdesc.pp_signature pdesc) sorted_procs
|