[Java] New JavaProfilerSamples module to create Procnames from sampled Java methods

Reviewed By: mbouaziz

Differential Revision: D7584841

fbshipit-source-id: 1d43040
master
Martino Luca 7 years ago committed by Facebook Github Bot
parent 9c8342e956
commit 27941a11b6

@ -692,7 +692,11 @@ module Procname = struct
let is_close {method_name} = String.equal method_name "close"
let is_class_initializer {method_name} = String.equal method_name "<clinit>"
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 is_anonymous_inner_class_constructor {class_name} =
Name.Java.is_anonymous_inner_class_name class_name
@ -949,7 +953,7 @@ module Procname = struct
(** [is_constructor pname] returns true if [pname] is a constructor *)
let is_constructor = function
| Java js ->
String.equal js.method_name "<init>"
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 ->

@ -165,6 +165,10 @@ module Name : sig
val java_lang_object : t
val java_lang_string : t
val package : t -> string option
val type_name : t -> string
end
val from_string : string -> t
@ -291,6 +295,12 @@ module Procname : sig
type java_type = Name.Java.Split.t
val constructor_method_name : string
val class_initializer_method_name : string
val compare_java_type : java_type -> java_type -> int
val make : Name.t -> java_type option -> string -> java_type list -> kind -> t
(** Create a Java procedure name from its
class_name method_name args_type_name return_type_name method_kind. *)

