diff --git a/infer/src/java/jTrans.ml b/infer/src/java/jTrans.ml index 149436a3a..e3dff46f2 100644 --- a/infer/src/java/jTrans.ml +++ b/infer/src/java/jTrans.ml @@ -599,28 +599,47 @@ let rec expression (context : JContext.t) pc expr = let method_invocation (context : JContext.t) loc pc var_opt cn ms sil_obj_opt expr_list invoke_code method_kind = - (* This function tries to recursively search for the classname of the class *) - (* where the method is defined. It returns the classname given as argument*) - (* when this classname cannot be found *) + (* This function tries to recursively search for the classname of the class + where the method is defined. Following Java8 invokevirtual spec, it + searches first in parent classes. Then, if nothing is found, it searches in super + interfaces. If nothing is found, it returns the classname given as argument. *) + let contains_ms_implem node ms = + match node with + | Javalib.JInterface {i_methods= mmap} | Javalib.JClass {c_methods= mmap} -> + if JBasics.MethodMap.mem ms mmap then + match JBasics.MethodMap.find ms mmap with + | Javalib.AbstractMethod _ -> + false + | Javalib.ConcreteMethod _ -> + true + else false + in let resolve_method (context : JContext.t) cn ms = - let rec loop fallback_cn cn = + let rec search_in_parents get_parents cn = match JClasspath.lookup_node cn context.program with | None -> - fallback_cn - | Some node -> ( - if Javalib.defines_method node ms then cn - else - match node with - | Javalib.JInterface _ -> - fallback_cn - | Javalib.JClass jclass -> ( - match jclass.Javalib.c_super_class with - | None -> - fallback_cn - | Some super_cn -> - loop fallback_cn super_cn ) ) + None + | Some node -> + if contains_ms_implem node ms then Some cn + else List.find_map (get_parents node) ~f:(search_in_parents get_parents) + in + let super_classes = function + | Javalib.JInterface _ -> + [] + | Javalib.JClass {c_super_class} -> + Option.to_list c_super_class in - loop cn cn + let super_interfaces = function + | Javalib.JInterface {i_interfaces} -> + i_interfaces + | Javalib.JClass {c_interfaces} -> + c_interfaces + in + match search_in_parents super_classes cn with + | None -> + Option.value ~default:cn (search_in_parents super_interfaces cn) + | Some cn_implem -> + cn_implem in let cn' = resolve_method context cn ms in let tenv = JContext.get_tenv context in diff --git a/infer/tests/codetoanalyze/java/biabduction/DefaultInInterface.java b/infer/tests/codetoanalyze/java/biabduction/DefaultInInterface.java new file mode 100644 index 000000000..19d67180b --- /dev/null +++ b/infer/tests/codetoanalyze/java/biabduction/DefaultInInterface.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +interface I { + default Object defaultMethod1() { + return null; + } + + default Object defaultMethod2() { + return "foo"; + } +} + +public class DefaultInInterface implements I { + + public void bad() { + System.out.println(this.defaultMethod1().toString()); + } + + public void ok() { + System.out.println(this.defaultMethod2().toString()); + } +} diff --git a/infer/tests/codetoanalyze/java/biabduction/issues.exp b/infer/tests/codetoanalyze/java/biabduction/issues.exp index 3b2459e48..806a82fc7 100644 --- a/infer/tests/codetoanalyze/java/biabduction/issues.exp +++ b/infer/tests/codetoanalyze/java/biabduction/issues.exp @@ -38,6 +38,7 @@ codetoanalyze/java/biabduction/CursorLeaks.java, codetoanalyze.java.infer.Cursor codetoanalyze/java/biabduction/CursorNPEs.java, codetoanalyze.java.infer.CursorNPEs.cursorFromContentResolverNPE(java.lang.String):void, 8, NULL_DEREFERENCE, B1, ERROR, [start of procedure cursorFromContentResolverNPE(...)] codetoanalyze/java/biabduction/CursorNPEs.java, codetoanalyze.java.infer.CursorNPEs.cursorFromDownloadManagerNPE(android.app.DownloadManager):int, 5, NULL_DEREFERENCE, B2, ERROR, [start of procedure cursorFromDownloadManagerNPE(...)] codetoanalyze/java/biabduction/CursorNPEs.java, codetoanalyze.java.infer.CursorNPEs.cursorFromMediaNPE():void, 2, NULL_DEREFERENCE, B1, ERROR, [start of procedure cursorFromMediaNPE()] +codetoanalyze/java/biabduction/DefaultInInterface.java, DefaultInInterface.bad():void, 1, NULL_DEREFERENCE, B1, ERROR, [start of procedure bad(),start of procedure defaultMethod1(),return from a call to Object I.defaultMethod1()] codetoanalyze/java/biabduction/DivideByZero.java, codetoanalyze.java.infer.DivideByZero.callDivideByZeroInterProc():int, 1, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure callDivideByZeroInterProc(),start of procedure divideByZeroInterProc(...)] codetoanalyze/java/biabduction/DivideByZero.java, codetoanalyze.java.infer.DivideByZero.divByZeroLocal(java.lang.String):int, 3, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure divByZeroLocal(...)] codetoanalyze/java/biabduction/DivideByZero.java, codetoanalyze.java.infer.DivideByZero.divideByZeroWithStaticField():int, 2, DIVIDE_BY_ZERO, no_bucket, ERROR, [start of procedure divideByZeroWithStaticField(),start of procedure setXToZero(),return from a call to void DivideByZero.setXToZero(),start of procedure divideByZeroInterProc(...)] diff --git a/infer/tests/codetoanalyze/java/quandary/issues.exp b/infer/tests/codetoanalyze/java/quandary/issues.exp index c6a000b8f..ce47acca3 100644 --- a/infer/tests/codetoanalyze/java/quandary/issues.exp +++ b/infer/tests/codetoanalyze/java/quandary/issues.exp @@ -106,7 +106,7 @@ codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.ex codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.reuseIntentBad(android.app.Activity):void, 1, INSECURE_INTENT_HANDLING, no_bucket, ERROR, [Return from Intent Activity.getIntent(),Call to void Activity.startActivity(Intent) with tainted index 1] codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.startWithUri1Bad(android.net.Uri):void, 1, CREATE_INTENT_FROM_URI, no_bucket, ERROR, [Return from Intent.(String,Uri),Call to void Activity.startActivity(Intent) with tainted index 1] codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.startWithUri2Bad(android.net.Uri):void, 1, CREATE_INTENT_FROM_URI, no_bucket, ERROR, [Return from Intent.(String,Uri,Context,Class),Call to void Activity.startActivity(Intent) with tainted index 1] -codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.subclassCallBad(codetoanalyze.java.quandary.IntentSubclass,codetoanalyze.java.quandary.ContextSubclass):void, 3, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void Context.startActivity(Intent) with tainted index 1] +codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.Intents.subclassCallBad(codetoanalyze.java.quandary.IntentSubclass,codetoanalyze.java.quandary.ContextSubclass):void, 3, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void ContextSubclass.startActivity(Intent) with tainted index 1] codetoanalyze/java/quandary/Intents.java, codetoanalyze.java.quandary.MyActivity.startServiceWithTaintedIntent():void, 2, CREATE_INTENT_FROM_URI, no_bucket, ERROR, [Return from Intent.(String,Uri),Call to ComponentName ContextWrapper.startService(Intent) with tainted index 1] codetoanalyze/java/quandary/Interprocedural.java, codetoanalyze.java.quandary.Interprocedural.FP_divergenceInCallee():void, 3, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void InferTaint.inferSensitiveSink(Object) with tainted index 0] codetoanalyze/java/quandary/Interprocedural.java, codetoanalyze.java.quandary.Interprocedural.FP_reassignInCallee():void, 4, QUANDARY_TAINT_ERROR, no_bucket, ERROR, [Return from Object InferTaint.inferSecretSource(),Call to void InferTaint.inferSensitiveSink(Object) with tainted index 0]