From b903b2d93261334b49c8a7a6abf18fc61f163eb2 Mon Sep 17 00:00:00 2001 From: Jeremy Dubreil Date: Thu, 8 Feb 2018 12:43:01 -0800 Subject: [PATCH] [infer][java] translate the annotations of the callees Summary: This allows Eradicate to lookup the annotations from the classpath and without requiring the code in the classpath to have been previously analyzed. The benefit is that source files can be analyzed independently of each other as long as the classpath is known. The main goal is to run be able to run Eradicate as a linter without losing warnings. We may have to add some more models of the standard libraries as no `Nullable` on a parameter does not necessarily mean that the method does not accept `null`. Reviewed By: sblackshear Differential Revision: D6921720 fbshipit-source-id: f525269 --- infer/src/backend/paths.ml | 2 +- infer/src/java/jClasspath.ml | 27 ++++++++++++++++-- infer/src/java/jClasspath.mli | 10 +++++++ infer/src/java/jFrontend.ml | 1 + infer/src/java/jMain.ml | 8 ++++++ infer/src/java/jTrans.ml | 30 ++++++++++++++++++++ infer/src/java/jTrans.mli | 4 +++ infer/tests/build_systems/genrule/issues.exp | 4 +++ 8 files changed, 83 insertions(+), 3 deletions(-) diff --git a/infer/src/backend/paths.ml b/infer/src/backend/paths.ml index f6731f38f..ce7f759ae 100644 --- a/infer/src/backend/paths.ml +++ b/infer/src/backend/paths.ml @@ -464,7 +464,7 @@ end = struct trace := Errlog.make_trace_element level curr_loc descr node_tags :: !trace ; Option.iter ~f:(fun loc -> - if Typ.Procname.is_java pname then + if Typ.Procname.is_java pname && not (SourceFile.is_invalid loc.Location.file) then let definition_descr = Format.sprintf "Definition of %s" (Typ.Procname.to_simplified_string pname) in diff --git a/infer/src/java/jClasspath.ml b/infer/src/java/jClasspath.ml index e82b6ef86..b11f6c2a1 100644 --- a/infer/src/java/jClasspath.ml +++ b/infer/src/java/jClasspath.ml @@ -226,9 +226,15 @@ let load_from_arguments classes_out_path = (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 program = {classpath: Javalib.class_path; models: classmap; mutable classmap: classmap} +type program = + { classpath: Javalib.class_path + ; models: classmap + ; mutable classmap: classmap + ; callees: callee_status Typ.Procname.Hash.t } let get_classmap program = program.classmap @@ -240,6 +246,20 @@ 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 let lookup_node cn program = @@ -270,7 +290,10 @@ let load_program classpath classes = else collect_classes JBasics.ClassMap.empty !models_jar in let program = - {classpath= Javalib.class_path classpath; models; classmap= JBasics.ClassMap.empty} + { classpath= 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@." ; diff --git a/infer/src/java/jClasspath.mli b/infer/src/java/jClasspath.mli index 43bdea897..41426c359 100644 --- a/infer/src/java/jClasspath.mli +++ b/infer/src/java/jClasspath.mli @@ -44,3 +44,13 @@ val load_program : string -> JBasics.ClassSet.t -> program val lookup_node : JBasics.class_name -> program -> JCode.jcode Javalib.interface_or_class option (** retrive a Java node from the classname *) + +val add_missing_callee : + program -> Typ.Procname.t -> JBasics.class_name -> JBasics.method_signature -> unit +(** add the class name of method signature to the list of callees *) + +val set_callee_translated : program -> Typ.Procname.t -> unit +(** set that the CFG for the procedure has been created *) + +val iter_missing_callees : + program -> f:(Typ.Procname.t -> JBasics.class_name -> JBasics.method_signature -> unit) -> unit diff --git a/infer/src/java/jFrontend.ml b/infer/src/java/jFrontend.ml index 800074d4e..10108347e 100644 --- a/infer/src/java/jFrontend.ml +++ b/infer/src/java/jFrontend.ml @@ -141,6 +141,7 @@ let create_icfg source_file linereader program icfg cn node = if Config.dependency_mode && not (is_classname_cached cn) then cache_classname cn ; let translate m = let proc_name = JTransType.translate_method_name m in + JClasspath.set_callee_translated program proc_name ; if JClasspath.is_model proc_name then (* do not translate the method if there is a model for it *) L.(debug Capture Verbose) diff --git a/infer/src/java/jMain.ml b/infer/src/java/jMain.ml index abd5e4aac..a8bab5ecf 100644 --- a/infer/src/java/jMain.ml +++ b/infer/src/java/jMain.ml @@ -79,6 +79,13 @@ let save_tenv tenv = Tenv.store_global tenv +let store_callee_attributes tenv program = + let f proc_name cn ms = + Option.iter ~f:Attributes.store (JTrans.create_callee_attributes tenv program cn ms proc_name) + in + JClasspath.iter_missing_callees program ~f + + (* The program is loaded and translated *) let do_all_files classpath sources classes = L.(debug Capture Quiet) @@ -113,6 +120,7 @@ let do_all_files classpath sources classes = source_files ) sources ; if Config.dependency_mode then capture_libs linereader program tenv ; + store_callee_attributes tenv program ; save_tenv tenv ; JClasspath.cleanup program ; L.(debug Capture Quiet) "done capturing all files@." diff --git a/infer/src/java/jTrans.ml b/infer/src/java/jTrans.ml index ae52d5609..5c04b654c 100644 --- a/infer/src/java/jTrans.ml +++ b/infer/src/java/jTrans.ml @@ -284,6 +284,35 @@ let trans_access = function PredSymb.Protected +let create_callee_attributes tenv program cn ms procname = + let f jclass = + try + let jmethod = Javalib.get_method jclass ms in + let formals = + formals_from_signature program tenv cn ms (JTransType.get_method_kind jmethod) + in + let ret_type = JTransType.return_type program tenv ms in + let access, method_annotation, exceptions, is_abstract = + match jmethod with + | Javalib.AbstractMethod am -> + ( trans_access am.Javalib.am_access + , JAnnotation.translate_method am.Javalib.am_annotations + , List.map ~f:JBasics.cn_name am.Javalib.am_exceptions + , true ) + | Javalib.ConcreteMethod cm -> + ( trans_access cm.Javalib.cm_access + , JAnnotation.translate_method cm.Javalib.cm_annotations + , List.map ~f:JBasics.cn_name cm.Javalib.cm_exceptions + , false ) + in + Some + { (ProcAttributes.default procname) with + ProcAttributes.access; exceptions; method_annotation; formals; ret_type; is_abstract } + with Not_found -> None + in + Option.bind ~f (JClasspath.lookup_node cn program) + + let create_empty_cfg proc_name source_file procdesc = let start_kind = Procdesc.Node.Start_node proc_name in let start_node = Procdesc.create_node procdesc (Location.none source_file) start_kind [] in @@ -625,6 +654,7 @@ let method_invocation (context: JContext.t) loc pc var_opt cn ms sil_obj_opt exp then proc else JTransType.get_method_procname cn' ms method_kind in + JClasspath.add_missing_callee program callee_procname cn' ms ; let call_instrs = let callee_fun = Exp.Const (Const.Cfun callee_procname) in let return_type = diff --git a/infer/src/java/jTrans.mli b/infer/src/java/jTrans.mli index b8cbb0cfb..a3aa0e7ba 100644 --- a/infer/src/java/jTrans.mli +++ b/infer/src/java/jTrans.mli @@ -21,6 +21,10 @@ type translation = val is_java_native : JCode.jcode Javalib.concrete_method -> bool +val create_callee_attributes : + Tenv.t -> JClasspath.program -> JBasics.class_name -> JBasics.method_signature -> Typ.Procname.t + -> ProcAttributes.t option + val create_am_procdesc : SourceFile.t -> JClasspath.program -> JContext.icfg -> Javalib.abstract_method -> Typ.Procname.t -> Procdesc.t diff --git a/infer/tests/build_systems/genrule/issues.exp b/infer/tests/build_systems/genrule/issues.exp index 9b5412472..60cf4a3b5 100644 --- a/infer/tests/build_systems/genrule/issues.exp +++ b/infer/tests/build_systems/genrule/issues.exp @@ -1,3 +1,7 @@ build_systems/genrule/module2/Class2.java, void Class2.dereferenceInterTargetFieldBad(Class1), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `class1.field1` in the call to `toString()` could be null. (Origin: field Class1.field1 at line 55)] build_systems/genrule/module2/Class2.java, void Class2.dereferenceLocalNullableFieldBad(), 1, ERADICATE_NULL_METHOD_CALL, [origin,The value of `Class2.field2` in the call to `toString()` could be null. (Origin: field Class2.field2 at line 51)] +build_systems/genrule/module2/Class2.java, void Class2.followMethodDeclarationOnlyBad(SkipImplementationClass1), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `obj2` in the call to `toString()` could be null. (Origin: call to annotatedNullable() at line 41)] +build_systems/genrule/module2/Class2.java, void Class2.interTargetAbstractNPEBad(Class1), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `obj` in the call to `toString()` could be null. (Origin: call to abstractMayReturnNull() at line 31)] +build_systems/genrule/module2/Class2.java, void Class2.interTargetNPEBad(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `obj` in the call to `toString()` could be null. (Origin: call to returnsNull() at line 26)] +build_systems/genrule/module2/Class2.java, void Class2.interTargetNativeNPEBad(Class1), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `obj` in the call to `toString()` could be null. (Origin: call to nativeMayReturnNull() at line 36)] build_systems/genrule/module2/Class2.java, void Class2.localNPE2Bad(), 2, ERADICATE_NULL_METHOD_CALL, [origin,The value of `obj` in the call to `toString()` could be null. (Origin: null constant at line 21)]