@ -0,0 +1,308 @@
(*
* Copyright (c) 2018 - 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 L = Logging
module JNI = struct
(* https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html *)
type t =
| Boolean
| Byte
| Char
| Short
| Int
| Long
| Float
| Double
| Void
(* FullyQualifiedClass is split between (package, class) *)
| FullyQualifiedClass of (string * string)
| Array of t
| Method of (t list * t)
[@@deriving compare]
let equal = [%compare.equal : t]
type non_terminal_symbol = SymArray | SymMethodOpen | SymMethodClose | SymMethod of t list
type symbol = Terminal of t | NonTerminal of non_terminal_symbol
let to_fully_qualified_class str =
let open String in
rstrip ~drop:(Char.equal ';') str |> tr ~target:'/' ~replacement:'.' |> rsplit2_exn ~on:'.'
let rec pp fmt jni =
match jni with
| Boolean ->
Format.fprintf fmt "Z"
| Byte ->
Format.fprintf fmt "B"
| Char ->
Format.fprintf fmt "C"
| Short ->
Format.fprintf fmt "S"
| Int ->
Format.fprintf fmt "I"
| Long ->
Format.fprintf fmt "J"
| Float ->
Format.fprintf fmt "F"
| Double ->
Format.fprintf fmt "D"
| Void ->
Format.fprintf fmt "V"
| FullyQualifiedClass (pkg, cl) ->
let pkg' = String.tr ~target:'.' ~replacement:'/' pkg in
Format.fprintf fmt "L%s/%s;" pkg' cl
| Array t ->
Format.fprintf fmt "[%a" pp t
| Method (args, ret_typ) ->
Format.fprintf fmt "(%a)%a" (Pp.seq ~sep:"" pp) args pp ret_typ
let rec to_java_type jni =
let make = Typ.Name.Java.Split.make in
match jni with
| Boolean ->
make "bool"
| Byte ->
make "byte"
| Char ->
make "char"
| Short ->
make "short"
| Int ->
make "int"
| Long ->
make "long"
| Float ->
make "float"
| Double ->
make "double"
| Void ->
make "void"
| FullyQualifiedClass (pkg, cl) ->
make ~package:pkg cl
| Array typ ->
let java_type = to_java_type typ in
let typ_str = Typ.Name.Java.Split.type_name java_type in
make ?package:(Typ.Name.Java.Split.package java_type) (Printf.sprintf "%s[]" typ_str)
| Method _ ->
L.(die UserError "Cannot express a method as a Procname.Java.java_type")
let tokenize input =
let rec tokenize_aux input acc =
match input with
| [] ->
List.rev acc
| c :: cs ->
match c with
| '(' ->
tokenize_aux cs (NonTerminal SymMethodOpen :: acc)
| ')' ->
tokenize_aux cs (NonTerminal SymMethodClose :: acc)
| '[' ->
tokenize_aux cs (NonTerminal SymArray :: acc)
| 'Z' ->
tokenize_aux cs (Terminal Boolean :: acc)
| 'B' ->
tokenize_aux cs (Terminal Byte :: acc)
| 'C' ->
tokenize_aux cs (Terminal Char :: acc)
| 'S' ->
tokenize_aux cs (Terminal Short :: acc)
| 'I' ->
tokenize_aux cs (Terminal Int :: acc)
| 'J' ->
tokenize_aux cs (Terminal Long :: acc)
| 'F' ->
tokenize_aux cs (Terminal Float :: acc)
| 'D' ->
tokenize_aux cs (Terminal Double :: acc)
| 'V' ->
tokenize_aux cs (Terminal Void :: acc)
| 'L' ->
let semicolon_pos =
match List.findi cs ~f:(fun _ c -> Char.equal ';' c) with
| None ->
let open L in
die UserError
"Cannot find a semicolon symbol to delimit the L token. Failed parsing input" c
| Some (pos, _) ->
pos
in
let consumed_input, new_input = List.split_n cs (semicolon_pos + 1) in
let fully_qualified_class =
String.of_char_list consumed_input |> to_fully_qualified_class
in
tokenize_aux new_input (Terminal (FullyQualifiedClass fully_qualified_class) :: acc)
| c ->
L.(die UserError "Unrecognized char '%c' while reading the input sequence" c)
in
let input_chars = String.to_list input in
tokenize_aux input_chars []
(** Very simple minded: keep reducing until no more non-terminals are left.
Fail if no reductions happened during a scan *)
let reduce symbols =
let rec reduce_aux ~symbols ~unchanged_symbols ~in_method ~jnis_in_method ~jnis =
let tl_or_empty l = match l with [] -> [] | [_] -> [] | _ :: tl -> tl in
let collected_symbols =
not (List.is_empty unchanged_symbols && List.is_empty jnis_in_method)
in
let all_collected_symbols_so_far () =
if in_method then
let terminals_in_method = List.map ~f:(fun jni -> Terminal jni) jnis_in_method in
terminals_in_method @ unchanged_symbols
else unchanged_symbols
in
match symbols with
| [] ->
if collected_symbols then
L.(die UserError "No symbols were reduced during a scan, failed parsing input")
else (* no more symbols in input, terminate *) List.rev jnis
| (Terminal t) :: tl when not collected_symbols && not in_method ->
reduce_aux ~symbols:tl ~unchanged_symbols ~in_method ~jnis_in_method ~jnis:(t :: jnis)
| (NonTerminal SymMethod method_jnis) :: (Terminal t) :: tl ->
let transformed_symbols = Terminal (Method (method_jnis, t)) :: tl in
let new_symbols =
List.rev_append (all_collected_symbols_so_far ()) transformed_symbols
in
reduce_aux ~symbols:new_symbols ~unchanged_symbols:[] ~in_method:false ~jnis_in_method:[]
~jnis
| (NonTerminal SymMethodOpen as nt) :: tl ->
reduce_aux ~symbols:tl ~unchanged_symbols:(nt :: all_collected_symbols_so_far ())
~in_method:true ~jnis_in_method:[] ~jnis
| (NonTerminal SymArray) :: (Terminal t) :: tl ->
let transformed_symbols = Terminal (Array t) :: tl in
let new_symbols =
List.rev_append (all_collected_symbols_so_far ()) transformed_symbols
in
reduce_aux ~symbols:new_symbols ~unchanged_symbols:[] ~in_method:false ~jnis_in_method:[]
~jnis
| (NonTerminal SymArray as nt) :: tl ->
reduce_aux ~symbols:tl ~unchanged_symbols:(nt :: all_collected_symbols_so_far ())
~in_method:false ~jnis_in_method:[] ~jnis
| (NonTerminal SymMethodClose) :: tl ->
let new_method_non_terminal = NonTerminal (SymMethod (List.rev jnis_in_method)) in
(* the first symbol in unchanged_symbols is the SymMethodOpen, remove it *)
let new_unchanged_symbols = tl_or_empty unchanged_symbols in
let new_tokens = List.rev_append new_unchanged_symbols (new_method_non_terminal :: tl) in
reduce_aux ~symbols:new_tokens ~unchanged_symbols:[] ~in_method:false ~jnis_in_method:[]
~jnis
| t :: tl ->
let unchanged_symbols' =
if in_method then unchanged_symbols else t :: unchanged_symbols
in
let collected_in_method' =
if in_method then
(* at this point, all tokens inside a method block are terminals *)
match t with
| Terminal s ->
s :: jnis_in_method
| NonTerminal _ ->
let open L in
die UserError
"Unexpected non-terminal symbol found within a method block, failed parsing \
input"
else jnis_in_method
in
reduce_aux ~symbols:tl ~unchanged_symbols:unchanged_symbols' ~in_method
~jnis_in_method:collected_in_method' ~jnis
in
reduce_aux ~symbols ~unchanged_symbols:[] ~in_method:false ~jnis_in_method:[] ~jnis:[]
let parse_str = Fn.compose reduce tokenize
let parse_method_str str =
match parse_str str with
| [(Method m)] ->
m
| _ ->
L.(die UserError "The input provided did not parse as one JNI method signature")
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct
type nonrec t = t =
| Boolean
| Byte
| Char
| Short
| Int
| Long
| Float
| Double
| Void
(* FullyQualifiedClass is split between (package, class) *)
| FullyQualifiedClass of (string * string)
| Array of t
| Method of (t list * t)
let compare = compare
let equal = equal
let parse_str = parse_str
let parse_method_str = parse_method_str
let to_java_type = to_java_type
let pp = pp
end
end
let create ~classname ~methodname ~signature ~kind =
let name = Typ.Name.Java.from_string classname in
let args, ret_typ = JNI.parse_method_str signature in
let java_type_args = List.map ~f:JNI.to_java_type args in
let java_type_ret_typ =
if String.equal methodname Typ.Procname.Java.constructor_method_name
|| String.equal methodname Typ.Procname.Java.class_initializer_method_name
then None
else Some (JNI.to_java_type ret_typ)
in
Typ.Procname.Java (Typ.Procname.Java.make name java_type_ret_typ methodname java_type_args kind)
let create_static = create ~kind:Typ.Procname.Java.Static
let create_non_static = create ~kind:Typ.Procname.Java.Non_Static
let from_json_string str =
let methods =
match Yojson.Basic.from_string str with
| `Assoc [_; ("methods", `List j); _] ->
j
| _ ->
L.(die UserError "Unexpected JSON input for the collection of methods")
in
let rec parse_json j acc =
match j with
| (`Assoc
[ ("class", `String classname)
; _
; ("method", `String methodname)
; ("signature", `String signature)
; _ ])
:: tl ->
let static_procname = create_static ~classname ~methodname ~signature in
let non_static_procname = create_non_static ~classname ~methodname ~signature in
parse_json tl (static_procname :: non_static_procname :: acc)
| [] ->
acc
| _ ->
L.(die UserError "Unexpected JSON input for the description of a single method")
in
parse_json methods []

