diff --git a/java/change-notes/2021-04-14-membertype.md b/java/change-notes/2021-04-14-membertype.md new file mode 100644 index 000000000000..910a5c914232 --- /dev/null +++ b/java/change-notes/2021-04-14-membertype.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* A CodeQL class `MemberType` is introduced to describe nested classes. Its `getQualifiedName` method returns `$`-delimited nested type names (for example, `mypackage.Outer$Middle$Inner`), where previously the same type would be named differently depending on whether it was addressed as a `NestedType` or a `Member`. diff --git a/java/ql/src/Performance/InnerClassCouldBeStatic.ql b/java/ql/src/Performance/InnerClassCouldBeStatic.ql index a18d801549d3..5dba77761c6e 100644 --- a/java/ql/src/Performance/InnerClassCouldBeStatic.ql +++ b/java/ql/src/Performance/InnerClassCouldBeStatic.ql @@ -100,8 +100,7 @@ predicate potentiallyStatic(InnerClass c) { m = a.getEnclosingCallable() and m.getDeclaringType() = c ) and - not c instanceof AnonymousClass and - not c instanceof LocalClass and + c instanceof MemberType and forall( InnerClass other // If nested and non-static, ... | diff --git a/java/ql/src/semmle/code/java/PrintAst.qll b/java/ql/src/semmle/code/java/PrintAst.qll index 4e219603024f..6a74c15316c0 100644 --- a/java/ql/src/semmle/code/java/PrintAst.qll +++ b/java/ql/src/semmle/code/java/PrintAst.qll @@ -433,9 +433,7 @@ final class ClassInterfaceNode extends ElementNode { or result.(FieldDeclaration).getAField().getDeclaringType() = ty or - result.(NestedType).getEnclosingType().getSourceDeclaration() = ty and - not result instanceof AnonymousClass and - not result instanceof LocalClass + result.(MemberType).getEnclosingType().getSourceDeclaration() = ty or isInitBlock(ty, result) } diff --git a/java/ql/src/semmle/code/java/Type.qll b/java/ql/src/semmle/code/java/Type.qll index 10d9b8b3a31d..5eaa52ee5482 100755 --- a/java/ql/src/semmle/code/java/Type.qll +++ b/java/ql/src/semmle/code/java/Type.qll @@ -517,7 +517,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { /** Holds if this is a top-level type, which is not nested inside any other types. */ predicate isTopLevel() { this instanceof TopLevelType } - /** Holds if this type is declared in a specified package with the specified name. */ + /** + * Holds if this type is declared in a specified package with the specified name. + * + * For nested types the name of the nested type is prefixed with a `$` and appended + * to the name of the enclosing type, which might be a nested type as well. + */ predicate hasQualifiedName(string package, string type) { this.getPackage().hasName(package) and type = this.nestedName() } @@ -532,7 +537,12 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { } /** - * Gets the qualified name of this type. + * Gets the qualified name of this type, consisting of the package name followed by + * a `.` and the name of this type. + * + * For nested types the name of the nested type is prefixed with a `$` and appended + * to the name of the enclosing type, which might be a nested type as well. For example: + * `java.lang.Thread$State`. */ string getQualifiedName() { exists(string pkgName | pkgName = getPackage().getName() | @@ -540,7 +550,13 @@ class RefType extends Type, Annotatable, Modifiable, @reftype { ) } - /** Gets the nested name of this type. */ + /** + * Gets the nested name of this type. + * + * If this type is not a nested type, the result is the same as `getName()`. + * Otherwise the name of the nested type is prefixed with a `$` and appended to + * the name of the enclosing type, which might be a nested type as well. + */ string nestedName() { not this instanceof NestedType and result = this.getName() or @@ -788,6 +804,21 @@ class NestedType extends RefType { } } +/** + * A nested type which is a direct member of the enclosing type, + * that is, neither an anonymous nor local class. + */ +class MemberType extends NestedType, Member { + /** + * Gets the qualified name of this member type. + * + * The qualified name consists of the package name, a `.`, the name of the declaring + * type (which might be a nested or member type as well), followed by a `$` and the + * name of this member type. For example: `java.lang.Thread$State`. + */ + override string getQualifiedName() { result = NestedType.super.getQualifiedName() } +} + /** * A class declared within another type. * @@ -797,8 +828,9 @@ class NestedType extends RefType { class NestedClass extends NestedType, Class { } /** - * An inner class is a nested class that is neither - * explicitly nor implicitly declared static. + * An inner class is a nested class that is neither explicitly nor + * implicitly declared static. This includes anonymous and local + * classes. */ class InnerClass extends NestedClass { InnerClass() { not this.isStatic() } diff --git a/java/ql/src/semmle/code/java/frameworks/play/Play.qll b/java/ql/src/semmle/code/java/frameworks/play/Play.qll index 0498ba9317c5..96c45cb22449 100644 --- a/java/ql/src/semmle/code/java/frameworks/play/Play.qll +++ b/java/ql/src/semmle/code/java/frameworks/play/Play.qll @@ -44,14 +44,8 @@ class PlayAddCsrfTokenAnnotation extends Annotation { /** * The type `play.libs.F.Promise`. */ -class PlayAsyncResultPromise extends Member { - PlayAsyncResultPromise() { - exists(Class c | - c.hasQualifiedName("play.libs", "F") and - this = c.getAMember() and - this.getQualifiedName() = "F.Promise" - ) - } +class PlayAsyncResultPromise extends MemberType { + PlayAsyncResultPromise() { hasQualifiedName("play.libs", "F$Promise") } } /**