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

(** Utility module for translating unary and binary operations and compound assignments *)

open! IStd
module L = Logging

(* Returns the translation of assignment when ARC mode is enabled in Obj-C *)
(* For __weak and __unsafe_unretained the translation is the same as non-ARC *)
(* (this is because, in these cases, there is no change in the reference counter *)
(* of the pointee).*)
(* The difference is when the lvalue is a __strong or __autoreleasing. In those*)
(* case we need to add proper retain/release.*)
(* See document: "Objective-C Automatic Reference Counting" describing the semantics *)
let assignment_arc_mode e1 typ e2 loc rhs_owning_method is_e1_decl =
  let assign = Sil.Store (e1, typ, e2, loc) in
  let retain_pname = BuiltinDecl.__objc_retain in
  let release_pname = BuiltinDecl.__objc_release in
  let autorelease_pname = BuiltinDecl.__set_autorelease_attribute in
  let mk_call procname e t =
    let bi_retain = Exp.Const (Const.Cfun procname) in
    Sil.Call (None, bi_retain, [(e, t)], loc, CallFlags.default)
  in
  match typ.Typ.desc with
  | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && not is_e1_decl
   -> (* for __strong e1 = e2 the semantics is*)
      (* retain(e2); tmp=e1; e1=e2; release(tmp); *)
      let retain = mk_call retain_pname e2 typ in
      let id = Ident.create_fresh Ident.knormal in
      let tmp_assign = Sil.Load (id, e1, typ, loc) in
      let release = mk_call release_pname (Exp.Var id) typ in
      (e1, [retain; tmp_assign; assign; release])
  | Typ.Tptr (_, Typ.Pk_pointer) when not rhs_owning_method && is_e1_decl
   -> (* for A __strong *e1 = e2 the semantics is*)
      (* retain(e2); e1=e2; *)
      let retain = mk_call retain_pname e2 typ in
      (e1, [retain; assign])
  | Typ.Tptr (_, Typ.Pk_objc_weak) | Typ.Tptr (_, Typ.Pk_objc_unsafe_unretained)
   -> (e1, [assign])
  | Typ.Tptr (_, Typ.Pk_objc_autoreleasing)
   -> (* for __autoreleasing e1 = e2 the semantics is*)
      (* retain(e2); autorelease(e2); e1=e2; *)
      let retain = mk_call retain_pname e2 typ in
      let autorelease = mk_call autorelease_pname e2 typ in
      (e1, [retain; autorelease; assign])
  | _
   -> (e1, [assign])

(* Returns a pair ([binary_expression], instructions) for binary operator representing a *)
(* CompoundAssignment. "binary_expression" is returned when we are calculating an expression*)
(* "instructions" is not empty when the binary operator is actually a statement like an *)
(* assignment. *)
let compound_assignment_binary_operation_instruction boi e1 typ e2 loc =
  let id = Ident.create_fresh Ident.knormal in
  let instr1 = Sil.Load (id, e1, typ, loc) in
  let e_res, instr_op =
    match boi.Clang_ast_t.boi_kind with
    | `AddAssign
     -> let e1_plus_e2 = Exp.BinOp (Binop.PlusA, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_plus_e2, loc)])
    | `SubAssign
     -> let e1_sub_e2 = Exp.BinOp (Binop.MinusA, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_sub_e2, loc)])
    | `MulAssign
     -> let e1_mul_e2 = Exp.BinOp (Binop.Mult, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_mul_e2, loc)])
    | `DivAssign
     -> let e1_div_e2 = Exp.BinOp (Binop.Div, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_div_e2, loc)])
    | `ShlAssign
     -> let e1_shl_e2 = Exp.BinOp (Binop.Shiftlt, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_shl_e2, loc)])
    | `ShrAssign
     -> let e1_shr_e2 = Exp.BinOp (Binop.Shiftrt, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_shr_e2, loc)])
    | `RemAssign
     -> let e1_mod_e2 = Exp.BinOp (Binop.Mod, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_mod_e2, loc)])
    | `AndAssign
     -> let e1_and_e2 = Exp.BinOp (Binop.BAnd, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_and_e2, loc)])
    | `OrAssign
     -> let e1_or_e2 = Exp.BinOp (Binop.BOr, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_or_e2, loc)])
    | `XorAssign
     -> let e1_xor_e2 = Exp.BinOp (Binop.BXor, Exp.Var id, e2) in
        (e1, [Sil.Store (e1, typ, e1_xor_e2, loc)])
    | _
     -> assert false
  in
  (e_res, instr1 :: instr_op)

