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.

344 lines
10 KiB

(*
* Copyright (c) 2009 - 2013 Monoidics ltd.
* Copyright (c) 2013 - 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! Utils
open Javalib_pack
module L = Logging
let models_specs_filenames = ref StringSet.empty
let models_jar = ref ""
let models_tenv = ref (Tenv.create ())
let load_models_tenv zip_channel =
let models_tenv_filename_in_jar =
let root = Filename.concat Config.default_in_zip_results_dir Config.captured_dir_name in
Filename.concat root Config.global_tenv_filename in
let temp_tenv_filename =
DB.filename_from_string (Filename.temp_file "tmp_" Config.global_tenv_filename) in
let entry = Zip.find_entry zip_channel models_tenv_filename_in_jar in
let temp_tenv_file = DB.filename_to_string temp_tenv_filename in
let models_tenv =
try
Zip.copy_entry_to_file zip_channel entry temp_tenv_file;
match Tenv.load_from_file temp_tenv_filename with
| None -> failwith "Models tenv file could not be loaded"
| Some tenv -> tenv
with
| Not_found -> failwith "Models tenv not found in jar file"
| Sys_error msg -> failwith ("Models jar could not be opened "^msg) in
DB.file_remove temp_tenv_filename;
models_tenv
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
StringSet.add proc_filename set in
models_specs_filenames :=
IList.fold_left collect !models_specs_filenames (Zip.entries zip_channel);
models_tenv := load_models_tenv zip_channel;
Zip.close_in zip_channel
let add_models jar_filename =
models_jar := jar_filename;
if Sys.file_exists !models_jar then
collect_specs_filenames jar_filename
else
failwith "Java model file not found"
let is_model procname =
StringSet.mem (Procname.to_filename procname) !models_specs_filenames
let split_classpath cp = Str.split (Str.regexp JFile.sep) cp
let append_path classpath path =
if Sys.file_exists path then
let full_path = filename_to_absolute path in
if String.length classpath = 0 then
full_path
else
classpath^JFile.sep^full_path
else
classpath
type file_entry =
| Singleton of DB.source_file
| Duplicate of (string * DB.source_file) list
(* 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 = DB.source_file_to_abs_path source_file in
let file_in = open_in 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 (input_line file_in) in
match Str.split (Str.regexp "[ \t]+") line with
| [] -> loop ()
| hd::package::[] when hd = "package" -> package
| _ -> loop ()
with End_of_file ->
close_in 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 =
DB.source_file_from_abs_path (convert_to_absolute path) in
try
match StringMap.find basename map 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 ->
(* Most common case: there is no conflict with the base name of the source file *)
Singleton current_source_file in
StringMap.add basename entry map
let add_root_path path roots =
if StringSet.mem path roots then roots
else StringSet.add path roots
let load_from_verbose_output () =
let file_in = open_in Config.javac_verbose_out in
let class_filename_re =
Str.regexp
"\\[wrote RegularFileObject\\[\\(.*\\)\\]\\]" in
let source_filename_re =
Str.regexp
"\\[parsing started RegularFileObject\\[\\(.*\\)\\]\\]" in
let classpath_re =
Str.regexp
"\\[search path for class files: \\(.*\\)\\]" in
let rec loop paths roots sources classes =
try
let line = input_line file_in in
if Str.string_match class_filename_re line 0 then
let path = Str.matched_group 1 line in
let cn, root_info = Javalib.extract_class_name_from_file path in
let root_dir = if 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 1 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 = Str.split (Str.regexp_string ",") 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 ->
close_in file_in;
let classpath =
IList.fold_left
append_path
""
((StringSet.elements roots) @ paths) in
(classpath, sources, classes) in
loop [] StringSet.empty StringMap.empty JBasics.ClassSet.empty
let classname_of_class_filename class_filename =
JBasics.make_cn
(String.map
(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
try
let () = ignore (Str.search_forward (Str.regexp "class") class_filename 0) in
(classname_of_class_filename (Filename.chop_extension class_filename) :: classes)
with Not_found -> classes in
let classnames_after = IList.fold_left collect classnames (Zip.entries file_in) in
Zip.close_in file_in;
classnames_after
let collect_classnames start_classmap jar_filename =
IList.fold_left
(fun map cn -> JBasics.ClassSet.add cn map)
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
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)
(StringSet.empty, JBasics.ClassSet.empty)
path
let search_sources () =
let initial_map =
IList.fold_left
(fun map path -> add_source_file path map)
StringMap.empty
Config.sources in
match Config.sourcepath with
| None -> initial_map
| Some sourcepath ->
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.map_default split_classpath [] cp_option in
let combine path_list classpath =
IList.fold_left append_path classpath (IList.rev path_list) in
let classpath =
combine (split Config.classpath) ""
|> combine (StringSet.elements roots)
|> combine (split Config.bootclasspath) in
(classpath, search_sources (), classes)
let load_sources_and_classes () =
match Config.generated_classes with
| None -> load_from_verbose_output ()
| Some path -> load_from_arguments path
type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t
type program = {
classpath: Javalib.class_path;
models: classmap;
mutable classmap: classmap
}
let get_classmap program =
program.classmap
let get_classpath program =
program.classpath
let get_models program =
program.models
let add_class cn jclass program =
program.classmap <- JBasics.ClassMap.add cn jclass program.classmap
let cleanup program =
Javalib.close_class_path program.classpath
let lookup_node cn program =
try
Some (JBasics.ClassMap.find cn (get_classmap program))
with Not_found ->
try
let jclass = Javalib.get_class (get_classpath program) cn in
add_class cn jclass program;
Some jclass
with
| JBasics.No_class_found _
| JBasics.Class_structure_error _
| Invalid_argument _ -> 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 =
IList.fold_left
collect
start_classmap
(extract_classnames [] jar_filename) in
Javalib.close_class_path classpath;
classmap
let load_program classpath classes =
L.out_debug "loading program ... %!";
let models =
if !models_jar = "" then JBasics.ClassMap.empty
else collect_classes JBasics.ClassMap.empty !models_jar in
let program = {
classpath = Javalib.class_path classpath;
models = models;
classmap = JBasics.ClassMap.empty
} in
JBasics.ClassSet.iter
(fun cn -> ignore (lookup_node cn program))
classes;
L.out_debug "done@.";
program