Summary: As per title Reviewed By: jvillard Differential Revision: D22019146 fbshipit-source-id: 3837993e5master
parent
4bbe5a064b
commit
23c1b4f960
@ -1,138 +0,0 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
(* Sources: Java Virtual Machine Specification
|
||||
- Chapter 5. Loading, Linking and Initializing
|
||||
- Chapter 6. The Java Virtual Machine Instruction Set
|
||||
*)
|
||||
|
||||
(* TODO
|
||||
- catch / throw with exception classes
|
||||
*)
|
||||
|
||||
let do_call {InterproceduralAnalysis.analyze_dependency} callee loc init =
|
||||
analyze_dependency callee
|
||||
|> Option.fold ~init ~f:(fun acc (_, summary) ->
|
||||
ClassLoadsDomain.integrate_summary callee loc acc summary )
|
||||
|
||||
|
||||
(** fully load a class given the typename *)
|
||||
let rec load_class ({InterproceduralAnalysis.tenv} as analysis_data) loc astate class_name =
|
||||
(* don't bother if class is already loaded *)
|
||||
if ClassLoadsDomain.mem_typename class_name astate then astate
|
||||
else
|
||||
(* load the class itself *)
|
||||
let astate1 = ClassLoadsDomain.add_typename loc astate class_name in
|
||||
(* load classes referenced by the class initializer *)
|
||||
let astate2 =
|
||||
let class_initializer = Procname.(Java (Java.get_class_initializer class_name)) in
|
||||
(* NB may recurse if we are in class init but the shortcircuiting above makes it a no-op *)
|
||||
do_call analysis_data class_initializer loc astate1
|
||||
in
|
||||
(* finally, recursively load all superclasses *)
|
||||
Tenv.lookup tenv class_name
|
||||
|> Option.value_map ~default:[] ~f:(fun tstruct -> tstruct.Struct.supers)
|
||||
|> List.fold ~init:astate2 ~f:(load_class analysis_data loc)
|
||||
|
||||
|
||||
let load_type analysis_data loc (typ : Typ.t) astate =
|
||||
match typ with
|
||||
| {desc= Tstruct name} | {desc= Tptr ({desc= Tstruct name}, _)} ->
|
||||
load_class analysis_data loc astate name
|
||||
| _ ->
|
||||
astate
|
||||
|
||||
|
||||
let rec load_array analysis_data loc (typ : Typ.t) astate =
|
||||
match typ with
|
||||
| {desc= Tarray {elt}} ->
|
||||
load_array analysis_data loc elt astate
|
||||
| _ ->
|
||||
load_type analysis_data loc typ astate
|
||||
|
||||
|
||||
let rec add_loads_of_exp analysis_data loc (exp : Exp.t) astate =
|
||||
match exp with
|
||||
| Const (Cclass class_ident) ->
|
||||
(* [X.class] expressions *)
|
||||
let class_str = Ident.name_to_string class_ident |> JavaClassName.from_string in
|
||||
let class_name = Typ.JavaClass class_str in
|
||||
load_class analysis_data loc astate class_name
|
||||
| Sizeof {typ= {desc= Tarray {elt}}} ->
|
||||
(* anewarray / multinewarray *)
|
||||
load_array analysis_data loc elt astate
|
||||
| Cast (_, e) | UnOp (_, e, _) | Exn e ->
|
||||
(* NB Cast is only used for primitive types *)
|
||||
add_loads_of_exp analysis_data loc e astate
|
||||
| BinOp (_, e1, e2) ->
|
||||
add_loads_of_exp analysis_data loc e1 astate |> add_loads_of_exp analysis_data loc e2
|
||||
| Lfield (e, _, typ') ->
|
||||
(* getfield / getstatic / putfield / putstatic *)
|
||||
load_type analysis_data loc typ' astate |> add_loads_of_exp analysis_data loc e
|
||||
| Var _ | Const _ | Closure _ | Sizeof _ | Lindex _ | Lvar _ ->
|
||||
astate
|
||||
|
||||
|
||||
let exec_call analysis_data callee args loc astate =
|
||||
match args with
|
||||
| [_; (Exp.Sizeof {typ}, _)] when Procname.equal callee BuiltinDecl.__instanceof ->
|
||||
(* this matches downcasts/instanceof and exception handlers *)
|
||||
load_type analysis_data loc typ astate
|
||||
| _ ->
|
||||
(* invokeinterface / invokespecial / invokestatic / invokevirtual / new *)
|
||||
List.fold args ~init:astate ~f:(fun acc (exp, _) ->
|
||||
add_loads_of_exp analysis_data loc exp acc )
|
||||
|> do_call analysis_data callee loc
|
||||
|
||||
|
||||
let exec_instr analysis_data astate _ (instr : Sil.instr) =
|
||||
match instr with
|
||||
| Call (_, Const (Cfun callee), args, loc, _) ->
|
||||
exec_call analysis_data callee args loc astate
|
||||
| Load {e= exp; loc} | Prune (exp, loc, _, _) ->
|
||||
(* NB the java frontend seems to always translate complex guards into a sequence of
|
||||
instructions plus a prune on logical vars only. So the below is only for completeness. *)
|
||||
add_loads_of_exp analysis_data loc exp astate
|
||||
| Store {e1; e2; loc} ->
|
||||
add_loads_of_exp analysis_data loc e1 astate |> add_loads_of_exp analysis_data loc e2
|
||||
| _ ->
|
||||
astate
|
||||
|
||||
|
||||
let report_loads {InterproceduralAnalysis.proc_desc; err_log} astate =
|
||||
let report_load ({ClassLoadsDomain.Event.loc; elem} as event) =
|
||||
if String.is_prefix ~prefix:"java." elem then ()
|
||||
else
|
||||
let ltr = ClassLoadsDomain.Event.make_loc_trace event in
|
||||
let msg = Format.asprintf "Class %s loaded" elem in
|
||||
Reporting.log_issue proc_desc err_log ~loc ~ltr ClassLoads IssueType.class_load msg
|
||||
in
|
||||
let pname = Procdesc.get_proc_name proc_desc in
|
||||
Procname.get_class_name pname
|
||||
|> Option.iter ~f:(fun clazz ->
|
||||
let method_strname = Procname.get_method pname in
|
||||
let fullname = clazz ^ "." ^ method_strname in
|
||||
if String.Set.mem Config.class_loads_roots fullname then
|
||||
ClassLoadsDomain.iter report_load astate )
|
||||
|
||||
|
||||
let analyze_procedure ({InterproceduralAnalysis.proc_desc} as analysis_data) =
|
||||
let proc_name = Procdesc.get_proc_name proc_desc in
|
||||
L.debug Analysis Verbose "CL: ANALYZING %a@." Procname.pp proc_name ;
|
||||
let loc = Procdesc.get_loc proc_desc in
|
||||
(* load the method's class *)
|
||||
let init =
|
||||
Procname.get_class_type_name proc_name
|
||||
|> Option.fold ~init:ClassLoadsDomain.bottom ~f:(load_class analysis_data loc)
|
||||
in
|
||||
let post = Procdesc.fold_instrs proc_desc ~init ~f:(exec_instr analysis_data) in
|
||||
report_loads analysis_data post ;
|
||||
L.debug Analysis Verbose "CL: FINISHED ANALYZING %a@." Procname.pp proc_name ;
|
||||
Some post
|
@ -1,11 +0,0 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
val analyze_procedure :
|
||||
ClassLoadsDomain.summary InterproceduralAnalysis.t -> ClassLoadsDomain.summary option
|
@ -1,56 +0,0 @@
|
||||
(*
|
||||
* 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
|
||||
|
||||
module ClassLoad = struct
|
||||
include String
|
||||
|
||||
let describe = pp
|
||||
end
|
||||
|
||||
module Event =
|
||||
ExplicitTrace.MakeTraceElemModuloLocation (ClassLoad) (ExplicitTrace.DefaultCallPrinter)
|
||||
include Event.FiniteSet
|
||||
|
||||
let add ({Event.trace} as x) astate =
|
||||
match find_opt x astate with
|
||||
| None ->
|
||||
add x astate
|
||||
| Some ({Event.trace= trace'} as x') ->
|
||||
if Int.( <= ) (List.length trace') (List.length trace) then astate
|
||||
else remove x' astate |> add x
|
||||
|
||||
|
||||
let union xs ys = fold add xs ys
|
||||
|
||||
let add_string loc astate clazz =
|
||||
L.debug Analysis Verbose "CL: LOADING class %s@." clazz ;
|
||||
let new_event = Event.make clazz loc in
|
||||
add new_event astate
|
||||
|
||||
|
||||
let mem_typename name astate =
|
||||
let str_name = Typ.Name.name name in
|
||||
(* comparison of elements is only over the string component so fake the rest *)
|
||||
let fake_event = Event.make str_name Location.dummy in
|
||||
mem fake_event astate
|
||||
|
||||
|
||||
let add_typename loc astate name = Typ.Name.name name |> add_string loc astate
|
||||
|
||||
let integrate_summary callee_pname loc astate callee_summary =
|
||||
L.debug Analysis Verbose "CL: ADDING SUMMARY OF %a@." Procname.pp callee_pname ;
|
||||
let callsite = CallSite.make callee_pname loc in
|
||||
let summary = with_callsite callee_summary callsite in
|
||||
union astate summary
|
||||
|
||||
|
||||
type summary = t
|
||||
|
||||
let pp_summary = pp
|
@ -1,26 +0,0 @@
|
||||
(*
|
||||
* 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 ClassLoad : ExplicitTrace.Element with type t = string
|
||||
|
||||
module Event : ExplicitTrace.TraceElem with type elem_t = ClassLoad.t
|
||||
|
||||
include AbstractDomain.WithBottom
|
||||
|
||||
type summary = t
|
||||
|
||||
val pp_summary : F.formatter -> summary -> unit
|
||||
|
||||
val mem_typename : Typ.Name.t -> t -> bool
|
||||
|
||||
val add_typename : Location.t -> t -> Typ.Name.t -> t
|
||||
|
||||
val integrate_summary : Procname.t -> Location.t -> t -> summary -> t
|
||||
|
||||
val iter : (Event.t -> unit) -> t -> unit
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Arrays {
|
||||
static ArraysB[] arrayB = new ArraysB[10];
|
||||
|
||||
static ArraysC[] arrayC = ArraysD.arrayC;
|
||||
|
||||
public static void main(String args[]) {
|
||||
ArraysA[] arrayA = new ArraysA[10];
|
||||
|
||||
System.out.println(ArraysF.foo()[0]);
|
||||
|
||||
ArraysG[][] arrayG = new ArraysG[10][10];
|
||||
}
|
||||
}
|
||||
|
||||
class ArraysA {}
|
||||
|
||||
class ArraysB {}
|
||||
|
||||
class ArraysC {}
|
||||
|
||||
class ArraysD {
|
||||
static ArraysC[] arrayC = new ArraysC[10];
|
||||
}
|
||||
|
||||
class ArraysE {}
|
||||
|
||||
class ArraysF {
|
||||
static ArraysE[] foo() {
|
||||
return new ArraysE[10];
|
||||
}
|
||||
}
|
||||
|
||||
class ArraysG {}
|
@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Basic {
|
||||
public static void main(String args[]) {
|
||||
new BasicObj();
|
||||
}
|
||||
}
|
||||
|
||||
class BasicObj {}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Casts {
|
||||
// there is no way to test this dynamically, as to avoid an exception
|
||||
// we must first create a CastsC object. So this is more for documenting.
|
||||
public static void main(String args[]) {
|
||||
CastsA.downcast(new CastsC());
|
||||
}
|
||||
}
|
||||
|
||||
class CastsA {
|
||||
static CastsC downcast(CastsB c) {
|
||||
return (CastsC) c;
|
||||
}
|
||||
|
||||
static boolean checkclass(CastsD d) {
|
||||
return d instanceof CastsE;
|
||||
}
|
||||
}
|
||||
|
||||
class CastsB {}
|
||||
|
||||
class CastsC extends CastsB {}
|
||||
|
||||
class CastsD {}
|
||||
|
||||
class CastsE extends CastsD {}
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Catch {
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
foo();
|
||||
} catch (CatchA a) {
|
||||
}
|
||||
}
|
||||
|
||||
static void foo() throws CatchA {}
|
||||
}
|
||||
|
||||
class CatchA extends Exception {
|
||||
static final long serialVersionUID = 0L;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Const {
|
||||
public static void main(String args[]) {
|
||||
synchronized (ConstA.class) {
|
||||
}
|
||||
|
||||
java.lang.Class<ConstB> b = ConstB.class;
|
||||
}
|
||||
}
|
||||
|
||||
class ConstA {}
|
||||
|
||||
class ConstB {}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Fields {
|
||||
FieldsA a;
|
||||
|
||||
Fields() {
|
||||
a = new FieldsA();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
System.out.println(a.b.c);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
Fields f = new Fields();
|
||||
f.foo();
|
||||
}
|
||||
}
|
||||
|
||||
class FieldsA {
|
||||
FieldsA() {
|
||||
b = new FieldsB();
|
||||
}
|
||||
|
||||
FieldsB b;
|
||||
}
|
||||
|
||||
class FieldsB {
|
||||
FieldsB() {
|
||||
c = null;
|
||||
}
|
||||
|
||||
FieldsCNoLoad c;
|
||||
}
|
||||
|
||||
class FieldsCNoLoad {}
|
@ -1,70 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# Convention: each Java file must have a single [main] method in the eponymous public class
|
||||
# This is the root for class loads analysis, and the one executed in the JVM to log loads.
|
||||
# There is no .exp file committed, the loads are generated by JVM during testing.
|
||||
|
||||
TESTS_DIR = ../../..
|
||||
|
||||
INFERPRINT_OPTIONS = --issues-tests
|
||||
|
||||
SOURCES = $(wildcard *.java)
|
||||
LOADS = $(patsubst %.java,%.loads,$(SOURCES))
|
||||
OBJECTS = $(patsubst %.java,%.class,$(SOURCES))
|
||||
CLASS_LOADS_OPT = $(patsubst %.java,--class-loads-roots %.main,$(SOURCES))
|
||||
INFER_OPTIONS = --class-loads-only $(CLASS_LOADS_OPT) --debug-exceptions
|
||||
CLEAN_EXTRA = *.class *.loads loads.exp loads.exp.test
|
||||
|
||||
include $(TESTS_DIR)/java.make
|
||||
|
||||
INFER_OUT ?= infer-out$(TEST_SUFFIX)
|
||||
|
||||
include $(TESTS_DIR)/base.make
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(REMOVE_DIR) codetoanalyze com issues.exp.test$(TEST_SUFFIX) $(CLEAN_EXTRA)
|
||||
ifneq ($(INFER_OUT),.)
|
||||
$(REMOVE_DIR) $(INFER_OUT)
|
||||
endif
|
||||
|
||||
PROJECT_ROOT ?= $(TESTS_DIR)
|
||||
|
||||
|
||||
issues.exp.test$(TEST_SUFFIX): $(INFER_OUT)/report.json $(INFER_BIN)
|
||||
$(QUIET)$(INFER_BIN) report -q --results-dir $(<D) \
|
||||
$(INFERPRINT_OPTIONS) $@
|
||||
|
||||
infer-out/report.json: $(JAVA_DEPS) $(SOURCES) $(MAKEFILE_LIST)
|
||||
$(QUIET)$(call silent_on_success,Testing infer/java in $(TEST_REL_DIR),\
|
||||
$(INFER_BIN) --project-root $(PROJECT_ROOT) \
|
||||
$(INFER_OPTIONS) -- \
|
||||
$(JAVAC) -cp $(CLASSPATH) $(SOURCES))
|
||||
|
||||
%.loads: %.class
|
||||
ifeq ($(JDK11_ENABLED),yes)
|
||||
$(QUIET)java -verbose:class $* | grep "\[info\]\[class,load\]" | cut -f2 -d' ' | grep -vE "^(java|sun|jdk|opened:)" > $@
|
||||
else
|
||||
$(QUIET)java -verbose:class $* | grep "^\[Loaded" | cut -f2 -d' ' | grep -vE "^(java|sun)" > $@
|
||||
endif
|
||||
|
||||
# infer compiles as well as analyses; dependency ensures class files are generated
|
||||
$(OBJECTS): issues.exp.test$(TEST_SUFFIX)
|
||||
|
||||
loads.exp: $(LOADS)
|
||||
$(QUIET)for F in $(LOADS) ; do sed -e "s#^#$(TEST_REL_DIR)/$${F%.*}.java, #" $$F ; done | sort > loads.exp
|
||||
|
||||
loads.exp.test: issues.exp.test$(TEST_SUFFIX)
|
||||
$(QUIET)cat $< | sed 's/^\([^,]*\),.*[,\[]\([^,]*\)\]$$/\1, \2/' | sort > $@
|
||||
|
||||
.PHONY: test
|
||||
test: loads.exp loads.exp.test
|
||||
$(QUIET)cd $(TESTS_DIR) && \
|
||||
$(call check_no_diff,$(TEST_REL_DIR)/loads.exp,$(TEST_REL_DIR)/loads.exp.test)
|
||||
|
||||
.PHONY: replace
|
||||
replace:
|
||||
@:
|
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Prune {
|
||||
public static void main(String args[]) {
|
||||
if (PruneA.f == 0) {
|
||||
System.out.println(PruneB.g < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PruneA {
|
||||
static int f;
|
||||
}
|
||||
|
||||
class PruneB {
|
||||
static int g;
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Static {
|
||||
// this loads StaticA
|
||||
static StaticA s = new StaticA();
|
||||
|
||||
public static void main(String args[]) {
|
||||
// this loads StaticD
|
||||
System.out.println(StaticD.static_data);
|
||||
}
|
||||
}
|
||||
|
||||
class StaticA {
|
||||
// this loads StaticB
|
||||
static StaticB b = new StaticB();
|
||||
}
|
||||
|
||||
class StaticB {
|
||||
// no load here
|
||||
static StaticCNoLoad c = null;
|
||||
}
|
||||
|
||||
class StaticCNoLoad {}
|
||||
|
||||
class StaticD {
|
||||
static int static_data = 5;
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Super {
|
||||
public static void main(String args[]) {
|
||||
// this loads SuperB and SuperA
|
||||
System.out.println(SuperB.static_data);
|
||||
|
||||
// this loads SuperC and SuperD
|
||||
SuperD.foo();
|
||||
}
|
||||
}
|
||||
|
||||
class SuperA {}
|
||||
|
||||
class SuperB extends SuperA {
|
||||
static int static_data = 5;
|
||||
}
|
||||
|
||||
class SuperC {}
|
||||
|
||||
class SuperD extends SuperC {
|
||||
public static void foo() {}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Unique {
|
||||
public static void main(String args[]) {
|
||||
// two loads for the same class with distinct locations and traces should be merged into one
|
||||
UniqueObj u = new UniqueObj();
|
||||
u.foo();
|
||||
}
|
||||
}
|
||||
|
||||
class UniqueObj {
|
||||
void foo() {}
|
||||
}
|
Loading…
Reference in new issue