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.

283 lines
9.6 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
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]
let void_method_with_no_arguments = "()V"
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.pp_print_char fmt 'Z'
| Byte ->
Format.pp_print_char fmt 'B'
| Char ->
Format.pp_print_char fmt 'C'
| Short ->
Format.pp_print_char fmt 'S'
| Int ->
Format.pp_print_char fmt 'I'
| Long ->
Format.pp_print_char fmt 'J'
| Float ->
Format.pp_print_char fmt 'F'
| Double ->
Format.pp_print_char fmt 'D'
| Void ->
Format.pp_print_char 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 "'%s' did not parse as one JNI method signature" str)
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_procname ~classname ~methodname ~signature ~use_signature =
let signature = if use_signature then signature else JNI.void_method_with_no_arguments in
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
Typ.Procname.Java.Non_Static)
let make_void_signature_procname ~classname ~methodname =
let signature = JNI.void_method_with_no_arguments in
create_procname ~signature ~classname ~methodname ~use_signature:true