(*
 * 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! Utils

module L = Logging
module F = Format
open Dataflow

(** Simple check for dead code. *)

let verbose = false

module State = struct
  type t =
    {
      visited : Cfg.NodeSet.t;
    }

  let initial =
    {
      visited = Cfg.NodeSet.empty;
    }

  let equal t1 t2 =
    Cfg.NodeSet.equal t1.visited t2.visited

  let join t1 t2 =
    {
      visited = Cfg.NodeSet.union t1.visited t2.visited
    }

  let add_visited node t =
    {
      visited = Cfg.NodeSet.add node t.visited;
    }

  let get_visited t =
    t.visited

  let pp fmt t =
    F.fprintf fmt "visited: %a"
      (pp_seq Cfg.Node.pp) (Cfg.NodeSet.elements t.visited)

  let num_visited t =
    Cfg.NodeSet.cardinal t.visited
end

let do_node _ node (s : State.t) : (State.t list) * (State.t list) =
  let s' = State.add_visited node s in
  if verbose then L.stderr "  N:%a (#visited: %a)@."
      Cfg.Node.pp node
      State.pp s';
  [s'], [s']


(** Report an error. *)
let report_error tenv description pn pd loc =
  if verbose then L.stderr "ERROR: %s@." description;
  Checkers.ST.report_error tenv pn pd "CHECKERS_DEAD_CODE" loc description


(** Check the final state at the end of the analysis. *)
let check_final_state tenv proc_name proc_desc final_s =
  let proc_nodes = Cfg.Procdesc.get_nodes proc_desc in
  let tot_nodes = IList.length proc_nodes in
  let tot_visited = State.num_visited final_s in
  if verbose then L.stderr "TOT nodes: %d (visited: %n)@." tot_nodes tot_visited;
  if tot_nodes <> tot_visited then
    begin
      let not_visited =
        IList.filter (fun n -> not (Cfg.NodeSet.mem n (State.get_visited final_s))) proc_nodes in
      let do_node n =
        let loc = Cfg.Node.get_loc n in
        let description = Format.sprintf "Node not visited: %d" (Cfg.Node.get_id n :> int) in
        let report = match Cfg.Node.get_kind n with
          | Cfg.Node.Join_node -> false
          | k when k = Cfg.Node.exn_sink_kind -> false
          | _ -> true in
        if report
        then report_error tenv description proc_name proc_desc loc in
      IList.iter do_node not_visited
    end

(** Simple check for dead code. *)
let callback_check_dead_code { Callbacks.proc_desc; proc_name; tenv } =

  let module DFDead = MakeDF(struct
      type t = State.t
      let equal = State.equal
      let join = State.join
      let do_node = do_node
      let proc_throws _ = DontKnow
    end) in

  let do_check () =
    begin
      if verbose then L.stderr "@.--@.PROC: %a@." Procname.pp proc_name;
      let transitions = DFDead.run tenv proc_desc State.initial in
      let exit_node = Cfg.Procdesc.get_exit_node proc_desc in
      match transitions exit_node with
      | DFDead.Transition (pre_final_s, _, _) ->
          let final_s = State.add_visited exit_node pre_final_s in
          check_final_state tenv proc_name proc_desc final_s
      | DFDead.Dead_state -> ()
    end in

  do_check ()
