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.
580 lines
15 KiB
580 lines
15 KiB
/*
|
|
* vim: set ft=rust:
|
|
* vim: set ft=reason:
|
|
*
|
|
* 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! Utils;
|
|
|
|
|
|
/** Module for Procedure Names */
|
|
let module L = Logging;
|
|
|
|
let module F = Format;
|
|
|
|
type java_type = (option string, string); /* e.g. ("", "int") for primitive types or ("java.io", "PrintWriter") for objects */
|
|
|
|
type method_kind =
|
|
| Static /* in Java, procedures called with invokestatic */
|
|
| Non_Static /* in Java, procedures called with invokevirtual, invokespecial, and invokeinterface */;
|
|
|
|
|
|
/** Type of java procedure names. */
|
|
type java = {
|
|
class_name: java_type,
|
|
return_type: option java_type, /* option because constructors have no return type */
|
|
method_name: string,
|
|
parameters: list java_type,
|
|
kind: method_kind
|
|
};
|
|
|
|
|
|
/** Type of c procedure names. */
|
|
type c = (string, option string);
|
|
|
|
|
|
/** Type of Objective C and C++ procedure names: method signatures. */
|
|
type objc_cpp = {class_name: string, method_name: string, mangled: option string};
|
|
|
|
|
|
/** Type of Objective C block names. */
|
|
type block = string;
|
|
|
|
|
|
/** Type of procedure names. */
|
|
type t = | Java of java | C of c | ObjC_Cpp of objc_cpp | Block of block;
|
|
|
|
|
|
/** Level of verbosity of some to_string functions. */
|
|
type detail_level = | Verbose | Non_verbose | Simple;
|
|
|
|
type objc_method_kind = | Instance_objc_method | Class_objc_method;
|
|
|
|
let mangled_of_objc_method_kind kind =>
|
|
switch kind {
|
|
| Instance_objc_method => Some "instance"
|
|
| Class_objc_method => Some "class"
|
|
};
|
|
|
|
let objc_method_kind_of_bool is_instance =>
|
|
if is_instance {
|
|
Instance_objc_method
|
|
} else {
|
|
Class_objc_method
|
|
};
|
|
|
|
let empty_block = Block "";
|
|
|
|
let is_verbose v =>
|
|
switch v {
|
|
| Verbose => true
|
|
| _ => false
|
|
};
|
|
|
|
type proc_name = t;
|
|
|
|
let mangled_compare so1 so2 =>
|
|
switch (so1, so2) {
|
|
| (None, None) => 0
|
|
| (None, Some _) => (-1)
|
|
| (Some _, None) => 1
|
|
| (Some s1, Some s2) => string_compare s1 s2
|
|
};
|
|
|
|
let method_kind_compare k0 k1 =>
|
|
switch (k0, k1) {
|
|
| _ when k0 == k1 => 0
|
|
| (Static, _) => 1
|
|
| (Non_Static, _) => (-1)
|
|
};
|
|
|
|
|
|
/** 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
|
|
};
|
|
|
|
let java_type_compare (p1, c1) (p2, c2) => string_compare c1 c2 |> next mangled_compare p1 p2;
|
|
|
|
let rec java_type_list_compare jt1 jt2 =>
|
|
switch (jt1, jt2) {
|
|
| ([], []) => 0
|
|
| ([], _) => (-1)
|
|
| (_, []) => 1
|
|
| ([x1, ...rest1], [x2, ...rest2]) =>
|
|
java_type_compare x1 x2 |> next java_type_list_compare rest1 rest2
|
|
};
|
|
|
|
let java_return_type_compare jr1 jr2 =>
|
|
switch (jr1, jr2) {
|
|
| (None, None) => 0
|
|
| (None, Some _) => (-1)
|
|
| (Some _, None) => 1
|
|
| (Some jt1, Some jt2) => java_type_compare jt1 jt2
|
|
};
|
|
|
|
|
|
/** Compare java procedure names. */
|
|
let java_compare (j1: java) (j2: java) =>
|
|
string_compare j1.method_name j2.method_name |>
|
|
next java_type_list_compare j1.parameters j2.parameters |>
|
|
next java_type_compare j1.class_name j2.class_name |>
|
|
next java_return_type_compare j1.return_type j2.return_type |>
|
|
next method_kind_compare j1.kind j2.kind;
|
|
|
|
let c_function_mangled_compare mangled1 mangled2 =>
|
|
switch (mangled1, mangled2) {
|
|
| (Some _, None) => 1
|
|
| (None, Some _) => (-1)
|
|
| (None, None) => 0
|
|
| (Some mangled1, Some mangled2) => string_compare mangled1 mangled2
|
|
};
|
|
|
|
|
|
/** Compare c_method signatures. */
|
|
let c_meth_sig_compare osig1 osig2 =>
|
|
string_compare osig1.method_name osig2.method_name |>
|
|
next string_compare osig1.class_name osig2.class_name |>
|
|
next c_function_mangled_compare osig1.mangled osig2.mangled;
|
|
|
|
|
|
/** 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 => string_split_character package_classname '.';
|
|
|
|
let from_string_c_fun (s: string) => C (s, None);
|
|
|
|
let c (plain: string) (mangled: string) => (plain, Some mangled);
|
|
|
|
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 mangled => {class_name, method_name, mangled};
|
|
|
|
let get_default_objc_class_method objc_class => {
|
|
let objc_cpp = objc_cpp objc_class "__find_class_" (Some "internal");
|
|
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;
|
|
|
|
|
|
/** Replace the class name component of a procedure name.
|
|
In case of Java, replace package and class name. */
|
|
let replace_class t new_class =>
|
|
switch t {
|
|
| Java j => Java {...j, class_name: split_classname new_class}
|
|
| ObjC_Cpp osig => ObjC_Cpp {...osig, class_name: new_class}
|
|
| C _
|
|
| Block _ => t
|
|
};
|
|
|
|
|
|
/** Get the class name of a Objective-C/C++ procedure name. */
|
|
let objc_cpp_get_class_name objc_cpp => objc_cpp.class_name;
|
|
|
|
|
|
/** Return the package.classname of a java procname. */
|
|
let java_get_class_name (j: java) => java_type_to_string j.class_name;
|
|
|
|
|
|
/** Return the class name of a java procedure name. */
|
|
let java_get_simple_class_name (j: java) => snd j.class_name;
|
|
|
|
|
|
/** Return the package of a java procname. */
|
|
let java_get_package (j: java) => fst j.class_name;
|
|
|
|
|
|
/** 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, _) => name
|
|
| Block name => name
|
|
| Java j => j.method_name;
|
|
|
|
|
|
/** Return the language of the procedure. */
|
|
let get_language =
|
|
fun
|
|
| ObjC_Cpp _ => Config.Clang
|
|
| C _ => Config.Clang
|
|
| Block _ => 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 =>
|
|
IList.map (fun param => java_type_to_string param) j.parameters;
|
|
|
|
|
|
/** Return true if the java procedure is static */
|
|
let java_is_static =
|
|
fun
|
|
| Java j => j.kind == Static
|
|
| _ => false;
|
|
|
|
|
|
/** Prints a string of a java procname with the given level of verbosity */
|
|
let java_to_string withclass::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 j.class_name verbosity;
|
|
let separator =
|
|
switch (j.return_type, verbosity) {
|
|
| (None, _) => ""
|
|
| (Some _, Verbose) => ":"
|
|
| _ => " "
|
|
};
|
|
let output = class_name ^ "." ^ j.method_name ^ "(" ^ params ^ ")";
|
|
if (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 j.class_name verbosity ^ "."
|
|
} else {
|
|
""
|
|
};
|
|
let params =
|
|
switch j.parameters {
|
|
| [] => ""
|
|
| _ => "..."
|
|
};
|
|
let method_name =
|
|
if (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 =>
|
|
switch (string_split_character class_name '$') {
|
|
| (Some _, s) =>
|
|
let is_int =
|
|
try {
|
|
ignore (int_of_string (String.trim 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 (snd 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 (IList.rev js.parameters) {
|
|
| [(_, s), ...par'] =>
|
|
if (is_anonymous_inner_class_name s) {
|
|
Some (Java {...js, parameters: IList.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 => {
|
|
let (_, name) = js.class_name;
|
|
is_anonymous_inner_class_name 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_split_character js.method_name '$') {
|
|
| (Some "access", s) =>
|
|
let is_int =
|
|
try {
|
|
ignore (int_of_string s);
|
|
true
|
|
} {
|
|
| Failure _ => false
|
|
};
|
|
is_int
|
|
| _ => false
|
|
}
|
|
| _ => 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 (IList.rev js.parameters) {
|
|
| [(_, "java.lang.Object[]"), ..._] => true
|
|
| _ => false
|
|
}
|
|
| _ => false;
|
|
|
|
|
|
/** [is_constructor pname] returns true if [pname] is a constructor */
|
|
let is_constructor =
|
|
fun
|
|
| Java js => js.method_name == "<init>"
|
|
| ObjC_Cpp name => name.method_name == "new" || string_is_prefix "init" name.method_name
|
|
| _ => false;
|
|
|
|
|
|
/** [is_objc_dealloc pname] returns true if [pname] is the dealloc method in Objective-C */
|
|
let is_objc_dealloc =
|
|
fun
|
|
| ObjC_Cpp name => name.method_name == "dealloc"
|
|
| _ => false;
|
|
|
|
let java_is_close =
|
|
fun
|
|
| Java js => js.method_name == "close"
|
|
| _ => false;
|
|
|
|
|
|
/** [is_class_initializer pname] returns true if [pname] is a class initializer */
|
|
let is_class_initializer =
|
|
fun
|
|
| Java js => 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.models.InferUndefined";
|
|
Str.string_match regexp (java_get_class_name j) 0
|
|
| _ =>
|
|
/* TODO: add cases for obj-c, c, c++ */
|
|
false
|
|
};
|
|
|
|
|
|
/** to_string for C_function type */
|
|
let to_readable_string (c1, c2) verbose => {
|
|
let plain = c1;
|
|
if verbose {
|
|
switch c2 {
|
|
| None => plain
|
|
| Some s => plain ^ "{" ^ s ^ "}"
|
|
}
|
|
} else {
|
|
plain
|
|
}
|
|
};
|
|
|
|
let c_method_to_string osig detail_level =>
|
|
switch detail_level {
|
|
| Simple => osig.method_name
|
|
| Non_verbose => osig.class_name ^ "_" ^ osig.method_name
|
|
| Verbose =>
|
|
let m_str =
|
|
switch osig.mangled {
|
|
| None => ""
|
|
| Some s => "{" ^ s ^ "}"
|
|
};
|
|
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 (c1, c2) => to_readable_string (c1, c2) true
|
|
| ObjC_Cpp osig => c_method_to_string osig Verbose
|
|
| Block name => name
|
|
};
|
|
|
|
|
|
/** 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 (c1, c2) => to_readable_string (c1, c2) false
|
|
| ObjC_Cpp osig => c_method_to_string osig Non_verbose
|
|
| Block name => name
|
|
};
|
|
|
|
|
|
/** Convenient representation of a procname for external tools (e.g. eclipse plugin) */
|
|
let to_simplified_string withclass::withclass=false p =>
|
|
switch p {
|
|
| Java j => java_to_string withclass::withclass j Simple
|
|
| C (c1, c2) => to_readable_string (c1, c2) false ^ "()"
|
|
| ObjC_Cpp osig => c_method_to_string osig Simple
|
|
| Block _ => "block"
|
|
};
|
|
|
|
|
|
/** Convert a proc name to a filename */
|
|
let to_filename proc_name =>
|
|
Escape.escape_filename @@ string_append_crc_cutoff @@ to_unique_id proc_name;
|
|
|
|
|
|
/** Pretty print a proc name */
|
|
let pp f pn => F.fprintf f "%s" (to_string pn);
|
|
|
|
|
|
/** Compare function for Procname.t types.
|
|
These rules create an ordered set of procnames grouped with the following
|
|
priority (lowest to highest): */
|
|
let compare pn1 pn2 =>
|
|
switch (pn1, pn2) {
|
|
| (Java j1, Java j2) => java_compare j1 j2
|
|
| (Java _, _) => (-1)
|
|
| (_, Java _) => 1
|
|
| (
|
|
C (c1, c2), /* Compare C_function types */
|
|
C (c3, c4)
|
|
) =>
|
|
string_compare c1 c3 |> next mangled_compare c2 c4
|
|
| (C _, _) => (-1)
|
|
| (_, C _) => 1
|
|
| (
|
|
Block s1, /* Compare ObjC_block types */
|
|
Block s2
|
|
) =>
|
|
string_compare s1 s2
|
|
| (Block _, _) => (-1)
|
|
| (_, Block _) => 1
|
|
| (ObjC_Cpp osig1, ObjC_Cpp osig2) => c_meth_sig_compare osig1 osig2
|
|
};
|
|
|
|
let equal pn1 pn2 => compare pn1 pn2 == 0;
|
|
|
|
|
|
/** hash function for procname */
|
|
let hash_pname = Hashtbl.hash;
|
|
|
|
let module Hash = Hashtbl.Make {
|
|
type t = proc_name;
|
|
let equal = equal;
|
|
let hash = hash_pname;
|
|
};
|
|
|
|
let module Map = Map.Make {
|
|
type t = proc_name;
|
|
let compare = compare;
|
|
};
|
|
|
|
let module Set = Set.Make {
|
|
type t = proc_name;
|
|
let compare = compare;
|
|
};
|
|
|
|
|
|
/** Pretty print a set of proc names */
|
|
let pp_set fmt set => Set.iter (fun pname => F.fprintf fmt "%a " pp pname) set;
|