
  * 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 . 
 
 * 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

let iter_captured_procs_and_callees f =
  (* Only query the capturedb (this function is run before the analysis phase) *)
  let db = Database.get_database CaptureDatabase in
  only load procedure info for those we have a CFG
  let stmt =
    Sqlite3.prepare db "SELECT proc_attributes, callees FROM procedures WHERE cfg IS NOT NULL"
  in
  SqliteUtils.result_fold_rows db ~log:"loading captured procs" stmt ~init:() ~f:(fun () stmt ->
      let proc_name =
        Sqlite3.column stmt 0 |> ProcAttributes.SQLite.deserialize |> ProcAttributes.get_proc_name
      in
      let callees : Procname.t list = Sqlite3.column stmt 1 |> Procname.SQLiteList.deserialize in
      f proc_name callees )


type hashconsed_procname_info =
  {name: Procname.t; mutable defined: bool; mutable callees: Procname.t list}

let hashcons_pname pname_info pname =
  match Procname.Hash.find_opt pname_info pname with
  | Some {name} ->
      name
  | None ->
      Procname.Hash.add pname_info pname {name= pname; defined= false; callees= []} ;
      pname


let hashcons_and_update_pname pname_info pname callees =
  let callees = List.map ~f:(hashcons_pname pname_info) callees in
  match Procname.Hash.find_opt pname_info pname with
  | Some info when info.defined ->
      L.die InternalError "SyntacticCallGraph: Tried to define %a twice.@." Procname.pp pname
  | Some info ->
      info.callees <- callees ;
      info.defined <- true
  | None ->
      Procname.Hash.add pname_info pname {name= pname; defined= true; callees}


(* load and hashcons all captured procs and their callees ; return also number of defined procs *)
let pname_info_from_captured_procs () =
  let pname_info = Procname.Hash.create 1009 in
  let n_captured = ref 0 in
  iter_captured_procs_and_callees (fun pname callees ->
      incr n_captured ;
      hashcons_and_update_pname pname_info pname callees ) ;
  (pname_info, !n_captured)


let enqueue q pname = Procname.HashQueue.enqueue_back q pname pname |> ignore

let dequeue q = Procname.HashQueue.dequeue_front q

let queue_from_sources pname_info sources =
  let q = Procname.HashQueue.create () in
  List.iter sources ~f:(fun sf ->
      SourceFiles.proc_names_of_source sf
      |> List.iter ~f:(fun pname -> hashcons_pname pname_info pname |> enqueue q) ) ;
  q


let rec bfs pname_info g q =
  match dequeue q with
  | Some pname ->
      ( match Procname.Hash.find_opt pname_info pname with
      | Some {defined= true; callees} ->
          CallGraph.create_node g pname callees ;
          List.iter callees ~f:(fun pname ->
              if not (CallGraph.mem_procname g pname) then enqueue q pname )
      | _ ->
          () ) ;
      bfs pname_info g q
  | _ ->
      ()


let build_from_sources sources =
  let g = CallGraph.create CallGraph.default_initial_capacity in
  let time0 = Mtime_clock.counter () in
  L.progress "Building call graph...@\n%!" ;
  let pname_info, n_captured = pname_info_from_captured_procs () in
  let q = queue_from_sources pname_info sources in
  bfs pname_info g q ;
  L.progress
    "Built call graph in %a, from %d total procs, %d reachable defined procs and takes %d bytes@."
    Mtime.Span.pp (Mtime_clock.count time0) n_captured (CallGraph.n_procs g)
    (Obj.(reachable_words (repr g)) * (Sys.word_size_in_bits / 8)) ;
  g


let to_dotty g = CallGraph.to_dotty g "syntactic_callgraph.dot"

