open Core_kernel
open Bap.Std
open Log_utils

let name = "CWE457"
let version = "0.1"

let get_defs sub_ssa =
  Term.enum blk_t sub_ssa
  |> Seq.concat_map ~f:(fun blk -> Term.enum def_t blk)

let collect_stores_of_exp = Exp.fold ~init:0 (object
          inherit [int] Exp.visitor
          method! enter_store ~mem:_ ~addr:_ ~exp:_ _ _ stores =
            stores + 1
        end)

let exp_has_store e =
  collect_stores_of_exp e > 0

let ints_of_exp = Exp.fold ~init:Word.Set.empty (object
          inherit [Word.Set.t] Exp.visitor
          method! enter_int i ints = Set.add ints i
        end)

let vars_of_exp = Exp.fold ~init:Var.Set.empty (object
    inherit [Var.Set.t] Exp.visitor
    method! enter_var var vars = Set.add vars var
end)

let vars_contain_mem vars =
  let mems = Set.filter vars ~f:(fun var -> match Var.to_string var with
                                            | "mem" -> true
                                            | _ -> false) in
  Set.length mems > 0

(*FIXME: this is architecture dependent and ugly*)
let get_min_fp_offset arch =
  match arch with
  | `x86 | `x86_64 -> 0x10000
  | _ -> 0x0

(*FIXME: this is architecture dependent and ugly*)
let get_fp_of_arch arch =
  match arch with
  | `x86 -> "EBP"
  | `x86_64 -> "RBP"
  | `armv4 | `armv5 | `armv6 | `armv7 | `armv4eb | `armv5eb | `armv6eb | `armv7eb -> "R11"
  | `mips | `mips64 | `mips64el | `mipsel -> "FP"
  | `ppc | `ppc64 | `ppc64le -> "R31"
  | _ -> failwith  "Unknown architecture."

let vars_contain_fp vars fp_pointer =
  let regs = Set.filter vars ~f:(fun var -> String.(=) (Var.to_string var) fp_pointer) in
  Set.length regs > 0

let is_interesting_load_store def fp_pointer =
  let vars = vars_of_exp (Def.rhs def) in
  let contains_fp = vars_contain_fp vars fp_pointer in
  let contains_mem = vars_contain_mem vars in
  contains_mem && contains_fp

(*TODO: implement real filtering*)
let filter_mem_address i min_fp_offset = Set.filter i ~f:(fun elem -> Word.(<) (Word.of_int ~width:32 min_fp_offset) elem)

let log_cwe_warning sub i d tid_map =
   let word = Word.to_string i in
   let other =[["word"; word]] in 
   let symbol = Sub.name sub in
   let address = Address_translation.translate_tid_to_assembler_address_string (Term.tid d) tid_map in
   let tid = Address_translation.tid_to_string @@ Term.tid d in
   let description = sprintf
                       "(Use of Uninitialized Variable) Found potentially unitialized stack variable (FP + %s) in function %s at %s"
                       word
                       symbol
                       address in
   let cwe_warning = cwe_warning_factory name version ~other:other ~addresses:[address] ~tids:[tid] ~symbols:[symbol] description in
   collect_cwe_warning cwe_warning

let check_subfunction _prog proj tid_map sub =
  let fp_pointer = get_fp_of_arch (Project.arch proj) in
  let min_fp_offset = get_min_fp_offset (Project.arch proj) in
  let stores = ref [||] in
  let defs = get_defs sub in
  Seq.iter defs ~f:(fun d ->
      if is_interesting_load_store d fp_pointer then
        let rhs = Def.rhs d in
        let ints = ints_of_exp rhs in
        begin
          if exp_has_store rhs then
            begin
              let filter_mem_addresses = filter_mem_address ints min_fp_offset in
              Set.iter filter_mem_addresses ~f:(fun addr -> stores := Array.append !stores [|addr|])
            end
          else
            begin
              let filter_mem_addresses = filter_mem_address ints min_fp_offset in
              Set.iter filter_mem_addresses ~f:(fun i -> if not (Array.exists !stores ~f:(fun elem -> Word.(=) elem i)) then
                log_cwe_warning sub i d tid_map)
            end
        end)

let check_cwe prog proj tid_map _symbol_names _ =
  Seq.iter (Term.enum sub_t prog) ~f:(fun sub -> check_subfunction prog proj tid_map sub)
