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
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
|