diff --git a/infer/src/IR/Typ.ml b/infer/src/IR/Typ.ml index 12187ac9e..5002aadd5 100644 --- a/infer/src/IR/Typ.ml +++ b/infer/src/IR/Typ.ml @@ -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 "" + let constructor_method_name = "" + + let class_initializer_method_name = "" + + 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 "" + 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 -> diff --git a/infer/src/IR/Typ.mli b/infer/src/IR/Typ.mli index f2030cc7b..0070f3964 100644 --- a/infer/src/IR/Typ.mli +++ b/infer/src/IR/Typ.mli @@ -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. *) diff --git a/infer/src/java/JavaProfilerSamples.ml b/infer/src/java/JavaProfilerSamples.ml new file mode 100644 index 000000000..ff8b10f57 --- /dev/null +++ b/infer/src/java/JavaProfilerSamples.ml @@ -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 [] diff --git a/infer/src/java/JavaProfilerSamples.mli b/infer/src/java/JavaProfilerSamples.mli new file mode 100644 index 000000000..7f6115df3 --- /dev/null +++ b/infer/src/java/JavaProfilerSamples.mli @@ -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 diff --git a/infer/src/java_stubs/JavaProfilerSamples.ml b/infer/src/java_stubs/JavaProfilerSamples.ml new file mode 120000 index 000000000..6a8e125b9 --- /dev/null +++ b/infer/src/java_stubs/JavaProfilerSamples.ml @@ -0,0 +1 @@ +../java/JavaProfilerSamples.ml \ No newline at end of file diff --git a/infer/src/java_stubs/JavaProfilerSamples.mli b/infer/src/java_stubs/JavaProfilerSamples.mli new file mode 120000 index 000000000..5ecde28f8 --- /dev/null +++ b/infer/src/java_stubs/JavaProfilerSamples.mli @@ -0,0 +1 @@ +../java/JavaProfilerSamples.mli \ No newline at end of file diff --git a/infer/src/unit/JavaProfilerSamplesTest.ml b/infer/src/unit/JavaProfilerSamplesTest.ml new file mode 100644 index 000000000..a494c6e83 --- /dev/null +++ b/infer/src/unit/JavaProfilerSamplesTest.ml @@ -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:"" (Typ.Name.Java.Split.package expected) in + let exp_cl = Typ.Name.Java.Split.type_name expected in + let actual_pkg = Option.value ~default:"" (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\": \"\", \"signature\": \ + \"(Ljava/lang/String;[IJ)V\", \"wat\": \"\"},{\"class\": \"lll.mmm.Nnn\", \"boo\": \"\", \ + \"method\": \"\", \"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 "" + [ 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 "" + [ 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 "" + [ 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 "" + [ 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 diff --git a/infer/src/unit/inferunit.ml b/infer/src/unit/inferunit.ml index 719f521ff..b580885e5 100644 --- a/infer/src/unit/inferunit.ml +++ b/infer/src/unit/inferunit.ml @@ -35,6 +35,7 @@ let () = ; BoundedCallTreeTests.tests ; DifferentialTests.tests ; DifferentialFiltersTests.tests + ; JavaProfilerSamplesTest.tests ; ProcCfgTests.tests ; LivenessTests.tests ; SchedulerTests.tests