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.
308 lines
11 KiB
308 lines
11 KiB
(*
|
|
* Copyright (c) 2009-2013, Monoidics ltd.
|
|
* Copyright (c) 2013-present, Facebook, Inc.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*)
|
|
|
|
open! IStd
|
|
open PolyVariantEqual
|
|
open Javalib_pack
|
|
module L = Logging
|
|
|
|
(** version of Javalib.get_class that does not spam stderr *)
|
|
let javalib_get_class = Utils.suppress_stderr2 Javalib.get_class
|
|
|
|
let models_specs_filenames = ref String.Set.empty
|
|
|
|
let models_jar = ref ""
|
|
|
|
let collect_specs_filenames jar_filename =
|
|
let zip_channel = Zip.open_in jar_filename in
|
|
let collect set e =
|
|
let filename = e.Zip.filename in
|
|
if not (Filename.check_suffix filename Config.specs_files_suffix) then set
|
|
else
|
|
let proc_filename = Filename.chop_extension (Filename.basename filename) in
|
|
String.Set.add set proc_filename
|
|
in
|
|
models_specs_filenames :=
|
|
List.fold ~f:collect ~init:!models_specs_filenames (Zip.entries zip_channel) ;
|
|
Zip.close_in zip_channel
|
|
|
|
|
|
let add_models jar_filename =
|
|
models_jar := jar_filename ;
|
|
if Sys.file_exists !models_jar = `Yes then collect_specs_filenames jar_filename
|
|
else L.(die InternalError) "Java model file not found"
|
|
|
|
|
|
let is_model procname = String.Set.mem !models_specs_filenames (Typ.Procname.to_filename procname)
|
|
|
|
let split_classpath cp = Str.split (Str.regexp JFile.sep) cp
|
|
|
|
let append_path classpath path =
|
|
let full_path = Utils.filename_to_absolute ~root:Config.project_root path in
|
|
if Sys.file_exists full_path = `Yes then
|
|
if Int.equal (String.length classpath) 0 then full_path else classpath ^ JFile.sep ^ full_path
|
|
else (
|
|
L.debug Capture Medium "Path %s not found" full_path ;
|
|
classpath )
|
|
|
|
|
|
type file_entry = Singleton of SourceFile.t | Duplicate of (string * SourceFile.t) list
|
|
|
|
type t = string * file_entry String.Map.t * JBasics.ClassSet.t
|
|
|
|
(* Open the source file and search for the package declaration.
|
|
Only the case where the package is declared in a single line is supported *)
|
|
let read_package_declaration source_file =
|
|
let path = SourceFile.to_abs_path source_file in
|
|
let file_in = In_channel.create path in
|
|
let remove_trailing_semicolon = Str.replace_first (Str.regexp ";") "" in
|
|
let empty_package = "" in
|
|
let rec loop () =
|
|
try
|
|
let line = remove_trailing_semicolon (In_channel.input_line_exn file_in) in
|
|
match Str.split (Str.regexp "[ \t]+") line with
|
|
| [] ->
|
|
loop ()
|
|
| [hd; package] when String.equal hd "package" ->
|
|
package
|
|
| _ ->
|
|
loop ()
|
|
with End_of_file -> In_channel.close file_in ; empty_package
|
|
in
|
|
loop ()
|
|
|
|
|
|
let add_source_file path map =
|
|
let convert_to_absolute p =
|
|
if Filename.is_relative p then Filename.concat (Sys.getcwd ()) p else p
|
|
in
|
|
let basename = Filename.basename path in
|
|
let entry =
|
|
let current_source_file = SourceFile.from_abs_path (convert_to_absolute path) in
|
|
try
|
|
match String.Map.find_exn map basename with
|
|
| Singleton previous_source_file ->
|
|
(* Another source file with the same base name has been found.
|
|
Reading the package from the source file to resolve the ambiguity
|
|
only happens in this case *)
|
|
let previous_package = read_package_declaration previous_source_file
|
|
and current_package = read_package_declaration current_source_file in
|
|
let source_list =
|
|
[(current_package, current_source_file); (previous_package, previous_source_file)]
|
|
in
|
|
Duplicate source_list
|
|
| Duplicate previous_source_files ->
|
|
(* Two or more source file with the same base name have been found *)
|
|
let current_package = read_package_declaration current_source_file in
|
|
Duplicate ((current_package, current_source_file) :: previous_source_files)
|
|
with
|
|
| Not_found_s _ | Caml.Not_found ->
|
|
(* Most common case: there is no conflict with the base name of the source file *)
|
|
Singleton current_source_file
|
|
in
|
|
String.Map.set ~key:basename ~data:entry map
|
|
|
|
|
|
let add_root_path path roots = String.Set.add roots path
|
|
|
|
let load_from_verbose_output javac_verbose_out =
|
|
let file_in = In_channel.create javac_verbose_out in
|
|
let class_filename_re =
|
|
Str.regexp
|
|
(Printf.sprintf
|
|
(* the unreadable regexp below captures 3 possible forms:
|
|
1. [wrote DirectoryFileObject[/path/to/classes_out:path/to/File.java]], leaves `path/to/File.java` in match group 2
|
|
2. [wrote RegularFileObject[path/to/File.java]], leaves `path/to/File.java` in match group 5
|
|
3. [wrote SimpleFileObject[path/to/File.java]], also leaves `path/to/File.java` in match group 5 *)
|
|
"\\[wrote \
|
|
\\(DirectoryFileObject\\[%s:\\(.*\\)\\|\\(\\(Regular\\|Simple\\)FileObject\\[\\(.*\\)\\)\\)\\]\\]"
|
|
Config.javac_classes_out)
|
|
in
|
|
let source_filename_re =
|
|
Str.regexp "\\[parsing started \\(Regular\\|Simple\\)FileObject\\[\\(.*\\)\\]\\]"
|
|
in
|
|
let classpath_re = Str.regexp "\\[search path for class files: \\(.*\\)\\]" in
|
|
let rec loop paths roots sources classes =
|
|
try
|
|
let line = In_channel.input_line_exn file_in in
|
|
if Str.string_match class_filename_re line 0 then
|
|
let path =
|
|
try Str.matched_group 5 line with Caml.Not_found ->
|
|
(* either matched group 5 is found, or matched group 2 is found, see doc for [class_filename_re] above *)
|
|
Config.javac_classes_out ^/ Str.matched_group 2 line
|
|
in
|
|
let cn, root_info = Javalib.extract_class_name_from_file path in
|
|
let root_dir =
|
|
if String.equal root_info "" then Filename.current_dir_name else root_info
|
|
in
|
|
loop paths (add_root_path root_dir roots) sources (JBasics.ClassSet.add cn classes)
|
|
else if Str.string_match source_filename_re line 0 then
|
|
let path = Str.matched_group 2 line in
|
|
loop paths roots (add_source_file path sources) classes
|
|
else if Str.string_match classpath_re line 0 then
|
|
let classpath = Str.matched_group 1 line in
|
|
let parsed_paths = String.split ~on:',' classpath in
|
|
loop parsed_paths roots sources classes
|
|
else (* skip this line *)
|
|
loop paths roots sources classes
|
|
with
|
|
| JBasics.Class_structure_error _ | Invalid_argument _ ->
|
|
loop paths roots sources classes
|
|
| End_of_file ->
|
|
In_channel.close file_in ;
|
|
let classpath = List.fold ~f:append_path ~init:"" (String.Set.elements roots @ paths) in
|
|
(classpath, sources, classes)
|
|
in
|
|
loop [] String.Set.empty String.Map.empty JBasics.ClassSet.empty
|
|
|
|
|
|
let classname_of_class_filename class_filename =
|
|
JBasics.make_cn (String.map ~f:(function '/' -> '.' | c -> c) class_filename)
|
|
|
|
|
|
let extract_classnames classnames jar_filename =
|
|
let file_in = Zip.open_in jar_filename in
|
|
let collect classes entry =
|
|
let class_filename = entry.Zip.filename in
|
|
match Filename.split_extension class_filename with
|
|
| basename, Some "class" ->
|
|
classname_of_class_filename basename :: classes
|
|
| _ ->
|
|
classes
|
|
in
|
|
let classnames_after = List.fold ~f:collect ~init:classnames (Zip.entries file_in) in
|
|
Zip.close_in file_in ; classnames_after
|
|
|
|
|
|
let collect_classnames start_classmap jar_filename =
|
|
List.fold
|
|
~f:(fun map cn -> JBasics.ClassSet.add cn map)
|
|
~init:start_classmap
|
|
(extract_classnames [] jar_filename)
|
|
|
|
|
|
let search_classes path =
|
|
let add_class roots classes class_filename =
|
|
let cn, root_dir = Javalib.extract_class_name_from_file class_filename in
|
|
(add_root_path root_dir roots, JBasics.ClassSet.add cn classes)
|
|
in
|
|
Utils.directory_fold
|
|
(fun accu p ->
|
|
let paths, classes = accu in
|
|
if Filename.check_suffix p "class" then add_class paths classes p
|
|
else if Filename.check_suffix p "jar" then
|
|
(add_root_path p paths, collect_classnames classes p)
|
|
else accu )
|
|
(String.Set.empty, JBasics.ClassSet.empty) path
|
|
|
|
|
|
let search_sources () =
|
|
let initial_map =
|
|
List.fold ~f:(fun map path -> add_source_file path map) ~init:String.Map.empty Config.sources
|
|
in
|
|
match Config.sourcepath with
|
|
| None ->
|
|
initial_map
|
|
| Some sourcepath ->
|
|
Utils.directory_fold
|
|
(fun map p -> if Filename.check_suffix p "java" then add_source_file p map else map)
|
|
initial_map sourcepath
|
|
|
|
|
|
let load_from_arguments classes_out_path =
|
|
let roots, classes = search_classes classes_out_path in
|
|
let split cp_option = Option.value_map ~f:split_classpath ~default:[] cp_option in
|
|
let combine path_list classpath =
|
|
List.fold ~f:append_path ~init:classpath (List.rev path_list)
|
|
in
|
|
let classpath =
|
|
combine (split Config.classpath) "" |> combine (String.Set.elements roots)
|
|
|> combine (split Config.bootclasspath)
|
|
in
|
|
(classpath, search_sources (), classes)
|
|
|
|
|
|
type callee_status = Translated | Missing of JBasics.class_name * JBasics.method_signature
|
|
|
|
type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t
|
|
|
|
type classpath = {path: string; channel: Javalib.class_path}
|
|
|
|
type program =
|
|
{ classpath: classpath
|
|
; models: classmap
|
|
; mutable classmap: classmap
|
|
; callees: callee_status Typ.Procname.Hash.t }
|
|
|
|
let get_classmap program = program.classmap
|
|
|
|
let get_classpath_channel program = program.classpath.channel
|
|
|
|
let get_models program = program.models
|
|
|
|
let add_class cn jclass program =
|
|
program.classmap <- JBasics.ClassMap.add cn jclass program.classmap
|
|
|
|
|
|
let set_callee_translated program pname =
|
|
Typ.Procname.Hash.replace program.callees pname Translated
|
|
|
|
|
|
let add_missing_callee program pname cn ms =
|
|
if not (Typ.Procname.Hash.mem program.callees pname) then
|
|
Typ.Procname.Hash.add program.callees pname (Missing (cn, ms))
|
|
|
|
|
|
let iter_missing_callees program ~f =
|
|
let select proc_name = function Translated -> () | Missing (cn, ms) -> f proc_name cn ms in
|
|
Typ.Procname.Hash.iter select program.callees
|
|
|
|
|
|
let cleanup program = Javalib.close_class_path program.classpath.channel
|
|
|
|
let lookup_node cn program =
|
|
try Some (JBasics.ClassMap.find cn (get_classmap program)) with Caml.Not_found ->
|
|
try
|
|
let jclass = javalib_get_class (get_classpath_channel program) cn in
|
|
add_class cn jclass program ; Some jclass
|
|
with
|
|
| JBasics.No_class_found _ ->
|
|
(* TODO T28155039 Figure out when and what to log *)
|
|
None
|
|
| (JBasics.Class_structure_error _ | Invalid_argument _) as exn ->
|
|
L.internal_error "ERROR: %s@." (Exn.to_string exn) ;
|
|
None
|
|
|
|
|
|
let collect_classes start_classmap jar_filename =
|
|
let classpath = Javalib.class_path jar_filename in
|
|
let collect classmap cn =
|
|
try JBasics.ClassMap.add cn (javalib_get_class classpath cn) classmap
|
|
with JBasics.Class_structure_error _ -> classmap
|
|
in
|
|
let classmap = List.fold ~f:collect ~init:start_classmap (extract_classnames [] jar_filename) in
|
|
Javalib.close_class_path classpath ;
|
|
classmap
|
|
|
|
|
|
let load_program classpath classes =
|
|
L.(debug Capture Medium) "loading program ... %!" ;
|
|
let models =
|
|
if String.equal !models_jar "" then JBasics.ClassMap.empty
|
|
else collect_classes JBasics.ClassMap.empty !models_jar
|
|
in
|
|
let program =
|
|
{ classpath= {path= classpath; channel= Javalib.class_path classpath}
|
|
; models
|
|
; classmap= JBasics.ClassMap.empty
|
|
; callees= Typ.Procname.Hash.create 128 }
|
|
in
|
|
JBasics.ClassSet.iter (fun cn -> ignore (lookup_node cn program)) classes ;
|
|
L.(debug Capture Medium) "done@." ;
|
|
program
|