@ -0,0 +1,42 @@
(*
* Copyright (c) 2018 - 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 JNI : sig
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig
type t =
| Boolean
| Byte
| Char
| Short
| Int
| Long
| Float
| Double
| Void
(* FullyQualifiedClass is split between (package, class) *)
| FullyQualifiedClass of (string * string)
| Array of t
| Method of (t list * t)
[@@deriving compare]
val equal : t -> t -> bool
val parse_str : string -> t list
val parse_method_str : string -> t list * t
val to_java_type : t -> Typ.Procname.Java.java_type
val pp : Format.formatter -> t -> unit
end
end
val from_json_string : string -> Typ.Procname.t list

@ -0,0 +1 @@
../java/JavaProfilerSamples.ml

@ -0,0 +1 @@
../java/JavaProfilerSamples.mli

@ -0,0 +1,273 @@
(*
* Copyright (c) 2018 - 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
open OUnit2
module T = JavaProfilerSamples.JNI.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY
let mk_split (pkg, typ) = Typ.Name.Java.Split.make ?package:pkg typ
let test_jni_pp =
let create_test input expected _ =
let found = Format.asprintf "%a" T.pp input in
let pp_diff fmt (expected, actual) =
Format.fprintf fmt "Expected: '%s', found: '%s'" expected actual
in
assert_equal ~cmp:String.equal ~pp_diff expected found
in
[ ( "test_jni_pp_1"
, T.(Method ([Int; Boolean; FullyQualifiedClass ("java.lang", "String")], Array Char))
, "(IZLjava/lang/String;)[C" )
; ( "test_jni_pp_2"
, (let open T in
Method
( [ Array
(Method
( [Int; Method ([Long; Array (Array Long); Boolean], Long)]
, Array
(Array
(Array
(Method
([Int; FullyQualifiedClass ("aaa.bbb", "Ccc"); Boolean], Array Char))))
)) ]
, Void ))
, "([(I(J[[JZ)J)[[[(ILaaa/bbb/Ccc;Z)[C)V" ) ]
|> List.map ~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output )
let test_jni_parse_method_str_with_invalid_input =
let create_test input expected_exception _ =
let run () = T.parse_method_str input in
assert_raises expected_exception run
in
[ ( "test_jni_parse_method_str_with_empty_input"
, ""
, Logging.InferUserError "The input provided did not parse as one JNI method signature" )
; ( "test_jni_parse_method_str_with_valid_non_method_input"
, "I"
, Logging.InferUserError "The input provided did not parse as one JNI method signature" ) ]
|> List.map ~f:(fun (name, test_input, expected_exception) ->
name >:: create_test test_input expected_exception )
let test_jni_parse_str_with_valid_input =
let create_test input expected _ =
let found = T.parse_str input in
let pp_diff fmt (expected, actual) =
Format.fprintf fmt "Expected: '%a', found: '%a'" (Format.pp_print_list T.pp) expected
(Format.pp_print_list T.pp) actual
in
assert_equal ~cmp:(List.equal ~equal:T.equal) ~pp_diff expected found
in
[ ( "test_jni_parse_str_with_method_signature"
, "(IZLjava/lang/String;)[C"
, T.([Method ([Int; Boolean; FullyQualifiedClass ("java.lang", "String")], Array Char)]) )
; ( "test_jni_parse_str_with_multiple_separate_types"
, "I[[[CIJ(I)[C"
, T.([Int; Array (Array (Array Char)); Int; Long; Method ([Int], Array Char)]) )
; ( "test_jni_parse_str_with_multiple_fully_qualified_classes"
, "(Laaa/bbb/Ccc;Laaa/bbb/Ccc;)V"
, let open T in
[ Method
([FullyQualifiedClass ("aaa.bbb", "Ccc"); FullyQualifiedClass ("aaa.bbb", "Ccc")], Void)
] )
; ( "test_jni_parse_str_with_complex_method_signature_1"
, "([[J(I)V)V"
, T.([Method ([Array (Array Long); Method ([Int], Void)], Void)]) )
; ( "test_jni_parse_str_with_complex_method_signature_2"
, "([C()V)V"
, T.([Method ([Array Char; Method ([], Void)], Void)]) )
; ( "test_jni_parse_str_with_complex_method_signature_3"
, "(J[[J)V"
, T.([Method ([Long; Array (Array Long)], Void)]) )
; ( "test_jni_parse_str_with_complex_method_signature_4"
, "([(I(J[[JZ)J)[[[(ILaaa/bbb/Ccc;Z)[C)V"
, let open T in
[ Method
( [ Array
(Method
( [Int; Method ([Long; Array (Array Long); Boolean], Long)]
, Array
(Array
(Array
(Method
([Int; FullyQualifiedClass ("aaa.bbb", "Ccc"); Boolean], Array Char))))
)) ]
, Void ) ] )
; ("test_jni_parse_str_with_empty_method_signature", "()V", T.([Method ([], Void)]))
; ("test_jni_parse_str_with_empty_input", "", []) ]
|> List.map ~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output )
let test_jni_parse_str_with_invalid_input =
let create_test input expected_exception _ =
let run () = T.parse_str input in
assert_raises expected_exception run
in
[ ( "test_jni_parse_str_with_missing_semicolon"
, "Ljava/lang/String"
, Logging.InferUserError
"Cannot find a semicolon symbol to delimit the L token. Failed parsing input" )
; ( "test_jni_parse_str_with_unrecognized_char"
, "M"
, Logging.InferUserError "Unrecognized char 'M' while reading the input sequence" )
; ( "test_jni_parse_str_with_no_reductions_in_a_scan"
, "(((("
, Logging.InferUserError "No symbols were reduced during a scan, failed parsing input" ) ]
|> List.map ~f:(fun (name, test_input, expected_exception) ->
name >:: create_test test_input expected_exception )
let test_jni_to_java_type_with_valid_input =
let create_test input expected _ =
let found = T.to_java_type input in
let pp_diff fmt (expected, actual) =
let exp_pkg = Option.value ~default:"<None>" (Typ.Name.Java.Split.package expected) in
let exp_cl = Typ.Name.Java.Split.type_name expected in
let actual_pkg = Option.value ~default:"<None>" (Typ.Name.Java.Split.package actual) in
let actual_cl = Typ.Name.Java.Split.type_name actual in
Format.fprintf fmt "Expected: '(%s, %s)', found: '(%s, %s)'" exp_pkg exp_cl actual_pkg
actual_cl
in
let cmp a b = Int.equal 0 (Typ.Procname.Java.compare_java_type a b) in
assert_equal ~cmp ~pp_diff expected found
in
[ ("test_jni_to_java_type_1", T.Boolean, mk_split (None, "bool"))
; ( "test_jni_to_java_type_2"
, T.FullyQualifiedClass ("java.lang", "String")
, mk_split (Some "java.lang", "String") ) ]
|> List.map ~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output )
let test_jni_to_java_type_with_invalid_input =
let run () = T.to_java_type (Method ([], Void)) in
let expected_exception =
Logging.InferUserError "Cannot express a method as a Procname.Java.java_type"
in
let do_assert _ = assert_raises expected_exception run in
"test_jni_to_java_type_with_method_should_fail" >:: do_assert
let test_from_json_string_with_valid_input =
let create_test input expected _ =
let found = JavaProfilerSamples.from_json_string input in
assert_equal ~cmp:(List.equal ~equal:Typ.Procname.equal) expected found
in
let input1 = "{\"whatever\": {}, \"methods\": [], \"foo\": {}}" in
let expected1 = [] in
let input2 =
"{\"whatever\": {}, \"methods\": [{\"class\": \"aaa.bbb.Ccc\", \"boo\": \"\", \"method\": \
\"methodOne\", \"signature\": \"()V\", \"wat\": \"\"},{\"class\": \"ddd.eee.Fff\", \"boo\": \
\"\", \"method\": \"methodTwo\", \"signature\": \"(Ljava/lang/String;[IJ)[[C\", \"wat\": \
\"\"},{\"class\": \"ggg.hhh.Iii\", \"boo\": \"\", \"method\": \"<clinit>\", \"signature\": \
\"(Ljava/lang/String;[IJ)V\", \"wat\": \"\"},{\"class\": \"lll.mmm.Nnn\", \"boo\": \"\", \
\"method\": \"<init>\", \"signature\": \"(Ljava/lang/String;[IJ)V\", \"wat\": \"\"}], \
\"foo\": {}}"
in
let expected2 =
[ Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "lll.mmm.Nnn")
None "<init>"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "lll.mmm.Nnn")
None "<init>"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Non_Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "ggg.hhh.Iii")
None "<clinit>"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "ggg.hhh.Iii")
None "<clinit>"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Non_Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "ddd.eee.Fff")
(Some (mk_split (None, "char[][]")))
"methodTwo"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "ddd.eee.Fff")
(Some (mk_split (None, "char[][]")))
"methodTwo"
[ mk_split (Some "java.lang", "String")
; mk_split (None, "int[]")
; mk_split (None, "long") ]
Java.Non_Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "aaa.bbb.Ccc")
(Some (mk_split (None, "void")))
"methodOne" [] Java.Static))
; Typ.Procname.(
Java
(Java.make
(Typ.Name.Java.from_string "aaa.bbb.Ccc")
(Some (mk_split (None, "void")))
"methodOne" [] Java.Non_Static)) ]
in
[("test_from_json_string_1", input1, expected1); ("test_from_json_string_2", input2, expected2)]
|> List.map ~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output )
let test_from_json_string_with_invalid_input =
let create_test input expected_exception _ =
let run () = JavaProfilerSamples.from_json_string input in
assert_raises expected_exception run
in
[ ( "test_from_json_string_1"
, "{\"whatever\": {}, \"methods\": []}"
, Logging.InferUserError "Unexpected JSON input for the collection of methods" )
; ( "test_from_json_string_2"
, "{\"whatever\": {}, \"methods\": [{\"class\": \"aaa.bbb.Ccc\", \"boo\": \"\", \"method\": \
\"methodOne\", \"signature\": \"()V\"}], \"foo\": {}}"
, Logging.InferUserError "Unexpected JSON input for the description of a single method" )
; ("test_from_json_string_3", "(", Yojson.Json_error "Line 1, bytes 0-1:\nInvalid token '('") ]
|> List.map ~f:(fun (name, test_input, expected_exception) ->
name >:: create_test test_input expected_exception )
let tests =
"java_profiler_samples"
>::: test_jni_to_java_type_with_invalid_input :: test_jni_parse_str_with_valid_input
@ test_jni_parse_str_with_invalid_input @ test_jni_parse_method_str_with_invalid_input
@ test_jni_pp @ test_jni_to_java_type_with_valid_input
@ test_from_json_string_with_valid_input @ test_from_json_string_with_invalid_input

@ -35,6 +35,7 @@ let () =
; BoundedCallTreeTests.tests
; DifferentialTests.tests
; DifferentialFiltersTests.tests
; JavaProfilerSamplesTest.tests
; ProcCfgTests.tests
; LivenessTests.tests
; SchedulerTests.tests

Loading…
Cancel
Save