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.
1135 lines
33 KiB
1135 lines
33 KiB
/*
|
|
* Copyright (c) 2009 - 2013 Monoidics ltd.
|
|
* Copyright (c) 2013 - present Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
open! IStd;
|
|
|
|
module Hashtbl = Caml.Hashtbl;
|
|
|
|
|
|
/** The Smallfoot Intermediate Language: Types */
|
|
module L = Logging;
|
|
|
|
module F = Format;
|
|
|
|
|
|
/** 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 ikind_to_string =
|
|
fun
|
|
| 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 ikind_is_char =
|
|
fun
|
|
| IChar
|
|
| ISChar
|
|
| IUChar => true
|
|
| _ => false;
|
|
|
|
let ikind_is_unsigned =
|
|
fun
|
|
| IUChar
|
|
| IUInt
|
|
| IUShort
|
|
| IULong
|
|
| IULongLong => true
|
|
| _ => false;
|
|
|
|
let int_of_int64_kind i ik => IntLit.of_int64_unsigned i (ikind_is_unsigned ik);
|
|
|
|
|
|
/** Kinds of floating-point numbers */
|
|
type fkind =
|
|
| FFloat /** [float] */
|
|
| FDouble /** [double] */
|
|
| FLongDouble /** [long double] */
|
|
[@@deriving compare];
|
|
|
|
let fkind_to_string =
|
|
fun
|
|
| 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 =
|
|
fun
|
|
| Pk_reference => "&"
|
|
| Pk_pointer => "*"
|
|
| Pk_objc_weak => "__weak *"
|
|
| Pk_objc_unsafe_unretained => "__unsafe_unretained *"
|
|
| Pk_objc_autoreleasing => "__autoreleasing *";
|
|
|
|
module T = {
|
|
type type_quals = {is_const: bool, is_restrict: bool, is_volatile: bool} [@@deriving compare];
|
|
|
|
/** types for sil (structured) expressions */
|
|
type t = {desc, quals: type_quals} [@@deriving compare]
|
|
and desc =
|
|
| Tint ikind /** integer type */
|
|
| Tfloat fkind /** float type */
|
|
| Tvoid /** void type */
|
|
| Tfun bool /** function type with noreturn attribute */
|
|
| Tptr t ptr_kind /** pointer type */
|
|
| Tstruct name /** structured value type name */
|
|
| TVar string /** type variable (ie. C++ template variables) */
|
|
| Tarray t (option IntLit.t) (option IntLit.t) /** array type with statically fixed length and stride */
|
|
[@@deriving compare]
|
|
and name =
|
|
| CStruct QualifiedCppName.t
|
|
| CUnion QualifiedCppName.t
|
|
| CppClass QualifiedCppName.t template_spec_info
|
|
| JavaClass Mangled.t
|
|
| ObjcClass QualifiedCppName.t
|
|
| ObjcProtocol QualifiedCppName.t
|
|
[@@deriving compare]
|
|
and template_spec_info =
|
|
| NoTemplate
|
|
| Template (list (option t))
|
|
[@@deriving compare];
|
|
let equal_desc = [%compare.equal : desc];
|
|
let equal_quals = [%compare.equal : type_quals];
|
|
let equal = [%compare.equal : t];
|
|
let hash = Hashtbl.hash;
|
|
};
|
|
|
|
include T;
|
|
|
|
let mk_type_quals ::default=? ::is_const=? ::is_restrict=? ::is_volatile=? () => {
|
|
let default_ = {is_const: false, is_restrict: false, is_volatile: false};
|
|
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
|
|
};
|
|
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 mk ::default=? ::quals=? desc :t => {
|
|
let default_ = {desc, quals: mk_type_quals ()};
|
|
let mk_aux ::default=default_ ::quals=default.quals desc => {desc, quals};
|
|
mk_aux ::?default ::?quals desc
|
|
};
|
|
|
|
let escape pe =>
|
|
if (Pp.equal_print_kind pe.Pp.kind Pp.HTML) {
|
|
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) {
|
|
F.fprintf f " const "
|
|
};
|
|
if (is_restrict quals) {
|
|
F.fprintf f " __restrict "
|
|
};
|
|
if (is_volatile quals) {
|
|
F.fprintf f " volatile "
|
|
}
|
|
};
|
|
let pp_desc f {desc} =>
|
|
switch desc {
|
|
| Tstruct tname => F.fprintf f "%a" (pp_name_c_syntax pe) tname
|
|
| TVar name => F.fprintf f "%s" name
|
|
| Tint ik => F.fprintf f "%s" (ikind_to_string ik)
|
|
| Tfloat fk => F.fprintf f "%s" (fkind_to_string fk)
|
|
| Tvoid => F.fprintf f "void"
|
|
| Tfun false => F.fprintf f "_fn_"
|
|
| Tfun true => F.fprintf 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 typ static_len static_stride =>
|
|
let pp_int_opt fmt => (
|
|
fun
|
|
| Some x => IntLit.pp fmt x
|
|
| None => F.fprintf fmt "_"
|
|
);
|
|
F.fprintf f "%a[%a*%a]" (pp_full pe) typ pp_int_opt static_len pp_int_opt static_stride
|
|
};
|
|
F.fprintf f "%a%a" pp_desc typ pp_quals typ
|
|
}
|
|
and pp_name_c_syntax pe f =>
|
|
fun
|
|
| CStruct name
|
|
| CUnion name
|
|
| ObjcClass name
|
|
| ObjcProtocol name => F.fprintf f "%a" QualifiedCppName.pp name
|
|
| CppClass name template_spec =>
|
|
F.fprintf f "%a%a" QualifiedCppName.pp name (pp_template_spec_info pe) template_spec
|
|
| JavaClass name => F.fprintf f "%a" Mangled.pp name
|
|
and pp_template_spec_info pe f =>
|
|
fun
|
|
| NoTemplate => ()
|
|
| Template args => {
|
|
let pp_arg_opt f => (
|
|
fun
|
|
| Some typ => F.fprintf f "%a" (pp_full pe) typ
|
|
| None => F.fprintf f "_"
|
|
);
|
|
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 {
|
|
pp_full pe f te
|
|
} else {
|
|
()
|
|
};
|
|
|
|
let to_string typ => {
|
|
let pp fmt => pp_full Pp.text fmt typ;
|
|
F.asprintf "%t" pp
|
|
};
|
|
|
|
module Name = {
|
|
type t = name [@@deriving compare];
|
|
let equal = [%compare.equal : t];
|
|
let qual_name =
|
|
fun
|
|
| 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;
|
|
QualifiedCppName.append_template_args_to_last name args::template_suffix
|
|
}
|
|
| JavaClass _ => QualifiedCppName.empty;
|
|
let unqualified_name =
|
|
fun
|
|
| CStruct name
|
|
| CUnion name
|
|
| ObjcClass name
|
|
| ObjcProtocol name => name
|
|
| CppClass name _ => name
|
|
| JavaClass _ => QualifiedCppName.empty;
|
|
let name n =>
|
|
switch n {
|
|
| CStruct _
|
|
| CUnion _
|
|
| CppClass _ _
|
|
| ObjcClass _
|
|
| ObjcProtocol _ => qual_name n |> QualifiedCppName.to_qual_string
|
|
| JavaClass name => Mangled.to_string name
|
|
};
|
|
let pp fmt tname => {
|
|
let prefix =
|
|
fun
|
|
| CStruct _ => "struct"
|
|
| CUnion _ => "union"
|
|
| CppClass _ _
|
|
| JavaClass _
|
|
| ObjcClass _ => "class"
|
|
| ObjcProtocol _ => "protocol";
|
|
F.fprintf fmt "%s %a" (prefix tname) (pp_name_c_syntax Pp.text) tname
|
|
};
|
|
let to_string = F.asprintf "%a" pp;
|
|
let is_class =
|
|
fun
|
|
| CppClass _ _
|
|
| JavaClass _
|
|
| ObjcClass _ => true
|
|
| _ => false;
|
|
let is_same_type t1 t2 =>
|
|
switch (t1, t2) {
|
|
| (CStruct _, CStruct _)
|
|
| (CUnion _, CUnion _)
|
|
| (CppClass _ _, CppClass _ _)
|
|
| (JavaClass _, JavaClass _)
|
|
| (ObjcClass _, ObjcClass _)
|
|
| (ObjcProtocol _, ObjcProtocol _) => true
|
|
| _ => false
|
|
};
|
|
module C = {
|
|
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;
|
|
};
|
|
module Java = {
|
|
let from_string name_str => JavaClass (Mangled.from_string name_str);
|
|
let from_package_class package_name class_name =>
|
|
if (String.equal package_name "") {
|
|
from_string class_name
|
|
} else {
|
|
from_string (package_name ^ "." ^ class_name)
|
|
};
|
|
let is_class =
|
|
fun
|
|
| JavaClass _ => true
|
|
| _ => false;
|
|
let java_lang_object = from_string "java.lang.Object";
|
|
let java_io_serializable = from_string "java.io.Serializable";
|
|
let java_lang_cloneable = from_string "java.lang.Cloneable";
|
|
};
|
|
module Cpp = {
|
|
let from_qual_name template_spec_info qual_name => CppClass qual_name template_spec_info;
|
|
let is_class =
|
|
fun
|
|
| CppClass _ => true
|
|
| _ => false;
|
|
};
|
|
module Objc = {
|
|
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 =
|
|
fun
|
|
| ObjcClass _ => true
|
|
| _ => false;
|
|
};
|
|
module Set =
|
|
Caml.Set.Make {
|
|
type nonrec t = t;
|
|
let compare = compare;
|
|
};
|
|
};
|
|
|
|
|
|
/** {2 Sets and maps of types} */
|
|
module Set = Caml.Set.Make T;
|
|
|
|
module Map = Caml.Map.Make T;
|
|
|
|
module Tbl = Hashtbl.Make T;
|
|
|
|
|
|
/** dump a type with all the details. */
|
|
let d_full (t: t) => L.add_print_action (L.PTtyp_full, Obj.repr t);
|
|
|
|
|
|
/** dump a list of types. */
|
|
let d_list (tl: list t) => L.add_print_action (L.PTtyp_list, Obj.repr tl);
|
|
|
|
let name typ =>
|
|
switch typ.desc {
|
|
| Tstruct name => Some name
|
|
| _ => None
|
|
};
|
|
|
|
let unsome s =>
|
|
fun
|
|
| 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 =>
|
|
switch typ.desc {
|
|
| 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 =>
|
|
switch typ.desc {
|
|
| Tarray t_el _ _ => t_el
|
|
| _ => unsome "array_elem" default_opt
|
|
};
|
|
|
|
let is_class_of_kind check_fun typ =>
|
|
switch typ.desc {
|
|
| 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_java_class = is_class_of_kind Name.Java.is_class;
|
|
|
|
let rec is_array_of_cpp_class typ =>
|
|
switch typ.desc {
|
|
| Tarray typ _ _ => is_array_of_cpp_class typ
|
|
| _ => is_cpp_class typ
|
|
};
|
|
|
|
let is_pointer_to_cpp_class typ =>
|
|
switch typ.desc {
|
|
| Tptr t _ => is_cpp_class t
|
|
| _ => false
|
|
};
|
|
|
|
let has_block_prefix s =>
|
|
switch (Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) s) {
|
|
| [_, _, ..._] => true
|
|
| _ => false
|
|
};
|
|
|
|
|
|
/** Check if type is a type for a block in objc */
|
|
let is_block_type typ => has_block_prefix (to_string typ);
|
|
|
|
|
|
/** Java types by name */
|
|
let rec java_from_string: string => t =
|
|
fun
|
|
| ""
|
|
| "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);
|
|
mk (Tptr (mk (Tarray (java_from_string stripped_typ) None None)) Pk_pointer)
|
|
}
|
|
| typ_str => mk (Tstruct (Name.Java.from_string typ_str));
|
|
|
|
type typ = t;
|
|
|
|
module Procname = {
|
|
/* e.g. ("", "int") for primitive types or ("java.io", "PrintWriter") for objects */
|
|
type java_type = (option string, string);
|
|
/* compare in inverse order */
|
|
let compare_java_type (p1, c1) (p2, c2) =>
|
|
[%compare : (string, option string)] (c1, p1) (c2, p2);
|
|
type method_kind =
|
|
| Non_Static /* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface */
|
|
| Static /* in Java, procedures called with invokestatic */
|
|
[@@deriving compare];
|
|
let equal_method_kind = [%compare.equal : method_kind];
|
|
|
|
/** Type of java procedure names. */
|
|
type java = {
|
|
method_name: string,
|
|
parameters: list java_type,
|
|
class_name: Name.t,
|
|
return_type: option java_type, /* option because constructors have no return type */
|
|
kind: method_kind
|
|
}
|
|
[@@deriving compare];
|
|
|
|
/** Type of c procedure names. */
|
|
type c = {
|
|
name: QualifiedCppName.t,
|
|
mangled: option string,
|
|
template_args: template_spec_info,
|
|
is_generic_model: bool
|
|
}
|
|
[@@deriving compare];
|
|
type objc_cpp_method_kind =
|
|
| CPPMethod (option string) /** with mangling */
|
|
| CPPConstructor (option string, bool) /** with mangling + is it constexpr? */
|
|
| ObjCClassMethod
|
|
| ObjCInstanceMethod
|
|
| ObjCInternalMethod
|
|
[@@deriving compare];
|
|
|
|
/** Type of Objective C and C++ procedure names: method signatures. */
|
|
type objc_cpp = {
|
|
method_name: string,
|
|
class_name: Name.t,
|
|
kind: objc_cpp_method_kind,
|
|
template_args: template_spec_info,
|
|
is_generic_model: bool
|
|
}
|
|
[@@deriving compare];
|
|
|
|
/** Type of Objective C block names. */
|
|
type block = string [@@deriving compare];
|
|
|
|
/** Type of procedure names. */
|
|
type t =
|
|
| Java java
|
|
| C c
|
|
| Linters_dummy_method
|
|
| Block block
|
|
| ObjC_Cpp objc_cpp
|
|
[@@deriving compare];
|
|
let equal = [%compare.equal : t];
|
|
|
|
/** 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 objc_method_kind_of_bool is_instance =>
|
|
if is_instance {ObjCInstanceMethod} else {ObjCClassMethod};
|
|
let empty_block = Block "";
|
|
let is_verbose v =>
|
|
switch v {
|
|
| Verbose => true
|
|
| _ => false
|
|
};
|
|
|
|
/** A type is a pair (package, type_name) that is translated in a string package.type_name */
|
|
let java_type_to_string_verbosity p verbosity =>
|
|
switch p {
|
|
| (None, typ) => typ
|
|
| (Some p, cls) =>
|
|
if (is_verbose verbosity) {
|
|
p ^ "." ^ cls
|
|
} else {
|
|
cls
|
|
}
|
|
};
|
|
let java_type_to_string p => java_type_to_string_verbosity p Verbose;
|
|
|
|
/** Given a list of types, it creates a unique string of types separated by commas */
|
|
let rec java_param_list_to_string inputList verbosity =>
|
|
switch inputList {
|
|
| [] => ""
|
|
| [head] => java_type_to_string_verbosity head verbosity
|
|
| [head, ...rest] =>
|
|
java_type_to_string_verbosity head verbosity ^ "," ^ java_param_list_to_string rest verbosity
|
|
};
|
|
|
|
/** It is the same as java_type_to_string, but Java return types are optional because of constructors without type */
|
|
let java_return_type_to_string j verbosity =>
|
|
switch j.return_type {
|
|
| None => ""
|
|
| Some typ => java_type_to_string_verbosity typ verbosity
|
|
};
|
|
|
|
/** Given a package.class_name string, it looks for the latest dot and split the string
|
|
in two (package, class_name) */
|
|
let split_classname package_classname =>
|
|
switch (String.rsplit2 package_classname on::'.') {
|
|
| Some (x, y) => (Some x, y)
|
|
| None => (None, package_classname)
|
|
};
|
|
let split_typename typename => split_classname (Name.name typename);
|
|
let c name mangled template_args ::is_generic_model => {
|
|
name,
|
|
mangled: Some mangled,
|
|
template_args,
|
|
is_generic_model
|
|
};
|
|
let from_string_c_fun (name: string) =>
|
|
C {
|
|
name: QualifiedCppName.of_qual_string name,
|
|
mangled: None,
|
|
template_args: NoTemplate,
|
|
is_generic_model: false
|
|
};
|
|
let java class_name return_type method_name parameters kind => {
|
|
class_name,
|
|
return_type,
|
|
method_name,
|
|
parameters,
|
|
kind
|
|
};
|
|
|
|
/** Create an objc procedure name from a class_name and method_name. */
|
|
let objc_cpp class_name method_name kind template_args ::is_generic_model => {
|
|
class_name,
|
|
method_name,
|
|
kind,
|
|
template_args,
|
|
is_generic_model
|
|
};
|
|
let get_default_objc_class_method objc_class => {
|
|
let objc_cpp =
|
|
objc_cpp objc_class "__find_class_" ObjCInternalMethod NoTemplate is_generic_model::false;
|
|
ObjC_Cpp objc_cpp
|
|
};
|
|
|
|
/** Create an objc procedure name from a class_name and method_name. */
|
|
let mangled_objc_block name => Block name;
|
|
let is_java =
|
|
fun
|
|
| Java _ => true
|
|
| _ => false;
|
|
let is_c_method =
|
|
fun
|
|
| ObjC_Cpp _ => true
|
|
| _ => false;
|
|
let is_constexpr =
|
|
fun
|
|
| ObjC_Cpp {kind: CPPConstructor (_, true)} => true
|
|
| _ => false;
|
|
|
|
/** Replace the class name component of a procedure name.
|
|
In case of Java, replace package and class name. */
|
|
let replace_class t (new_class: Name.t) =>
|
|
switch t {
|
|
| Java j => Java {...j, class_name: new_class}
|
|
| ObjC_Cpp osig => ObjC_Cpp {...osig, class_name: new_class}
|
|
| C _
|
|
| Block _
|
|
| Linters_dummy_method => t
|
|
};
|
|
|
|
/** Get the class name of a Objective-C/C++ procedure name. */
|
|
let objc_cpp_get_class_name objc_cpp => Name.name objc_cpp.class_name;
|
|
let objc_cpp_get_class_type_name objc_cpp => objc_cpp.class_name;
|
|
|
|
/** Return the package.classname of a java procname. */
|
|
let java_get_class_name (j: java) => Name.name j.class_name;
|
|
|
|
/** Return the package.classname as a typename of a java procname. */
|
|
let java_get_class_type_name (j: java) => j.class_name;
|
|
|
|
/** Return the class name of a java procedure name. */
|
|
let java_get_simple_class_name (j: java) => snd (split_classname (java_get_class_name j));
|
|
|
|
/** Return the package of a java procname. */
|
|
let java_get_package (j: java) => fst (split_classname (java_get_class_name j));
|
|
|
|
/** Return the method of a java procname. */
|
|
let java_get_method (j: java) => j.method_name;
|
|
|
|
/** Replace the method of a java procname. */
|
|
let java_replace_method (j: java) mname => {...j, method_name: mname};
|
|
|
|
/** Replace the return type of a java procname. */
|
|
let java_replace_return_type j ret_type => {...j, return_type: Some ret_type};
|
|
|
|
/** Replace the parameters of a java procname. */
|
|
let java_replace_parameters j parameters => {...j, parameters};
|
|
|
|
/** Return the method/function of a procname. */
|
|
let get_method =
|
|
fun
|
|
| ObjC_Cpp name => name.method_name
|
|
| C {name} => QualifiedCppName.to_qual_string name
|
|
| Block name => name
|
|
| Java j => j.method_name
|
|
| Linters_dummy_method => "Linters_dummy_method";
|
|
|
|
/** Return the language of the procedure. */
|
|
let get_language =
|
|
fun
|
|
| ObjC_Cpp _ => Config.Clang
|
|
| C _ => Config.Clang
|
|
| Block _ => Config.Clang
|
|
| Linters_dummy_method => Config.Clang
|
|
| Java _ => Config.Java;
|
|
|
|
/** Return the return type of a java procname. */
|
|
let java_get_return_type (j: java) => java_return_type_to_string j Verbose;
|
|
|
|
/** Return the parameters of a java procname. */
|
|
let java_get_parameters j => j.parameters;
|
|
|
|
/** Return the parameters of a java procname as strings. */
|
|
let java_get_parameters_as_strings j =>
|
|
List.map f::(fun param => java_type_to_string param) j.parameters;
|
|
|
|
/** Return true if the java procedure is static */
|
|
let java_is_static =
|
|
fun
|
|
| Java j => equal_method_kind j.kind Static
|
|
| _ => false;
|
|
let java_is_lambda =
|
|
fun
|
|
| Java j => String.is_prefix prefix::"lambda$" j.method_name
|
|
| _ => false;
|
|
let java_is_generated =
|
|
fun
|
|
| Java j => String.is_prefix prefix::"$" j.method_name
|
|
| _ => false;
|
|
|
|
/** Prints a string of a java procname with the given level of verbosity */
|
|
let java_to_string ::withclass=false (j: java) verbosity =>
|
|
switch verbosity {
|
|
| 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 return_type = java_return_type_to_string j verbosity;
|
|
let params = java_param_list_to_string j.parameters verbosity;
|
|
let class_name = java_type_to_string_verbosity (split_typename j.class_name) verbosity;
|
|
let separator =
|
|
switch (j.return_type, verbosity) {
|
|
| (None, _) => ""
|
|
| (Some _, Verbose) => ":"
|
|
| _ => " "
|
|
};
|
|
let output = class_name ^ "." ^ j.method_name ^ "(" ^ params ^ ")";
|
|
if (equal_detail_level verbosity Verbose) {
|
|
output ^ separator ^ return_type
|
|
} else {
|
|
return_type ^ separator ^ output
|
|
}
|
|
| Simple =>
|
|
/* methodname(...) or without ... if there are no parameters */
|
|
let cls_prefix =
|
|
if withclass {
|
|
java_type_to_string_verbosity (split_typename j.class_name) verbosity ^ "."
|
|
} else {
|
|
""
|
|
};
|
|
let params =
|
|
switch j.parameters {
|
|
| [] => ""
|
|
| _ => "..."
|
|
};
|
|
let method_name =
|
|
if (String.equal j.method_name "<init>") {
|
|
java_get_simple_class_name j
|
|
} else {
|
|
cls_prefix ^ j.method_name
|
|
};
|
|
method_name ^ "(" ^ params ^ ")"
|
|
};
|
|
|
|
/** Check if the class name is for an anonymous inner class. */
|
|
let is_anonymous_inner_class_name class_name => {
|
|
let class_name_no_package = snd (split_typename class_name);
|
|
switch (String.rsplit2 class_name_no_package on::'$') {
|
|
| Some (_, s) =>
|
|
let is_int =
|
|
try {
|
|
ignore (int_of_string (String.strip s));
|
|
true
|
|
} {
|
|
| Failure _ => false
|
|
};
|
|
is_int
|
|
| None => false
|
|
}
|
|
};
|
|
|
|
/** Check if the procedure belongs to an anonymous inner class. */
|
|
let java_is_anonymous_inner_class =
|
|
fun
|
|
| Java j => is_anonymous_inner_class_name j.class_name
|
|
| _ => false;
|
|
|
|
/** Check if the last parameter is a hidden inner class, and remove it if present.
|
|
This is used in private constructors, where a proxy constructor is generated
|
|
with an extra parameter and calls the normal constructor. */
|
|
let java_remove_hidden_inner_class_parameter =
|
|
fun
|
|
| Java js =>
|
|
switch (List.rev js.parameters) {
|
|
| [(_, s), ...par'] =>
|
|
if (is_anonymous_inner_class_name (Name.Java.from_string s)) {
|
|
Some (Java {...js, parameters: List.rev par'})
|
|
} else {
|
|
None
|
|
}
|
|
| [] => None
|
|
}
|
|
| _ => None;
|
|
|
|
/** Check if the procedure name is an anonymous inner class constructor. */
|
|
let java_is_anonymous_inner_class_constructor =
|
|
fun
|
|
| Java js => is_anonymous_inner_class_name js.class_name
|
|
| _ => false;
|
|
|
|
/** Check if the procedure name is an acess method (e.g. access$100 used to
|
|
access private members from a nested class. */
|
|
let java_is_access_method =
|
|
fun
|
|
| Java js =>
|
|
switch (String.rsplit2 js.method_name on::'$') {
|
|
| Some ("access", s) =>
|
|
let is_int =
|
|
try {
|
|
ignore (int_of_string s);
|
|
true
|
|
} {
|
|
| Failure _ => false
|
|
};
|
|
is_int
|
|
| _ => false
|
|
}
|
|
| _ => false;
|
|
|
|
/** Check if the procedure name is of an auto-generated method containing '$'. */
|
|
let java_is_autogen_method =
|
|
fun
|
|
| Java js => String.contains js.method_name '$'
|
|
| _ => false;
|
|
|
|
/** Check if the proc name has the type of a java vararg.
|
|
Note: currently only checks that the last argument has type Object[]. */
|
|
let java_is_vararg =
|
|
fun
|
|
| Java js =>
|
|
switch (List.rev js.parameters) {
|
|
| [(_, "java.lang.Object[]"), ..._] => true
|
|
| _ => false
|
|
}
|
|
| _ => false;
|
|
let is_objc_constructor method_name =>
|
|
String.equal method_name "new" || String.is_prefix prefix::"init" method_name;
|
|
let is_objc_kind =
|
|
fun
|
|
| ObjCClassMethod
|
|
| ObjCInstanceMethod
|
|
| ObjCInternalMethod => true
|
|
| _ => false;
|
|
|
|
/** [is_constructor pname] returns true if [pname] is a constructor */
|
|
let is_constructor =
|
|
fun
|
|
| Java js => String.equal js.method_name "<init>"
|
|
| ObjC_Cpp {kind: CPPConstructor _} => true
|
|
| ObjC_Cpp {kind, method_name} when is_objc_kind kind => is_objc_constructor method_name
|
|
| _ => false;
|
|
let is_objc_dealloc method_name => String.equal method_name "dealloc";
|
|
|
|
/** [is_dealloc pname] returns true if [pname] is the dealloc method in Objective-C
|
|
TODO: add case for C++ */
|
|
let is_destructor =
|
|
fun
|
|
| ObjC_Cpp name => is_objc_dealloc name.method_name
|
|
| _ => false;
|
|
let java_is_close =
|
|
fun
|
|
| Java js => String.equal js.method_name "close"
|
|
| _ => false;
|
|
|
|
/** [is_class_initializer pname] returns true if [pname] is a class initializer */
|
|
let is_class_initializer =
|
|
fun
|
|
| Java js => String.equal js.method_name "<clinit>"
|
|
| _ => false;
|
|
|
|
/** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc */
|
|
let is_infer_undefined pn =>
|
|
switch pn {
|
|
| Java j =>
|
|
let regexp = Str.regexp "com.facebook.infer.builtins.InferUndefined";
|
|
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 =
|
|
fun
|
|
| C {name}
|
|
when
|
|
String.is_prefix
|
|
prefix::Config.clang_initializer_prefix (QualifiedCppName.to_qual_string name) => {
|
|
let name_str = QualifiedCppName.to_qual_string name;
|
|
let prefix_len = String.length Config.clang_initializer_prefix;
|
|
Some (String.sub name_str pos::prefix_len len::(String.length name_str - prefix_len))
|
|
}
|
|
| _ => None;
|
|
|
|
/** to_string for C_function type */
|
|
let to_readable_string (c1, c2) verbose => {
|
|
let plain = QualifiedCppName.to_qual_string c1;
|
|
if verbose {
|
|
switch c2 {
|
|
| None => plain
|
|
| Some s => plain ^ "{" ^ s ^ "}"
|
|
}
|
|
} else {
|
|
plain
|
|
}
|
|
};
|
|
let c_method_kind_verbose_str kind =>
|
|
switch kind {
|
|
| CPPMethod m =>
|
|
"(" ^
|
|
(
|
|
switch m {
|
|
| None => ""
|
|
| Some s => s
|
|
}
|
|
) ^ ")"
|
|
| CPPConstructor (m, is_constexpr) =>
|
|
"{" ^
|
|
(
|
|
switch m {
|
|
| None => ""
|
|
| Some s => s
|
|
}
|
|
) ^
|
|
(if is_constexpr {"|constexpr"} else {""}) ^ "}"
|
|
| ObjCClassMethod => "class"
|
|
| ObjCInstanceMethod => "instance"
|
|
| ObjCInternalMethod => "internal"
|
|
};
|
|
let c_method_to_string osig detail_level =>
|
|
switch detail_level {
|
|
| Simple => osig.method_name
|
|
| Non_verbose => Name.name osig.class_name ^ "_" ^ osig.method_name
|
|
| Verbose =>
|
|
let m_str = c_method_kind_verbose_str osig.kind;
|
|
Name.name osig.class_name ^ "_" ^ osig.method_name ^ m_str
|
|
};
|
|
|
|
/** Very verbose representation of an existing Procname.t */
|
|
let to_unique_id pn =>
|
|
switch pn {
|
|
| Java j => java_to_string j Verbose
|
|
| C {name, mangled} => to_readable_string (name, mangled) true
|
|
| ObjC_Cpp osig => c_method_to_string osig Verbose
|
|
| Block name => name
|
|
| Linters_dummy_method => "Linters_dummy_method"
|
|
};
|
|
|
|
/** Convert a proc name to a string for the user to see */
|
|
let to_string p =>
|
|
switch p {
|
|
| Java j => java_to_string j Non_verbose
|
|
| C {name, mangled} => to_readable_string (name, mangled) false
|
|
| ObjC_Cpp osig => c_method_to_string osig Non_verbose
|
|
| Block name => name
|
|
| Linters_dummy_method => to_unique_id p
|
|
};
|
|
|
|
/** Convenient representation of a procname for external tools (e.g. eclipse plugin) */
|
|
let to_simplified_string ::withclass=false p =>
|
|
switch p {
|
|
| Java j => java_to_string ::withclass j Simple
|
|
| C {name, mangled} => to_readable_string (name, mangled) false ^ "()"
|
|
| ObjC_Cpp osig => c_method_to_string osig Simple
|
|
| Block _ => "block"
|
|
| Linters_dummy_method => to_unique_id p
|
|
};
|
|
|
|
/** Pretty print a proc name */
|
|
let pp f pn => F.fprintf f "%s" (to_string pn);
|
|
|
|
/** hash function for procname */
|
|
let hash_pname = Hashtbl.hash;
|
|
module Hash =
|
|
Hashtbl.Make {
|
|
type nonrec t = t;
|
|
let equal = equal;
|
|
let hash = hash_pname;
|
|
};
|
|
module Map =
|
|
PrettyPrintable.MakePPMap {
|
|
type nonrec t = t;
|
|
let compare = compare;
|
|
let pp = pp;
|
|
};
|
|
module Set =
|
|
PrettyPrintable.MakePPSet {
|
|
type nonrec t = t;
|
|
let compare = compare;
|
|
let pp = pp;
|
|
};
|
|
|
|
/** Pretty print a set of proc names */
|
|
let pp_set fmt set => Set.iter (fun pname => F.fprintf fmt "%a " pp pname) set;
|
|
let objc_cpp_get_class_qualifiers objc_cpp => Name.qual_name objc_cpp.class_name;
|
|
let get_qualifiers pname =>
|
|
switch pname {
|
|
| 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_concrete_filename pname => {
|
|
/* filenames for clang procs are REVERSED qualifiers with '#' as separator */
|
|
let get_qual_name_str pname =>
|
|
get_qualifiers pname |> QualifiedCppName.to_rev_list |> String.concat sep::"#";
|
|
let proc_id =
|
|
switch pname {
|
|
| C {mangled} =>
|
|
[get_qual_name_str pname, ...Option.to_list mangled] |> String.concat sep::"#"
|
|
| ObjC_Cpp objc_cpp =>
|
|
get_qual_name_str pname ^ "#" ^ c_method_kind_verbose_str objc_cpp.kind
|
|
| _ => to_unique_id pname
|
|
};
|
|
Escape.escape_filename @@ DB.append_crc_cutoff proc_id
|
|
};
|
|
let to_generic_filename pname => {
|
|
let proc_id =
|
|
get_qualifiers pname |> QualifiedCppName.strip_template_args |> QualifiedCppName.to_rev_list |>
|
|
String.concat sep::"#";
|
|
Escape.escape_filename @@ DB.append_crc_cutoff proc_id
|
|
};
|
|
let to_filename pname =>
|
|
switch pname {
|
|
| C {is_generic_model}
|
|
| ObjC_Cpp {is_generic_model} when Bool.equal is_generic_model true =>
|
|
to_generic_filename pname
|
|
| _ => to_concrete_filename pname
|
|
};
|
|
};
|
|
|
|
|
|
/** Return the return type of [pname_java]. */
|
|
let java_proc_return_typ pname_java :t => {
|
|
let typ = java_from_string (Procname.java_get_return_type pname_java);
|
|
switch typ.desc {
|
|
| Tstruct _ => mk (Tptr typ Pk_pointer)
|
|
| _ => typ
|
|
}
|
|
};
|
|
|
|
module Struct = {
|
|
type field = (Fieldname.t, T.t, Annot.Item.t) [@@deriving compare];
|
|
type fields = list field;
|
|
|
|
/** Type for a structured value. */
|
|
type t = {
|
|
fields, /** non-static fields */
|
|
statics: fields, /** static fields */
|
|
supers: list Name.t, /** superclasses */
|
|
methods: list Procname.t, /** methods defined */
|
|
annots: Annot.Item.t /** annotations */
|
|
};
|
|
type lookup = Name.t => option t;
|
|
let pp pe name f {fields, supers, methods, annots} =>
|
|
if Config.debug_mode {
|
|
/* 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\tannots: {%a@\n\t}"
|
|
Name.pp
|
|
name
|
|
(
|
|
Pp.seq (
|
|
fun f (fld, t, a) =>
|
|
F.fprintf f "@\n\t\t%a %a %a" (pp_full pe) t Fieldname.pp fld Annot.Item.pp a
|
|
)
|
|
)
|
|
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
|
|
Annot.Item.pp
|
|
annots
|
|
} else {
|
|
F.fprintf f "%a" Name.pp name
|
|
};
|
|
let internal_mk_struct ::default=? ::fields=? ::statics=? ::methods=? ::supers=? ::annots=? () => {
|
|
let default_ = {fields: [], statics: [], methods: [], supers: [], annots: Annot.Item.empty};
|
|
let mk_struct_
|
|
::default=default_
|
|
::fields=default.fields
|
|
::statics=default.statics
|
|
::methods=default.methods
|
|
::supers=default.supers
|
|
::annots=default.annots
|
|
() => {
|
|
fields,
|
|
statics,
|
|
methods,
|
|
supers,
|
|
annots
|
|
};
|
|
mk_struct_ ::?default ::?fields ::?statics ::?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) =>
|
|
switch typ.desc {
|
|
| Tarray typ _ _ => Some typ
|
|
| Tstruct name =>
|
|
switch (lookup name) {
|
|
| Some {fields} =>
|
|
switch (List.last fields) {
|
|
| 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) =>
|
|
switch typ.desc {
|
|
| Tstruct name =>
|
|
switch (lookup name) {
|
|
| 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) =>
|
|
switch typ.desc {
|
|
| Tstruct name
|
|
| Tptr {desc: Tstruct name} _ =>
|
|
switch (lookup name) {
|
|
| Some {fields, statics} =>
|
|
List.find_map
|
|
f::(fun (f, t, a) => Fieldname.equal f fn ? Some (t, a) : None) (fields @ statics)
|
|
| None => None
|
|
}
|
|
| _ => None
|
|
};
|
|
let objc_ref_counter_annot = [({Annot.class_name: "ref_counter", parameters: []}, false)];
|
|
|
|
/** Field used for objective-c reference counting */
|
|
let objc_ref_counter_field = (Fieldname.hidden, mk (T.Tint IInt), objc_ref_counter_annot);
|
|
let is_objc_ref_counter_field (fld, _, a) =>
|
|
Fieldname.is_hidden fld && Annot.Item.equal a objc_ref_counter_annot;
|
|
};
|