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.

1623 lines
49 KiB

(*
* 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: Types *)
open! IStd
module Hashtbl = Caml.Hashtbl
module L = Logging
module F = Format
module IntegerWidths = struct
type t = {char_width: int; short_width: int; int_width: int; long_width: int; longlong_width: int}
[@@deriving compare]
let java = {char_width= 16; short_width= 16; int_width= 32; long_width= 64; longlong_width= 64}
module SQLite = SqliteUtils.MarshalledNullableDataNOTForComparison (struct
type nonrec t = t
end)
let load_statement =
ResultsDatabase.register_statement
"SELECT integer_type_widths FROM source_files WHERE source_file = :k"
let load source =
ResultsDatabase.with_registered_statement load_statement ~f:(fun db load_stmt ->
SourceFile.SQLite.serialize source
|> Sqlite3.bind load_stmt 1
|> SqliteUtils.check_result_code db ~log:"load bind source file" ;
SqliteUtils.result_single_column_option ~finalize:false ~log:"Typ.IntegerWidths.load" db
load_stmt
|> Option.bind ~f:SQLite.deserialize )
end
(** Kinds of integers *)
type ikind =
| IChar (** [char] *)
| ISChar (** [signed char] *)
| IUChar (** [unsigned char] *)
| IBool (** [bool] *)
| IInt (** [int] *)
| IUInt (** [unsigned int] *)
| IShort (** [short] *)
| IUShort (** [unsigned short] *)
| ILong (** [long] *)
| IULong (** [unsigned long] *)
| ILongLong (** [long long] (or [_int64] on Microsoft Visual C) *)
| IULongLong (** [unsigned long long] (or [unsigned int64_] on Microsoft Visual C) *)
| I128 (** [__int128_t] *)
| IU128 (** [__uint128_t] *)
[@@deriving compare]
let equal_ikind = [%compare.equal: ikind]
let ikind_to_string = function
| IChar ->
"char"
| ISChar ->
"signed char"
| IUChar ->
"unsigned char"
| IBool ->
"_Bool"
| IInt ->
"int"
| IUInt ->
"unsigned int"
| IShort ->
"short"
| IUShort ->
"unsigned short"
| ILong ->
"long"
| IULong ->
"unsigned long"
| ILongLong ->
"long long"
| IULongLong ->
"unsigned long long"
| I128 ->
"__int128_t"
| IU128 ->
"__uint128_t"
let width_of_ikind {IntegerWidths.char_width; short_width; int_width; long_width; longlong_width} =
function
| IBool ->
8
| ISChar | IChar | IUChar ->
char_width
| IShort | IUShort ->
short_width
| IInt | IUInt ->
int_width
| ILong | IULong ->
long_width
| ILongLong | IULongLong ->
longlong_width
| I128 | IU128 ->
128
let ikind_is_unsigned = function
| IBool | IUChar | IUShort | IUInt | IULong | IULongLong | IU128 ->
true
| ISChar | IChar | IShort | IInt | ILong | ILongLong | I128 ->
false
let range_of_ikind =
let range bits ~unsigned =
if unsigned then Z.(~$0, shift_left ~$1 bits - ~$1)
else
let bound = Z.(shift_left ~$1) (bits - 1) in
Z.(~-bound, bound - ~$1)
in
fun integer_widths x ->
let bits_for_range = match x with IBool -> 1 | _ -> width_of_ikind integer_widths x in
range bits_for_range ~unsigned:(ikind_is_unsigned x)
let ikind_is_char = function IChar | ISChar | IUChar -> true | _ -> false
(** Kinds of floating-point numbers *)
type fkind = FFloat (** [float] *) | FDouble (** [double] *) | FLongDouble (** [long double] *)
[@@deriving compare]
let equal_fkind = [%compare.equal: fkind]
let fkind_to_string = function
| FFloat ->
"float"
| FDouble ->
"double"
| FLongDouble ->
"long double"
(** kind of pointer *)
type ptr_kind =
| Pk_pointer (** C/C++, Java, Objc standard/__strong pointer *)
| Pk_reference (** C++ reference *)
| Pk_objc_weak (** Obj-C __weak pointer *)
| Pk_objc_unsafe_unretained (** Obj-C __unsafe_unretained pointer *)
| Pk_objc_autoreleasing (** Obj-C __autoreleasing pointer *)
[@@deriving compare]
let equal_ptr_kind = [%compare.equal: ptr_kind]
let ptr_kind_string = function
| Pk_reference ->
"&"
| Pk_pointer ->
"*"
| Pk_objc_weak ->
"__weak *"
| Pk_objc_unsafe_unretained ->
"__unsafe_unretained *"
| Pk_objc_autoreleasing ->
"__autoreleasing *"
module T = struct
type type_quals = {is_const: bool; is_restrict: bool; is_volatile: bool} [@@deriving compare]
(** types for sil (structured) expressions *)
type t = {desc: desc; quals: type_quals} [@@deriving compare]
and desc =
| Tint of ikind (** integer type *)
| Tfloat of fkind (** float type *)
| Tvoid (** void type *)
| Tfun of {no_return: bool} (** function type with noreturn attribute *)
| Tptr of t * ptr_kind (** pointer type *)
| Tstruct of name (** structured value type name *)
| TVar of string (** type variable (ie. C++ template variables) *)
| Tarray of {elt: t; length: IntLit.t option; stride: IntLit.t option}
(** array type with statically fixed length and stride *)
[@@deriving compare]
and name =
| CStruct of QualifiedCppName.t
| CUnion of QualifiedCppName.t
| CppClass of QualifiedCppName.t * template_spec_info
| JavaClass of Mangled.t
| ObjcClass of QualifiedCppName.t
| ObjcProtocol of QualifiedCppName.t
[@@deriving compare]
and template_arg = TType of t | TInt of Int64.t | TNull | TNullPtr | TOpaque
[@@deriving compare]
and template_spec_info =
| NoTemplate
| Template of {mangled: string option; args: template_arg list}
[@@deriving compare]
let equal_desc = [%compare.equal: desc]
let equal_name = [%compare.equal: name]
let equal_quals = [%compare.equal: type_quals]
let equal = [%compare.equal: t]
let rec equal_ignore_quals t1 t2 = equal_desc_ignore_quals t1.desc t2.desc
and equal_desc_ignore_quals d1 d2 =
match (d1, d2) with
| Tint ikind1, Tint ikind2 ->
equal_ikind ikind1 ikind2
| Tfloat fkind1, Tfloat fkind2 ->
equal_fkind fkind1 fkind2
| Tvoid, Tvoid ->
true
| Tptr (t1, ptr_kind1), Tptr (t2, ptr_kind2) ->
equal_ptr_kind ptr_kind1 ptr_kind2 && equal_ignore_quals t1 t2
| Tarray {elt= t1}, Tarray {elt= t2} ->
equal_ignore_quals t1 t2
| _, _ ->
false
end
include T
let mk_type_quals ?default ?is_const ?is_restrict ?is_volatile () =
let default_ = {is_const= false; is_restrict= false; is_volatile= false} in
let mk_aux ?(default = default_) ?(is_const = default.is_const)
?(is_restrict = default.is_restrict) ?(is_volatile = default.is_volatile) () =
{is_const; is_restrict; is_volatile}
in
mk_aux ?default ?is_const ?is_restrict ?is_volatile ()
let is_const {is_const} = is_const
let is_restrict {is_restrict} = is_restrict
let is_volatile {is_volatile} = is_volatile
let is_weak_pointer t = match t.desc with Tptr (_, Pk_objc_weak) -> true | _ -> false
let is_strong_pointer t = match t.desc with Tptr (_, Pk_pointer) -> true | _ -> false
let mk ?default ?quals desc : t =
let default_ = {desc; quals= mk_type_quals ()} in
let mk_aux ?(default = default_) ?(quals = default.quals) desc = {desc; quals} in
mk_aux ?default ?quals desc
let mk_array ?default ?quals ?length ?stride elt : t =
mk ?default ?quals (Tarray {elt; length; stride})
let void = mk Tvoid
let void_star = mk (Tptr (mk Tvoid, Pk_pointer))
let uint = mk (Tint IUInt)
let get_ikind_opt {desc} = match desc with Tint ikind -> Some ikind | _ -> None
(* TODO: size_t should be implementation-dependent. *)
let size_t = IULong
let escape pe = if Pp.equal_print_kind pe.Pp.kind Pp.HTML then Escape.escape_xml else ident
(** Pretty print a type with all the details, using the C syntax. *)
let rec pp_full pe f typ =
let pp_quals f {quals} =
if is_const quals then F.pp_print_string f " const " ;
if is_restrict quals then F.pp_print_string f " __restrict " ;
if is_volatile quals then F.pp_print_string f " volatile "
in
let pp_desc f {desc} =
match desc with
| Tstruct tname ->
(pp_name_c_syntax pe) f tname
| TVar name ->
F.pp_print_string f name
| Tint ik ->
F.pp_print_string f (ikind_to_string ik)
| Tfloat fk ->
F.pp_print_string f (fkind_to_string fk)
| Tvoid ->
F.pp_print_string f "void"
| Tfun {no_return= false} ->
F.pp_print_string f "_fn_"
| Tfun {no_return= true} ->
F.pp_print_string f "_fn_noreturn_"
| Tptr (({desc= Tarray _ | Tfun _} as typ), pk) ->
F.fprintf f "%a(%s)" (pp_full pe) typ (ptr_kind_string pk |> escape pe)
| Tptr (typ, pk) ->
F.fprintf f "%a%s" (pp_full pe) typ (ptr_kind_string pk |> escape pe)
| Tarray {elt; length; stride} ->
let pp_int_opt fmt = function
| Some x ->
IntLit.pp fmt x
| None ->
F.pp_print_char fmt '_'
in
F.fprintf f "%a[%a*%a]" (pp_full pe) elt pp_int_opt length pp_int_opt stride
in
F.fprintf f "%a%a" pp_desc typ pp_quals typ
and pp_name_c_syntax pe f = function
| CStruct name | CUnion name | ObjcClass name | ObjcProtocol name ->
QualifiedCppName.pp f name
| CppClass (name, template_spec) ->
F.fprintf f "%a%a" QualifiedCppName.pp name (pp_template_spec_info pe) template_spec
| JavaClass name ->
Mangled.pp f name
and pp_template_spec_info pe f = function
| NoTemplate ->
()
| Template {args} ->
let pp_arg_opt f = function
| TType typ ->
pp_full pe f typ
| TInt i ->
Int64.pp f i
| TNull ->
F.pp_print_string f "null"
| TNullPtr ->
F.pp_print_string f "NullPtr"
| TOpaque ->
F.pp_print_string f "Opaque"
in
F.fprintf f "%s%a%s" (escape pe "<") (Pp.comma_seq pp_arg_opt) args (escape pe ">")
(** Pretty print a type. Do nothing by default. *)
let pp pe f te = if Config.print_types then pp_full pe f te else ()
let to_string typ =
let pp fmt = pp_full Pp.text fmt typ in
F.asprintf "%t" pp
module Name = struct
type t = name [@@deriving compare]
let equal = [%compare.equal: t]
let hash = Hashtbl.hash
let qual_name = function
| CStruct name | CUnion name | ObjcClass name | ObjcProtocol name ->
name
| CppClass (name, templ_args) ->
let template_suffix = F.asprintf "%a" (pp_template_spec_info Pp.text) templ_args in
QualifiedCppName.append_template_args_to_last name ~args:template_suffix
| JavaClass _ ->
QualifiedCppName.empty
let unqualified_name = function
| CStruct name | CUnion name | ObjcClass name | ObjcProtocol name ->
name
| CppClass (name, _) ->
name
| JavaClass _ ->
QualifiedCppName.empty
let get_template_spec_info = function CppClass (_, templ_args) -> Some templ_args | _ -> None
let name n =
match n with
| CStruct _ | CUnion _ | CppClass _ | ObjcClass _ | ObjcProtocol _ ->
qual_name n |> QualifiedCppName.to_qual_string
| JavaClass name ->
Mangled.to_string name
let pp fmt tname =
let prefix = function
| CStruct _ ->
"struct"
| CUnion _ ->
"union"
| CppClass _ | JavaClass _ | ObjcClass _ ->
"class"
| ObjcProtocol _ ->
"protocol"
in
F.fprintf fmt "%s %a" (prefix tname) (pp_name_c_syntax Pp.text) tname
let to_string = F.asprintf "%a" pp
let is_class = function CppClass _ | JavaClass _ | ObjcClass _ -> true | _ -> false
let is_union = function CUnion _ -> true | _ -> false
let is_objc_protocol name = match name with ObjcProtocol _ -> true | _ -> false
let is_same_type t1 t2 =
match (t1, t2) with
| CStruct _, CStruct _
| CUnion _, CUnion _
| CppClass _, CppClass _
| JavaClass _, JavaClass _
| ObjcClass _, ObjcClass _
| ObjcProtocol _, ObjcProtocol _ ->
true
| _ ->
false
module C = struct
let from_qual_name qual_name = CStruct qual_name
let from_string name_str = QualifiedCppName.of_qual_string name_str |> from_qual_name
let union_from_qual_name qual_name = CUnion qual_name
end
module Java = struct
module Split = struct
(** e.g. {type_name="int"; package=None} for primitive types
* or {type_name="PrintWriter"; package=Some "java.io"} for objects.
*)
type t = {package: string option; type_name: string}
let make ?package type_name = {type_name; package}
(** Given a package.class_name string, it looks for the latest dot and split the string
in two (package, class_name) *)
let of_string package_classname =
match String.rsplit2 package_classname ~on:'.' with
| Some (package, type_name) ->
{type_name; package= Some package}
| None ->
{type_name= package_classname; package= None}
let package {package} = package
let type_name {type_name} = type_name
let java_lang_object = make ~package:"java.lang" "Object"
let java_lang_string = make ~package:"java.lang" "String"
let void = make "void"
end
let from_string name_str = JavaClass (Mangled.from_string name_str)
let from_package_class package_name class_name =
if String.equal package_name "" then from_string class_name
else from_string (package_name ^ "." ^ class_name)
let is_class = function JavaClass _ -> true | _ -> false
let java_io_serializable = from_string "java.io.Serializable"
let java_lang_class = from_string "java.lang.Class"
let java_lang_cloneable = from_string "java.lang.Cloneable"
let java_lang_object = from_string "java.lang.Object"
let split_typename typename = Split.of_string (name typename)
let get_outer_class class_name =
let {Split.package; type_name} = split_typename class_name in
match String.rsplit2 ~on:'$' type_name with
| Some (parent_class, _) ->
Some (from_package_class (Option.value ~default:"" package) parent_class)
| None ->
None
let is_anonymous_inner_class_name class_name =
let class_name_no_package = Split.type_name (split_typename class_name) in
match String.rsplit2 class_name_no_package ~on:'$' with
| Some (_, s) ->
let is_int =
try
ignore (int_of_string (String.strip s)) ;
true
with Failure _ -> false
in
is_int
| None ->
false
let is_external_classname name_string =
let {Split.package} = Split.of_string name_string in
Option.exists ~f:Config.java_package_is_external package
let is_external t = is_external_classname (name t)
end
module Cpp = struct
let from_qual_name template_spec_info qual_name = CppClass (qual_name, template_spec_info)
let is_class = function CppClass _ -> true | _ -> false
end
module Objc = struct
let from_qual_name qual_name = ObjcClass qual_name
let from_string name_str = QualifiedCppName.of_qual_string name_str |> from_qual_name
let protocol_from_qual_name qual_name = ObjcProtocol qual_name
let is_class = function ObjcClass _ -> true | _ -> false
end
module Set = Caml.Set.Make (struct
type nonrec t = t
let compare = compare
end)
end
(** dump a type with all the details. *)
let d_full (t : t) = L.d_pp_with_pe pp_full t
(** dump a list of types. *)
let d_list (tl : t list) =
let pp pe = Pp.seq (pp pe) in
L.d_pp_with_pe pp tl
let name typ = match typ.desc with Tstruct name -> Some name | _ -> None
let unsome s = function
| Some default_typ ->
default_typ
| None ->
L.internal_error "No default typ in %s@." s ;
assert false
(** turn a *T into a T. fails if [typ] is not a pointer type *)
let strip_ptr typ = match typ.desc with Tptr (t, _) -> t | _ -> assert false
(** If an array type, return the type of the element.
If not, return the default type if given, otherwise raise an exception *)
let array_elem default_opt typ =
match typ.desc with Tarray {elt} -> elt | _ -> unsome "array_elem" default_opt
let is_class_of_kind check_fun typ =
match typ.desc with Tstruct tname -> check_fun tname | _ -> false
let is_objc_class = is_class_of_kind Name.Objc.is_class
let is_cpp_class = is_class_of_kind Name.Cpp.is_class
let is_pointer typ = match typ.desc with Tptr _ -> true | _ -> false
let is_reference typ = match typ.desc with Tptr (_, Pk_reference) -> true | _ -> false
let is_struct typ = match typ.desc with Tstruct _ -> true | _ -> false
let is_pointer_to_cpp_class typ = match typ.desc with Tptr (t, _) -> is_cpp_class t | _ -> false
let is_pointer_to_void typ = match typ.desc with Tptr ({desc= Tvoid}, _) -> true | _ -> false
let is_pointer_to_int typ = match typ.desc with Tptr ({desc= Tint _}, _) -> true | _ -> false
let is_int typ = match typ.desc with Tint _ -> true | _ -> false
let is_unsigned_int typ = match typ.desc with Tint ikind -> ikind_is_unsigned ikind | _ -> false
let is_char typ = match typ.desc with Tint ikind -> ikind_is_char ikind | _ -> false
let has_block_prefix s =
match Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) s with
| _ :: _ :: _ ->
true
| _ ->
false
type typ = t
module Procname = struct
(** Level of verbosity of some to_string functions. *)
type detail_level = Verbose | Non_verbose | Simple [@@deriving compare]
let equal_detail_level = [%compare.equal: detail_level]
let is_verbose v = match v with Verbose -> true | _ -> false
module Java = struct
type kind =
| Non_Static
(** in Java, procedures called with invokevirtual, invokespecial, and invokeinterface *)
| Static (** in Java, procedures called with invokestatic *)
[@@deriving compare]
(* TODO: use Mangled.t here *)
type java_type = Name.Java.Split.t = {package: string option; type_name: string}
[@@deriving compare]
let java_void = {package= None; type_name= "void"}
(** Type of java procedure names. *)
type t =
{ method_name: string
; parameters: java_type list
; class_name: Name.t
; return_type: java_type option (* option because constructors have no return type *)
; kind: kind }
[@@deriving compare]
let make class_name return_type method_name parameters kind =
{class_name; return_type; method_name; parameters; kind}
(** A type is a pair (package, type_name) that is translated in a string package.type_name *)
let pp_type_verbosity verbosity fmt p =
match p with
| {package= Some package; type_name} when is_verbose verbosity ->
F.fprintf fmt "%s.%s" package type_name
| {type_name} ->
F.pp_print_string fmt type_name
(** Given a list of types, it creates a unique string of types separated by commas *)
let rec pp_param_list verbosity fmt inputList =
match inputList with
| [] ->
()
| [head] ->
pp_type_verbosity verbosity fmt head
| head :: rest ->
pp_type_verbosity verbosity fmt head ;
F.pp_print_string fmt "," ;
pp_param_list verbosity fmt rest
let java_type_of_name class_name = Name.Java.Split.of_string (Name.name class_name)
(** It is the same as java_type_to_string_verbosity, but Java return types are optional because
of constructors without type *)
let pp_return_type verbosity fmt j =
match j.return_type with None -> () | Some typ -> pp_type_verbosity verbosity fmt typ
let get_class_name j = Name.name j.class_name
let get_class_type_name j = j.class_name
let get_simple_class_name j = Name.Java.Split.(j |> get_class_name |> of_string |> type_name)
let get_package j = Name.Java.Split.(j |> get_class_name |> of_string |> package)
let get_method j = j.method_name
let replace_method_name method_name j = {j with method_name}
let replace_parameters parameters j = {j with parameters}
let replace_return_type ret_type j = {j with return_type= Some ret_type}
let get_parameters j = j.parameters
(** Prints a string of a java procname with the given level of verbosity *)
let pp ?(withclass = false) verbosity fmt j =
match verbosity with
| Verbose | Non_verbose ->
(* if verbose, then package.class.method(params): rtype,
else rtype package.class.method(params)
verbose is used for example to create unique filenames, non_verbose to create reports *)
let pp_class_name verbosity fmt j =
pp_type_verbosity verbosity fmt (Name.Java.split_typename j.class_name)
in
let separator =
match (j.return_type, verbosity) with
| None, _ ->
""
| Some _, Verbose ->
":"
| _ ->
" "
in
if not (equal_detail_level verbosity Verbose) then
F.fprintf fmt "%a%s" (pp_return_type verbosity) j separator ;
F.fprintf fmt "%a.%s(%a)" (pp_class_name verbosity) j j.method_name
(pp_param_list verbosity) j.parameters ;
if equal_detail_level verbosity Verbose then
F.fprintf fmt "%s%a" separator (pp_return_type verbosity) j
| Simple ->
(* methodname(...) or without ... if there are no parameters *)
let pp_class_prefix ~withclass verbosity fmt j =
if withclass then
F.fprintf fmt "%a." (pp_type_verbosity verbosity)
(Name.Java.split_typename j.class_name)
in
let params = match j.parameters with [] -> "" | _ -> "..." in
let pp_method_name ~withclass verbosity fmt j =
if String.equal j.method_name "<init>" then
F.pp_print_string fmt (get_simple_class_name j)
else F.fprintf fmt "%a%s" (pp_class_prefix ~withclass verbosity) j j.method_name
in
F.fprintf fmt "%a(%s)" (pp_method_name ~withclass verbosity) j params
let get_return_typ pname_java =
let rec java_from_string = function
| "" | "void" ->
mk Tvoid
| "int" ->
mk (Tint IInt)
| "byte" ->
mk (Tint IShort)
| "short" ->
mk (Tint IShort)
| "boolean" ->
mk (Tint IBool)
| "char" ->
mk (Tint IChar)
| "long" ->
mk (Tint ILong)
| "float" ->
mk (Tfloat FFloat)
| "double" ->
mk (Tfloat FDouble)
| typ_str when String.contains typ_str '[' ->
let stripped_typ = String.sub typ_str ~pos:0 ~len:(String.length typ_str - 2) in
mk (Tptr (mk_array (java_from_string stripped_typ), Pk_pointer))
| typ_str ->
mk (Tstruct (Name.Java.from_string typ_str))
in
let typ = java_from_string (F.asprintf "%a" (pp_return_type Verbose) pname_java) in
match typ.desc with Tstruct _ -> mk (Tptr (typ, Pk_pointer)) | _ -> typ
let is_close {method_name} = String.equal method_name "close"
let constructor_method_name = "<init>"
let class_initializer_method_name = "<clinit>"
let is_class_initializer {method_name} = String.equal method_name class_initializer_method_name
let get_class_initializer class_name =
{ method_name= class_initializer_method_name
; parameters= []
; class_name
; return_type= Some java_void
; kind= Static }
let is_constructor {method_name} = String.equal method_name constructor_method_name
let is_anonymous_inner_class_constructor {class_name} =
Name.Java.is_anonymous_inner_class_name class_name
let is_static {kind} = match kind with Static -> true | _ -> false
let is_lambda {method_name} = String.is_prefix ~prefix:"lambda$" method_name
let is_generated {method_name} = String.is_prefix ~prefix:"$" method_name
let is_access_method {method_name} =
match String.rsplit2 method_name ~on:'$' with
| Some ("access", s) ->
let is_int =
try
ignore (int_of_string s) ;
true
with Failure _ -> false
in
is_int
| _ ->
false
let is_autogen_method {method_name} = String.contains method_name '$'
(** Check if the proc name has the type of a java vararg.
Note: currently only checks that the last argument has type Object[]. *)
let is_vararg {parameters} =
match List.last parameters with Some {type_name= "java.lang.Object[]"} -> true | _ -> false
let is_external java_pname =
let package = get_package java_pname in
Option.exists ~f:Config.java_package_is_external package
end
module Parameter = struct
(** Type for parameters in clang procnames, [Some name] means the parameter is of type pointer to struct, with [name]
being the name of the struct, [None] means the parameter is of some other type. *)
type clang_parameter = Name.t option [@@deriving compare]
(** Type for parameters in procnames, for java and clang. *)
type t = JavaParameter of Java.java_type | ClangParameter of clang_parameter
[@@deriving compare]
let of_typ typ =
match typ.T.desc with T.Tptr ({desc= Tstruct name}, Pk_pointer) -> Some name | _ -> None
let pp_parameters fmt parameters =
if List.exists ~f:Option.is_some parameters then
(* the tests rely on the fact that we discard non-pointer-to-struct types for some reason,
hence the slight re-implementation of [Pp.seq] to avoid building the list of [Some] items
explicitly *)
let rec pp_parameters_aux fmt = function
| [] ->
()
| [Some param] ->
F.pp_print_string fmt (Name.to_string param)
| None :: parameters ->
pp_parameters_aux fmt parameters
| (Some _ as param_some) :: None :: parameters ->
pp_parameters_aux fmt (param_some :: parameters)
| Some param :: (Some _ :: _ as parameters) ->
F.fprintf fmt "%s," (Name.to_string param) ;
pp_parameters_aux fmt parameters
in
F.fprintf fmt "(%a)" pp_parameters_aux parameters
let clang_param_of_name class_name : clang_parameter = Some class_name
end
module ObjC_Cpp = struct
type kind =
| CPPMethod of {mangled: string option}
| CPPConstructor of {mangled: string option; is_constexpr: bool}
| CPPDestructor of {mangled: string option}
| ObjCClassMethod
| ObjCInstanceMethod
| ObjCInternalMethod
[@@deriving compare]
type t =
{ class_name: Name.t
; kind: kind
; method_name: string
; parameters: Parameter.clang_parameter list
; template_args: template_spec_info }
[@@deriving compare]
let make class_name method_name kind template_args parameters =
{class_name; method_name; kind; template_args; parameters}
let get_class_name objc_cpp = Name.name objc_cpp.class_name
let get_class_type_name objc_cpp = objc_cpp.class_name
let get_class_qualifiers objc_cpp = Name.qual_name objc_cpp.class_name
let objc_method_kind_of_bool is_instance =
if is_instance then ObjCInstanceMethod else ObjCClassMethod
let is_objc_constructor method_name =
String.equal method_name "new" || String.is_prefix ~prefix:"init" method_name
let is_objc_kind = function
| ObjCClassMethod | ObjCInstanceMethod | ObjCInternalMethod ->
true
| _ ->
false
let is_objc_method {kind} = is_objc_kind kind
let is_objc_dealloc method_name = String.equal method_name "dealloc"
let is_destructor = function
| {kind= CPPDestructor _} ->
true
| name ->
is_objc_dealloc name.method_name
let is_inner_destructor ({method_name} as pname) =
is_destructor pname
&& String.is_prefix ~prefix:Config.clang_inner_destructor_prefix method_name
let is_constexpr = function {kind= CPPConstructor {is_constexpr= true}} -> true | _ -> false
let is_cpp_lambda {method_name} = String.is_substring ~substring:"operator()" method_name
let is_operator_equal {method_name} = String.is_substring ~substring:"operator=" method_name
let pp_verbose_kind fmt = function
| CPPMethod {mangled} | CPPDestructor {mangled} ->
F.fprintf fmt "(%s)" (Option.value ~default:"" mangled)
| CPPConstructor {mangled; is_constexpr} ->
F.fprintf fmt "{%s%s}"
(Option.value ~default:"" mangled)
(if is_constexpr then "|constexpr" else "")
| ObjCClassMethod ->
F.pp_print_string fmt "class"
| ObjCInstanceMethod ->
F.pp_print_string fmt "instance"
| ObjCInternalMethod ->
F.pp_print_string fmt "internal"
let pp verbosity fmt osig =
match verbosity with
| Simple ->
F.pp_print_string fmt osig.method_name
| Non_verbose ->
F.fprintf fmt "%s::%s" (Name.name osig.class_name) osig.method_name
| Verbose ->
F.fprintf fmt "%s::%s%a%a" (Name.name osig.class_name) osig.method_name
Parameter.pp_parameters osig.parameters pp_verbose_kind osig.kind
let get_parameters osig = osig.parameters
let replace_parameters new_parameters osig = {osig with parameters= new_parameters}
end
module C = struct
(** Type of c procedure names. *)
type t =
{ name: QualifiedCppName.t
; mangled: string option
; parameters: Parameter.clang_parameter list
; template_args: template_spec_info }
[@@deriving compare]
let c name mangled parameters template_args =
{name; mangled= Some mangled; parameters; template_args}
let from_string name =
{ name= QualifiedCppName.of_qual_string name
; mangled= None
; parameters= []
; template_args= NoTemplate }
let pp verbosity fmt {name; mangled; parameters} =
let plain = QualifiedCppName.to_qual_string name in
match verbosity with
| Simple ->
F.fprintf fmt "%s()" plain
| Non_verbose ->
F.pp_print_string fmt plain
| Verbose ->
let pp_mangled fmt = function None -> () | Some s -> F.fprintf fmt "{%s}" s in
F.fprintf fmt "%s%a%a" plain Parameter.pp_parameters parameters pp_mangled mangled
let get_parameters c = c.parameters
let replace_parameters new_parameters c = {c with parameters= new_parameters}
end
module Block = struct
(** Type of Objective C block names. *)
type block_name = string [@@deriving compare]
type t = {name: block_name; parameters: Parameter.clang_parameter list} [@@deriving compare]
let make name parameters = {name; parameters}
let pp verbosity fmt bsig =
match verbosity with
| Simple ->
F.pp_print_string fmt "block"
| Non_verbose ->
F.pp_print_string fmt bsig.name
| Verbose ->
F.fprintf fmt "%s%a" bsig.name Parameter.pp_parameters bsig.parameters
let get_parameters block = block.parameters
let replace_parameters new_parameters block = {block with parameters= new_parameters}
end
(** Type of procedure names. *)
type t =
| Java of Java.t
| C of C.t
| Linters_dummy_method
| Block of Block.t
| ObjC_Cpp of ObjC_Cpp.t
| WithBlockParameters of t * Block.block_name list
[@@deriving compare]
let equal = [%compare.equal: t]
(** hash function for procname *)
let hash = Hashtbl.hash
let with_block_parameters base blocks = WithBlockParameters (base, blocks)
let is_java = function Java _ -> true | _ -> false
(* TODO: deprecate this unfortunately named function and use is_clang instead *)
let is_c_method = function ObjC_Cpp _ -> true | _ -> false
let is_c_function = function C _ -> true | _ -> false
let is_clang = function
| ObjC_Cpp name ->
ObjC_Cpp.is_objc_method name
| name ->
is_c_function name
let is_java_lift f = function Java java_pname -> f java_pname | _ -> false
let is_java_access_method = is_java_lift Java.is_access_method
let is_java_class_initializer = is_java_lift Java.is_class_initializer
let is_objc_method procname =
match procname with ObjC_Cpp name -> ObjC_Cpp.is_objc_method name | _ -> false
let block_name_of_procname procname =
match procname with
| Block block ->
block.name
| _ ->
Logging.die InternalError "Only to be called with Objective-C block names"
let empty_block = Block {name= ""; parameters= []}
(** Replace the class name component of a procedure name.
In case of Java, replace package and class name. *)
let rec replace_class t (new_class : Name.t) =
match t with
| Java j ->
Java {j with class_name= new_class}
| ObjC_Cpp osig ->
ObjC_Cpp {osig with class_name= new_class}
| WithBlockParameters (base, blocks) ->
WithBlockParameters (replace_class base new_class, blocks)
| C _ | Block _ | Linters_dummy_method ->
t
let get_class_type_name = function
| Java java_pname ->
Some (Java.get_class_type_name java_pname)
| ObjC_Cpp objc_pname ->
Some (ObjC_Cpp.get_class_type_name objc_pname)
| _ ->
None
let get_class_name = function
| Java java_pname ->
Some (Java.get_class_name java_pname)
| ObjC_Cpp objc_pname ->
Some (ObjC_Cpp.get_class_name objc_pname)
| _ ->
None
let is_method_in_objc_protocol t =
match t with ObjC_Cpp osig -> Name.is_objc_protocol osig.class_name | _ -> false
let rec objc_cpp_replace_method_name t (new_method_name : string) =
match t with
| ObjC_Cpp osig ->
ObjC_Cpp {osig with method_name= new_method_name}
| WithBlockParameters (base, blocks) ->
WithBlockParameters (objc_cpp_replace_method_name base new_method_name, blocks)
| C _ | Block _ | Linters_dummy_method | Java _ ->
t
(** Return the method/function of a procname. *)
let rec get_method = function
| ObjC_Cpp name ->
name.method_name
| WithBlockParameters (base, _) ->
get_method base
| C {name} ->
QualifiedCppName.to_qual_string name
| Block {name} ->
name
| Java j ->
j.method_name
| Linters_dummy_method ->
"Linters_dummy_method"
(** Return whether the procname is a block procname. *)
let is_objc_block = function Block _ -> true | _ -> false
(** Return the language of the procedure. *)
let get_language = function
| ObjC_Cpp _ ->
Language.Clang
| C _ ->
Language.Clang
| Block _ ->
Language.Clang
| Linters_dummy_method ->
Language.Clang
| WithBlockParameters _ ->
Language.Clang
| Java _ ->
Language.Java
(** [is_constructor pname] returns true if [pname] is a constructor *)
let is_constructor = function
| Java js ->
String.equal js.method_name Java.constructor_method_name
| ObjC_Cpp {kind= CPPConstructor _} ->
true
| ObjC_Cpp {kind; method_name} when ObjC_Cpp.is_objc_kind kind ->
ObjC_Cpp.is_objc_constructor method_name
| _ ->
false
(** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc *)
let is_infer_undefined pn =
match pn with
| Java j ->
let regexp = Str.regexp_string "com.facebook.infer.builtins.InferUndefined" in
Str.string_match regexp (Java.get_class_name j) 0
| _ ->
(* TODO: add cases for obj-c, c, c++ *)
false
let get_global_name_of_initializer = function
| C {name}
when String.is_prefix ~prefix:Config.clang_initializer_prefix
(QualifiedCppName.to_qual_string name) ->
let name_str = QualifiedCppName.to_qual_string name in
let prefix_len = String.length Config.clang_initializer_prefix in
Some (String.sub name_str ~pos:prefix_len ~len:(String.length name_str - prefix_len))
| _ ->
None
(** Very verbose representation of an existing Procname.t *)
let rec pp_unique_id fmt = function
| Java j ->
Java.pp Verbose fmt j
| C osig ->
C.pp Verbose fmt osig
| ObjC_Cpp osig ->
ObjC_Cpp.pp Verbose fmt osig
| Block bsig ->
Block.pp Verbose fmt bsig
| WithBlockParameters (base, []) ->
pp_unique_id fmt base
| WithBlockParameters (base, (_ :: _ as blocks)) ->
pp_unique_id fmt base ;
F.pp_print_string fmt "_" ;
Pp.seq ~sep:"_" F.pp_print_string fmt blocks
| Linters_dummy_method ->
F.pp_print_string fmt "Linters_dummy_method"
let to_unique_id proc_name = F.asprintf "%a" pp_unique_id proc_name
(** Convert a proc name to a string for the user to see *)
let rec pp fmt = function
| Java j ->
Java.pp Non_verbose fmt j
| C osig ->
C.pp Non_verbose fmt osig
| ObjC_Cpp osig ->
ObjC_Cpp.pp Non_verbose fmt osig
| Block bsig ->
Block.pp Non_verbose fmt bsig
| WithBlockParameters (base, []) ->
pp fmt base
| WithBlockParameters (base, (_ :: _ as blocks)) ->
pp fmt base ;
F.pp_print_string fmt "_" ;
Pp.seq ~sep:"_" F.pp_print_string fmt blocks
| Linters_dummy_method ->
pp_unique_id fmt Linters_dummy_method
let to_string proc_name = F.asprintf "%a" pp proc_name
(** Convenient representation of a procname for external tools (e.g. eclipse plugin) *)
let rec pp_simplified_string ?(withclass = false) fmt = function
| Java j ->
Java.pp ~withclass Simple fmt j
| C osig ->
C.pp Simple fmt osig
| ObjC_Cpp osig ->
ObjC_Cpp.pp Simple fmt osig
| Block bsig ->
Block.pp Simple fmt bsig
| WithBlockParameters (base, _) ->
pp_simplified_string fmt base
| Linters_dummy_method ->
pp_unique_id fmt Linters_dummy_method
let to_simplified_string ?withclass proc_name =
F.asprintf "%a" (pp_simplified_string ?withclass) proc_name
let from_string_c_fun func = C (C.from_string func)
let java_inner_class_prefix_regex = Str.regexp "\\$[0-9]+"
let hashable_name proc_name =
match proc_name with
| Java pname -> (
(* Strip autogenerated anonymous inner class numbers in order to keep the bug hash
invariant when introducing new anonymous classes *)
let name = F.asprintf "%a" (Java.pp ~withclass:true Simple) pname in
match Str.search_forward java_inner_class_prefix_regex name 0 with
| _ ->
Str.global_replace java_inner_class_prefix_regex "$_" name
| exception Caml.Not_found ->
name )
| ObjC_Cpp m when ObjC_Cpp.is_objc_method m ->
(* In Objective C, the list of parameters is part of the method name. To prevent the bug
hash to change when a parameter is introduced or removed, only the part of the name
before the first colon is used for the bug hash *)
let name = F.asprintf "%a" (pp_simplified_string ~withclass:true) proc_name in
List.hd_exn (String.split_on_chars name ~on:[':'])
| _ ->
(* Other cases for C and C++ method names *)
F.asprintf "%a" (pp_simplified_string ~withclass:true) proc_name
let rec get_parameters procname =
let clang_param_to_param clang_params =
List.map ~f:(fun par -> Parameter.ClangParameter par) clang_params
in
match procname with
| Java j ->
List.map ~f:(fun par -> Parameter.JavaParameter par) (Java.get_parameters j)
| C osig ->
clang_param_to_param (C.get_parameters osig)
| ObjC_Cpp osig ->
clang_param_to_param (ObjC_Cpp.get_parameters osig)
| Block bsig ->
clang_param_to_param (Block.get_parameters bsig)
| WithBlockParameters (base, _) ->
get_parameters base
| Linters_dummy_method ->
[]
let rec replace_parameters new_parameters procname =
let params_to_java_params params =
List.map
~f:(fun param ->
match param with
| Parameter.JavaParameter par ->
par
| _ ->
Logging.(die InternalError)
"Expected Java parameters in Java procname, but got Clang parameters" params )
params
in
let params_to_clang_params params =
List.map
~f:(fun param ->
match param with
| Parameter.ClangParameter par ->
par
| _ ->
Logging.(die InternalError)
"Expected Clang parameters in Clang procname, but got Java parameters" params )
params
in
match procname with
| Java j ->
Java (Java.replace_parameters (params_to_java_params new_parameters) j)
| C osig ->
C (C.replace_parameters (params_to_clang_params new_parameters) osig)
| ObjC_Cpp osig ->
ObjC_Cpp (ObjC_Cpp.replace_parameters (params_to_clang_params new_parameters) osig)
| Block bsig ->
Block (Block.replace_parameters (params_to_clang_params new_parameters) bsig)
| WithBlockParameters (base, blocks) ->
WithBlockParameters (replace_parameters new_parameters base, blocks)
| Linters_dummy_method ->
procname
let parameter_of_name procname class_name =
match procname with
| Java _ ->
Parameter.JavaParameter (Java.java_type_of_name class_name)
| _ ->
Parameter.ClangParameter (Parameter.clang_param_of_name class_name)
let describe f pn =
let name = hashable_name pn in
match String.lsplit2 ~on:'<' name with
| Some (name_without_template, _template_part) ->
F.pp_print_string f name_without_template
| None ->
F.pp_print_string f name
module Hashable = struct
type nonrec t = t
let equal = equal
let hash = hash
end
module Hash = Hashtbl.Make (Hashable)
module Map = PrettyPrintable.MakePPMap (struct
type nonrec t = t
let compare = compare
let pp = pp
end)
module Set = PrettyPrintable.MakePPSet (struct
type nonrec t = t
let compare = compare
let pp = pp
end)
let get_qualifiers pname =
match pname with
| C {name} ->
name
| ObjC_Cpp objc_cpp ->
ObjC_Cpp.get_class_qualifiers objc_cpp
|> QualifiedCppName.append_qualifier ~qual:objc_cpp.method_name
| _ ->
QualifiedCppName.empty
(** Convert a proc name to a filename *)
let to_filename ?crc_only pname =
(* filenames for clang procs are REVERSED qualifiers with '#' as separator *)
let pp_rev_qualified fmt pname =
let rev_qualifiers = get_qualifiers pname |> QualifiedCppName.to_rev_list in
Pp.seq ~sep:"#" F.pp_print_string fmt rev_qualifiers
in
let proc_id =
match pname with
| C {parameters; mangled} ->
let pp_mangled fmt = function
| None ->
()
| Some mangled ->
F.fprintf fmt "#%s" mangled
in
F.asprintf "%a%a%a" pp_rev_qualified pname Parameter.pp_parameters parameters pp_mangled
mangled
| ObjC_Cpp objc_cpp ->
F.asprintf "%a%a#%a" pp_rev_qualified pname Parameter.pp_parameters objc_cpp.parameters
ObjC_Cpp.pp_verbose_kind objc_cpp.kind
| _ ->
F.asprintf "%a" pp_unique_id pname
in
Escape.escape_filename @@ DB.append_crc_cutoff ?crc_only proc_id
module SQLite = struct
module T = struct
type nonrec t = t
let compare = compare
let hash = hash
let sexp_of_t p = Sexp.Atom (F.asprintf "%a" pp p)
end
module Serializer = SqliteUtils.MarshalledDataForComparison (T)
let pname_to_key = Base.Hashtbl.create (module T)
let serialize pname =
let default () = Serializer.serialize pname in
Base.Hashtbl.find_or_add pname_to_key pname ~default
let deserialize = Serializer.deserialize
let clear_cache () = Base.Hashtbl.clear pname_to_key
end
module SQLiteList = SqliteUtils.MarshalledDataNOTForComparison (struct
type nonrec t = t list
end)
end
module Fieldname = struct
type t = Clang of {class_name: Name.t; field_name: string} | Java of string [@@deriving compare]
let equal = [%compare.equal: t]
module T = struct
type nonrec t = t
let compare = compare
end
module Set = Caml.Set.Make (T)
module Map = Caml.Map.Make (T)
(** Convert a fieldname to a string. *)
let to_string = function Java fname -> fname | Clang {field_name} -> field_name
(** Convert a fieldname to a simplified string with at most one-level path. *)
let to_simplified_string fn =
let s = to_string fn in
match String.rsplit2 s ~on:'.' with
| Some (s1, s2) -> (
match String.rsplit2 s1 ~on:'.' with Some (_, s4) -> s4 ^ "." ^ s2 | _ -> s )
| _ ->
s
let to_full_string fname =
match fname with
| Clang {class_name; field_name} ->
Name.to_string class_name ^ "::" ^ field_name
| _ ->
to_string fname
(** Convert a fieldname to a flat string without path. *)
let to_flat_string fn =
let s = to_string fn in
match String.rsplit2 s ~on:'.' with Some (_, s2) -> s2 | _ -> s
let pp f = function Java field_name | Clang {field_name} -> Format.pp_print_string f field_name
let clang_get_qual_class = function
| Clang {class_name} ->
Some (Name.qual_name class_name)
| _ ->
None
module Clang = struct
let from_class_name class_name field_name = Clang {class_name; field_name}
end
module Java = struct
let from_string n = Java n
let is_captured_parameter field_name =
match field_name with
| Java _ ->
String.is_prefix ~prefix:"val$" (to_flat_string field_name)
| Clang _ ->
false
let get_class fn =
let fn = to_string fn in
let ri = String.rindex_exn fn '.' in
String.slice fn 0 ri
let get_field fn =
let fn = to_string fn in
let ri = 1 + String.rindex_exn fn '.' in
String.slice fn ri 0
let is_outer_instance fn =
let fn = to_string fn in
let fn_len = String.length fn in
fn_len <> 0
&&
let this = ".this$" in
let last_char = fn.[fn_len - 1] in
(last_char >= '0' && last_char <= '9')
&& String.is_suffix fn ~suffix:(this ^ String.of_char last_char)
end
end
module Struct = struct
type field = Fieldname.t * T.t * Annot.Item.t [@@deriving compare]
type fields = field list
(** Type for a structured value. *)
type t =
{ fields: fields (** non-static fields *)
; statics: fields (** static fields *)
; supers: Name.t list (** superclasses *)
; methods: Procname.t list (** methods defined *)
; exported_objc_methods: Procname.t list (** methods in ObjC interface, subset of [methods] *)
; annots: Annot.Item.t (** annotations *) }
type lookup = Name.t -> t option
let pp_field pe f (field_name, typ, ann) =
F.fprintf f "@\n\t\t%a %a %a" (pp_full pe) typ Fieldname.pp field_name Annot.Item.pp ann
let pp pe name f {fields; supers; methods; exported_objc_methods; annots} =
if Config.debug_mode then
(* change false to true to print the details of struct *)
F.fprintf f
"%a @\n\
\tfields: {%a@\n\
\t}@\n\
\tsupers: {%a@\n\
\t}@\n\
\tmethods: {%a@\n\
\t}@\n\
\texported_obj_methods: {%a@\n\
\t}@\n\
\tannots: {%a@\n\
\t}"
Name.pp name
(Pp.seq (pp_field pe))
fields
(Pp.seq (fun f n -> F.fprintf f "@\n\t\t%a" Name.pp n))
supers
(Pp.seq (fun f m -> F.fprintf f "@\n\t\t%a" Procname.pp m))
methods
(Pp.seq (fun f m -> F.fprintf f "@\n\t\t%a" Procname.pp m))
exported_objc_methods Annot.Item.pp annots
else Name.pp f name
let internal_mk_struct ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots
() =
let default_ =
{ fields= []
; statics= []
; methods= []
; exported_objc_methods= []
; supers= []
; annots= Annot.Item.empty }
in
let mk_struct_ ?(default = default_) ?(fields = default.fields) ?(statics = default.statics)
?(methods = default.methods) ?(exported_objc_methods = default.exported_objc_methods)
?(supers = default.supers) ?(annots = default.annots) () =
{fields; statics; methods; exported_objc_methods; supers; annots}
in
mk_struct_ ?default ?fields ?statics ?methods ?exported_objc_methods ?supers ?annots ()
(** the element typ of the final extensible array in the given typ, if any *)
let rec get_extensible_array_element_typ ~lookup (typ : T.t) =
match typ.desc with
| Tarray {elt} ->
Some elt
| Tstruct name -> (
match lookup name with
| Some {fields} -> (
match List.last fields with
| Some (_, fld_typ, _) ->
get_extensible_array_element_typ ~lookup fld_typ
| None ->
None )
| None ->
None )
| _ ->
None
(** If a struct type with field f, return the type of f. If not, return the default *)
let fld_typ ~lookup ~default fn (typ : T.t) =
match typ.desc with
| Tstruct name -> (
match lookup name with
| Some {fields} ->
List.find ~f:(fun (f, _, _) -> Fieldname.equal f fn) fields
|> Option.value_map ~f:snd3 ~default
| None ->
default )
| _ ->
default
let get_field_type_and_annotation ~lookup fn (typ : T.t) =
match typ.desc with
| Tstruct name | Tptr ({desc= Tstruct name}, _) -> (
match lookup name with
| Some {fields; statics} ->
List.find_map
~f:(fun (f, t, a) ->
match Fieldname.equal f fn with true -> Some (t, a) | false -> None )
(fields @ statics)
| None ->
None )
| _ ->
None
end