(* Returns a pair ([binary_expression], instructions). "binary_expression" *)
(* is returned when we are calculating an expression "instructions" is not *)
(* empty when the binary operator is actually a statement like an          *)
(* assignment.                                                             *)
let binary_operation_instruction boi e1 typ e2 loc rhs_owning_method =
  let binop_exp op = Exp.BinOp (op, e1, e2) in
  match boi.Clang_ast_t.boi_kind with
  | `Add
   -> (binop_exp Binop.PlusA, [])
  | `Mul
   -> (binop_exp Binop.Mult, [])
  | `Div
   -> (binop_exp Binop.Div, [])
  | `Rem
   -> (binop_exp Binop.Mod, [])
  | `Sub
   -> (binop_exp Binop.MinusA, [])
  | `Shl
   -> (binop_exp Binop.Shiftlt, [])
  | `Shr
   -> (binop_exp Binop.Shiftrt, [])
  | `Or
   -> (binop_exp Binop.BOr, [])
  | `And
   -> (binop_exp Binop.BAnd, [])
  | `Xor
   -> (binop_exp Binop.BXor, [])
  | `LT
   -> (binop_exp Binop.Lt, [])
  | `GT
   -> (binop_exp Binop.Gt, [])
  | `LE
   -> (binop_exp Binop.Le, [])
  | `GE
   -> (binop_exp Binop.Ge, [])
  | `NE
   -> (binop_exp Binop.Ne, [])
  | `EQ
   -> (binop_exp Binop.Eq, [])
  | `LAnd
   -> (binop_exp Binop.LAnd, [])
  | `LOr
   -> (binop_exp Binop.LOr, [])
  | `Assign
   -> if !Config.arc_mode && ObjcInterface_decl.is_pointer_to_objc_class typ then
        assignment_arc_mode e1 typ e2 loc rhs_owning_method false
      else (e1, [Sil.Store (e1, typ, e2, loc)])
  | `Comma
   -> (e2, []) (* C99 6.5.17-2 *)
  | `MulAssign
  | `DivAssign
  | `RemAssign
  | `AddAssign
  | `SubAssign
  | `ShlAssign
  | `ShrAssign
  | `AndAssign
  | `XorAssign
  | `OrAssign
   -> compound_assignment_binary_operation_instruction boi e1 typ e2 loc
  (* We should not get here.  *)
  (* These should be treated by compound_assignment_binary_operation_instruction*)
  | bok
   -> L.(debug Capture Medium)
        "@\nWARNING: Missing translation for Binary Operator Kind %s. Construct ignored...@\n"
        (Clang_ast_j.string_of_binary_operator_kind bok) ;
      (Exp.minus_one, [])

let unary_operation_instruction translation_unit_context uoi e typ loc =
  let un_exp op = Exp.UnOp (op, e, Some typ) in
  match uoi.Clang_ast_t.uoi_kind with
  | `PostInc
   -> let id = Ident.create_fresh Ident.knormal in
      let instr1 = Sil.Load (id, e, typ, loc) in
      let e_plus_1 = Exp.BinOp (Binop.PlusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in
      (Exp.Var id, [instr1; Sil.Store (e, typ, e_plus_1, loc)])
  | `PreInc
   -> let id = Ident.create_fresh Ident.knormal in
      let instr1 = Sil.Load (id, e, typ, loc) in
      let e_plus_1 = Exp.BinOp (Binop.PlusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in
      let exp =
        if CGeneral_utils.is_cpp_translation translation_unit_context then e else e_plus_1
      in
      (exp, [instr1; Sil.Store (e, typ, e_plus_1, loc)])
  | `PostDec
   -> let id = Ident.create_fresh Ident.knormal in
      let instr1 = Sil.Load (id, e, typ, loc) in
      let e_minus_1 = Exp.BinOp (Binop.MinusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in
      (Exp.Var id, [instr1; Sil.Store (e, typ, e_minus_1, loc)])
  | `PreDec
   -> let id = Ident.create_fresh Ident.knormal in
      let instr1 = Sil.Load (id, e, typ, loc) in
      let e_minus_1 = Exp.BinOp (Binop.MinusA, Exp.Var id, Exp.Const (Const.Cint IntLit.one)) in
      let exp =
        if CGeneral_utils.is_cpp_translation translation_unit_context then e else e_minus_1
      in
      (exp, [instr1; Sil.Store (e, typ, e_minus_1, loc)])
  | `Not
   -> (un_exp Unop.BNot, [])
  | `Minus
   -> (un_exp Unop.Neg, [])
  | `Plus
   -> (e, [])
  | `LNot
   -> (un_exp Unop.LNot, [])
  | `Deref
   -> (* Actual dereferencing is handled by implicit cast from rvalue to lvalue *)
      (e, [])
  | `AddrOf
   -> (e, [])
  | `Real | `Imag | `Extension | `Coawait
   -> let uok = Clang_ast_j.string_of_unary_operator_kind uoi.Clang_ast_t.uoi_kind in
      L.(debug Capture Medium)
        "@\nWARNING: Missing translation for Unary Operator Kind %s. The construct has been ignored...@\n"
        uok ;
      (e, [])

let bin_op_to_string boi =
  match boi.Clang_ast_t.boi_kind with
  | `PtrMemD
   -> "PtrMemD"
  | `PtrMemI
   -> "PtrMemI"
  | `Mul
   -> "Mul"
  | `Div
   -> "Div"
  | `Rem
   -> "Rem"
  | `Add
   -> "Add"
  | `Sub
   -> "Sub"
  | `Shl
   -> "Shl"
  | `Shr
   -> "Shr"
  | `LT
   -> "LT"
  | `GT
   -> "GT"
  | `LE
   -> "LE"
  | `GE
   -> "GE"
  | `EQ
   -> "EQ"
  | `NE
   -> "NE"
  | `And
   -> "And"
  | `Xor
   -> "Xor"
  | `Or
   -> "Or"
  | `LAnd
   -> "LAnd"
  | `LOr
   -> "LOr"
  | `Assign
   -> "Assign"
  | `MulAssign
   -> "MulAssign"
  | `DivAssign
   -> "DivAssign"
  | `RemAssign
   -> "RemAssing"
  | `AddAssign
   -> "AddAssign"
  | `SubAssign
   -> "SubAssign"
  | `ShlAssign
   -> "ShlAssign"
  | `ShrAssign
   -> "ShrAssign"
  | `AndAssign
   -> "AndAssign"
  | `XorAssign
   -> "XorAssign"
  | `OrAssign
   -> "OrAssign"
  | `Comma
   -> "Comma"

let sil_const_plus_one const =
  match const with
  | Exp.Const Const.Cint n
   -> Exp.Const (Const.Cint (IntLit.add n IntLit.one))
  | _
   -> Exp.BinOp (Binop.PlusA, const, Exp.Const (Const.Cint IntLit.one))
