diff --git a/java/ql/lib/change-notes/2023-10-07-MemberRefExpr-getReceiverExpr.md b/java/ql/lib/change-notes/2023-10-07-MemberRefExpr-getReceiverExpr.md new file mode 100644 index 00000000000..150a1615df2 --- /dev/null +++ b/java/ql/lib/change-notes/2023-10-07-MemberRefExpr-getReceiverExpr.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added predicate `MemberRefExpr::getReceiverExpr` diff --git a/java/ql/lib/semmle/code/java/Expr.qll b/java/ql/lib/semmle/code/java/Expr.qll index 81c5dc64aea..a620a089f48 100644 --- a/java/ql/lib/semmle/code/java/Expr.qll +++ b/java/ql/lib/semmle/code/java/Expr.qll @@ -1333,6 +1333,40 @@ class MemberRefExpr extends FunctionalExpr, @memberref { */ override Method asMethod() { result = this.getAnonymousClass().getAMethod() } + private Expr getResultExpr() { + exists(Stmt stmt | + stmt = this.asMethod().getBody().(SingletonBlock).getStmt() and + ( + result = stmt.(ReturnStmt).getResult() + or + // Note: Currently never an ExprStmt, but might change once https://github.com/github/codeql/issues/3605 is fixed + result = stmt.(ExprStmt).getExpr() + ) + ) + } + + /** + * Gets the expression whose member this member reference refers to, that is, the left + * side of the `::`. For example, for the member reference `this::toString` the receiver + * expression is the `this` expression. + * + * This predicate might not have a result in all cases where the receiver expression is + * a type access, for example `MyClass::...`. + */ + Expr getReceiverExpr() { + exists(Expr resultExpr | resultExpr = this.getResultExpr() | + result = resultExpr.(Call).getQualifier() and + // Ignore if the qualifier is a parameter of the method of the synthetic anonymous class + // (this is the case for method refs of instance methods which don't capture the instance, e.g. `Object::toString`) + // Could try to use TypeAccess as result here from child of MemberRefExpr, but that complexity might not be worth it + not this.asMethod().getAParameter().getAnAccess() = result + or + result = resultExpr.(ClassInstanceExpr).getTypeName() + // Don't cover array creation because ArrayCreationExpr currently does not have a predicate + // to easily get ArrayTypeAccess which should probably be the result here + ) + } + /** * Gets the receiver type whose member this expression refers to. The result might not be * the type which actually declares the member. For example, for the member reference `ArrayList::toString`, @@ -1340,15 +1374,7 @@ class MemberRefExpr extends FunctionalExpr, @memberref { * `getReferencedCallable` will have `java.util.AbstractCollection.toString` as result, which `ArrayList` inherits. */ RefType getReceiverType() { - exists(Stmt stmt, Expr resultExpr | - stmt = this.asMethod().getBody().(SingletonBlock).getStmt() and - ( - resultExpr = stmt.(ReturnStmt).getResult() - or - // Note: Currently never an ExprStmt, but might change once https://github.com/github/codeql/issues/3605 is fixed - resultExpr = stmt.(ExprStmt).getExpr() - ) - | + exists(Expr resultExpr | resultExpr = this.getResultExpr() | result = resultExpr.(MethodAccess).getReceiverType() or result = resultExpr.(ClassInstanceExpr).getConstructedType() or result = resultExpr.(ArrayCreationExpr).getType() diff --git a/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.expected b/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.expected index 3260a055b94..a15930f26b8 100644 --- a/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.expected +++ b/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.expected @@ -1,14 +1,54 @@ -| Test.java:24:26:24:51 | ...::... | Test$Generic$Inner<>.Inner<> | Test$Generic$Inner.class:0:0:0:0 | Inner<> | -| Test.java:38:29:38:42 | ...::... | java.lang.Object.toString | Test.java:1:7:1:10 | Test | -| Test.java:39:29:39:42 | ...::... | java.lang.Object.hashCode | Test.java:1:7:1:10 | Test | -| Test.java:40:29:40:39 | ...::... | java.lang.Object.clone | Test.java:1:7:1:10 | Test | -| Test.java:41:40:41:64 | ...::... | java.lang.Object.toString | Test$Generic.class:0:0:0:0 | Generic | -| Test.java:43:23:43:36 | ...::... | java.lang.Object.toString | Test.java:1:7:1:10 | Test | -| Test.java:44:23:44:36 | ...::... | java.lang.Object.hashCode | Test.java:1:7:1:10 | Test | -| Test.java:45:23:45:33 | ...::... | java.lang.Object.clone | Test.java:1:7:1:10 | Test | -| Test.java:48:22:48:35 | ...::... | java.lang.Object.toString | Test.java:1:7:1:10 | Test | -| Test.java:51:13:51:21 | ...::... | Test.Test | Test.java:1:7:1:10 | Test | -| Test.java:52:13:52:32 | ...::... | Test$Generic.Generic | Test$Generic.class:0:0:0:0 | Generic | -| Test.java:56:13:56:22 | ...::... | | file://:0:0:0:0 | int[] | -| Test.java:57:13:57:26 | ...::... | | file://:0:0:0:0 | Generic<>[] | -| Test.java:61:31:61:47 | ...::... | Test.doSomething | Test.java:1:7:1:10 | Test | +getReferencedCallable +| Test.java:26:31:26:52 | ...::... | java.lang.Object.toString | +| Test.java:27:31:27:53 | ...::... | java.lang.Object.toString | +| Test.java:32:27:32:52 | ...::... | Test$Generic$Inner<>.Inner<> | +| Test.java:33:27:33:41 | ...::... | java.lang.Object.toString | +| Test.java:49:29:49:42 | ...::... | java.lang.Object.toString | +| Test.java:50:29:50:42 | ...::... | java.lang.Object.hashCode | +| Test.java:51:29:51:39 | ...::... | java.lang.Object.clone | +| Test.java:52:40:52:64 | ...::... | java.lang.Object.toString | +| Test.java:54:23:54:36 | ...::... | java.lang.Object.toString | +| Test.java:55:23:55:36 | ...::... | java.lang.Object.hashCode | +| Test.java:56:23:56:33 | ...::... | java.lang.Object.clone | +| Test.java:57:23:57:59 | ...::... | java.lang.Object.toString | +| Test.java:57:35:57:48 | ...::... | java.lang.Object.toString | +| Test.java:60:23:60:36 | ...::... | java.lang.Object.toString | +| Test.java:62:23:62:40 | ...::... | Test.staticMethod | +| Test.java:65:13:65:21 | ...::... | Test.Test | +| Test.java:66:13:66:32 | ...::... | Test$Generic.Generic | +| Test.java:75:31:75:47 | ...::... | Test.doSomething | +getReceiverExpr +| Test.java:26:31:26:52 | ...::... | Test.java:26:31:26:42 | Generic<>.this | +| Test.java:27:31:27:53 | ...::... | Test.java:27:31:27:43 | Generic<>.super | +| Test.java:32:27:32:52 | ...::... | Test.java:32:27:32:47 | Generic.Inner<> | +| Test.java:33:27:33:41 | ...::... | Test.java:33:27:33:31 | super | +| Test.java:54:23:54:36 | ...::... | Test.java:54:23:54:26 | this | +| Test.java:55:23:55:36 | ...::... | Test.java:55:23:55:26 | this | +| Test.java:56:23:56:33 | ...::... | Test.java:56:23:56:26 | this | +| Test.java:57:23:57:59 | ...::... | Test.java:57:24:57:48 | (...)... | +| Test.java:57:35:57:48 | ...::... | Test.java:57:35:57:38 | this | +| Test.java:60:23:60:36 | ...::... | Test.java:60:23:60:26 | this | +| Test.java:62:23:62:40 | ...::... | Test.java:62:23:62:26 | Test | +| Test.java:65:13:65:21 | ...::... | Test.java:65:13:65:16 | Test | +| Test.java:66:13:66:32 | ...::... | Test.java:66:13:66:27 | Generic | +getReceiverType +| Test.java:26:31:26:52 | ...::... | Test.java:19:18:19:24 | Generic | +| Test.java:27:31:27:53 | ...::... | Test.java:16:18:16:26 | BaseClass | +| Test.java:32:27:32:52 | ...::... | Test$Generic$Inner.class:0:0:0:0 | Inner<> | +| Test.java:33:27:33:41 | ...::... | Test.java:16:18:16:26 | BaseClass | +| Test.java:49:29:49:42 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:50:29:50:42 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:51:29:51:39 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:52:40:52:64 | ...::... | Test$Generic.class:0:0:0:0 | Generic | +| Test.java:54:23:54:36 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:55:23:55:36 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:56:23:56:33 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:57:23:57:59 | ...::... | Test.java:10:15:10:22 | Supplier | +| Test.java:57:35:57:48 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:60:23:60:36 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:62:23:62:40 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:65:13:65:21 | ...::... | Test.java:1:7:1:10 | Test | +| Test.java:66:13:66:32 | ...::... | Test$Generic.class:0:0:0:0 | Generic | +| Test.java:70:13:70:22 | ...::... | file://:0:0:0:0 | int[] | +| Test.java:71:13:71:26 | ...::... | file://:0:0:0:0 | Generic<>[] | +| Test.java:75:31:75:47 | ...::... | Test.java:1:7:1:10 | Test | diff --git a/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.ql b/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.ql index 2f83916c50e..5270663a909 100644 --- a/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.ql +++ b/java/ql/test/library-tests/MemberRefExpr/MemberRefExpr.ql @@ -1,10 +1,10 @@ import java -string getReferencedCallable(MemberRefExpr e) { - if exists(e.getReferencedCallable()) - then result = e.getReferencedCallable().getQualifiedName() - else result = "" +query string getReferencedCallable(MemberRefExpr e) { + // Use qualified name because some callables don't have a source location (e.g. `Object.toString`) + result = e.getReferencedCallable().getQualifiedName() } -from MemberRefExpr e -select e, getReferencedCallable(e), e.getReceiverType() +query Expr getReceiverExpr(MemberRefExpr e) { result = e.getReceiverExpr() } + +query RefType getReceiverType(MemberRefExpr e) { result = e.getReceiverType() } diff --git a/java/ql/test/library-tests/MemberRefExpr/Test.java b/java/ql/test/library-tests/MemberRefExpr/Test.java index d477cf659a9..88997a7339b 100644 --- a/java/ql/test/library-tests/MemberRefExpr/Test.java +++ b/java/ql/test/library-tests/MemberRefExpr/Test.java @@ -13,20 +13,31 @@ class Test { public Test() { } - static class Generic { + static class BaseClass { + } + + static class Generic extends BaseClass { public Generic() { } class Inner { public Inner() { } + + void test() { + Supplier s0 = Generic.this::toString; + Supplier s1 = Generic.super::toString; + } } void test() { - Supplier s = Generic.Inner::new; + Supplier s0 = Generic.Inner::new; + Supplier s1 = super::toString; } } void doSomething() { } + static void staticMethod() { } + static class Sub extends Test { } @@ -43,9 +54,12 @@ class Test { Supplier s0 = this::toString; Supplier s1 = this::hashCode; Supplier s2 = this::clone; + Supplier s3 = ((Supplier) this::toString)::toString; // Discards result of method call - Runnable r = this::toString; + Runnable r0 = this::toString; + + Runnable r1 = Test::staticMethod; Supplier[] classInstances = { Test::new,