let bottom_up sources : (TaskSchedulerTypes.target, string) ProcessPool.TaskGenerator.t =
  let open TaskSchedulerTypes in
  let syntactic_call_graph = build_from_sources sources in
  if Config.debug_level_analysis > 0 then to_dotty syntactic_call_graph ;
  let remaining = ref (CallGraph.n_procs syntactic_call_graph) in
  let remaining_tasks () = !remaining in
  let pending : CallGraph.Node.t Queue.t = Queue.create () in
  let fill_queue () =
    CallGraph.iter_unflagged_leaves ~f:(Queue.enqueue pending) syntactic_call_graph
  in
  (* prime the pending queue so that [empty] doesn't immediately return true *)
  fill_queue () ;
  let scheduled = ref 0 in
  let is_empty () =
    let empty = Int.equal 0 !scheduled && Queue.is_empty pending in
    if empty then (
      remaining := 0 ;
      L.progress "Finished call graph scheduling, %d procs remaining (in, or reaching, cycles).@."
        (CallGraph.n_procs syntactic_call_graph) ;
      if Config.debug_level_analysis > 0 then CallGraph.to_dotty syntactic_call_graph "cycles.dot" ;
      (* save some memory *)
      CallGraph.reset syntactic_call_graph ;
      (* there is no equivalent to [Hashtbl.reset] so set capacity to min, freeing the old array *)
      Queue.set_capacity pending 1 ) ;
    empty
  in
  let rec next () =
    match Queue.dequeue pending with
    | None ->
        fill_queue () ;
        if Queue.is_empty pending then None else next ()
    | Some n when n.flag || not (CallGraph.mem syntactic_call_graph n.id) ->
        next ()
    | Some n ->
        incr scheduled ;
        CallGraph.flag syntactic_call_graph n.pname ;
        Some (Procname n.pname)
  in
  let finished ~result:_ = function
    | Procname pname ->
        decr remaining ;
        decr scheduled ;
        CallGraph.remove syntactic_call_graph pname
    | File _ | ProcUID _ ->
        L.die InternalError "Only Procnames are scheduled but File/ProcUID target was received"
  in
  {remaining_tasks; is_empty; finished; next}


let make sources = ProcessPool.TaskGenerator.chain (bottom_up sources) (FileScheduler.make sources)

 Only query the capturedb (this function is run before the analysis phase) 
 load and hashcons all captured procs and their callees ; return also number of defined procs 
 prime the pending queue so that [empty] doesn't immediately return true 
 save some memory 
 there is no equivalent to [Hashtbl.reset] so set capacity to min, freeing the old array 

  * 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 . 
 
 * 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

let iter_captured_procs_and_callees f =
  let db = Database.get_database CaptureDatabase in
  only load procedure info for those we have a CFG
  let stmt =
    Sqlite3.prepare db "SELECT proc_attributes, callees FROM procedures WHERE cfg IS NOT NULL"
  in
  SqliteUtils.result_fold_rows db ~log:"loading captured procs" stmt ~init:() ~f:(fun () stmt ->
      let proc_name =
        Sqlite3.column stmt 0 |> ProcAttributes.SQLite.deserialize |> ProcAttributes.get_proc_name
      in
      let callees : Procname.t list = Sqlite3.column stmt 1 |> Procname.SQLiteList.deserialize in
      f proc_name callees )


type hashconsed_procname_info =
  {name: Procname.t; mutable defined: bool; mutable callees: Procname.t list}

let hashcons_pname pname_info pname =
  match Procname.Hash.find_opt pname_info pname with
  | Some {name} ->
      name
  | None ->
      Procname.Hash.add pname_info pname {name= pname; defined= false; callees= []} ;
      pname


let hashcons_and_update_pname pname_info pname callees =
  let callees = List.map ~f:(hashcons_pname pname_info) callees in
  match Procname.Hash.find_opt pname_info pname with
  | Some info when info.defined ->
      L.die InternalError "SyntacticCallGraph: Tried to define %a twice.@." Procname.pp pname
  | Some info ->
      info.callees <- callees ;
      info.defined <- true
  | None ->
      Procname.Hash.add pname_info pname {name= pname; defined= true; callees}


let pname_info_from_captured_procs () =
  let pname_info = Procname.Hash.create 1009 in
  let n_captured = ref 0 in
  iter_captured_procs_and_callees (fun pname callees ->
      incr n_captured ;
      hashcons_and_update_pname pname_info pname callees ) ;
  (pname_info, !n_captured)


let enqueue q pname = Procname.HashQueue.enqueue_back q pname pname |> ignore

let dequeue q = Procname.HashQueue.dequeue_front q

let queue_from_sources pname_info sources =
  let q = Procname.HashQueue.create () in
  List.iter sources ~f:(fun sf ->
      SourceFiles.proc_names_of_source sf
      |> List.iter ~f:(fun pname -> hashcons_pname pname_info pname |> enqueue q) ) ;
  q


let rec bfs pname_info g q =
  match dequeue q with
  | Some pname ->
      ( match Procname.Hash.find_opt pname_info pname with
      | Some {defined= true; callees} ->
          CallGraph.create_node g pname callees ;
          List.iter callees ~f:(fun pname ->
              if not (CallGraph.mem_procname g pname) then enqueue q pname )
      | _ ->
          () ) ;
      bfs pname_info g q
  | _ ->
      ()


let build_from_sources sources =
  let g = CallGraph.create CallGraph.default_initial_capacity in
  let time0 = Mtime_clock.counter () in
  L.progress "Building call graph...@\n%!" ;
  let pname_info, n_captured = pname_info_from_captured_procs () in
  let q = queue_from_sources pname_info sources in
  bfs pname_info g q ;
  L.progress
    "Built call graph in %a, from %d total procs, %d reachable defined procs and takes %d bytes@."
    Mtime.Span.pp (Mtime_clock.count time0) n_captured (CallGraph.n_procs g)
    (Obj.(reachable_words (repr g)) * (Sys.word_size_in_bits / 8)) ;
  g


let to_dotty g = CallGraph.to_dotty g "syntactic_callgraph.dot"

let bottom_up sources : (TaskSchedulerTypes.target, string) ProcessPool.TaskGenerator.t =
  let open TaskSchedulerTypes in
  let syntactic_call_graph = build_from_sources sources in
  if Config.debug_level_analysis > 0 then to_dotty syntactic_call_graph ;
  let remaining = ref (CallGraph.n_procs syntactic_call_graph) in
  let remaining_tasks () = !remaining in
  let pending : CallGraph.Node.t Queue.t = Queue.create () in
  let fill_queue () =
    CallGraph.iter_unflagged_leaves ~f:(Queue.enqueue pending) syntactic_call_graph
  in
  fill_queue () ;
  let scheduled = ref 0 in
  let is_empty () =
    let empty = Int.equal 0 !scheduled && Queue.is_empty pending in
    if empty then (
      remaining := 0 ;
      L.progress "Finished call graph scheduling, %d procs remaining (in, or reaching, cycles).@."
        (CallGraph.n_procs syntactic_call_graph) ;
      if Config.debug_level_analysis > 0 then CallGraph.to_dotty syntactic_call_graph "cycles.dot" ;
      CallGraph.reset syntactic_call_graph ;
      Queue.set_capacity pending 1 ) ;
    empty
  in
  let rec next () =
    match Queue.dequeue pending with
    | None ->
        fill_queue () ;
        if Queue.is_empty pending then None else next ()
    | Some n when n.flag || not (CallGraph.mem syntactic_call_graph n.id) ->
        next ()
    | Some n ->
        incr scheduled ;
        CallGraph.flag syntactic_call_graph n.pname ;
        Some (Procname n.pname)
  in
  let finished ~result:_ = function
    | Procname pname ->
        decr remaining ;
        decr scheduled ;
        CallGraph.remove syntactic_call_graph pname
    | File _ | ProcUID _ ->
        L.die InternalError "Only Procnames are scheduled but File/ProcUID target was received"
  in
  {remaining_tasks; is_empty; finished; next}


let make sources = ProcessPool.TaskGenerator.chain (bottom_up sources) (FileScheduler.make sources)
