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

(** The Smallfoot Intermediate Language: Decompiled Expressions *)

open! IStd
module L = Logging
module F = Format

(** expression representing the result of decompilation *)
type t =
  | Darray of t * t
  | Dbinop of Binop.t * t * t
  | Dconst of Const.t
  | Dsizeof of Typ.t * t option * Subtype.t
  | Dderef of t
  | Dfcall of t * t list * Location.t * CallFlags.t
  | Darrow of t * Typ.Fieldname.t
  | Ddot of t * Typ.Fieldname.t
  | Dpvar of Pvar.t
  | Dpvaraddr of Pvar.t
  | Dunop of Unop.t * t
  | Dunknown
  | Dretcall of t * t list * Location.t * CallFlags.t

(** Value paths: identify an occurrence of a value in a symbolic heap
    each expression represents a path, with Dpvar being the simplest one *)
type vpath = t option

let java () = Config.equal_language !Config.curr_language Config.Java

let eradicate_java () = Config.eradicate && java ()

(** convert a dexp to a string *)
let rec to_string = function
  | Darray (de1, de2)
   -> to_string de1 ^ "[" ^ to_string de2 ^ "]"
  | Dbinop (op, de1, de2)
   -> "(" ^ to_string de1 ^ Binop.str Pp.text op ^ to_string de2 ^ ")"
  | Dconst Cfun pn
   -> Typ.Procname.to_simplified_string pn
  | Dconst c
   -> Const.to_string c
  | Dderef de
   -> "*" ^ to_string de
  | Dfcall (fun_dexp, args, _, {cf_virtual= isvirtual})
   -> let pp_arg fmt de = F.fprintf fmt "%s" (to_string de) in
      let pp_args fmt des =
        if eradicate_java () then ( if des <> [] then F.fprintf fmt "..." )
        else Pp.comma_seq pp_arg fmt des
      in
      let pp_fun fmt = function
        | Dconst Cfun pname
         -> let s =
              match pname with
              | Typ.Procname.Java pname_java
               -> Typ.Procname.java_get_method pname_java
              | _
               -> Typ.Procname.to_string pname
            in
            F.fprintf fmt "%s" s
        | de
         -> F.fprintf fmt "%s" (to_string de)
      in
      let receiver, args' =
        match args with
        | (Dpvar pv) :: args' when isvirtual && Pvar.is_this pv
         -> (None, args')
        | a :: args' when isvirtual
         -> (Some a, args')
        | _
         -> (None, args)
      in
      let pp fmt =
        let pp_receiver fmt = function None -> () | Some arg -> F.fprintf fmt "%a." pp_arg arg in
        F.fprintf fmt "%a%a(%a)" pp_receiver receiver pp_fun fun_dexp pp_args args'
      in
      F.asprintf "%t" pp
  | Darrow (Dpvar pv, f) when Pvar.is_this pv
   -> (* this->fieldname *)
      Typ.Fieldname.to_simplified_string f
  | Darrow (de, f)
   -> if Typ.Fieldname.is_hidden f then to_string de
      else if java () then to_string de ^ "." ^ Typ.Fieldname.to_flat_string f
      else to_string de ^ "->" ^ Typ.Fieldname.to_string f
  | Ddot (Dpvar _, fe) when eradicate_java ()
   -> (* static field access *)
      Typ.Fieldname.to_simplified_string fe
  | Ddot (de, f)
   -> if Typ.Fieldname.is_hidden f then "&" ^ to_string de
      else if java () then to_string de ^ "." ^ Typ.Fieldname.to_flat_string f
      else to_string de ^ "." ^ Typ.Fieldname.to_string f
  | Dpvar pv
   -> Mangled.to_string (Pvar.get_name pv)
  | Dpvaraddr pv
   -> let s =
        if eradicate_java () then Pvar.get_simplified_name pv
        else Mangled.to_string (Pvar.get_name pv)
      in
      let ampersand = if eradicate_java () then "" else "&" in
      ampersand ^ s
  | Dunop (op, de)
   -> Unop.str op ^ to_string de
  | Dsizeof (typ, _, _)
   -> F.asprintf "%a" (Typ.pp_full Pp.text) typ
  | Dunknown
   -> "unknown"
  | Dretcall (de, _, _, _)
   -> "returned by " ^ to_string de

(** Pretty print a dexp. *)
let pp fmt de = F.fprintf fmt "%s" (to_string de)

(** Pretty print a value path *)
let pp_vpath pe fmt vpath =
  let pp fmt = function Some de -> pp fmt de | None -> () in
  if Pp.equal_print_kind pe.Pp.kind Pp.HTML then
    F.fprintf fmt " %a{vpath: %a}%a" Io_infer.Html.pp_start_color Pp.Orange pp vpath
      Io_infer.Html.pp_end_color ()
  else F.fprintf fmt "%a" pp vpath

let rec has_tmp_var = function
  | Dpvar pvar | Dpvaraddr pvar
   -> Pvar.is_frontend_tmp pvar
  | Dderef dexp | Ddot (dexp, _) | Darrow (dexp, _) | Dunop (_, dexp) | Dsizeof (_, Some dexp, _)
   -> has_tmp_var dexp
  | Darray (dexp1, dexp2) | Dbinop (_, dexp1, dexp2)
   -> has_tmp_var dexp1 || has_tmp_var dexp2
  | Dretcall (dexp, dexp_list, _, _) | Dfcall (dexp, dexp_list, _, _)
   -> has_tmp_var dexp || List.exists ~f:has_tmp_var dexp_list
  | Dconst _ | Dunknown | Dsizeof (_, None, _)
   -> false
