open Core_kernel
open Bap.Std
open Log_utils

let name = "CWE367"
let version = "0.1"

let get_calls_to_symbol symbol_name callsites program =
  match Symbol_utils.find_symbol program symbol_name with
      | Some symbol ->
        begin
          Seq.filter callsites ~f:(fun callsite -> match Jmp.kind callsite with
            | Goto _ | Ret _ | Int (_,_) -> false
            | Call destination -> match Call.target destination with
              | Direct addr -> addr = symbol
              | _ -> false)
        end
      | None -> Seq.empty

let get_blk_tid_of_tid sub tid =
 let blk = Seq.find (Term.enum blk_t sub) ~f:(
      fun b ->
      match Term.last jmp_t b with
      | Some last_term -> tid = (Term.tid last_term)
      | None -> false) in
 match blk with
 | Some b -> Term.tid b
 | _ -> assert(false)

let is_reachable sub source sink =
  let cfg = Sub.to_graph sub in
  let source_tid = Term.tid source in
  let sink_tid = Term.tid sink in
  let source_blk = get_blk_tid_of_tid sub source_tid in
  let sink_blk = get_blk_tid_of_tid sub sink_tid in
  Graphlib.Std.Graphlib.is_reachable (module Graphs.Tid) cfg source_blk sink_blk

let handle_sub sub program tid_map _symbols source_sink_pair =
  match source_sink_pair with
  | [source;sink;] -> begin
      if (Symbol_utils.sub_calls_symbol program sub source) && (Symbol_utils.sub_calls_symbol program sub sink) then
        begin
          let calls = Symbol_utils.get_direct_callsites_of_sub sub in
          let source_calls = get_calls_to_symbol source calls program in
          let sink_calls = get_calls_to_symbol sink calls program in
          Seq.iter source_calls ~f:(fun source_call ->
              Seq.iter sink_calls ~f:(fun sink_call ->
                  if is_reachable sub source_call sink_call then
                    let address = (Address_translation.translate_tid_to_assembler_address_string (Term.tid sub) tid_map) in
                    let tid = Address_translation.tid_to_string @@ Term.tid sub in
                    let symbol = (Term.name sub) in
                    let other = [["source"; source]; ["sink"; sink]] in
                    let description = sprintf 
                     "(Time-of-check Time-of-use Race Condition) %s is reachable from %s at %s (%s). This could lead to a TOCTOU."
                      sink
                      source
                      address
                      symbol in
                    let cwe_warning = cwe_warning_factory
                                        name
                                        version
                                        description
                                        ~other:other
                                        ~addresses:[address]
                                        ~tids:[tid]
                                        ~symbols:[symbol] in
                    collect_cwe_warning cwe_warning
                  else
                    ()))
        end
      else
        ()
    end
  | _ -> ()

let check_cwe program _proj tid_map symbol_pairs _ =
  List.iter symbol_pairs ~f:(fun current_pair -> 
  let symbols = Symbol_utils.build_symbols current_pair in
  Seq.iter (Term.enum sub_t program) ~f:(fun s -> handle_sub s program tid_map symbols current_pair))
