From 64289cde4d695cc95db625707335ff12d33f5f54 Mon Sep 17 00:00:00 2001 From: David Pichardie Date: Wed, 19 Feb 2020 08:15:41 -0800 Subject: [PATCH] [Java frontend]Javalib's lambda rewritting is making his way through Infer Reviewed By: ngorogiannis Differential Revision: D19970219 fbshipit-source-id: b14bb36a4 --- infer/src/IR/Cfg.ml | 8 +++++++- infer/src/java/jClasspath.ml | 11 ++++++++--- infer/src/java/jTrans.ml | 10 ++++++---- .../codetoanalyze/java/infer/InvokeDynamic.java | 15 ++++++++++++++- infer/tests/codetoanalyze/java/infer/issues.exp | 2 ++ 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/infer/src/IR/Cfg.ml b/infer/src/IR/Cfg.ml index e10eaa2f1..f6e9d068b 100644 --- a/infer/src/IR/Cfg.ml +++ b/infer/src/IR/Cfg.ml @@ -128,7 +128,13 @@ let proc_inline_synthetic_methods cfg pdesc : unit = let attributes = Procdesc.get_attributes pd in let is_synthetic = attributes.is_synthetic_method in let is_bridge = attributes.is_bridge_method in - if is_access || is_bridge || is_synthetic then + let is_generated_for_lambda = + String.is_substring ~substring:"$Lambda$" (Procname.get_method pn) + in + (* this is a temporary hack in order to stop synthetic inlining on + methods that are generated for lambda rewritting *) + if is_generated_for_lambda then instr + else if is_access || is_bridge || is_synthetic then inline_synthetic_method ret_id_typ etl pd loc |> Option.value ~default:instr else instr | exception (Caml.Not_found | Not_found_s _) -> diff --git a/infer/src/java/jClasspath.ml b/infer/src/java/jClasspath.ml index a31271595..3d5bf326c 100644 --- a/infer/src/java/jClasspath.ml +++ b/infer/src/java/jClasspath.ml @@ -255,9 +255,12 @@ let get_classpath_channel program = program.classpath.channel let get_models program = program.models +(* this string should characterize the methods we generate for lambda rewriting *) +let lambda_str = "$Lambda$" + let add_class cn jclass program = (* [prefix] must be a fresh class name *) - let prefix = JBasics.cn_name cn ^ "$Lambda$" in + let prefix = JBasics.cn_name cn ^ lambda_str in (* we rewrite each class to replace invokedynamic (closure construction) with equivalent old-style Java code that implements a suitable Java interface *) let rewritten_jclass, new_classes = Javalib.remove_invokedynamics jclass ~prefix in @@ -265,7 +268,9 @@ let add_class cn jclass program = (* the rewrite will generate new classes and we add them to the program *) JBasics.ClassMap.iter (fun cn jcl -> program.classmap <- JBasics.ClassMap.add cn jcl program.classmap) - new_classes + new_classes ; + rewritten_jclass + let set_callee_translated program pname = Procname.Hash.replace program.callees pname Translated @@ -286,7 +291,7 @@ let lookup_node cn program = with Caml.Not_found -> ( try let jclass = javalib_get_class (get_classpath_channel program) cn in - add_class cn jclass program ; Some jclass + Some (add_class cn jclass program) with | JBasics.No_class_found _ -> (* TODO T28155039 Figure out when and what to log *) diff --git a/infer/src/java/jTrans.ml b/infer/src/java/jTrans.ml index 430a613f4..863db86f3 100644 --- a/infer/src/java/jTrans.ml +++ b/infer/src/java/jTrans.ml @@ -217,10 +217,12 @@ let get_bytecode cm = L.(die InternalError) "native method %s found in %s@." (JBasics.ms_name ms) (JBasics.cn_name cn) | Javalib.Java t -> - (* Sawja doesn't handle invokedynamic, and it will crash with a Match_failure if we give it - bytecode with this instruction. hack around this problem by converting all invokedynamic's - to invokestatic's that call a method with the same signature as the lambda on - java.lang.Object. this isn't great, but it's a lot better than crashing *) + (* Java frontend doesn't know how to translate Sawja invokedynamics, but most + of them will be rewritten by Javalib before arriving to Sawja. For the + remainings we (still) use this hack that convert an invokedynamic + into an invokestatic that calls a method with the same signature as the lambda. + But the objective is to never have to do that and hope Javalib rewriting + is complete enough *) let bytecode = Lazy.force t in let c_code = Array.map diff --git a/infer/tests/codetoanalyze/java/infer/InvokeDynamic.java b/infer/tests/codetoanalyze/java/infer/InvokeDynamic.java index 6ed554e6c..4c89e1472 100644 --- a/infer/tests/codetoanalyze/java/infer/InvokeDynamic.java +++ b/infer/tests/codetoanalyze/java/infer/InvokeDynamic.java @@ -9,6 +9,7 @@ package codetoanalyze.java.infer; import java.util.Collections; import java.util.List; +import java.util.function.Function; public class InvokeDynamic { @@ -32,7 +33,8 @@ public class InvokeDynamic { }); } - // we won't get this one because we don't actually translate the invocation of the lambda + // we still don't get this one (even with Javalib lambda rewriting) + // because Collections.sort is skipped void FN_npeViaCaptureBad(List list) { String s = null; Collections.sort( @@ -41,4 +43,15 @@ public class InvokeDynamic { return s.compareTo(a); }); } + + Integer npeViaSimpleCapture() { + String s = null; + Function f = (s1) -> s.length(); + return f.apply(null); + } + + Integer npeViaSimpleParamPassing() { + Function f = (s) -> s.length(); + return f.apply(null); + } } diff --git a/infer/tests/codetoanalyze/java/infer/issues.exp b/infer/tests/codetoanalyze/java/infer/issues.exp index 5e118a6e8..54dd0ccab 100644 --- a/infer/tests/codetoanalyze/java/infer/issues.exp +++ b/infer/tests/codetoanalyze/java/infer/issues.exp @@ -100,6 +100,8 @@ codetoanalyze/java/infer/HashMapExample.java, codetoanalyze.java.infer.HashMapEx codetoanalyze/java/infer/IntegerExample.java, codetoanalyze.java.infer.IntegerExample.testIntegerEqualsBad():void, 5, NULL_DEREFERENCE, B1, ERROR, [start of procedure testIntegerEqualsBad(),Taking true branch] codetoanalyze/java/infer/InvokeDynamic.java, codetoanalyze.java.infer.InvokeDynamic.invokeDynamicThenNpeBad(java.util.List):void, 6, NULL_DEREFERENCE, B1, ERROR, [start of procedure invokeDynamicThenNpeBad(...),start of procedure callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_3_3(),return from a call to Comparator InvokeDynamic.callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_3_3(),Skipping sort(...): unknown method] codetoanalyze/java/infer/InvokeDynamic.java, codetoanalyze.java.infer.InvokeDynamic.lambda$npeInLambdaBad$1(java.lang.String,java.lang.String):int, 1, NULL_DEREFERENCE, B1, ERROR, [start of procedure lambda$npeInLambdaBad$1(...)] +codetoanalyze/java/infer/InvokeDynamic.java, codetoanalyze.java.infer.InvokeDynamic.npeViaSimpleCapture():java.lang.Integer, 2, PRECONDITION_NOT_MET, no_bucket, WARNING, [start of procedure npeViaSimpleCapture(),start of procedure callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_10_3(...),return from a call to Function InvokeDynamic.callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_10_3(String)] +codetoanalyze/java/infer/InvokeDynamic.java, codetoanalyze.java.infer.InvokeDynamic.npeViaSimpleParamPassing():java.lang.Integer, 1, PRECONDITION_NOT_MET, no_bucket, WARNING, [start of procedure npeViaSimpleParamPassing(),start of procedure callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_11_0(),return from a call to Function InvokeDynamic.callsite_codetoanalyze.java.infer.InvokeDynamic$Lambda$_11_0()] codetoanalyze/java/infer/JunitAssertion.java, codetoanalyze.java.infer.JunitAssertion.consistentAssertion(codetoanalyze.java.infer.JunitAssertion$A):void, 0, PRECONDITION_NOT_MET, no_bucket, WARNING, [start of procedure consistentAssertion(...),Taking false branch] codetoanalyze/java/infer/JunitAssertion.java, codetoanalyze.java.infer.JunitAssertion.inconsistentAssertion(codetoanalyze.java.infer.JunitAssertion$A):void, 1, NULL_DEREFERENCE, B5, ERROR, [start of procedure inconsistentAssertion(...),Taking false branch] codetoanalyze/java/infer/Lists.java, codetoanalyze.java.infer.Lists.clearCausesEmptinessNPE(java.util.List,int):void, 4, NULL_DEREFERENCE, B1, ERROR, [start of procedure clearCausesEmptinessNPE(...),Taking true branch,Taking true branch]