diff --git a/java/ql/lib/change-notes/2022-09-20-CompilationUnit-getATypeInScope.md b/java/ql/lib/change-notes/2022-09-20-CompilationUnit-getATypeInScope.md new file mode 100644 index 000000000000..dfb7e9390601 --- /dev/null +++ b/java/ql/lib/change-notes/2022-09-20-CompilationUnit-getATypeInScope.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* Added the predicate `CompilationUnit.getATypeInScope()`. diff --git a/java/ql/lib/semmle/code/java/CompilationUnit.qll b/java/ql/lib/semmle/code/java/CompilationUnit.qll index 9b4b58e9a9bd..74396e7086b4 100644 --- a/java/ql/lib/semmle/code/java/CompilationUnit.qll +++ b/java/ql/lib/semmle/code/java/CompilationUnit.qll @@ -31,5 +31,38 @@ class CompilationUnit extends Element, File { */ Module getModule() { cumodule(this, result) } + /** + * Gets a type which is available in the top-level scope of this compilation unit. + * This can be a type: + * - declared in this compilation unit as top-level type + * - imported with an `import` declaration + * - declared in the same package as this compilation unit + * - declared in the package `java.lang` + * + * This predicate not consider "shadowing", it can have types as result whose simple name is + * shadowed by another type in scope. + */ + ClassOrInterface getATypeInScope() { + // See "Shadowing", https://docs.oracle.com/javase/specs/jls/se17/html/jls-6.html#jls-6.4.1 + // Currently shadowing is not considered + result.(TopLevelType).getCompilationUnit() = this + or + exists(Import importDecl | importDecl.getCompilationUnit() = this | + result = + [ + importDecl.(ImportStaticTypeMember).getATypeImport(), + importDecl.(ImportType).getImportedType(), + importDecl.(ImportStaticOnDemand).getATypeImport(), + importDecl.(ImportOnDemandFromType).getAnImport(), + importDecl.(ImportOnDemandFromPackage).getAnImport(), + ] + ) + or + // From same package or java.lang, see https://docs.oracle.com/javase/specs/jls/se17/html/jls-7.html + result.(TopLevelType).getPackage() = this.getPackage() + or + result.(TopLevelType).getPackage().hasName("java.lang") + } + override string getAPrimaryQlClass() { result = "CompilationUnit" } } diff --git a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql index 7e2738af1e1b..3111c704b4d3 100644 --- a/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql +++ b/java/ql/src/Advisory/Documentation/ImpossibleJavadocThrows.ql @@ -11,22 +11,20 @@ import java -RefType getTaggedType(ThrowsTag tag) { +ClassOrInterface getTaggedType(ThrowsTag tag) { result.hasName(tag.getExceptionName()) and - exists(ImportType i | i.getFile() = tag.getFile() | i.getImportedType() = result) + result = tag.getFile().(CompilationUnit).getATypeInScope() } -predicate canThrow(Callable callable, RefType exception) { - exists(string uncheckedException | - uncheckedException = "RuntimeException" or uncheckedException = "Error" - | - exception.getAnAncestor().hasQualifiedName("java.lang", uncheckedException) - ) +predicate canThrow(Callable callable, Class exception) { + exception instanceof UncheckedThrowableType or callable.getAnException().getType().getADescendant() = exception } -from ThrowsTag throwsTag, RefType thrownType, Callable docMethod +// Uses ClassOrInterface as type for thrownType to also cover case where erroneously an interface +// type is declared as thrown exception +from ThrowsTag throwsTag, ClassOrInterface thrownType, Callable docMethod where getTaggedType(throwsTag) = thrownType and docMethod.getDoc().getJavadoc().getAChild*() = throwsTag and diff --git a/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.expected b/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.expected index f94a6f61f33a..5b031604e567 100644 --- a/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.expected +++ b/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.expected @@ -1,2 +1,3 @@ | ImpossibleJavadocThrows.java:9:5:9:12 | @throws | Javadoc for bad1 claims to throw InterruptedException but this is impossible. | | ImpossibleJavadocThrows.java:16:5:16:15 | @exception | Javadoc for bad2 claims to throw Exception but this is impossible. | +| ImpossibleJavadocThrows.java:23:5:23:12 | @throws | Javadoc for bad3 claims to throw Runnable but this is impossible. | diff --git a/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.java b/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.java index 7ba8988c38be..b0b27397359b 100644 --- a/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.java +++ b/java/ql/test/query-tests/Javadoc/ImpossibleJavadocThrows.java @@ -18,6 +18,13 @@ public void bad1() { public void bad2() { } + /** + * + * @throws Runnable + */ + public void bad3() { + } + /** * * @throws InterruptedException