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.

206 lines
7.6 KiB

(*
* 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 ~compare:[%compare : Procdesc.t * Procdesc.Node.t]
|> List.iter ~f:(fun (d, n) -> f d n)
let is_proc_cfg_connected proc_desc =
let is_exit_node n =
match Procdesc.Node.get_kind n with Procdesc.Node.Exit_node _ -> true | _ -> false
in
let is_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
not (List.exists ~f:is_broken_node (Procdesc.get_nodes proc_desc))
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 )
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 (Typ.Procname.Java java_pn as pn)), etl, loc, _) -> (
match Typ.Procname.Hash.find cfg pn with
| pd ->
let is_access = Typ.Procname.Java.is_access_method java_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 Caml.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
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
let pp_proc_signatures fmt cfg =
F.fprintf fmt "METHOD SIGNATURES@\n@." ;
let sorted_procs = List.sort ~compare:Procdesc.compare (get_all_proc_descs cfg) in
List.iter ~f:(fun pdesc -> F.fprintf fmt "%a@." Procdesc.pp_signature pdesc) sorted_procs