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.
126 lines
4.0 KiB
126 lines
4.0 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 F = Format
|
|
module L = Logging
|
|
|
|
(** invariant: if [package = Some str] then [not (String.equal str "")]. [classname] appears first
|
|
so that the comparator fails earlier *)
|
|
type t = {classname: string; package: string option} [@@deriving compare, equal, yojson_of]
|
|
|
|
module Map = Caml.Map.Make (struct
|
|
type nonrec t = t [@@deriving compare]
|
|
end)
|
|
|
|
module Set = Caml.Set.Make (struct
|
|
type nonrec t = t [@@deriving compare]
|
|
end)
|
|
|
|
let make ~package ~classname =
|
|
match package with Some "" -> {package= None; classname} | _ -> {package; classname}
|
|
|
|
|
|
let from_string str =
|
|
match String.rsplit2 str ~on:'.' with
|
|
| None ->
|
|
{classname= str; package= None}
|
|
| Some ("", _) ->
|
|
L.die InternalError "Empty package path in Java qualified classname.@."
|
|
| Some (pkg, classname) ->
|
|
{classname; package= Some pkg}
|
|
|
|
|
|
let to_string = function
|
|
| {classname; package= None} ->
|
|
classname
|
|
| {classname; package= Some pkg} ->
|
|
String.concat ~sep:"." [pkg; classname]
|
|
|
|
|
|
let pp fmt = function
|
|
| {classname; package= None} ->
|
|
F.pp_print_string fmt classname
|
|
| {classname; package= Some pkg} ->
|
|
F.fprintf fmt "%s.%s" pkg classname
|
|
|
|
|
|
let package {package} = package
|
|
|
|
let classname {classname} = classname
|
|
|
|
let is_int s =
|
|
try
|
|
ignore (int_of_string s) ;
|
|
true
|
|
with Failure _ -> false
|
|
|
|
|
|
let get_outer_class_name {package; classname} =
|
|
String.rsplit2 classname ~on:'$' |> Option.map ~f:(fun (outer, _) -> {package; classname= outer})
|
|
|
|
|
|
(*
|
|
Anonymous classes have two forms:
|
|
- classic anonymous classes: suffixes in form of $<int>.
|
|
- classes corresponding to lambda-expressions: they are manifested as $Lambda$.
|
|
- two forms above nested inside each other.
|
|
Also non-anonymous (user-defined) name can be nested as well (Class$NestedClass).
|
|
In general case anonymous class name looks something like
|
|
Class$NestedClass$1$17$5$Lambda$_1_2, and we need to return Class$NestedClass *)
|
|
let get_user_defined_class_if_anonymous_inner {package; classname} =
|
|
let is_anonymous_name = function
|
|
| "Lambda" ->
|
|
true
|
|
| name when is_int name ->
|
|
true
|
|
| _ ->
|
|
false
|
|
in
|
|
let pieces = String.split classname ~on:'$' in
|
|
let first_anonymous_name = List.findi pieces ~f:(fun _index name -> is_anonymous_name name) in
|
|
Option.bind first_anonymous_name ~f:(fun (index, _name) ->
|
|
(* Everything before this index did not have anonymous prefixes. Deem it user defined. *)
|
|
match List.take pieces index with
|
|
| [] ->
|
|
(* This is a weird situation - our class _starts_ with an anonymous name.
|
|
This should not happen normally, but we can not rule this out completely since there is no physical limitations on
|
|
the bytecode names.
|
|
In this case, we return [None] because formally this is not an anonymous _inner_ class, but anonymous outermost class instead.
|
|
TODO: redesign this API so this case is modelled directly
|
|
*)
|
|
None
|
|
| list ->
|
|
(* Assemble back all pieces together *)
|
|
Some {package; classname= String.concat ~sep:"$" list} )
|
|
|
|
|
|
let is_anonymous_inner_class_name t = get_user_defined_class_if_anonymous_inner t |> is_some
|
|
|
|
let is_external_via_config t =
|
|
let package = package t in
|
|
Option.exists ~f:Config.java_package_is_external package
|
|
|
|
|
|
let pp_with_verbosity ~verbose fmt t =
|
|
if verbose then pp fmt t else F.pp_print_string fmt (classname t)
|
|
|
|
|
|
module Normalizer = HashNormalizer.Make (struct
|
|
type nonrec t = t [@@deriving equal]
|
|
|
|
let hash = Hashtbl.hash
|
|
|
|
let normalize t =
|
|
let classname = HashNormalizer.StringNormalizer.normalize t.classname in
|
|
let package =
|
|
IOption.map_changed t.package ~equal:phys_equal ~f:HashNormalizer.StringNormalizer.normalize
|
|
in
|
|
if phys_equal classname t.classname && phys_equal package t.package then t
|
|
else {classname; package}
|
|
end)
|