|
|
|
(*
|
|
|
|
* Copyright (c) 2009-2013, Monoidics ltd.
|
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*)
|
|
|
|
|
|
|
|
(** The Smallfoot Intermediate Language: Expressions *)
|
|
|
|
|
|
|
|
open! IStd
|
|
|
|
module Hashtbl = Caml.Hashtbl
|
|
|
|
module F = Format
|
|
|
|
module L = Logging
|
|
|
|
|
|
|
|
(* reverse the natural order on Var *)
|
|
|
|
type ident_ = Ident.t
|
|
|
|
|
|
|
|
let compare_ident_ x y = Ident.compare y x
|
|
|
|
|
|
|
|
type closure = {name: Procname.t; captured_vars: (t * Pvar.t * Typ.t) list}
|
|
|
|
|
|
|
|
(** This records information about a [sizeof(typ)] expression.
|
|
|
|
|
|
|
|
[nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known.
|
|
|
|
|
|
|
|
If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number
|
|
|
|
of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may
|
|
|
|
differ from the [static_length] obtained from the type definition, e.g. when an array is
|
|
|
|
over-allocated.
|
|
|
|
|
|
|
|
If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*)
|
|
|
|
and sizeof_data = {typ: Typ.t; nbytes: int option; dynamic_length: t option; subtype: Subtype.t}
|
|
|
|
|
|
|
|
(** Program expressions. *)
|
|
|
|
and t =
|
|
|
|
| Var of ident_ (** Pure variable: it is not an lvalue *)
|
|
|
|
| UnOp of Unop.t * t * Typ.t option (** Unary operator with type of the result if known *)
|
|
|
|
| BinOp of Binop.t * t * t (** Binary operator *)
|
|
|
|
| Exn of t (** Exception *)
|
|
|
|
| Closure of closure (** Anonymous function *)
|
|
|
|
| Const of Const.t (** Constants *)
|
|
|
|
| Cast of Typ.t * t (** Type cast *)
|
|
|
|
| Lvar of Pvar.t (** The address of a program variable *)
|
|
|
|
| Lfield of t * Fieldname.t * Typ.t
|
|
|
|
(** A field offset, the type is the surrounding struct type *)
|
|
|
|
| Lindex of t * t (** An array index offset: [exp1\[exp2\]] *)
|
|
|
|
| Sizeof of sizeof_data
|
|
|
|
[@@deriving compare]
|
|
|
|
|
|
|
|
let equal = [%compare.equal: t]
|
|
|
|
|
|
|
|
let hash = Hashtbl.hash
|
|
|
|
|
|
|
|
module Set = Caml.Set.Make (struct
|
|
|
|
type nonrec t = t
|
|
|
|
|
|
|
|
let compare = compare
|
|
|
|
end)
|
|
|
|
|
|
|
|
module Map = Caml.Map.Make (struct
|
|
|
|
type nonrec t = t
|
|
|
|
|
|
|
|
let compare = compare
|
|
|
|
end)
|
|
|
|
|
|
|
|
module Hash = Hashtbl.Make (struct
|
|
|
|
type nonrec t = t
|
|
|
|
|
|
|
|
let equal = equal
|
|
|
|
|
|
|
|
let hash = hash
|
|
|
|
end)
|
|
|
|
|
|
|
|
let is_null_literal = function Const (Cint n) -> IntLit.isnull n | _ -> false
|
|
|
|
|
|
|
|
let is_this = function Lvar pvar -> Pvar.is_this pvar | _ -> false
|
|
|
|
|
|
|
|
let is_zero = function Const (Cint n) -> IntLit.iszero n | _ -> false
|
|
|
|
|
|
|
|
let rec is_const = function Const _ -> true | Cast (_, x) -> is_const x | _ -> false
|
|
|
|
|
|
|
|
(** {2 Utility Functions for Expressions} *)
|
|
|
|
|
|
|
|
(** Turn an expression representing a type into the type it represents If not a sizeof, return the
|
|
|
|
default type if given, otherwise raise an exception *)
|
|
|
|
let texp_to_typ default_opt = function
|
|
|
|
| Sizeof {typ} ->
|
|
|
|
typ
|
|
|
|
| _ ->
|
|
|
|
Typ.unsome "texp_to_typ" default_opt
|
|
|
|
|
|
|
|
|
|
|
|
(** Return the root of [lexp]. *)
|
|
|
|
let rec root_of_lexp lexp =
|
|
|
|
match (lexp : t) with
|
|
|
|
| Var _ ->
|
|
|
|
lexp
|
|
|
|
| Const _ ->
|
|
|
|
lexp
|
|
|
|
| Cast (_, e) ->
|
|
|
|
root_of_lexp e
|
|
|
|
| UnOp _ | BinOp _ | Exn _ | Closure _ ->
|
|
|
|
lexp
|
|
|
|
| Lvar _ ->
|
|
|
|
lexp
|
|
|
|
| Lfield (e, _, _) ->
|
|
|
|
root_of_lexp e
|
|
|
|
| Lindex (e, _) ->
|
|
|
|
root_of_lexp e
|
|
|
|
| Sizeof _ ->
|
|
|
|
lexp
|
|
|
|
|
|
|
|
|
|
|
|
(** Checks whether an expression denotes a location by pointer arithmetic. Currently, catches
|
|
|
|
array-indexing expressions such as a[i] only. *)
|
|
|
|
let rec pointer_arith = function
|
|
|
|
| Lfield (e, _, _) ->
|
|
|
|
pointer_arith e
|
|
|
|
| Lindex _ ->
|
|
|
|
true
|
|
|
|
| _ ->
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
|
|
let get_undefined footprint =
|
|
|
|
Var (Ident.create_fresh (if footprint then Ident.kfootprint else Ident.kprimed))
|
|
|
|
|
|
|
|
|
|
|
|
(** returns true if the express operates on address of local variable *)
|
|
|
|
let rec has_local_addr e =
|
|
|
|
match (e : t) with
|
|
|
|
| Lvar pv ->
|
|
|
|
Pvar.is_local pv
|
|
|
|
| UnOp (_, e', _) | Cast (_, e') | Lfield (e', _, _) ->
|
|
|
|
has_local_addr e'
|
|
|
|
| BinOp (_, e0, e1) | Lindex (e0, e1) ->
|
|
|
|
has_local_addr e0 || has_local_addr e1
|
|
|
|
| _ ->
|
|
|
|
false
|
|
|
|
|
|
|
|
|
|
|
|
(** Create integer constant *)
|
|
|
|
let int i = Const (Cint i)
|
|
|
|
|
|
|
|
(** Create float constant *)
|
|
|
|
let float v = Const (Cfloat v)
|
|
|
|
|
|
|
|
(** Integer constant 0 *)
|
|
|
|
let zero = int IntLit.zero
|
|
|
|
|
|
|
|
(** Null constant *)
|
|
|
|
let null = int IntLit.null
|
|
|
|
|
|
|
|
(** Integer constant 1 *)
|
|
|
|
let one = int IntLit.one
|
|
|
|
|
|
|
|
(** Integer constant -1 *)
|
|
|
|
let minus_one = int IntLit.minus_one
|
|
|
|
|
|
|
|
(** Create integer constant corresponding to the boolean value *)
|
|
|
|
let bool b = if b then one else zero
|
|
|
|
|
|
|
|
let and_2ary e1 e2 = BinOp (LAnd, e1, e2)
|
|
|
|
|
|
|
|
(** Create expression [e1 == e2] *)
|
|
|
|
let eq e1 e2 = BinOp (Eq, e1, e2)
|
|
|
|
|
|
|
|
(** Create expression [e1 != e2] *)
|
|
|
|
let ne e1 e2 = BinOp (Ne, e1, e2)
|
|
|
|
|
|
|
|
(** Create expression [e1 <= e2] *)
|
|
|
|
let le e1 e2 = BinOp (Le, e1, e2)
|
|
|
|
|
|
|
|
(** Create expression [e1 < e2] *)
|
|
|
|
let lt e1 e2 = BinOp (Lt, e1, e2)
|
|
|
|
|
|
|
|
let nary_of_2ary op_2ary zero es =
|
|
|
|
match es with [] -> zero | e :: es -> List.fold ~init:e ~f:op_2ary es
|
|
|
|
|
|
|
|
|
|
|
|
let and_nary = nary_of_2ary and_2ary one
|
|
|
|
|
|
|
|
let fold_captured ~f exp acc =
|
|
|
|
let rec fold_captured_ exp captured_acc =
|
|
|
|
match exp with
|
|
|
|
| Cast (_, e) | UnOp (_, e, _) | Lfield (e, _, _) | Exn e | Sizeof {dynamic_length= Some e} ->
|
|
|
|
fold_captured_ e captured_acc
|
|
|
|
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
|
|
|
|
fold_captured_ e1 captured_acc |> fold_captured_ e2
|
|
|
|
| Closure {captured_vars} ->
|
|
|
|
List.fold captured_vars
|
|
|
|
~f:(fun acc (captured_exp, _, _) -> f acc captured_exp)
|
|
|
|
~init:captured_acc
|
|
|
|
| Const _ | Lvar _ | Var _ | Sizeof _ ->
|
|
|
|
captured_acc
|
|
|
|
in
|
|
|
|
fold_captured_ exp acc
|
|
|
|
|
|
|
|
|
|
|
|
(** Pretty print an expression. *)
|
|
|
|
let rec pp_ pe pp_t f e =
|
|
|
|
let pp_exp = pp_ pe pp_t in
|
|
|
|
let print_binop_stm_output e1 op e2 =
|
|
|
|
match (op : Binop.t) with
|
|
|
|
| Eq | Ne | PlusA _ | Mult _ ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe op) pp_exp e1
|
|
|
|
| Lt ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Gt) pp_exp e1
|
|
|
|
| Gt ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Lt) pp_exp e1
|
|
|
|
| Le ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Ge) pp_exp e1
|
|
|
|
| Ge ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Le) pp_exp e1
|
|
|
|
| _ ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2
|
|
|
|
in
|
|
|
|
match (e : t) with
|
|
|
|
| Var id ->
|
|
|
|
Ident.pp f id
|
|
|
|
| Const c ->
|
|
|
|
(Const.pp pe) f c
|
|
|
|
| Cast (typ, e) ->
|
|
|
|
F.fprintf f "(%a)%a" pp_t typ pp_exp e
|
|
|
|
| UnOp (op, e, _) ->
|
|
|
|
F.fprintf f "%s%a" (Unop.to_string op) pp_exp e
|
|
|
|
| BinOp (op, Const c, e2) when Config.smt_output ->
|
|
|
|
print_binop_stm_output (Const c) op e2
|
|
|
|
| BinOp (op, e1, e2) ->
|
|
|
|
F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2
|
|
|
|
| Exn e ->
|
|
|
|
F.fprintf f "EXN %a" pp_exp e
|
|
|
|
| Closure {name; captured_vars} ->
|
|
|
|
if List.is_empty captured_vars then F.fprintf f "(%a)" pp_exp (Const (Cfun name))
|
|
|
|
else
|
|
|
|
F.fprintf f "(%a,%a)" pp_exp (Const (Cfun name))
|
|
|
|
(Pp.comma_seq (pp_captured_var pe pp_t))
|
|
|
|
captured_vars
|
|
|
|
| Lvar pv ->
|
|
|
|
Pvar.pp pe f pv
|
|
|
|
| Lfield (e, fld, _) ->
|
|
|
|
F.fprintf f "%a.%a" pp_exp e Fieldname.pp fld
|
|
|
|
| Lindex (e1, e2) ->
|
|
|
|
F.fprintf f "%a[%a]" pp_exp e1 pp_exp e2
|
|
|
|
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
|
|
|
|
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" pp_exp) l in
|
|
|
|
let pp_size f size = Option.iter ~f:(Int.pp f) size in
|
|
|
|
let pp_if b pp label f v = if b then F.fprintf f ";%s=%a" label pp v in
|
|
|
|
let pp_if_some pp_opt label f opt = pp_if (Option.is_some opt) pp_opt label f opt in
|
|
|
|
let subt_s = F.asprintf "%a" Subtype.pp subtype in
|
|
|
|
F.fprintf f "sizeof(t=%a%a%a%a)" pp_t typ (pp_if_some pp_size "nbytes") nbytes
|
|
|
|
(pp_if_some pp_len "len") dynamic_length
|
|
|
|
(pp_if (not (String.equal "" subt_s)) Subtype.pp "sub_t")
|
|
|
|
subtype
|
|
|
|
|
|
|
|
|
|
|
|
and pp_captured_var pe pp_t f (exp, var, typ) =
|
|
|
|
match exp with
|
|
|
|
| Lvar evar when Pvar.equal var evar ->
|
|
|
|
(Pvar.pp pe) f var
|
|
|
|
| _ ->
|
|
|
|
F.fprintf f "(%a %a:%a)" (pp_ pe pp_t) exp (Pvar.pp pe) var (Typ.pp pe) typ
|
|
|
|
|
|
|
|
|
|
|
|
let pp_printenv ~print_types pe f e =
|
|
|
|
let pp_typ = if print_types then Typ.pp_full else Typ.pp in
|
|
|
|
pp_ pe (pp_typ pe) f e
|
|
|
|
|
|
|
|
|
|
|
|
let pp f e = pp_printenv ~print_types:false Pp.text f e
|
|
|
|
|
|
|
|
let to_string e = F.asprintf "%a" pp e
|
|
|
|
|
|
|
|
let color_wrapper ~f = if Config.print_using_diff then Pp.color_wrapper ~f else f
|
|
|
|
|
|
|
|
let pp_diff ?(print_types = false) =
|
|
|
|
color_wrapper ~f:(fun pe f e0 ->
|
|
|
|
let e =
|
|
|
|
match pe.Pp.obj_sub with
|
|
|
|
| Some sub ->
|
|
|
|
(* apply object substitution to expression *) Obj.obj (sub (Obj.repr e0))
|
|
|
|
| None ->
|
|
|
|
e0
|
|
|
|
in
|
|
|
|
if not (equal e0 e) then match e with Lvar pvar -> Pvar.pp_value f pvar | _ -> assert false
|
|
|
|
else pp_printenv ~print_types pe f e )
|
|
|
|
|
|
|
|
|
|
|
|
(** dump an expression. *)
|
|
|
|
let d_exp (e : t) = L.d_pp_with_pe pp_diff e
|
|
|
|
|
|
|
|
(** Pretty print a list of expressions. *)
|
|
|
|
let pp_list pe f expl = Pp.seq (pp_diff pe) f expl
|
|
|
|
|
|
|
|
(** dump a list of expressions. *)
|
|
|
|
let d_list (el : t list) = L.d_pp_with_pe pp_list el
|
|
|
|
|
|
|
|
let pp_texp pe f = function
|
|
|
|
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
|
|
|
|
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_diff pe)) l in
|
|
|
|
let pp_size f size = Option.iter ~f:(Int.pp f) size in
|
|
|
|
F.fprintf f "%a%a%a%a" (Typ.pp pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp subtype
|
|
|
|
| e ->
|
|
|
|
pp_diff pe f e
|
|
|
|
|
|
|
|
|
|
|
|
(** Pretty print a type with all the details. *)
|
|
|
|
let pp_texp_full pe f = function
|
|
|
|
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
|
|
|
|
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_diff pe)) l in
|
|
|
|
let pp_size f size = Option.iter ~f:(Int.pp f) size in
|
|
|
|
F.fprintf f "%a%a%a%a" (Typ.pp_full pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp
|
|
|
|
subtype
|
|
|
|
| e ->
|
|
|
|
pp_diff ~print_types:true pe f e
|
|
|
|
|
|
|
|
|
|
|
|
(** Dump a type expression with all the details. *)
|
|
|
|
let d_texp_full (te : t) = L.d_pp_with_pe pp_texp_full te
|
|
|
|
|
|
|
|
let is_objc_block_closure = function Closure {name} -> Procname.is_objc_block name | _ -> false
|
|
|
|
|
|
|
|
let rec gen_free_vars =
|
|
|
|
let open Sequence.Generator in
|
|
|
|
function
|
|
|
|
| Var id ->
|
|
|
|
yield id
|
|
|
|
| Cast (_, e) | Exn e | Lfield (e, _, _) | Sizeof {dynamic_length= Some e} | UnOp (_, e, _) ->
|
|
|
|
gen_free_vars e
|
|
|
|
| Closure {captured_vars} ->
|
|
|
|
ISequence.gen_sequence_list captured_vars ~f:(fun (e, _, _) -> gen_free_vars e)
|
|
|
|
| Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) | Lvar _ | Sizeof {dynamic_length= None}
|
|
|
|
->
|
|
|
|
return ()
|
|
|
|
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
|
|
|
|
gen_free_vars e1 >>= fun () -> gen_free_vars e2
|
|
|
|
|
|
|
|
|
|
|
|
let free_vars e = Sequence.Generator.run (gen_free_vars e)
|
|
|
|
|
|
|
|
let ident_mem e id = free_vars e |> Sequence.exists ~f:(Ident.equal id)
|
|
|
|
|
|
|
|
let rec gen_program_vars =
|
|
|
|
let open Sequence.Generator in
|
|
|
|
function
|
|
|
|
| Lvar name ->
|
|
|
|
yield name
|
|
|
|
| Const _ | Var _ | Sizeof {dynamic_length= None} ->
|
|
|
|
return ()
|
|
|
|
| Cast (_, e) | Exn e | Lfield (e, _, _) | Sizeof {dynamic_length= Some e} | UnOp (_, e, _) ->
|
|
|
|
gen_program_vars e
|
|
|
|
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
|
|
|
|
gen_program_vars e1 >>= fun () -> gen_program_vars e2
|
|
|
|
| Closure {captured_vars} ->
|
|
|
|
ISequence.gen_sequence_list captured_vars ~f:(fun (e, _, _) -> gen_program_vars e)
|
|
|
|
|
|
|
|
|
|
|
|
let program_vars e = Sequence.Generator.run (gen_program_vars e)
|
[clang] enforce that `instruction` always returns one SIL expression
Summary:
Previously, the type of `trans_result` contained a list of SIL expressions.
However, most of the time we expect to get exactly one, and getting a different
number is a soft(!) error, usually returning `-1`.
This splits `trans_result` into `control`, which contains the information
needed for temporary computation (hence when we don't necessarily know the
return value yet), and a new version of `trans_result` that includes `control`,
the previous `exps` list but replaced by a single `return` expression instead,
and a couple other values that made sense to move out of `control`. This allows
some flexibility in the frontend compared to enforcing exactly one return
expression always: if they are not known yet we stick to `control` instead (see
eg `compute_controls_to_parent`).
This creates more garbage temporary identifiers, however they do not show up in
the final cfg. Instead, we see that temporary IDs are now often not
consecutive...
The most painful complication is in the treatment of `DeclRefExpr`, which was
actually returning *two* expressions: the method name and the `this` object.
Now the method name is a separate (optional) field in `trans_result`.
Reviewed By: mbouaziz
Differential Revision: D7881088
fbshipit-source-id: 41ad3b5
7 years ago
|
|
|
|
|
|
|
let rec rename_pvars ~(f : string -> string) : t -> t =
|
|
|
|
let re e = rename_pvars ~f e in
|
|
|
|
let rv v = Pvar.rename ~f v in
|
|
|
|
function
|
|
|
|
| UnOp (op, e, t) ->
|
|
|
|
UnOp (op, re e, t)
|
|
|
|
| BinOp (op, e1, e2) ->
|
|
|
|
BinOp (op, re e1, re e2)
|
|
|
|
| Exn e ->
|
|
|
|
Exn (re e)
|
|
|
|
| Closure {name; captured_vars} ->
|
|
|
|
let captured_vars = List.map ~f:(function e, v, t -> (re e, rv v, t)) captured_vars in
|
|
|
|
Closure {name; captured_vars}
|
|
|
|
| Cast (t, e) ->
|
|
|
|
Cast (t, re e)
|
|
|
|
| Lvar v ->
|
|
|
|
Lvar (rv v)
|
|
|
|
| Lfield (e, fld, t) ->
|
|
|
|
Lfield (re e, fld, t)
|
|
|
|
| Lindex (e1, e2) ->
|
|
|
|
Lindex (re e1, re e2)
|
|
|
|
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
|
|
|
|
let dynamic_length = Option.map ~f:re dynamic_length in
|
|
|
|
Sizeof {typ; nbytes; dynamic_length; subtype}
|
|
|
|
| e (* Should have only cases without subexpressions. *) ->
|
|
|
|
e
|
|
|
|
|
|
|
|
|
[clang] enforce that `instruction` always returns one SIL expression
Summary:
Previously, the type of `trans_result` contained a list of SIL expressions.
However, most of the time we expect to get exactly one, and getting a different
number is a soft(!) error, usually returning `-1`.
This splits `trans_result` into `control`, which contains the information
needed for temporary computation (hence when we don't necessarily know the
return value yet), and a new version of `trans_result` that includes `control`,
the previous `exps` list but replaced by a single `return` expression instead,
and a couple other values that made sense to move out of `control`. This allows
some flexibility in the frontend compared to enforcing exactly one return
expression always: if they are not known yet we stick to `control` instead (see
eg `compute_controls_to_parent`).
This creates more garbage temporary identifiers, however they do not show up in
the final cfg. Instead, we see that temporary IDs are now often not
consecutive...
The most painful complication is in the treatment of `DeclRefExpr`, which was
actually returning *two* expressions: the method name and the `this` object.
Now the method name is a separate (optional) field in `trans_result`.
Reviewed By: mbouaziz
Differential Revision: D7881088
fbshipit-source-id: 41ad3b5
7 years ago
|
|
|
let zero_of_type typ =
|
|
|
|
match typ.Typ.desc with
|
|
|
|
| Typ.Tint _ ->
|
|
|
|
Some (Const (Cint IntLit.zero))
|
|
|
|
| Typ.Tfloat _ ->
|
|
|
|
Some (Const (Cfloat 0.0))
|
|
|
|
| Typ.Tptr _ ->
|
|
|
|
Some (Const (Cint IntLit.null))
|
|
|
|
| _ ->
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
|
|
let zero_of_type_exn typ = Option.value_exn (zero_of_type typ)
|
|
|
|
|
|
|
|
let rec ignore_cast e = match e with Cast (_, e) -> ignore_cast e | _ -> e
|
|
|
|
|
|
|
|
let rec ignore_integer_cast e =
|
|
|
|
match e with Cast (t, e) when Typ.is_int t -> ignore_integer_cast e | _ -> e
|
|
|
|
|
|
|
|
|
|
|
|
let rec get_java_class_initializer tenv = function
|
|
|
|
| Lfield (Lvar pvar, fn, typ) when Pvar.is_global pvar -> (
|
|
|
|
match Struct.get_field_type_and_annotation ~lookup:(Tenv.lookup tenv) fn typ with
|
|
|
|
| Some (field_typ, annot) when Annot.Item.is_final annot ->
|
|
|
|
let java_class = Typ.JavaClass (Pvar.get_name pvar) in
|
|
|
|
Some (Procname.Java (Procname.Java.get_class_initializer java_class), pvar, fn, field_typ)
|
|
|
|
| _ ->
|
|
|
|
None )
|
|
|
|
| Cast (_, e) | Lfield (e, _, _) ->
|
|
|
|
get_java_class_initializer tenv e
|
|
|
|
| Lvar _ | Var _ | UnOp _ | BinOp _ | Exn _ | Closure _ | Const _ | Lindex _ | Sizeof _ ->
|
|
|
|
None
|