You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
690 lines
25 KiB
690 lines
25 KiB
(*
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*)
|
|
|
|
open! IStd
|
|
module Hashtbl = Caml.Hashtbl
|
|
|
|
(** Utility methods to support the translation of clang ast constructs into sil instructions. *)
|
|
|
|
module L = Logging
|
|
|
|
(** Extract the element of a singleton list. If the list is not a singleton it crashes. *)
|
|
let extract_item_from_singleton l pp source_range warning_string =
|
|
match l with
|
|
| [item] ->
|
|
item
|
|
| _ ->
|
|
L.die InternalError "At %a: List has %d elements, 1 expected:@\n[@[<h>%a@]]@\n%s"
|
|
(Pp.to_string ~f:Clang_ast_j.string_of_source_range)
|
|
source_range (List.length l) (Pp.semicolon_seq pp) l warning_string
|
|
|
|
|
|
let source_range_of_stmt stmt =
|
|
let {Clang_ast_t.si_source_range}, _ = Clang_ast_proj.get_stmt_tuple stmt in
|
|
si_source_range
|
|
|
|
|
|
module Nodes = struct
|
|
let prune_kind b if_kind =
|
|
Procdesc.Node.Prune_node
|
|
(b, if_kind, if b then PruneNodeKind_TrueBranch else PruneNodeKind_FalseBranch)
|
|
|
|
|
|
let is_true_prune_node n =
|
|
match Procdesc.Node.get_kind n with
|
|
| Procdesc.Node.Prune_node (true, _, _) ->
|
|
true
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let create_prune_node proc_desc ~branch ~negate_cond e_cond instrs_cond loc if_kind =
|
|
let e_cond = if negate_cond then Exp.UnOp (Unop.LNot, e_cond, None) else e_cond in
|
|
let instrs_cond' = instrs_cond @ [Sil.Prune (e_cond, loc, branch, if_kind)] in
|
|
Procdesc.create_node proc_desc loc (prune_kind branch if_kind) instrs_cond'
|
|
|
|
|
|
(** Check if this binary opertor requires the creation of a node in the cfg. *)
|
|
let is_binary_assign_op boi =
|
|
match boi.Clang_ast_t.boi_kind with
|
|
| `Assign
|
|
| `MulAssign
|
|
| `DivAssign
|
|
| `RemAssign
|
|
| `AddAssign
|
|
| `SubAssign
|
|
| `ShlAssign
|
|
| `ShrAssign
|
|
| `AndAssign
|
|
| `XorAssign
|
|
| `OrAssign ->
|
|
true
|
|
| `PtrMemD
|
|
| `PtrMemI
|
|
| `Mul
|
|
| `Div
|
|
| `Rem
|
|
| `Add
|
|
| `Sub
|
|
| `Shl
|
|
| `Shr
|
|
| `LT
|
|
| `GT
|
|
| `LE
|
|
| `GE
|
|
| `EQ
|
|
| `NE
|
|
| `And
|
|
| `Xor
|
|
| `Or
|
|
| `LAnd
|
|
| `LOr
|
|
| `Cmp
|
|
| `Comma ->
|
|
false
|
|
end
|
|
|
|
module GotoLabel = struct
|
|
let find_goto_label context label sil_loc =
|
|
try Hashtbl.find context.CContext.label_map label with Caml.Not_found ->
|
|
let node_name = Format.sprintf "GotoLabel_%s" label in
|
|
let new_node =
|
|
Procdesc.create_node context.CContext.procdesc sil_loc (Procdesc.Node.Skip_node node_name)
|
|
[]
|
|
in
|
|
Hashtbl.add context.CContext.label_map label new_node ;
|
|
new_node
|
|
end
|
|
|
|
type continuation =
|
|
{ break: Procdesc.Node.t list
|
|
; continue: Procdesc.Node.t list
|
|
; return_temp: bool
|
|
(* true if temps should not be removed in the node but returned to ancestors *) }
|
|
|
|
let is_return_temp continuation =
|
|
match continuation with Some cont -> cont.return_temp | _ -> false
|
|
|
|
|
|
let mk_cond_continuation cont =
|
|
match cont with
|
|
| Some cont' ->
|
|
Some {cont' with return_temp= true}
|
|
| None ->
|
|
Some {break= []; continue= []; return_temp= true}
|
|
|
|
|
|
type priority_node = Free | Busy of Clang_ast_t.pointer
|
|
|
|
(** A translation state. It provides the translation function with the info it needs to carry on the
|
|
translation. *)
|
|
type trans_state =
|
|
{ context: CContext.t (** current context of the translation *)
|
|
; succ_nodes: Procdesc.Node.t list (** successor nodes in the cfg *)
|
|
; continuation: continuation option (** current continuation *)
|
|
; priority: priority_node
|
|
; var_exp_typ: (Exp.t * Typ.t) option
|
|
; opaque_exp: (Exp.t * Typ.t) option
|
|
; is_fst_arg_objc_instance_method_call: bool }
|
|
|
|
let default_trans_state context =
|
|
{ context
|
|
; succ_nodes= []
|
|
; continuation= None
|
|
; priority= Free
|
|
; var_exp_typ= None
|
|
; opaque_exp= None
|
|
; is_fst_arg_objc_instance_method_call= false }
|
|
|
|
|
|
type control =
|
|
{ root_nodes: Procdesc.Node.t list
|
|
; leaf_nodes: Procdesc.Node.t list
|
|
; instrs: Sil.instr list
|
|
; initd_exps: Exp.t list }
|
|
|
|
type trans_result =
|
|
{ control: control
|
|
; return: Exp.t * Typ.t
|
|
; method_name: Typ.Procname.t option
|
|
; is_cpp_call_virtual: bool }
|
|
|
|
let empty_control = {root_nodes= []; leaf_nodes= []; instrs= []; initd_exps= []}
|
|
|
|
let mk_trans_result ?method_name ?(is_cpp_call_virtual = false) return control =
|
|
{control; return; method_name; is_cpp_call_virtual}
|
|
|
|
|
|
let undefined_expression () = Exp.Var (Ident.create_fresh Ident.knormal)
|
|
|
|
(** Collect the results of translating a list of instructions, and link up the nodes created. *)
|
|
let collect_controls pdesc l =
|
|
let collect_one result_rev {root_nodes; leaf_nodes; instrs; initd_exps} =
|
|
if root_nodes <> [] then
|
|
List.iter
|
|
~f:(fun n -> Procdesc.node_set_succs_exn pdesc n root_nodes [])
|
|
result_rev.leaf_nodes ;
|
|
let root_nodes = if result_rev.root_nodes <> [] then result_rev.root_nodes else root_nodes in
|
|
let leaf_nodes = if leaf_nodes <> [] then leaf_nodes else result_rev.leaf_nodes in
|
|
{ root_nodes
|
|
; leaf_nodes
|
|
; instrs= List.rev_append instrs result_rev.instrs
|
|
; initd_exps= List.rev_append initd_exps result_rev.initd_exps }
|
|
in
|
|
let rev_result = List.fold l ~init:empty_control ~f:collect_one in
|
|
{rev_result with instrs= List.rev rev_result.instrs; initd_exps= List.rev rev_result.initd_exps}
|
|
|
|
|
|
let collect_trans_results pdesc ~return trans_results =
|
|
List.map trans_results ~f:(fun {control} -> control)
|
|
|> collect_controls pdesc |> mk_trans_result return
|
|
|
|
|
|
module PriorityNode = struct
|
|
type t = priority_node
|
|
|
|
let try_claim_priority_node trans_state stmt_info =
|
|
match trans_state.priority with
|
|
| Free ->
|
|
L.(debug Capture Verbose)
|
|
"Priority is free. Locking priority node in %d@\n@." stmt_info.Clang_ast_t.si_pointer ;
|
|
{trans_state with priority= Busy stmt_info.Clang_ast_t.si_pointer}
|
|
| _ ->
|
|
L.(debug Capture Verbose)
|
|
"Priority busy in %d. No claim possible@\n@." stmt_info.Clang_ast_t.si_pointer ;
|
|
trans_state
|
|
|
|
|
|
let force_claim_priority_node trans_state stmt_info =
|
|
{trans_state with priority= Busy stmt_info.Clang_ast_t.si_pointer}
|
|
|
|
|
|
let is_priority_free trans_state = match trans_state.priority with Free -> true | _ -> false
|
|
|
|
let own_priority_node pri stmt_info =
|
|
match pri with Busy p when Int.equal p stmt_info.Clang_ast_t.si_pointer -> true | _ -> false
|
|
|
|
|
|
(* Used by translation functions to handle potential cfg nodes. *)
|
|
(* It connects nodes returned by translation of stmt children and *)
|
|
(* deals with creating or not a cfg node depending of owning the *)
|
|
(* priority_node. It returns nodes, ids, instrs that should be passed to parent *)
|
|
let compute_controls_to_parent trans_state loc ~node_name stmt_info res_states_children =
|
|
let res_state = collect_controls trans_state.context.procdesc res_states_children in
|
|
let create_node = own_priority_node trans_state.priority stmt_info && res_state.instrs <> [] in
|
|
if create_node then (
|
|
(* We need to create a node *)
|
|
let node_kind = Procdesc.Node.Stmt_node node_name in
|
|
let node =
|
|
Procdesc.create_node trans_state.context.CContext.procdesc loc node_kind res_state.instrs
|
|
in
|
|
Procdesc.node_set_succs_exn trans_state.context.procdesc node trans_state.succ_nodes [] ;
|
|
List.iter
|
|
~f:(fun leaf -> Procdesc.node_set_succs_exn trans_state.context.procdesc leaf [node] [])
|
|
res_state.leaf_nodes ;
|
|
(* Invariant: if root_nodes is empty then the params have not created a node.*)
|
|
let root_nodes = if res_state.root_nodes <> [] then res_state.root_nodes else [node] in
|
|
{res_state with root_nodes; leaf_nodes= [node]; instrs= []} )
|
|
else (* The node is created by the parent. We just pass back nodes/leafs params *)
|
|
res_state
|
|
|
|
|
|
let compute_results_to_parent trans_state loc ~node_name stmt_info ~return trans_results =
|
|
List.map trans_results ~f:(fun trans_result -> trans_result.control)
|
|
|> compute_controls_to_parent trans_state loc ~node_name stmt_info
|
|
|> mk_trans_result return
|
|
|
|
|
|
let compute_control_to_parent trans_state loc ~node_name stmt_info control =
|
|
compute_controls_to_parent trans_state loc ~node_name stmt_info [control]
|
|
|
|
|
|
let compute_result_to_parent trans_state loc ~node_name stmt_info trans_result =
|
|
compute_control_to_parent trans_state loc ~node_name stmt_info trans_result.control
|
|
|> mk_trans_result trans_result.return
|
|
end
|
|
|
|
module Loops = struct
|
|
type loop_kind =
|
|
| For of
|
|
{ init: Clang_ast_t.stmt
|
|
; decl_stmt: Clang_ast_t.stmt
|
|
; condition: Clang_ast_t.stmt
|
|
; increment: Clang_ast_t.stmt
|
|
; body: Clang_ast_t.stmt }
|
|
| While of {decl_stmt: Clang_ast_t.stmt; condition: Clang_ast_t.stmt; body: Clang_ast_t.stmt}
|
|
| DoWhile of {condition: Clang_ast_t.stmt; body: Clang_ast_t.stmt}
|
|
|
|
let get_body loop_kind =
|
|
match loop_kind with For {body} | While {body} | DoWhile {body} -> body
|
|
|
|
|
|
let get_cond loop_kind =
|
|
match loop_kind with For {condition} | While {condition} | DoWhile {condition} -> condition
|
|
end
|
|
|
|
module Scope = struct
|
|
module StmtMap = ClangPointers.Map
|
|
|
|
let add_scope_vars_to_destroy var_map stmt_info vars =
|
|
let ptr = stmt_info.Clang_ast_t.si_pointer in
|
|
StmtMap.set var_map ~key:ptr ~data:vars
|
|
|
|
|
|
let rec compute_vars vars_in_scope break_count var_map stmt =
|
|
(* vars_in_scope corresponds to the list of all variables existing in the current scope *)
|
|
(* break_count saves the number of variables in the current scope when entering the most recent loop *)
|
|
(* there is an assumption that break can only be used in iteration statements *)
|
|
let open Clang_ast_t in
|
|
let get_var_info_from_decl = function VarDecl _ as decl -> Some decl | _ -> None in
|
|
let get_new_vars = function
|
|
| DeclStmt (_, _, decl_list) ->
|
|
List.filter_map ~f:get_var_info_from_decl decl_list
|
|
| _ ->
|
|
[]
|
|
in
|
|
let rec handle_instructions_block var_map vars_in_scope break_count instrs =
|
|
match instrs with
|
|
| [] ->
|
|
(vars_in_scope, var_map)
|
|
| stmt :: rest ->
|
|
let new_var_map = compute_vars vars_in_scope break_count var_map stmt in
|
|
let new_vars_in_stmt = get_new_vars stmt in
|
|
handle_instructions_block new_var_map (new_vars_in_stmt @ vars_in_scope) break_count rest
|
|
in
|
|
(* TODO handle following stmts: *)
|
|
(* GotoStmt _ | | LabelStmt_ *)
|
|
match stmt with
|
|
| CompoundStmt (stmt_info, stmt_list) ->
|
|
let vars, new_var_map =
|
|
handle_instructions_block var_map vars_in_scope break_count stmt_list
|
|
in
|
|
(* vars contains the variables defined in the current compound statement + vars_in_scope *)
|
|
let vars_to_destroy = List.take vars (List.length vars - List.length vars_in_scope) in
|
|
add_scope_vars_to_destroy new_var_map stmt_info vars_to_destroy
|
|
| ReturnStmt (stmt_info, _) ->
|
|
add_scope_vars_to_destroy var_map stmt_info vars_in_scope
|
|
| BreakStmt (stmt_info, _) | ContinueStmt (stmt_info, _) ->
|
|
let vars_to_destroy = List.take vars_in_scope (List.length vars_in_scope - break_count) in
|
|
add_scope_vars_to_destroy var_map stmt_info vars_to_destroy
|
|
| WhileStmt (_, stmt_list)
|
|
| DoStmt (_, stmt_list)
|
|
| SwitchStmt (_, stmt_list)
|
|
(* TODO handle variable declarations inside for / foreach *)
|
|
| ForStmt (_, stmt_list)
|
|
| CXXForRangeStmt (_, stmt_list) ->
|
|
let break_count = List.length vars_in_scope in
|
|
List.fold_left ~f:(compute_vars vars_in_scope break_count) stmt_list ~init:var_map
|
|
| _ ->
|
|
let stmt_list = snd (Clang_ast_proj.get_stmt_tuple stmt) in
|
|
List.fold_left ~f:(compute_vars vars_in_scope break_count) stmt_list ~init:var_map
|
|
|
|
|
|
let compute_vars_to_destroy body =
|
|
List.fold_left ~f:(compute_vars [] 0) ~init:StmtMap.empty [body]
|
|
end
|
|
|
|
(** This function handles ObjC new/alloc and C++ new calls *)
|
|
let create_alloc_instrs integer_type_widths ~alloc_builtin ?size_exp ?placement_args_exps sil_loc
|
|
function_type =
|
|
let function_type, function_type_np =
|
|
match function_type.Typ.desc with
|
|
| Tptr (styp, Typ.Pk_pointer)
|
|
| Tptr (styp, Typ.Pk_objc_weak)
|
|
| Tptr (styp, Typ.Pk_objc_unsafe_unretained)
|
|
| Tptr (styp, Typ.Pk_objc_autoreleasing) ->
|
|
(function_type, styp)
|
|
| _ ->
|
|
(CType.add_pointer_to_typ function_type, function_type)
|
|
in
|
|
let ret_id = Ident.create_fresh Ident.knormal in
|
|
let args =
|
|
let nbytes =
|
|
match function_type_np.Typ.desc with
|
|
| Tint ikind ->
|
|
Some (Typ.width_of_ikind integer_type_widths ikind / 8)
|
|
| _ ->
|
|
None
|
|
in
|
|
let sizeof_exp_ =
|
|
Exp.Sizeof {typ= function_type_np; nbytes; dynamic_length= None; subtype= Subtype.exact}
|
|
in
|
|
let sizeof_exp =
|
|
match size_exp with
|
|
| Some exp ->
|
|
Exp.BinOp (Binop.Mult (Some Typ.size_t), sizeof_exp_, exp)
|
|
| None ->
|
|
sizeof_exp_
|
|
in
|
|
let exp = (sizeof_exp, Typ.mk (Tint Typ.IULong)) in
|
|
match placement_args_exps with Some args -> exp :: args | None -> [exp]
|
|
in
|
|
let ret_id_typ = (ret_id, function_type) in
|
|
let stmt_call =
|
|
Sil.Call (ret_id_typ, Exp.Const (Const.Cfun alloc_builtin), args, sil_loc, CallFlags.default)
|
|
in
|
|
(function_type, [stmt_call], Exp.Var ret_id)
|
|
|
|
|
|
let alloc_trans trans_state ~alloc_builtin loc stmt_info function_type =
|
|
let integer_type_widths = trans_state.context.translation_unit_context.integer_type_widths in
|
|
let function_type, instrs, exp =
|
|
create_alloc_instrs integer_type_widths ~alloc_builtin loc function_type
|
|
in
|
|
let control_tmp = {empty_control with instrs} in
|
|
PriorityNode.compute_control_to_parent trans_state loc ~node_name:(Call "alloc") stmt_info
|
|
control_tmp
|
|
|> mk_trans_result (exp, function_type)
|
|
|
|
|
|
let objc_new_trans trans_state ~alloc_builtin loc stmt_info cls_name function_type =
|
|
let integer_type_widths = trans_state.context.translation_unit_context.integer_type_widths in
|
|
let alloc_ret_type, alloc_stmt_call, alloc_ret_exp =
|
|
create_alloc_instrs integer_type_widths ~alloc_builtin loc function_type
|
|
in
|
|
let init_ret_id = Ident.create_fresh Ident.knormal in
|
|
let is_instance = true in
|
|
let call_flags = {CallFlags.default with CallFlags.cf_virtual= is_instance} in
|
|
let method_kind = ClangMethodKind.OBJC_INSTANCE in
|
|
let pname =
|
|
CType_decl.CProcname.NoAstDecl.objc_method_of_string_kind cls_name CFrontend_config.init
|
|
Typ.Procname.ObjC_Cpp.ObjCInstanceMethod
|
|
in
|
|
CMethod_trans.create_external_procdesc trans_state.context.CContext.translation_unit_context
|
|
trans_state.context.CContext.cfg pname method_kind None ;
|
|
let args = [(alloc_ret_exp, alloc_ret_type)] in
|
|
let ret_id_typ = (init_ret_id, alloc_ret_type) in
|
|
let init_stmt_call =
|
|
Sil.Call (ret_id_typ, Exp.Const (Const.Cfun pname), args, loc, call_flags)
|
|
in
|
|
let instrs = alloc_stmt_call @ [init_stmt_call] in
|
|
let res_trans_tmp = {empty_control with instrs} in
|
|
let node_name = Procdesc.Node.CallObjCNew in
|
|
PriorityNode.compute_control_to_parent trans_state loc ~node_name stmt_info res_trans_tmp
|
|
|> mk_trans_result (Exp.Var init_ret_id, alloc_ret_type)
|
|
|
|
|
|
let new_or_alloc_trans trans_state loc stmt_info qual_type class_name_opt selector =
|
|
let tenv = trans_state.context.CContext.tenv in
|
|
let function_type = CType_decl.qual_type_to_sil_type tenv qual_type in
|
|
let class_name =
|
|
match class_name_opt with
|
|
| Some class_name ->
|
|
class_name
|
|
| None ->
|
|
CType.objc_classname_of_type function_type
|
|
in
|
|
if String.equal selector CFrontend_config.alloc then
|
|
alloc_trans trans_state ~alloc_builtin:BuiltinDecl.__objc_alloc_no_fail loc stmt_info
|
|
function_type
|
|
else if String.equal selector CFrontend_config.new_str then
|
|
objc_new_trans trans_state ~alloc_builtin:BuiltinDecl.__objc_alloc_no_fail loc stmt_info
|
|
class_name function_type
|
|
else Logging.die InternalError "Expected selector new or alloc but got, %s" selector
|
|
|
|
|
|
let cpp_new_trans integer_type_widths sil_loc function_type size_exp placement_args_exps =
|
|
let alloc_builtin =
|
|
match placement_args_exps with
|
|
| [] -> (
|
|
match size_exp with Some _ -> BuiltinDecl.__new_array | None -> BuiltinDecl.__new )
|
|
| _ ->
|
|
(* TODO: call user defined `new` when there is more than one placement argument *)
|
|
BuiltinDecl.__placement_new
|
|
in
|
|
let function_type, stmt_call, exp =
|
|
create_alloc_instrs integer_type_widths ~alloc_builtin ?size_exp ~placement_args_exps sil_loc
|
|
function_type
|
|
in
|
|
mk_trans_result (exp, function_type) {empty_control with instrs= stmt_call}
|
|
|
|
|
|
let create_call_to_free_cf sil_loc exp typ =
|
|
let pname = BuiltinDecl.__free_cf in
|
|
let stmt_call =
|
|
Sil.Call
|
|
( (Ident.create_fresh Ident.knormal, Typ.mk Tvoid)
|
|
, Exp.Const (Const.Cfun pname)
|
|
, [(exp, typ)]
|
|
, sil_loc
|
|
, CallFlags.default )
|
|
in
|
|
stmt_call
|
|
|
|
|
|
let dereference_var_sil (exp, typ) sil_loc =
|
|
let id = Ident.create_fresh Ident.knormal in
|
|
let sil_instr = Sil.Load (id, exp, typ, sil_loc) in
|
|
([sil_instr], Exp.Var id)
|
|
|
|
|
|
let dereference_value_from_result ?(strip_pointer = false) source_range sil_loc trans_result =
|
|
let obj_sil, class_typ = trans_result.return in
|
|
let typ_no_ptr =
|
|
match class_typ.Typ.desc with
|
|
| Tptr (typ, _) ->
|
|
typ
|
|
| _ ->
|
|
CFrontend_config.incorrect_assumption __POS__ source_range
|
|
"Expected pointer type but found type %a" (Typ.pp_full Pp.text) class_typ
|
|
in
|
|
let cast_typ = if strip_pointer then typ_no_ptr else class_typ in
|
|
let cast_inst, cast_exp = dereference_var_sil (obj_sil, cast_typ) sil_loc in
|
|
{ trans_result with
|
|
control= {trans_result.control with instrs= trans_result.control.instrs @ cast_inst}
|
|
; return= (cast_exp, cast_typ) }
|
|
|
|
|
|
let cast_operation cast_kind ((exp, typ) as exp_typ) cast_typ sil_loc =
|
|
match cast_kind with
|
|
| `NoOp | `DerivedToBase | `UncheckedDerivedToBase ->
|
|
(* These casts ignore change of type *)
|
|
([], exp_typ)
|
|
| `BitCast when Typ.is_pointer_to_int cast_typ ->
|
|
([], (Exp.Cast (cast_typ, exp), cast_typ))
|
|
| `IntegralCast when Typ.is_unsigned_int cast_typ ->
|
|
([], (Exp.Cast (cast_typ, exp), cast_typ))
|
|
| `BitCast | `IntegralCast | `IntegralToBoolean ->
|
|
(* This is treated as a nop by returning the same expressions exps*)
|
|
([], (exp, cast_typ))
|
|
| `CPointerToObjCPointerCast when Objc_models.is_core_lib_type typ ->
|
|
(* Translation of __bridge_transfer *)
|
|
let instr = create_call_to_free_cf sil_loc exp typ in
|
|
([instr], (exp, cast_typ))
|
|
| `LValueToRValue ->
|
|
(* Takes an LValue and allow it to use it as RValue. *)
|
|
(* So we assign the LValue to a temp and we pass it to the parent.*)
|
|
let instrs, deref_exp = dereference_var_sil (exp, cast_typ) sil_loc in
|
|
(instrs, (deref_exp, cast_typ))
|
|
| `NullToPointer ->
|
|
if Exp.is_zero exp then ([], (Exp.null, cast_typ)) else ([], (exp, cast_typ))
|
|
| `ToVoid ->
|
|
(* If exp is not used later, i.e. as in `(void) exp;` we miss reads *)
|
|
(* We create a call to skip function passing the exp as a parameter *)
|
|
let skip_builtin = Exp.Const (Const.Cfun BuiltinDecl.__infer_skip) in
|
|
let args = [(exp, typ)] in
|
|
let no_id = Ident.create_none () in
|
|
let call_instr =
|
|
Sil.Call ((no_id, cast_typ), skip_builtin, args, sil_loc, CallFlags.default)
|
|
in
|
|
([call_instr], (exp, cast_typ))
|
|
| _ ->
|
|
L.(debug Capture Verbose)
|
|
"@\nWARNING: Missing translation for Cast Kind %s. The construct has been ignored...@\n"
|
|
(Clang_ast_j.string_of_cast_kind cast_kind) ;
|
|
([], (exp, cast_typ))
|
|
|
|
|
|
let trans_assertion_failure sil_loc (context : CContext.t) =
|
|
let assert_fail_builtin = Exp.Const (Const.Cfun BuiltinDecl.__infer_fail) in
|
|
let args = [(Exp.Const (Const.Cstr Config.default_failure_name), Typ.mk Tvoid)] in
|
|
let ret_id = Ident.create_fresh Ident.knormal in
|
|
let ret_typ = Typ.mk Tvoid in
|
|
let call_instr =
|
|
Sil.Call ((ret_id, ret_typ), assert_fail_builtin, args, sil_loc, CallFlags.default)
|
|
in
|
|
let exit_node = Procdesc.get_exit_node context.procdesc
|
|
and failure_node =
|
|
Procdesc.create_node context.CContext.procdesc sil_loc
|
|
(Procdesc.Node.Stmt_node AssertionFailure) [call_instr]
|
|
in
|
|
Procdesc.node_set_succs_exn context.procdesc failure_node [exit_node] [] ;
|
|
mk_trans_result (Exp.Var ret_id, ret_typ) {empty_control with root_nodes= [failure_node]}
|
|
|
|
|
|
let trans_assume_false sil_loc (context : CContext.t) succ_nodes =
|
|
let if_kind = Sil.Ik_land_lor in
|
|
let instrs_cond = [Sil.Prune (Exp.zero, sil_loc, true, if_kind)] in
|
|
let prune_node =
|
|
Procdesc.create_node context.CContext.procdesc sil_loc (Nodes.prune_kind true if_kind)
|
|
instrs_cond
|
|
in
|
|
Procdesc.node_set_succs_exn context.procdesc prune_node succ_nodes [] ;
|
|
mk_trans_result
|
|
(Exp.zero, Typ.(mk (Tint IInt)))
|
|
{empty_control with root_nodes= [prune_node]; leaf_nodes= [prune_node]}
|
|
|
|
|
|
let trans_assertion trans_state sil_loc =
|
|
let context = trans_state.context in
|
|
if Config.report_custom_error then trans_assertion_failure sil_loc context
|
|
else trans_assume_false sil_loc context trans_state.succ_nodes
|
|
|
|
|
|
let trans_builtin_expect params_trans_res =
|
|
(* Translate call to __builtin_expect as the first argument *)
|
|
(* for simpler symbolic execution *)
|
|
match params_trans_res with [_; fst_arg_res; _] -> Some fst_arg_res | _ -> None
|
|
|
|
|
|
let trans_std_addressof params_trans_res =
|
|
(* Translate call to std::addressof as the first argument *)
|
|
(* for simpler symbolic execution. *)
|
|
match params_trans_res with [_; fst_arg_res] -> Some fst_arg_res | _ -> assert false
|
|
|
|
|
|
let trans_replace_with_deref_first_arg source_range sil_loc params_trans_res ~cxx_method_call =
|
|
let first_arg_res_trans =
|
|
if cxx_method_call then
|
|
match params_trans_res with
|
|
| fst_arg_res :: _ ->
|
|
assert (Option.is_some fst_arg_res.method_name) ;
|
|
fst_arg_res
|
|
| [] ->
|
|
assert false
|
|
else
|
|
match params_trans_res with _ :: fst_arg_res :: _ -> fst_arg_res | [] | [_] -> assert false
|
|
in
|
|
dereference_value_from_result ~strip_pointer:true source_range sil_loc first_arg_res_trans
|
|
|
|
|
|
let builtin_trans trans_state source_range loc params_trans_res pname =
|
|
if CTrans_models.is_assert_log pname then Some (trans_assertion trans_state loc)
|
|
else if CTrans_models.is_builtin_expect pname then trans_builtin_expect params_trans_res
|
|
else if CTrans_models.is_replace_with_deref_first_arg pname then
|
|
Some
|
|
(trans_replace_with_deref_first_arg source_range loc params_trans_res ~cxx_method_call:false)
|
|
else if CTrans_models.is_std_addressof pname then trans_std_addressof params_trans_res
|
|
else None
|
|
|
|
|
|
let cxx_method_builtin_trans trans_state source_range loc params_trans_res pname =
|
|
if CTrans_models.is_assert_log pname then Some (trans_assertion trans_state loc)
|
|
else if CTrans_models.is_replace_with_deref_first_arg pname then
|
|
Some
|
|
(trans_replace_with_deref_first_arg source_range loc params_trans_res ~cxx_method_call:true)
|
|
else None
|
|
|
|
|
|
let define_condition_side_effects ((e_cond_exp, e_cond_typ) as e_cond) instrs_cond sil_loc =
|
|
match e_cond_exp with
|
|
| Exp.Lvar pvar ->
|
|
let id = Ident.create_fresh Ident.knormal in
|
|
((Exp.Var id, e_cond_typ), [Sil.Load (id, Exp.Lvar pvar, e_cond_typ, sil_loc)])
|
|
| _ ->
|
|
(e_cond, instrs_cond)
|
|
|
|
|
|
let is_superinstance mei =
|
|
match mei.Clang_ast_t.omei_receiver_kind with `SuperInstance -> true | _ -> false
|
|
|
|
|
|
let is_null_stmt s = match s with Clang_ast_t.NullStmt _ -> true | _ -> false
|
|
|
|
let extract_stmt_from_singleton stmt_list source_range warning_string =
|
|
extract_item_from_singleton stmt_list
|
|
(Pp.to_string ~f:Clang_ast_j.string_of_stmt)
|
|
source_range warning_string
|
|
|
|
|
|
module Self = struct
|
|
exception
|
|
SelfClassException of
|
|
{ class_name: Typ.Name.t
|
|
; position: Logging.ocaml_pos
|
|
; source_range: Clang_ast_t.source_range }
|
|
|
|
let add_self_parameter_for_super_instance stmt_info context procname loc mei =
|
|
if is_superinstance mei then
|
|
let typ, self_expr, instrs =
|
|
let t' =
|
|
CType.add_pointer_to_typ
|
|
(Typ.mk (Tstruct (CContext.get_curr_class_typename stmt_info context)))
|
|
in
|
|
let e = Exp.Lvar (Pvar.mk (Mangled.from_string CFrontend_config.self) procname) in
|
|
let id = Ident.create_fresh Ident.knormal in
|
|
(t', Exp.Var id, [Sil.Load (id, e, t', loc)])
|
|
in
|
|
Some (mk_trans_result (self_expr, typ) {empty_control with instrs})
|
|
else None
|
|
|
|
|
|
let is_var_self pvar is_objc_method =
|
|
let is_self = String.equal (Mangled.to_string (Pvar.get_name pvar)) CFrontend_config.self in
|
|
is_self && is_objc_method
|
|
end
|
|
|
|
let rec contains_opaque_value_expr s =
|
|
match s with
|
|
| Clang_ast_t.OpaqueValueExpr _ ->
|
|
true
|
|
| _ -> (
|
|
match snd (Clang_ast_proj.get_stmt_tuple s) with
|
|
| [] ->
|
|
false
|
|
| s'' :: _ ->
|
|
contains_opaque_value_expr s'' )
|
|
|
|
|
|
(* checks if a unary operator is a logic negation applied to integers*)
|
|
let is_logical_negation_of_int tenv ei uoi =
|
|
match
|
|
( (CType_decl.qual_type_to_sil_type tenv ei.Clang_ast_t.ei_qual_type).desc
|
|
, uoi.Clang_ast_t.uoi_kind )
|
|
with
|
|
| Typ.Tint _, `LNot ->
|
|
true
|
|
| _, _ ->
|
|
false
|
|
|
|
|
|
let mk_fresh_void_exp_typ () = (Exp.Var (Ident.create_fresh Ident.knormal), Typ.mk Tvoid)
|
|
|
|
let mk_fresh_void_id_typ () = (Ident.create_fresh Ident.knormal, Typ.mk Tvoid)
|
|
|
|
let mk_fresh_void_return () =
|
|
let id = Ident.create_fresh Ident.knormal and void = Typ.mk Tvoid in
|
|
((id, void), (Exp.Var id, void))
|
|
|
|
|
|
let last_or_mk_fresh_void_exp_typ exp_typs =
|
|
match List.last exp_typs with
|
|
| Some last_exp_typ ->
|
|
last_exp_typ
|
|
| None ->
|
|
mk_fresh_void_exp_typ ()
|