` or `` tags.
+ */
+private predicate hasCodeTags(Javadoc j) {
+ exists(string tag | tag = "pre" or tag = "code" |
+ j.getAChild().(JavadocText).getText().matches("%<" + tag + ">%") and
+ j.getAChild().(JavadocText).getText().matches("%"+ tag + ">%")
+ )
+}
+
+/**
+ * The comment immediately following `c`.
+ */
+private Javadoc getNextComment(Javadoc c) {
+ exists(int n, File f | javadocLines(c, f, _, n) |
+ javadocLines(result, f, n+1, _)
+ )
+}
+
+private predicate javadocLines(Javadoc j, File f, int start, int end) {
+ f = j.getFile() and
+ start = j.getLocation().getStartLine() and
+ end = j.getLocation().getEndLine()
+}
+
+private class JavadocFirst extends Javadoc {
+ JavadocFirst() {
+ not exists(Javadoc prev | this = getNextComment(prev))
+ }
+}
+
+/**
+ * The number of lines that look like code in the comment `first`, or ones that follow it.
+ */
+private int codeCount(JavadocFirst first) {
+ result = sum(Javadoc following |
+ following = getNextComment*(first) and not hasCodeTags(following)
+ |
+ count(JavadocText line | line = following.getAChild() and looksLikeCode(line))
+ )
+}
+
+/**
+ * The number of lines in the comment `first`, or ones that follow it.
+ */
+private int anyCount(JavadocFirst first) {
+ result = sum(Javadoc following |
+ following = getNextComment*(first) and not hasCodeTags(following)
+ |
+ count(JavadocText line | line = following.getAChild() and
+ not exists(string trimmed | trimmed = line.getText().trim() |
+ trimmed.regexpMatch("(|/\\*|/\\*\\*|\\*|\\*/)") or
+ trimmed.matches("@%")
+ )
+ )
+ )
+}
+
+/**
+ * A piece of commented-out code, identified using heuristics.
+ */
+class CommentedOutCode extends JavadocFirst {
+ CommentedOutCode() {
+ anyCount(this) > 0 and
+ ((float)codeCount(this))/((float)anyCount(this)) > 0.5 and
+ not this instanceof JSNIComment and
+ not this instanceof OCNIComment
+ }
+
+ /**
+ * The number of lines that appear to be commented-out code.
+ */
+ int getCodeLines(){
+ result = codeCount(this)
+ }
+
+ private Javadoc getLastSuccessor() {
+ result = getNextComment*(this) and
+ not exists(getNextComment(result))
+ }
+
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ path = getLocation().getFile().getAbsolutePath() and
+ sl = getLocation().getStartLine() and
+ sc = getLocation().getStartColumn() and
+ exists(Location end | end = this.getLastSuccessor().getLocation() |
+ el = end.getEndLine() and
+ ec = end.getEndColumn()
+ )
+ }
+}
diff --git a/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp b/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp
new file mode 100644
index 00000000000..b1a01ba7163
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Comments/TodoComments.qhelp
@@ -0,0 +1,53 @@
+
+
+
+
+
+A comment that includes the word TODO or FIXME often marks a part of
+the code that is incomplete or broken, or highlights ambiguities in the
+software's specification.
+
+For example, this list of comments is typical of those found in real
+programs:
+
+
+TODO: move this code somewhere else
+FIXME: handle this case
+FIXME: find a better solution to this workaround
+TODO: test this
+
+
+
+
+
+It is very important that TODO or FIXME comments are
+not just removed from the code. Each of them must be addressed in some way.
+
+Simpler comments can usually be immediately addressed by fixing the code,
+adding a test, doing some refactoring, or clarifying the intended behavior of
+a feature.
+
+In contrast, larger issues may require discussion, and a significant amount
+of work to address. In these cases it is a good idea to move the comment to an
+issue-tracking system, so that the issue can be tracked
+and prioritized relative to other defects and feature requests.
+
+
+
+
+
+
+Approxion:
+ TODO or not TODO.
+
+
+Wikipedia:
+Comment tags,
+Issue tracking system.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql b/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql
new file mode 100644
index 00000000000..430d23cb4b6
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Comments/TodoComments.ql
@@ -0,0 +1,19 @@
+/**
+ * @name TODO/FIXME comments
+ * @description A comment that contains 'TODO' or 'FIXME' may indicate code that is incomplete or
+ * broken, or it may highlight an ambiguity in the software's specification.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/todo-comment
+ * @tags maintainability
+ * readability
+ * external/cwe/cwe-546
+ */
+import java
+
+from JavadocText c
+where
+ c.getText().matches("%TODO%") or
+ c.getText().matches("%FIXME%")
+select c, "TODO/FIXME comment."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java
new file mode 100644
index 00000000000..4db695bf22f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.java
@@ -0,0 +1,7 @@
+public class Utilities
+{
+ public static int max(int a, int b, int c) {
+ int ret = Math.max(a, b)
+ return ret = Math.max(ret, c); // Redundant assignment
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp
new file mode 100644
index 00000000000..bb88ab26d95
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.qhelp
@@ -0,0 +1,44 @@
+
+
+
+
+
+An assignment is an expression. The value of an assignment expression
+is the value assigned to the variable. This can be useful, for example, when
+initializing two or more variables at once (for example, a = b = 0;).
+However, assigning to a local variable in the expression of a return statement
+is redundant because that value can never be read.
+
+
+
+
+Remove the redundant assignment from the return statement, leaving just the
+right-hand side of the assignment.
+
+
+
+
+In the following example, consider the second assignment to ret. The variable goes
+out of scope when the method returns, and the value assigned to it is never read. Therefore,
+the assignment is redundant. Instead, the last line of the method can be changed to
+return Math.max(ret, c);
+
+
+
+
+
+
+
+
+Java Language Specification:
+
+14.17 The return Statement,
+
+15.26 Assignment Operators.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql
new file mode 100644
index 00000000000..b0789b23816
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/AssignmentInReturn.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Assignment in return statement
+ * @description Assigning to a local variable in a 'return' statement has no effect.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/assignment-in-return
+ * @tags maintainability
+ * readability
+ */
+import java
+
+from AssignExpr e, LocalVariableDecl v
+where
+ e.getDest().(VarAccess).getVariable() = v and
+ e.getParent+() instanceof ReturnStmt
+select e, "Assignment to a local variable in a return statement may have no effect."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java
new file mode 100644
index 00000000000..ab777097cf8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.java
@@ -0,0 +1,21 @@
+class Archive implements Closeable
+{
+ private ZipOutputStream zipStream;
+
+ public Archive(File zip) throws IOException {
+ OutputStream stream = new FileOutputStream(zip);
+ stream = new BufferedOutputStream(stream);
+ zipStream = new ZipOutputStream(stream);
+ }
+
+ public void archive(String name, byte[] content) throws IOException {
+ ZipEntry entry = new ZipEntry(name);
+ zipStream.putNextEntry(entry);
+ // Missing call to 'write'
+ zipStream.closeEntry();
+ }
+
+ public void close() throws IOException {
+ zipStream.close();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp
new file mode 100644
index 00000000000..b7e6dc4611e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.qhelp
@@ -0,0 +1,45 @@
+
+
+
+
+
+The ZipOutputStream class is used to write ZIP files to a file
+or other stream. A ZIP file consists of a number of entries. Usually
+each entry corresponds to a file in the directory structure being zipped. There
+is a method on ZipOutputStream that is slightly confusingly named
+putNextEntry. Despite its name, it does not write a whole entry.
+Instead, it writes the metadata for an entry. The content for that entry
+is then written using the write method. Finally the entry is
+closed using closeEntry.
+
+Therefore, if you call putNextEntry and closeEntry but omit the
+call to write, an empty ZIP file entry is written to the output stream.
+
+
+
+
+Ensure that you include a call to ZipOutputStream.write.
+
+
+
+
+In the following example, the archive method calls putNextEntry and
+closeEntry but the call to write is left out.
+
+
+
+
+
+
+
+
+Java 2 Platform Standard Edition 5.0, API Specification:
+
+ZipOutputStream.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql
new file mode 100644
index 00000000000..abeaae0a60a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/CreatesEmptyZip.ql
@@ -0,0 +1,55 @@
+/**
+ * @name Creates empty ZIP file entry
+ * @description Omitting a call to 'ZipOutputStream.write' when writing a ZIP file to an output
+ * stream means that an empty ZIP file entry is written.
+ * @kind problem
+ * @problem.severity warning
+ * @precision medium
+ * @id java/empty-zip-file-entry
+ * @tags reliability
+ * readability
+ */
+import java
+import semmle.code.java.dataflow.SSA
+
+/** A class that is a descendant of `java.util.zip.ZipOutputStream`. */
+class ZipOutputStream extends Class {
+ ZipOutputStream() {
+ exists(Class zip | zip.hasQualifiedName("java.util.zip", "ZipOutputStream") |
+ this.hasSupertype*(zip)
+ )
+ }
+
+ Method putNextEntry() {
+ (
+ result.getDeclaringType() = this or
+ this.inherits(result)
+ ) and
+ result.getName() = "putNextEntry" and
+ result.getNumberOfParameters() = 1 and
+ result.getAParamType().(Class).hasQualifiedName("java.util.zip", "ZipEntry")
+ }
+
+ Method closeEntry() {
+ (
+ result.getDeclaringType() = this or
+ this.inherits(result)
+ ) and
+ result.getName() = "closeEntry" and
+ result.getNumberOfParameters() = 0
+ }
+}
+
+from ZipOutputStream jos, MethodAccess putNextEntry, MethodAccess closeEntry,
+ RValue putNextQualifier, RValue closeQualifier
+where
+ putNextEntry.getMethod() = jos.putNextEntry() and
+ closeEntry.getMethod() = jos.closeEntry() and
+ putNextQualifier = putNextEntry.getQualifier() and
+ closeQualifier = closeEntry.getQualifier() and
+ adjacentUseUseSameVar(putNextQualifier, closeQualifier) and
+ not exists(RValue other |
+ adjacentUseUseSameVar(other, closeQualifier) and
+ other != putNextQualifier
+ )
+select closeEntry, "Empty ZIP file entry created."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll
new file mode 100644
index 00000000000..d27c740d35f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadLocals.qll
@@ -0,0 +1,123 @@
+/*
+ * Provides classes and predicates for "dead locals": which variables are used, which assignments are useless, etc.
+ */
+
+import java
+import semmle.code.java.dataflow.SSA
+private import semmle.code.java.frameworks.Assertions
+
+private predicate emptyDecl(SsaExplicitUpdate ssa) {
+ exists(LocalVariableDeclExpr decl |
+ decl = ssa.getDefiningExpr() and
+ not exists(decl.getInit()) and
+ not exists(EnhancedForStmt for | for.getVariable() = decl)
+ )
+}
+
+/**
+ * A dead SSA variable. Excludes parameters, and phi nodes are never dead, so only includes `VariableUpdate`s.
+ */
+predicate deadLocal(SsaExplicitUpdate ssa) {
+ ssa.getSourceVariable().getVariable() instanceof LocalScopeVariable and
+ not exists(ssa.getAUse()) and not exists(SsaPhiNode phi | phi.getAPhiInput() = ssa) and not exists(SsaImplicitInit init | init.captures(ssa)) and
+ not emptyDecl(ssa) and
+ not readImplicitly(ssa, _)
+}
+
+/**
+ * A dead SSA variable that is expected to be dead as indicated by an assertion.
+ */
+predicate expectedDead(SsaExplicitUpdate ssa) {
+ deadLocal(ssa) and
+ assertFail(ssa.getBasicBlock(), _)
+}
+
+/**
+ * A dead SSA variable that is overwritten by a live SSA definition.
+ */
+predicate overwritten(SsaExplicitUpdate ssa) {
+ deadLocal(ssa) and
+ exists(SsaExplicitUpdate overwrite |
+ overwrite.getSourceVariable() = ssa.getSourceVariable() and
+ not deadLocal(overwrite) and
+ not overwrite.getDefiningExpr() instanceof LocalVariableDeclExpr and
+ exists(BasicBlock bb1, BasicBlock bb2, int i, int j |
+ bb1.getNode(i) = ssa.getCFGNode() and
+ bb2.getNode(j) = overwrite.getCFGNode()
+ |
+ bb1.getABBSuccessor+() = bb2 or
+ bb1 = bb2 and i < j
+ )
+ )
+}
+
+/**
+ * A local variable with a read access.
+ */
+predicate read(LocalScopeVariable v) {
+ exists(VarAccess va | va = v.getAnAccess() | va.isRValue()) or
+ readImplicitly(_, v)
+}
+
+private predicate readImplicitly(SsaExplicitUpdate ssa, LocalScopeVariable v) {
+ v = ssa.getSourceVariable().getVariable() and
+ exists(TryStmt try | try.getAResourceVariable() = ssa.getDefiningExpr().getDestVar())
+}
+
+/**
+ * A local variable with a write access.
+ */
+predicate assigned(LocalScopeVariable v) {
+ exists(VarAccess va | va = v.getAnAccess() | va.isLValue())
+}
+
+/**
+ * An expression without side-effects.
+ */
+predicate exprHasNoEffect(Expr e) {
+ inInitializer(e) and
+ not exists(Expr bad | bad = e.getAChildExpr*() |
+ bad instanceof Assignment or
+ bad instanceof UnaryAssignExpr or
+ exists(ClassInstanceExpr cie, Constructor c |
+ bad = cie and c = cie.getConstructor().getSourceDeclaration()
+ |
+ constructorHasEffect(c)
+ ) or
+ exists(MethodAccess ma, Method m |
+ bad = ma and m = ma.getMethod().getAPossibleImplementation()
+ |
+ methodHasEffect(m) or not m.fromSource()
+ )
+ )
+}
+
+private predicate inInitializer(Expr e) {
+ exists(LocalVariableDeclExpr decl | e = decl.getInit().getAChildExpr*())
+}
+
+// The next two predicates are somewhat conservative.
+
+private predicate constructorHasEffect(Constructor c) {
+ // Only assign fields of the class - do not call methods,
+ // create new objects or assign any other variables.
+ exists(MethodAccess ma | ma.getEnclosingCallable() = c) or
+ exists(ClassInstanceExpr cie | cie.getEnclosingCallable() = c) or
+ exists(Assignment a | a.getEnclosingCallable() = c |
+ not exists(VarAccess va | va = a.getDest() |
+ va.getVariable() instanceof LocalVariableDecl or
+ exists(Field f | f = va.getVariable() |
+ va.getQualifier() instanceof ThisAccess or
+ not exists(va.getQualifier())
+ )
+ )
+ )
+}
+
+private predicate methodHasEffect(Method m) {
+ exists(MethodAccess ma | ma.getEnclosingCallable() = m) or
+ exists(Assignment a | a.getEnclosingCallable() = m) or
+ exists(ClassInstanceExpr cie | cie.getEnclosingCallable() = m) or
+ exists(ThrowStmt throw | throw.getEnclosingCallable() = m) or
+ m.isNative()
+}
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp
new file mode 100644
index 00000000000..c367fcaead7
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.qhelp
@@ -0,0 +1,29 @@
+
+
+
+
+
+A non-public class or interface that is not used anywhere
+in the program may cause a programmer to waste time and effort maintaining and documenting it.
+
+
+
+
+Ensure that redundant types are removed from the program.
+
+
+
+
+
+
+ Wikipedia:
+ Unreachable code.
+
+
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql
new file mode 100644
index 00000000000..eb2103bbb83
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadRefTypes.ql
@@ -0,0 +1,56 @@
+/**
+ * @name Unused classes and interfaces
+ * @description A non-public class or interface that is not used anywhere in the program wastes
+ * programmer resources.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/unused-reference-type
+ * @tags maintainability
+ * useless-code
+ * external/cwe/cwe-561
+ */
+import java
+import semmle.code.java.Reflection
+
+/**
+ * A class or interface that is not used anywhere.
+ */
+predicate dead(RefType dead) {
+ dead.fromSource() and
+ // Nothing depends on this type.
+ not exists(RefType s | s.getMetrics().getADependency() = dead) and
+
+ // Exclude Struts or JSP classes (marked with Javadoc tags).
+ not exists(JavadocTag tag, string x |
+ tag = dead.getDoc().getJavadoc().getATag(x) and
+ (x.matches("@struts%") or x.matches("@jsp%"))
+ ) and
+ // Exclude public types.
+ not dead.isPublic() and
+ // Exclude results that have a `main` method.
+ not dead.getAMethod().hasName("main") and
+ // Exclude results that are referenced in XML files.
+ not exists(XMLAttribute xla | xla.getValue() = dead.getQualifiedName()) and
+ // Exclude type variables.
+ not dead instanceof BoundedType and
+ // Exclude JUnit tests.
+ not dead.getASupertype*().hasName("TestCase") and
+ // Exclude enum types.
+ not dead instanceof EnumType and
+ // Exclude classes that look like they may be reflectively constructed.
+ not dead.getAnAnnotation() instanceof ReflectiveAccessAnnotation and
+
+ // Insist all source ancestors are dead as well.
+ forall(RefType t | t.fromSource() and t = dead.getASupertype+() | dead(t))
+}
+
+from RefType t, string kind
+where
+ dead(t) and
+ (
+ t instanceof Class and kind = "class" or
+ t instanceof Interface and kind = "interface"
+ )
+select t, "Unused " + kind + ": " + t.getName() + " is not referenced within this codebase. " +
+ "If not used as an external API it should be removed."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java
new file mode 100644
index 00000000000..f0d0c4afbc5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.java
@@ -0,0 +1,7 @@
+Person find(String name) {
+ Person result;
+ for (Person p : people.values())
+ if (p.getName().equals(name))
+ result = p; // Redundant assignment
+ result = people.get(name);
+ return result;
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp
new file mode 100644
index 00000000000..6125bef380e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.qhelp
@@ -0,0 +1,35 @@
+
+
+
+
+
+A value is assigned to a local variable, but whenever the variable is subsequently
+read, there has been at least one other assignment to that variable. This means
+that the original assignment is suspect, because the state of the local variable that
+it creates is never used.
+
+
+
+Ensure that you check the control and data flow in the method carefully.
+If a value is really not needed, consider omitting the assignment. Be careful,
+though: if the right-hand side has a side-effect (like performing a method call),
+it is important to keep this to preserve the overall behavior.
+
+
+
+
+In the following example, the value assigned to result on line 5 is always
+overwritten (line 6) before being read (line 7). This is a strong indicator that
+there is something wrong. By examining the code, we can see that the
+loop in lines 3-5 seems to be left over from an old way of storing the list of
+persons, and line 6 represents the new (and better-performing) way. Consequently,
+we can delete lines 3-5 while preserving behavior.
+
+
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql
new file mode 100644
index 00000000000..b010500a44b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocal.ql
@@ -0,0 +1,51 @@
+/**
+ * @name Assigned value is overwritten
+ * @description An assignment to a local variable that is not used before a further assignment is
+ * made has no effect.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/overwritten-assignment-to-local
+ * @tags maintainability
+ * useless-code
+ * readability
+ * external/cwe/cwe-563
+ */
+import java
+import DeadLocals
+
+predicate minusOne(MinusExpr e) {
+ e.getExpr().(Literal).getValue() = "1"
+}
+
+predicate flowStep(Expr decl, Expr init) {
+ decl = init or
+ exists(Field f | f.isFinal() and decl.(FieldAccess).getField() = f |
+ init = f.getAnAssignedValue()
+ ) or
+ decl.(CastExpr).getExpr() = init
+}
+
+predicate excludedInit(Type t, Expr decl) {
+ exists(Expr init | flowStep(decl, init) |
+ // The `null` literal for reference types.
+ t instanceof RefType and init instanceof NullLiteral or
+ // The default value for primitive types.
+ init = t.(PrimitiveType).getADefaultValue() or
+ // The expression `-1` for integral types.
+ t instanceof IntegralType and minusOne(init)
+ )
+}
+
+from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa
+where
+ def = ssa.getDefiningExpr() and
+ v = ssa.getSourceVariable().getVariable() and
+ deadLocal(ssa) and
+ not expectedDead(ssa) and
+ overwritten(ssa) and
+ not exists(LocalVariableDeclExpr decl | def = decl |
+ excludedInit(decl.getVariable().getType(), decl.getInit())
+ )
+select def,
+ "This assignment to " + v.getName() + " is useless: the value is always overwritten before it is read."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp
new file mode 100644
index 00000000000..adfce3b0764
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.qhelp
@@ -0,0 +1,20 @@
+
+
+
+
+
+A value is assigned to a local variable, but the variable is never read subsequently. This means
+that the original assignment is suspect, because the state of the local variable that
+it creates is never used.
+
+
+
+Ensure that you check the control and data flow in the method carefully.
+If a value is really not needed, consider omitting the assignment. Be careful,
+though: if the right-hand side has a side-effect (like performing a method call),
+it is important to keep this to preserve the overall behavior.
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql
new file mode 100644
index 00000000000..46773ddbbd9
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/DeadStoreOfLocalUnread.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Useless assignment to local variable
+ * @description A value is assigned to a local variable, but the local variable
+ * is only read before the assignment, not after it.
+ * The assignment has no effect: either it should be removed,
+ * or the assigned value should be used.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/useless-assignment-to-local
+ * @tags external/cwe/cwe-561
+ */
+import java
+import DeadLocals
+
+from VariableUpdate def, LocalScopeVariable v, SsaExplicitUpdate ssa
+where
+ def = ssa.getDefiningExpr() and
+ v = ssa.getSourceVariable().getVariable() and
+ deadLocal(ssa) and
+ not expectedDead(ssa) and
+ not overwritten(ssa) and
+ read(v) and
+ not def.(AssignExpr).getSource() instanceof NullLiteral and
+ (def instanceof Assignment or def.(UnaryAssignExpr).getParent() instanceof ExprStmt)
+select
+ def, "This assignment to " + v.getName() + " is useless: the value is never read."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java
new file mode 100644
index 00000000000..a13217a306f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.java
@@ -0,0 +1,21 @@
+class ExtendedLog extends Log
+{
+ // ...
+
+ protected void finalize() {
+ // BAD: This empty 'finalize' stops 'super.finalize' from being executed.
+ }
+}
+
+class Log implements Closeable
+{
+ // ...
+
+ public void close() {
+ // ...
+ }
+
+ protected void finalize() {
+ close();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp
new file mode 100644
index 00000000000..80c32b87b4e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.qhelp
@@ -0,0 +1,38 @@
+
+
+
+
+
+An empty finalize method is useless and may prevent finalization from
+working properly. This is because, unlike a constructor, a finalizer does not implicitly call the
+finalizer of the superclass. Thus, an empty finalizer prevents any finalization logic that is
+defined in any of its superclasses from being executed.
+
+
+
+
+Do not include an empty finalize method.
+
+
+
+
+In the following example, the empty finalize method in class ExtendedLog
+prevents the finalize method in class Log from being called. The result is
+that the log file is not closed. To fix this, remove the empty finalize method.
+
+
+
+
+
+
+
+
+Java Language Specification:
+12.6 Finalization of Class Instances.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql
new file mode 100644
index 00000000000..c200e818af8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/EmptyFinalize.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Empty body of finalizer
+ * @description An empty 'finalize' method is useless and prevents its superclass's 'finalize'
+ * method (if any) from being called.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/empty-finalizer
+ * @tags reliability
+ * readability
+ * external/cwe/cwe-568
+ */
+import java
+
+from FinalizeMethod finalize
+where
+ finalize.fromSource() and
+ not exists(Stmt s | s.getEnclosingCallable() = finalize |
+ not s instanceof Block
+ )
+select finalize, "Empty finalize method."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java
new file mode 100644
index 00000000000..d55d76f835f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.java
@@ -0,0 +1,14 @@
+class FinalizedClass {
+ Object o = new Object();
+ String s = "abcdefg";
+ Integer i = Integer.valueOf(2);
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ //No need to nullify fields
+ this.o = null;
+ this.s = null;
+ this.i = null;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp
new file mode 100644
index 00000000000..c0944725c9b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.qhelp
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+A finalizer does not need to set an object's fields to null to help the garbage collector. At the point in the Java object life-cycle when the finalize method is
+called, the object is no longer reachable from the garbage collection roots. Explicitly setting the object's fields to null does not cause the referenced
+objects to be collected by the garbage collector any earlier, and may even adversely affect performance.
+
+
+
+The life-cycle of a Java object has 7 stages:
+
+
+ -
+ Created : Memory is allocated for the object and the initializers and constructors have been run.
+
+ -
+ In use : The object is reachable through a chain of strong references from a garbage collection root. A garbage collection
+ root is a special class of variable (which includes variables on the stack of any thread, static variables of any class, and
+ references from Java Native Interface code).
+
+ -
+ Invisible : The object has already gone out of scope, but the stack frame of the method that contained the scope is still
+ in memory. Not all objects transition into this state.
+
+ -
+ Unreachable : The object is no longer reachable through a chain of strong references. It becomes a candidate for garbage
+ collection.
+
+ -
+ Collected : The garbage collector has identified that the object can be deallocated. If it has a finalizer, it is marked for
+ finalization. Otherwise, it is deallocated.
+
+ -
+ Finalized : An object with a
finalize method transitions to this state after the finalize method is completed
+ and the object still remains unreachable.
+
+ -
+ Deallocated : The object is a candidate for deallocation.
+
+
+
+
+The call to the finalize method occurs when the object is in the 'Collected' stage. At that point, it is already unreachable from the
+garbage collection roots so any of its references to other objects no longer contribute to their reference counts.
+
+
+
+
+
+
+Ensure that the finalizer does not contain any null assignments because they are unlikely to help garbage collection.
+
+
+
+If a finalizer does nothing but nullify an object's fields, it is best to completely remove the finalizer. Objects with finalizers
+severely affect performance, and you should avoid defining finalize where possible.
+
+
+
+
+
+In the following example, finalize unnecessarily assigns the object's fields to null.
+
+
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition), Item 7. Addison-Wesley, 2008.
+
+
+ IBM developerWorks:
+ Explicit nulling.
+
+
+ Oracle Technology Network:
+
+ How to Handle Java Finalization's Memory-Retention Issues
+ .
+
+
+ S. Wilson and J. Kesselman, Java Platform Performance: Strategies and Tactics, 1st ed.,
+ Appendix A. Prentice Hall, 2001.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql
new file mode 100644
index 00000000000..35a3b23ac14
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/FinalizerNullsFields.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Finalizer nulls fields
+ * @description Setting fields to 'null' in a finalizer does not cause the object to be collected
+ * by the garbage collector any earlier, and may adversely affect performance.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/finalizer-nulls-fields
+ * @tags efficiency
+ * maintainability
+ */
+import java
+
+from FinalizeMethod m, Assignment assign, FieldAccess lhs, NullLiteral null
+where
+ assign.getEnclosingCallable() = m and
+ null.getParent() = assign and
+ lhs = assign.getDest() and
+ lhs.getField().getDeclaringType() = m.getDeclaringType().getASupertype*() and
+ m.fromSource()
+select assign, "Finalizer nulls fields."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java
new file mode 100644
index 00000000000..d5ee2812441
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.java
@@ -0,0 +1,9 @@
+interface I {
+ int clone();
+}
+
+class C implements I {
+ public int clone() {
+ return 23;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp
new file mode 100644
index 00000000000..78e13e2f707
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.qhelp
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+An interface that contains methods whose return types clash with protected
+methods on java.lang.Object can never be implemented, because
+methods cannot be overloaded based simply on their return type.
+
+
+
+
+
+If the interface is useful, name methods so that they
+do not clash with methods in Object. Otherwise you should delete the interface.
+
+
+
+
+
+In the following example, the interface I is useless because the
+clone method must return type java.lang.Object:
+
+
+
+
+Any attempt to implement the interface produces an error:
+
+
+
+InterfaceCannotBeImplemented.java:6: clone() in C cannot override
+ clone() in java.lang.Object; attempting to use incompatible return
+ type
+found : int
+required: java.lang.Object
+ public int clone() {
+ ^
+1 error
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+Java Language Specification, Third Edition:
+9.2 Interface Members.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql
new file mode 100644
index 00000000000..8c1a1bf4741
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/InterfaceCannotBeImplemented.ql
@@ -0,0 +1,29 @@
+/**
+ * @name Interface cannot be implemented
+ * @description An interface method that is incompatible with a protected method on
+ * 'java.lang.Object' means that the interface cannot be implemented.
+ * @kind problem
+ * @problem.severity warning
+ * @precision very-high
+ * @id java/unimplementable-interface
+ * @tags maintainability
+ * useless-code
+ */
+
+import java
+
+Method protectedObjectMethod(string signature) {
+ result.getSignature() = signature and
+ result.isProtected() and
+ result.getDeclaringType() instanceof TypeObject
+}
+
+from Method method, Method objMethod, Interface impossible
+where
+ method.getDeclaringType() = impossible and
+ objMethod = protectedObjectMethod(method.getSignature()) and
+ not hasSubtype*(objMethod.getReturnType(), method.getReturnType())
+select method,
+ "This method's return type conflicts with Object." + method.getName() + " so $@ can never be implemented.",
+ impossible,
+ impossible.getName()
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp
new file mode 100644
index 00000000000..ef1451768f5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.qhelp
@@ -0,0 +1,21 @@
+
+
+
+
+
+A local variable is initialized, but the variable is never read or written to subsequently. This suggests
+that the local variable is either useless and should be removed, or that the value was intended to be used
+somewhere.
+
+
+
+
+Ensure that you check the control and data flow in the method carefully.
+If a value is really not needed, consider removing the variable. Be careful,
+though: if the right-hand side has a side-effect (like performing a method call),
+it is important to keep this to preserve the overall behavior.
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql
new file mode 100644
index 00000000000..e4989436d54
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalInitialisedButNotUsed.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Local variable is initialized but not used
+ * @description A local variable is initialized once, but never read or written to. Either the local variable is useless, or its value was intended to be used but is not.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/unused-initialized-local
+ * @tags external/cwe/cwe-563
+ */
+import java
+import DeadLocals
+
+from LocalVariableDeclExpr ve, LocalVariableDecl v
+where
+ v = ve.getVariable() and
+ not assigned(v) and
+ not read(v) and
+ exists(ve.getInit()) and
+ not exprHasNoEffect(ve.getInit())
+select v, "Local variable " + v.getName() + " is never read or written to after it is initialised."
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp
new file mode 100644
index 00000000000..ea7474edc4a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.qhelp
@@ -0,0 +1,21 @@
+
+
+
+
+
+A local variable is assigned a value, but the variable is never read. This suggests
+that the local variable is either useless and should be removed, or that the value was intended to be used
+somewhere.
+
+
+
+
+Ensure that you check the control and data flow in the method carefully.
+If a value is really not needed, consider removing the variable. Be careful,
+though: if the right-hand side has a side-effect (like performing a method call),
+it is important to keep this to preserve the overall behavior.
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql
new file mode 100644
index 00000000000..0ceff67dc6e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/LocalNotRead.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Local variable is never read
+ * @description A local variable is written to, but never read. Either the local variable is useless, or its value was intended to be used but is not.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/assigned-local-unread
+ */
+import java
+import DeadLocals
+
+from LocalScopeVariable v
+where
+ assigned(v) and // Only assignments, not initialization
+ not read(v)
+select v, "Local variable " + v.getName() + " is only assigned to, never read."
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java
new file mode 100644
index 00000000000..02c27c6375c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.java
@@ -0,0 +1,16 @@
+class Person {
+ private String name;
+ private int age;
+
+ public Person(String name, int age) {
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+}
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp
new file mode 100644
index 00000000000..2eb7a2ed61a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.qhelp
@@ -0,0 +1,42 @@
+
+
+
+
+
+It is good practice to initialize every field in a constructor explicitly. A field that is never
+assigned any value (except possibly null) just returns
+the default value when it is read, or throws a NullPointerException.
+
+
+
+
+A field whose value is always null (or the
+corresponding default value for primitive types, for example 0) is not particularly
+useful. Ensure that the code contains an assignment or initialization for each field. To help
+satisfy this rule, it is good practice to explicitly initialize every field in the constructor,
+even if the default value is acceptable.
+
+If the field is genuinely never expected to hold a non-default value, check the statements that
+read the field and ensure that they are not making incorrect assumptions about the value of the
+field. Consider completely removing the field and rewriting the statements that read it,
+as appropriate.
+
+
+
+
+In the following example, the private field name is not initialized in the constructor
+(and thus is implicitly set to null), but there is a getter method to access it.
+
+
+
+Therefore, the following code throws a NullPointerException:
+
+Person p = new Person("Arthur Dent", 30);
+int l = p.getName().length();
+
+To fix the code, name should be initialized in the constructor by adding the
+following line: this.name = name;
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql
new file mode 100644
index 00000000000..2b674d013f8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/NonAssignedFields.ql
@@ -0,0 +1,90 @@
+/**
+ * @name Field is never assigned a non-null value
+ * @description A field that is never assigned a value (except possibly 'null') just returns the
+ * default value when it is read.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/unassigned-field
+ * @tags reliability
+ * maintainability
+ * useless-code
+ * external/cwe/cwe-457
+ */
+import java
+import semmle.code.java.Reflection
+
+/**
+ * Holds if `c` is of the form `Class`, where `t` represents `T`.
+ */
+predicate isClassOf(ParameterizedClass c, RefType t) {
+ c.getGenericType() instanceof TypeClass and
+ c.getATypeArgument().getSourceDeclaration() = t.getSourceDeclaration()
+}
+
+/**
+ * Holds if field `f` is potentially accessed by an `AtomicReferenceFieldUpdater`.
+ */
+predicate subjectToAtomicReferenceFieldUpdater(Field f) {
+ exists(Class arfu, Method newUpdater, MethodAccess c |
+ arfu.hasQualifiedName("java.util.concurrent.atomic", "AtomicReferenceFieldUpdater") and
+ newUpdater = arfu.getAMethod() and newUpdater.hasName("newUpdater") and
+ c.getMethod().getSourceDeclaration() = newUpdater and
+ isClassOf(c.getArgument(0).getType(), f.getDeclaringType()) and
+ isClassOf(c.getArgument(1).getType(), f.getType()) and
+ c.getArgument(2).(StringLiteral).getRepresentedString() = f.getName()
+ )
+}
+
+/**
+ * Holds if `f` is ever looked up reflectively.
+ */
+predicate lookedUpReflectively(Field f) {
+ exists(MethodAccess getDeclaredField |
+ isClassOf(getDeclaredField.getQualifier().getType(), f.getDeclaringType()) and
+ getDeclaredField.getMethod().hasName("getDeclaredField") and
+ getDeclaredField.getArgument(0).(StringLiteral).getRepresentedString() = f.getName()
+ )
+}
+
+/**
+ * Holds if `rt` registers a VM observer in its static initialiser.
+ */
+predicate isVMObserver(RefType rt) {
+ exists(Method register |
+ register.getDeclaringType().hasQualifiedName("sun.jvm.hotspot.runtime", "VM") and
+ register.hasName("registerVMInitializedObserver") and
+ register.getAReference().getEnclosingCallable().(StaticInitializer).getDeclaringType() = rt
+ )
+}
+
+from Field f, FieldRead fr
+where
+ f.fromSource() and
+ fr.getField().getSourceDeclaration() = f and
+ not f.getDeclaringType() instanceof EnumType and
+ forall(Assignment ae, Field g |
+ ae.getDest() = g.getAnAccess() and g.getSourceDeclaration() = f
+ |
+ ae.getSource() instanceof NullLiteral
+ ) and
+ not exists(UnaryAssignExpr ua, Field g |
+ ua.getExpr() = g.getAnAccess() and
+ g.getSourceDeclaration() = f
+ ) and
+ not f.isFinal() and
+ // Exclude fields that may be accessed reflectively.
+ not reflectivelyWritten(f) and
+ not lookedUpReflectively(f) and
+ not subjectToAtomicReferenceFieldUpdater(f) and
+ // If an object containing `f` is, or may be, passed to a native method,
+ // assume it initializes the field.
+ not exists(Callable c | c.isNative() |
+ c.getAParameter().getType() = f.getDeclaringType() or
+ c.getAReference().getAnArgument().getType() = f.getDeclaringType() or
+ c.getDeclaringType() = f.getDeclaringType()
+ ) and
+ // Exclude special VM classes.
+ not isVMObserver(f.getDeclaringType())
+select f, "Field " + f.getName() + " never assigned non-null value, yet it is read at $@.",
+ fr, fr.getFile().getStem() + ".java:" + fr.getLocation().getStartLine()
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java
new file mode 100644
index 00000000000..277b87a281b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.java
@@ -0,0 +1,41 @@
+import static java.lang.System.out;
+
+public class PointlessForwardingMethod {
+ private static class Bad {
+ // Violation: This print does nothing but forward to the other one, which is not
+ // called independently.
+ public void print(String firstName, String lastName) {
+ print(firstName + " " + lastName);
+ }
+
+ public void print(String fullName) {
+ out.println("Pointless forwarding methods are bad, " + fullName + "...");
+ }
+ }
+
+ private static class Better1 {
+ // Better: Merge the methods, using local variables to replace the parameters in
+ // the original version.
+ public void print(String firstName, String lastName) {
+ String fullName = firstName + " " + lastName;
+ out.println("Pointless forwarding methods are bad, " + fullName + "...");
+ }
+ }
+
+ private static class Better2 {
+ // Better: If there's no complicated logic, you can often remove the extra
+ // variables entirely.
+ public void print(String firstName, String lastName) {
+ out.println(
+ "Pointless forwarding methods are bad, " +
+ firstName + " " + lastName + "..."
+ );
+ }
+ }
+
+ public static void main(String[] args) {
+ new Bad().print("Foo", "Bar");
+ new Better1().print("Foo", "Bar");
+ new Better2().print("Foo", "Bar");
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp
new file mode 100644
index 00000000000..1047cdbb84e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
+If a class contains two distinct methods of the same name such that:
+- One method is only ever called from the other method.
+- The calling method calls only the other method and nothing else.
+
+Then the first method is no more than a forwarding method for the second and the two methods can
+probably be merged.
+
+
+There are several
+advantages to doing this:
+
+
+
+- It reduces the cognitive overhead involved in keeping track of the various different overloaded forms of a method.
+- If both methods are public, it simplifies the API of their containing class, making it more discoverable to other programmers.
+- It makes it clearer to other programmers that certain methods are called and other methods are not.
+
+
+
+
+In this example, the two print methods in Bad can be merged, as one is simply a forwarder for the other. The two classes Better1 and Better2
+show two alternative ways of merging the methods.
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql
new file mode 100644
index 00000000000..31e051cd77b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/PointlessForwardingMethod.ql
@@ -0,0 +1,39 @@
+/**
+ * @name Pointless forwarding method
+ * @description A method forwards calls to another method of the same name that is not called independently.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/useless-forwarding-method
+ * @tags maintainability
+ */
+
+import java
+
+predicate ignored(Method m) {
+ m.isAbstract() or
+ m.overrides(_)
+}
+
+Method forwarderCandidate(Method forwardee) {
+ result != forwardee and
+ result.getName() = forwardee.getName() and
+ result.getDeclaringType() = forwardee.getDeclaringType() and
+ forex(MethodAccess c | c.getMethod() = forwardee | c.getCaller() = result) and
+ forall(MethodAccess c | c.getCaller() = result | c.getMethod() = forwardee)
+}
+
+from Method forwarder, Method forwardee
+where
+ forwarder = forwarderCandidate(forwardee) and
+ // Exclusions
+ not ignored(forwarder) and
+ not ignored(forwardee) and
+ not exists(VirtualMethodAccess c |
+ c.getMethod() = forwardee and
+ c.getCaller() = forwarder and
+ c.(MethodAccess).hasQualifier()
+ )
+select forwarder.getSourceDeclaration(),
+ "This method is a forwarder for $@, which is not called independently - the methods can be merged.",
+ forwardee.getSourceDeclaration(), forwardee.getName()
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java
new file mode 100644
index 00000000000..ea1f4edc881
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.java
@@ -0,0 +1,25 @@
+// Version containing unread local variable
+public class Cart {
+ private Map- items = ...;
+ public void add(Item i) {
+ Integer quantity = items.get(i);
+ if (quantity = null)
+ quantity = 1;
+ else
+ quantity++;
+ Integer oldQuantity = items.put(i, quantity); // AVOID: Unread local variable
+ }
+}
+
+// Version with unread local variable removed
+public class Cart {
+ private Map
- items = ...;
+ public void add(Item i) {
+ Integer quantity = items.get(i);
+ if (quantity = null)
+ quantity = 1;
+ else
+ quantity++;
+ items.put(i, quantity);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp
new file mode 100644
index 00000000000..fea0b602a71
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.qhelp
@@ -0,0 +1,42 @@
+
+
+
+
+
+
A local variable that is never read is useless.
+
+As a matter of good practice, there should be no unused or useless code. It makes the program
+more difficult to understand and maintain, and can waste a programmer's time.
+
+
+
+
+This rule applies to variables that are never used as well as variables that are only
+written to but never read. In both cases, ensure that no operations are missing that would use the
+local variable. If appropriate, simply remove the declaration. However, if the variable is
+written to, ensure that any side-effects in the assignments are retained. (For further
+details, see the example.)
+
+
+
+
+In the following example, the local variable oldQuantity is assigned a value but
+never read. In the fixed version of the example, the variable is removed but the call to
+items.put in the assignment is retained.
+
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql
new file mode 100644
index 00000000000..c2052344b7b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnreadLocal.ql
@@ -0,0 +1,30 @@
+/**
+ * @name Unread local variable
+ * @description A local variable that is never read is redundant.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/local-variable-is-never-read
+ * @tags maintainability
+ * useless-code
+ * external/cwe/cwe-561
+ */
+
+import java
+
+VarAccess getARead(LocalVariableDecl v) {
+ v.getAnAccess() = result and
+ not exists(Assignment assign | assign.getDest() = result)
+}
+
+predicate readImplicitly(LocalVariableDecl v) {
+ exists(TryStmt t | t.getAResourceDecl().getAVariable() = v.getDeclExpr())
+}
+
+from LocalVariableDecl v
+where
+ not exists(getARead(v)) and
+ // Discarded exceptions are covered by another query.
+ not exists(CatchClause cc | cc.getVariable().getVariable() = v) and
+ not readImplicitly(v)
+select v, "Variable '" + v + "' is never read."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp
new file mode 100644
index 00000000000..eec006ec399
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.qhelp
@@ -0,0 +1,35 @@
+
+
+
+
+
+A field that is neither public nor protected and never accessed
+is typically a leftover from old refactorings or a sign of incomplete or pending
+code changes.
+
+This rule does not apply to a field in a serializable class because it may be accessed during
+serialization and deserialization.
+
+Fields annotated with @SuppressWarnings("unused") are also not reported.
+
+
+
+
+If an unused field is a leftover from old refactorings, you should just remove it. If it indicates
+incomplete or pending code changes, finish making the changes and remove the field if it is not
+needed.
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql
new file mode 100644
index 00000000000..114beaaabc8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedField.ql
@@ -0,0 +1,30 @@
+/**
+ * @name Unused field
+ * @description A field that is never used is probably unnecessary.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/unused-field
+ * @tags maintainability
+ * useless-code
+ * external/cwe/cwe-561
+ */
+import java
+import semmle.code.java.Reflection
+import semmle.code.java.frameworks.Lombok
+
+from Field f
+where
+ not (f.isPublic() or f.isProtected()) and
+ f.fromSource() and
+ not f.getDeclaringType() instanceof EnumType and
+ not exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f) and
+ // Exclude results in generated classes.
+ not f.getDeclaringType() instanceof GeneratedClass and
+ // Exclude fields that may be reflectively read (this includes standard serialization).
+ not reflectivelyRead(f) and
+ // Exclude fields with deliberately suppressed warnings.
+ not f.suppressesWarningsAbout("unused") and
+ // Exclude fields with relevant Lombok annotations.
+ not f instanceof LombokGetterAnnotatedField
+select f, "Unused field " + f.getName() + " in " + f.getDeclaringType().getName() + "."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java
new file mode 100644
index 00000000000..03b6c701820
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.java
@@ -0,0 +1,16 @@
+public class WebStore {
+ public boolean itemIsBeingBought(Item item) {
+ boolean found = false;
+ carts: // AVOID: Unused label
+ for (int i = 0; i < carts.size(); i++) {
+ Cart cart = carts.get(i);
+ for (int j = 0; j < cart.numItems(); j++) {
+ if (item.equals(cart.getItem(j))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ return found;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp
new file mode 100644
index 00000000000..ca936ceff8f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.qhelp
@@ -0,0 +1,49 @@
+
+
+
+
+
+Loop and switch statements can be labeled. These labels
+can serve as targets for break or
+continue statements, to specify which loop or switch statement they refer to.
+
+Apart from serving as such jump targets, the labels have no effect on
+program behavior, which means that having an unused label is suspicious.
+
+
+
+
+If the label is used to document the intended behavior of a loop or switch statement, remove it.
+It is better to use comments for this purpose. However, an unused label may
+indicate that something is wrong: that some of the nested break
+or continue statements should be using the label. In this case, the current control
+flow is probably wrong, and you should adjust some jumps to use the label
+after checking the desired behavior.
+
+
+
+
+The following example uses a loop and a nested loop to check whether any of the
+currently active shopping carts contains a particular item. On line 4, the carts: label
+is unused. Inspecting the code, we can see that the break statement on line 10
+is inefficient because it only breaks out of the nested loop. It
+could in fact break out of the outer loop, which should improve
+performance in common cases. By changing the statement on line 10 to read break carts;, the label
+is no longer unused and we improve the code.
+
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql
new file mode 100644
index 00000000000..cc453f1334e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLabel.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Unused label
+ * @description An unused label for a loop or 'switch' statement is either redundant or indicates
+ * incorrect 'break' or 'continue' statements.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/unused-label
+ * @tags maintainability
+ * useless-code
+ * external/cwe/cwe-561
+ */
+
+import java
+
+from LabeledStmt label
+where not exists(JumpStmt jump | jump.getTargetLabel() = label)
+select label, "Label '" + label.getLabel() + "' is not used."
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp
new file mode 100644
index 00000000000..b870eb8463e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.qhelp
@@ -0,0 +1,30 @@
+
+
+
+
+
+A local variable that is never accessed nor initialized
+is typically a leftover from old refactorings or a sign of incomplete or pending
+code changes.
+
+
+
+
+If an unused variable is a leftover from old refactorings, you should just remove it. If it indicates
+incomplete or pending code changes, finish making the changes and remove the variable if it is not
+needed.
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql
new file mode 100644
index 00000000000..482d5501d81
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Dead Code/UnusedLocal.ql
@@ -0,0 +1,31 @@
+/**
+ * @name Unused local variable
+ * @description A local variable is entirely unused: it is not initialized, written to or read. The variable serves no purpose and obscures the code. It should be removed.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/unused-local-variable
+ * @tags external/cwe/cwe-563
+ */
+import java
+import DeadLocals
+
+predicate exceptionVariable(LocalVariableDeclExpr ve) {
+ exists(CatchClause catch | catch.getVariable() = ve)
+}
+
+predicate enhancedForVariable(LocalVariableDeclExpr ve) {
+ exists(EnhancedForStmt for | for.getVariable() = ve)
+}
+
+from LocalVariableDeclExpr ve, LocalVariableDecl v
+where
+ v = ve.getVariable() and
+ not assigned(v) and
+ not read(v) and
+ (not exists(ve.getInit()) or exprHasNoEffect(ve.getInit())) and
+ // Remove contexts where Java forces a variable declaration: enhanced-for and catch clauses.
+ // Rules about catch clauses belong in an exception handling query
+ not exceptionVariable(ve) and
+ not enhancedForVariable(ve)
+select v, "Unused local variable " + v.getName() + ". The variable is never read or written to and should be removed."
diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java
new file mode 100644
index 00000000000..2618ccc9a7f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.java
@@ -0,0 +1,25 @@
+class Server
+{
+ public void respond(Event event)
+ {
+ Message reply = null;
+ switch (event) {
+ case PING:
+ reply = Message.PONG;
+ // Missing 'break' statement
+ case TIMEOUT:
+ reply = Message.PING;
+ case PONG:
+ // No reply needed
+ }
+ if (reply != null)
+ send(reply);
+ }
+
+ private void send(Message message) {
+ // ...
+ }
+}
+
+enum Event { PING, PONG, TIMEOUT }
+enum Message { PING, PONG }
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp
new file mode 100644
index 00000000000..688cf872be3
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.qhelp
@@ -0,0 +1,54 @@
+
+
+
+
+
+In a switch statement, execution 'falls through' from one case to
+the next, unless the case ends with a break statement. A
+common programming error is to forget to insert a break at the end
+of a case.
+
+
+
+
+End each case with a break statement or, if execution is
+supposed to fall through to the next case, comment the last line of the
+case with the following comment: /* falls through */
+
+Such comments are not required for a completely empty case that is
+supposed to share the same implementation with the subsequent case.
+
+
+
+
+In the following example, the PING case is missing a
+break statement. As a result, after reply is assigned the value of
+Message.PONG, execution falls through to the TIMEOUT case. Then the value
+of reply is erroneously assigned the value of Message.PING. To fix this,
+insert break; at the end of the PING case.
+
+
+
+
+
+
+
+
+
+ J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 23.
+ Addison-Wesley, 2005.
+
+
+Code Conventions for the Java Programming Language:
+7.8 switch Statements.
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql
new file mode 100644
index 00000000000..68d4e1c23f1
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/BreakInSwitchCase.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Unterminated switch case
+ * @description A 'case' statement that does not contain a 'break' statement allows execution to
+ * 'fall through' to the next 'case', which may not be intended.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/switch-fall-through
+ * @tags reliability
+ * readability
+ * external/cwe/cwe-484
+ */
+import java
+import Common
+
+from SwitchStmt s, Stmt c
+where
+ c = s.getACase() and
+ not c.(ControlFlowNode).getASuccessor() instanceof ConstCase and
+ not c.(ControlFlowNode).getASuccessor() instanceof DefaultCase and
+ not s.(Annotatable).suppressesWarningsAbout("fallthrough") and
+ mayDropThroughWithoutComment(s, c)
+select c, "Switch case may fall through to the next case. Use a break or return to terminate this case."
diff --git a/java/ql/src/Violations of Best Practice/Declarations/Common.qll b/java/ql/src/Violations of Best Practice/Declarations/Common.qll
new file mode 100644
index 00000000000..0ad18d598d6
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/Common.qll
@@ -0,0 +1,68 @@
+import java
+
+private Stmt getASwitchChild(SwitchStmt s) {
+ result = s.getAChild() or
+ exists(Stmt mid | mid = getASwitchChild(s) and not mid instanceof SwitchStmt and result = mid.getAChild())
+}
+
+private predicate blockInSwitch(SwitchStmt s, BasicBlock b) {
+ b.getFirstNode().getEnclosingStmt() = getASwitchChild(s)
+}
+
+private predicate switchCaseControlFlow(SwitchStmt switch, BasicBlock b1, BasicBlock b2) {
+ blockInSwitch(switch, b1) and
+ b1.getABBSuccessor() = b2 and
+ blockInSwitch(switch, b2)
+}
+
+predicate switchCaseControlFlowPlus(SwitchStmt switch, BasicBlock b1, BasicBlock b2) {
+ switchCaseControlFlow(switch, b1, b2) or
+ exists(BasicBlock mid |
+ switchCaseControlFlowPlus(switch, mid, b2) and
+ switchCaseControlFlow(switch, b1, mid) and
+ not mid.getFirstNode() = switch.getACase()
+ )
+}
+
+predicate mayDropThroughWithoutComment(SwitchStmt switch, Stmt switchCase) {
+ switchCase = switch.getACase() and
+ exists(Stmt other, BasicBlock b1, BasicBlock b2 |
+ b1.getFirstNode() = switchCase and
+ b2.getFirstNode() = other and
+ switchCaseControlFlowPlus(switch, b1, b2) and
+ other = switch.getACase() and
+ not fallThroughCommented(other)
+ )
+}
+
+private
+predicate fallThroughCommented(Stmt case) {
+ exists(Location loc |
+ loc = case.getLocation() and
+ loc.getStartLine() = fallThroughCommentedLine(loc.getFile())
+ )
+}
+
+private
+int fallThroughCommentedLine(File f) {
+ exists(Location loc, JavadocText text |
+ loc.getFile() = f and
+ text.getLocation() = loc and
+ text.getText().toLowerCase().regexpMatch(".*falls?[ -]?(through|thru).*") and
+ result = loc.getStartLine() + 1
+ ) or exists(int mid |
+ mid = fallThroughCommentedLine(f) and
+ not stmtLine(f) = mid and
+ mid < max(stmtLine(f)) and
+ result = mid + 1
+ )
+}
+
+private
+int stmtLine(File f) {
+ exists(Stmt s, Location loc |
+ s.getLocation() = loc and
+ loc.getFile() = f and
+ loc.getStartLine() = result
+ )
+}
diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java
new file mode 100644
index 00000000000..82b6cebdc97
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.java
@@ -0,0 +1,8 @@
+import java.util.*; // AVOID: Implicit import statements
+import java.awt.*;
+
+public class Customers {
+ public List getCustomers() { // Compiler error: 'List' is ambiguous.
+ ...
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp
new file mode 100644
index 00000000000..383f6f7a706
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.qhelp
@@ -0,0 +1,63 @@
+
+
+
+
+
+Imports can be categorized as explicit (for example
+import java.util.List;) or implicit (also known as
+'on-demand', for example import java.util.*;):
+
+
+- Implicit imports give access to all visible types in the type (or package) that precedes
+the ".*"; types imported in this way never shadow other types.
+- Explicit imports give access to just the named type; they can shadow other types
+that would normally be visible through an implicit import, or through the
+normal package visibility rules.
+
+
+It is often considered bad practice to use implicit imports. The only
+advantage to doing so is making the code more concise, and there are a number
+of disadvantages:
+
+
+- The exact dependencies of a file are not visible at a glance.
+- Confusing compile-time errors can be introduced if a type name
+is used that could originate from several implicit imports.
+
+
+
+
+
+For readability, it is recommended to use explicit imports instead of implicit imports.
+Many modern IDEs provide automatic functionality to help achieve this, typically under the name
+"Organize imports". They can also fold away the import declarations, and automatically
+manage imports: adding them when a particular type is auto-completed by the editor, and
+removing them when they are not necessary. This functionality makes implicit imports mainly
+redundant.
+
+
+
+
+The following example uses implicit imports. This means that it is not clear to a programmer
+where the List type on line 5 is imported from.
+
+
+
+To improve readability, the implicit imports should be replaced by explicit imports. For example,
+import java.util.*; should be replaced by import java.util.List; on line 1.
+
+
+
+
+
+
+
+Java Language Specification:
+ 6.4.1 Shadowing,
+ 7.5.2 Type-Import-on-Demand Declarations.
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql
new file mode 100644
index 00000000000..5137c48a696
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/MakeImportsExplicit.ql
@@ -0,0 +1,14 @@
+/**
+ * @name Implicit import
+ * @description An implicit import obscures the dependencies of a file and may cause confusing
+ * compile-time errors.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/implicit-import
+ * @tags maintainability
+ */
+import java
+
+from ImportOnDemandFromPackage i
+select i, "It is advisable to make imports explicit."
diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java
new file mode 100644
index 00000000000..2ce4f3067de
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.java
@@ -0,0 +1,15 @@
+public class NoConstantsOnly {
+ static interface MathConstants
+ {
+ public static final Double Pi = 3.14;
+ }
+
+ static class Circle implements MathConstants
+ {
+ public double radius;
+ public double area()
+ {
+ return Math.pow(radius, 2) * Pi;
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp
new file mode 100644
index 00000000000..05b5db1c9ac
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.qhelp
@@ -0,0 +1,65 @@
+
+
+
+
+
+Definitions of constants (meaning static, final fields) should be
+placed in an appropriate class where they belong logically.
+
+However, it is usually bad practice to implement an interface (or extend an abstract class)
+only to put a number of constant definitions into scope.
+
+
+
+
+
+The preferred way of putting the constant definitions into scope is to use the
+import static directive, which allows a compilation unit
+to put any visible static members from other classes into scope.
+
+
+This issue is discussed in [Bloch]:
+
+
+That a class uses some constants internally is an implementation detail.
+Implementing a constant interface causes this implementation detail to
+leak into the classes exported API. It is of no consequence to the
+users of a class that the class implements a constant interface. In
+fact, it may even confuse them. Worse, it represents a commitment: if
+in a future release the class is modified so that it no longer needs
+to use the constants, it still must implement the interface to ensure
+binary compatibility.
+
+
+
+To prevent this pollution of a class's binary interface, it
+is best to move the constant definitions to whatever concrete class
+uses them most frequently. Users of the definitions could use import static
+to access the relevant fields.
+
+
+
+
+In the following example, the interface MathConstants has been defined only to hold
+a constant.
+
+
+
+Instead, the constant should be moved to the Circle class or another class
+that uses the constant frequently.
+
+
+
+
+
+
+J. Bloch, Effective Java (second edition),
+Item 19.
+Addison-Wesley, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql
new file mode 100644
index 00000000000..534c0eb3670
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Declarations/NoConstantsOnly.ql
@@ -0,0 +1,54 @@
+/**
+ * @name Constant interface anti-pattern
+ * @description Implementing an interface (or extending an abstract class)
+ * only to put a number of constant definitions into scope is considered bad practice.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/constants-only-interface
+ * @tags maintainability
+ * modularity
+ */
+
+import semmle.code.java.Member
+
+class ConstantField extends Field {
+ ConstantField() {
+ this.isStatic() and this.isFinal()
+ }
+}
+
+pragma[noinline]
+predicate typeWithConstantField(RefType t) {
+ exists(ConstantField f | f.getDeclaringType() = t)
+}
+
+class ConstantRefType extends RefType {
+ ConstantRefType() {
+ fromSource() and
+ (
+ this instanceof Interface or
+ this instanceof Class and this.isAbstract()
+ ) and
+ typeWithConstantField(this) and
+ forall(Member m | m.getDeclaringType() = this |
+ m.(Constructor).isDefaultConstructor() or
+ m instanceof StaticInitializer or
+ m instanceof ConstantField
+ )
+ }
+
+ string getKind() {
+ result = "interface" and this instanceof Interface or
+ result = "class" and this instanceof Class
+ }
+}
+
+from ConstantRefType t, RefType sub
+where
+ sub.fromSource() and
+ sub.getASupertype() = t and
+ not sub instanceof ConstantRefType and
+ sub = sub.getSourceDeclaration()
+select sub, "Type " + sub.getName() + " implements constant " + t.getKind() + " $@.",
+ t, t.getName()
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java
new file mode 100644
index 00000000000..a71288df47d
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-comment.java
@@ -0,0 +1,9 @@
+synchronized void waitIfAutoSyncScheduled() {
+ try {
+ while (isAutoSyncScheduled) {
+ this.wait(1000);
+ }
+ } catch (InterruptedException e) {
+ // Expected exception. The file cannot be synchronized yet.
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java
new file mode 100644
index 00000000000..0085f15c2db
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-good.java
@@ -0,0 +1,10 @@
+// Exception is passed to 'ignore' method with a comment
+synchronized void waitIfAutoSyncScheduled() {
+ try {
+ while (isAutoSyncScheduled) {
+ this.wait(1000);
+ }
+ } catch (InterruptedException e) {
+ Exceptions.ignore(e, "Expected exception. The file cannot be synchronized yet.");
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java
new file mode 100644
index 00000000000..6985620e530
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions-ignore.java
@@ -0,0 +1,5 @@
+// 'ignore' method. This method does nothing, but can be called
+// to document the reason why the exception can be ignored.
+public static void ignore(Throwable e, String message) {
+
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java
new file mode 100644
index 00000000000..d41a6105e17
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.java
@@ -0,0 +1,10 @@
+// Dropped exception, with no information on whether
+// the exception is expected or not
+synchronized void waitIfAutoSyncScheduled() {
+ try {
+ while (isAutoSyncScheduled) {
+ this.wait(1000);
+ }
+ } catch (InterruptedException e) {
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp
new file mode 100644
index 00000000000..d8695826512
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.qhelp
@@ -0,0 +1,62 @@
+
+
+
+
+
+You should not drop an exception, because it
+indicates that an unusual program state has been reached.
+This usually requires corrective actions to be performed to recover
+from the exceptional state and try to resume normal program operation.
+
+
+
+
+
+
+You should do one of the following:
+
+
+ - Catch and handle the exception.
+ - Throw the exception to the outermost level of nesting.
+
+
+Note that usually you should catch and handle a checked exception, but you can throw an
+unchecked exception to the outermost level.
+
+
+There is occasionally a valid reason for ignoring an exception. In such cases,
+you should document the reason to improve the readability of the code. Alternatively,
+you can implement a static method with an empty body to handle these exceptions.
+Instead of dropping the exception altogether, you can then pass it to the static method with
+a string explaining the reason for ignoring it.
+
+
+
+The following example shows a dropped exception.
+
+
+The following example adds a comment to document why the exception may be ignored.
+
+
+The following example shows how you can improve code readability by defining a new utility method.
+
+
+The following example shows the exception being passed to ignore with a comment.
+
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition), Item 65. Addison-Wesley, 2008.
+
+
+ The Java Tutorials: Unchecked Exceptions - The Controversy.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql
new file mode 100644
index 00000000000..8a0fa7f02b8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/DroppedExceptions.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Discarded exception
+ * @description Dropping an exception may allow an unusual program state to continue
+ * without recovery.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/discarded-exception
+ * @tags reliability
+ * correctness
+ * exceptions
+ * external/cwe/cwe-391
+ */
+import java
+
+from CatchClause cc
+where
+ not exists(cc.getBlock().getAStmt()) and
+ not cc.getBlock().getNumberOfCommentLines() > 0 and
+ not cc.getEnclosingCallable().getDeclaringType() instanceof TestClass
+select cc, "Exceptions should not be dropped."
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java
new file mode 100644
index 00000000000..a12a44dc2e1
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.java
@@ -0,0 +1,13 @@
+FileInputStream fis = ...
+try {
+ fis.read();
+} catch (Throwable e) { // BAD: The exception is too general.
+ // Handle this exception
+}
+
+FileInputStream fis = ...
+try {
+ fis.read();
+} catch (IOException e) { // GOOD: The exception is specific.
+ // Handle this exception
+}
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp
new file mode 100644
index 00000000000..c5340797052
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.qhelp
@@ -0,0 +1,51 @@
+
+
+
+
+
+Catching Throwable or Exception is dangerous
+because these can include an Error such as OutOfMemoryError
+or a RuntimeException such as ArrayIndexOutOfBoundsException.
+These should normally be propagated to the outermost level because they generally
+indicate a program state from which normal operation cannot be recovered.
+
+
+
+
+It is usually best to ensure that exceptions that are caught in a catch clause
+are as specific as possible to avoid inadvertently suppressing more serious problems.
+
+
+
+
+In the following example, the catch clause in the first try block
+catches Throwable. However, when performing read operations on a
+FileInputStream within a try block, the corresponding catch
+clause should normally catch IOException instead. This is shown in the second, modified
+try block.
+
+
+
+
+
+
+
+
+
+ J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases,
+ Puzzle 44.
+ Addison-Wesley, 2005.
+
+
+ Java Platform, Standard Edition 6, API Specification:
+ Throwable,
+ Error,
+ Exception,
+ RuntimeException.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql
new file mode 100644
index 00000000000..6d2c7fc776d
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/ExceptionCatch.ql
@@ -0,0 +1,59 @@
+/**
+ * @name Overly-general catch clause
+ * @description Catching 'Throwable' or 'Exception' is dangerous because these can include
+ * 'Error' or 'RuntimeException'.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/overly-general-catch
+ * @tags reliability
+ * external/cwe/cwe-396
+ */
+import java
+
+private
+predicate relevantTypeNames(string typeName, string message) {
+ // `Throwable` is the more severe case due to `Error`s such as `OutOfMemoryError`.
+ typeName = "Throwable" and message = "Error"
+ or
+ // `Exception` includes `RuntimeException`s such as `ArrayIndexOutOfBoundsException`.
+ typeName = "Exception" and message = "RuntimeException"
+}
+
+private
+Type getAThrownExceptionType(TryStmt t) {
+ exists(MethodAccess ma, Exception e |
+ t.getBlock() = ma.getEnclosingStmt().getParent*() and
+ ma.getMethod().getAnException() = e and
+ result = e.getType()
+ ) or
+ exists(ClassInstanceExpr cie, Exception e |
+ t.getBlock() = cie.getEnclosingStmt().getParent*() and
+ cie.getConstructor().getAnException() = e and
+ result = e.getType()
+ ) or
+ exists(ThrowStmt ts |
+ t.getBlock() = ts.getParent*() and
+ result = ts.getExpr().getType()
+ )
+}
+
+from CatchClause cc, LocalVariableDeclExpr v, TryStmt t, string typeName, string message
+where
+ relevantTypeNames(typeName, message) and
+ t.getACatchClause() = cc and
+ cc.getVariable() = v and
+ v.getType().(RefType).hasQualifiedName("java.lang", typeName) and
+ // It's usually OK if the exception is logged in some way, or re-thrown.
+ not exists(v.getAnAccess()) and
+ // Exclude results in test code.
+ not cc.getEnclosingCallable().getDeclaringType() instanceof TestClass and
+ // Check that all exceptions thrown in the try block are
+ // either more specific than the caught type or unrelated to it.
+ not exists(Type et | et = getAThrownExceptionType(t) |
+ et.(RefType).getASubtype*().hasQualifiedName("java.lang", typeName)
+ )
+select
+ cc,
+ "Do not catch '" + cc.getVariable().getType() + "'"
+ + "; " + message + "s should normally be propagated."
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java
new file mode 100644
index 00000000000..edcace8594f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.java
@@ -0,0 +1,3 @@
+java.io.InputStream is = (...);
+byte[] b = new byte[16];
+is.read(b);
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp
new file mode 100644
index 00000000000..fcfcb5414cc
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.qhelp
@@ -0,0 +1,88 @@
+
+
+
+
+
+Many methods in the Java Development Kit (for examples, see the references below)
+return status values (for example, as an int) to indicate
+whether the method execution finished normally. They may return an
+error code if the method did not finish normally.
+If the method result is not checked, exceptional method executions
+may cause subsequent code to fail.
+
+
+
+
+
+You should insert additional code to check the return value and take appropriate action.
+
+
+
+
+
+The following example uses the java.io.InputStream.read method to
+read 16 bytes from an input stream and store them in an array.
+However, read may not actually be able to read as many
+bytes as requested, for example because the stream is exhausted.
+Therefore, the code should not simply rely on the array b being filled with
+precisely 16 bytes from the input stream.
+Instead, the code should check the method's return value, which indicates the number of bytes actually read.
+
+
+
+
+
+
+
+
+ CERT Secure Coding Standards:
+ EXP00-J. Do not ignore values returned by methods.
+
+
+ Java API Documentation, java.util.Queue:
+ offer.
+
+
+ Java API Documentation, java.util.concurrent.BlockingQueue:
+ offer.
+
+
+ Java API Documentation, java.util.concurrent.locks.Condition:
+ await,
+
+ awaitUntil,
+
+ awaitNanos.
+
+
+ Java API Documentation, java.io.File:
+ createNewFile,
+
+ delete,
+
+ mkdir,
+
+ renameTo,
+
+ setLastModified,
+
+ setReadOnly,
+
+ setWritable(boolean),
+
+ setWritable(boolean, boolean).
+
+
+ Java API Documentation, java.io.InputStream:
+ skip,
+
+ read(byte[]),
+
+ read(byte[], int, int).
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql
new file mode 100644
index 00000000000..b1c36234f68
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Exception Handling/IgnoreExceptionalReturn.ql
@@ -0,0 +1,60 @@
+/**
+ * @name Ignored error status of call
+ * @description Ignoring an exceptional value that is returned by a method may cause subsequent
+ * code to fail.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/ignored-error-status-of-call
+ * @tags reliability
+ * correctness
+ * external/cwe/cwe-391
+ */
+import java
+
+class SpecialMethod extends Method {
+ predicate isMethod(string pack, string clss, string name, int numparam) {
+ this.hasName(name) and
+ this.getNumberOfParameters() = numparam and
+ this.getDeclaringType().getASupertype*().getSourceDeclaration().hasQualifiedName(pack, clss)
+ }
+}
+
+predicate unboundedQueue(RefType t) {
+ exists(string pack, string clss |
+ t.getASupertype*().getSourceDeclaration().hasQualifiedName(pack, clss)
+ |
+ pack = "java.util" and clss = "ArrayDeque" or
+ pack = "java.util" and clss = "LinkedList" or
+ pack = "java.util" and clss = "PriorityQueue" or
+ pack = "java.util.concurrent" and clss = "ConcurrentLinkedQueue" or
+ pack = "java.util.concurrent" and clss = "ConcurrentLinkedDeque" or
+ pack = "java.util.concurrent" and clss = "DelayQueue" or
+ pack = "java.util.concurrent" and clss = "LinkedTransferQueue" or
+ pack = "java.util.concurrent" and clss = "PriorityBlockingQueue"
+ )
+}
+
+from MethodAccess ma, SpecialMethod m
+where
+ ma.getParent() instanceof ExprStmt and
+ m = ma.getMethod() and
+ (
+ m.isMethod("java.util", "Queue", "offer", 1) and not unboundedQueue(m.getDeclaringType()) or
+ m.isMethod("java.util.concurrent", "BlockingQueue", "offer", 3) and not unboundedQueue(m.getDeclaringType()) or
+ m.isMethod("java.util.concurrent.locks", "Condition", "await", 2) or
+ m.isMethod("java.util.concurrent.locks", "Condition", "awaitUntil", 1) or
+ m.isMethod("java.util.concurrent.locks", "Condition", "awaitNanos", 1) or
+ m.isMethod("java.io", "File", "createNewFile", 0) or
+ m.isMethod("java.io", "File", "mkdir", 0) or
+ m.isMethod("java.io", "File", "renameTo", 1) or
+ m.isMethod("java.io", "File", "setLastModified", 1) or
+ m.isMethod("java.io", "File", "setReadOnly", 0) or
+ m.isMethod("java.io", "File", "setWritable", 1) or
+ m.isMethod("java.io", "File", "setWritable", 2) or
+ m.isMethod("java.io", "InputStream", "skip", 1) or
+ m.isMethod("java.io", "InputStream", "read", 1) or
+ m.isMethod("java.io", "InputStream", "read", 3)
+ )
+select ma, "Method " + ma.getEnclosingCallable().getName() + " ignores exceptional return value of "
+ + ma.getMethod().getDeclaringType().getName() + "." + ma.getMethod().getName() + "."
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java
new file mode 100644
index 00000000000..01a43f27077
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.java
@@ -0,0 +1,12 @@
+Customer getNext(List queue) {
+ if (queue == null)
+ return null;
+ LinkedList myQueue = (LinkedList)queue; // AVOID: Cast to concrete type.
+ return myQueue.poll();
+}
+
+Customer getNext(Queue queue) {
+ if (queue == null)
+ return null;
+ return queue.poll(); // GOOD: Use abstract type.
+}
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp
new file mode 100644
index 00000000000..2e09bd7cc4a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.qhelp
@@ -0,0 +1,59 @@
+
+
+
+
+
+Most collections in the Java standard library are defined by an abstract interface
+(for example java.util.List or java.util.Set), which is
+implemented by a range of concrete classes and a range of wrappers. Normally, except
+when constructing an object, it is better to use the abstract types because this avoids
+assumptions about what the implementation is.
+
+A cast from an abstract to a concrete collection
+makes the code brittle by ensuring it works only for one possible
+implementation class and not others. Usually, such casts are
+either an indication of over-reliance on concrete implementation types, or of the
+fact that the wrong abstract type was used.
+
+
+
+
+It is usually best to use the abstract type consistently in variable, field and parameter
+declarations.
+
+There may be individual exceptions. For example, it is common to declare variables
+as LinkedHashSet rather than Set when the iteration order
+matters and only the LinkedHashSet implementation provides the right
+behavior.
+
+
+
+
+The following example illustrates a situation where the wrong abstract type is used.
+The List interface does not provide a poll method, so
+the original code casts queue down to the concrete type LinkedList, which
+does. To avoid this downcasting, simply use the correct abstract type for this method, namely
+Queue. This documents the intent of the programmer and allows for various implementations
+of queues to be used by clients of this method.
+
+
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition),
+ Item 52.
+ Addison-Wesley, 2008.
+
+
+ Java 6 API Specification:
+ Collection.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql
new file mode 100644
index 00000000000..009e0800dfa
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/AbstractToConcreteCollection.ql
@@ -0,0 +1,60 @@
+/**
+ * @name Cast from abstract to concrete collection
+ * @description A cast from an abstract collection to a concrete implementation type makes the
+ * code brittle.
+ * @kind problem
+ * @problem.severity warning
+ * @precision very-high
+ * @id java/abstract-to-concrete-cast
+ * @tags reliability
+ * maintainability
+ * modularity
+ * external/cwe/cwe-485
+ */
+import java
+import semmle.code.java.Collections
+
+predicate guardedByInstanceOf(VarAccess e, RefType t) {
+ exists(IfStmt s, InstanceOfExpr instanceCheck, Type checkType |
+ s.getCondition() = instanceCheck
+ and
+ instanceCheck.getTypeName().getType() = checkType
+ and
+ // The same variable appears as the subject of the `instanceof`.
+ instanceCheck.getExpr() = e.getVariable().getAnAccess()
+ and
+ // The checked type is either the type itself, or a raw version. For example, it is usually
+ // fine to check for `x instanceof ArrayList` and then cast to `ArrayList`, because
+ // the generic parameter is usually known.
+ (checkType = t or checkType = t.getSourceDeclaration().(GenericType).getRawType())
+ and
+ // The expression appears in one of the branches.
+ // (We do not verify here whether the guard is correctly implemented.)
+ exists(Stmt branch | branch = s.getThen() or branch = s.getElse() |
+ branch = e.getEnclosingStmt().getParent+()
+ )
+ )
+}
+
+from CastExpr e, CollectionType c, CollectionType coll, string abstractName, string concreteName
+where
+ coll instanceof Interface and
+ c instanceof Class and
+ // The result of the cast has type `c`.
+ e.getType() = c and
+ // The expression inside the cast has type `coll`.
+ e.getExpr().getType() = coll and
+ // The cast does not occur inside a check that the variable has that type.
+ // In this case there is not really a break of abstraction, since it is not
+ // *assumed* that the variable has that type. In practice, this usually corresponds
+ // to a branch optimized for a specific subtype, and then a generic branch.
+ not guardedByInstanceOf(e.getExpr(), c) and
+ // Exclude results if "unchecked" warnings are deliberately suppressed.
+ not e.getEnclosingCallable().suppressesWarningsAbout("unchecked") and
+ // Report the qualified names if the names are the same.
+ if coll.getName() = c.getName()
+ then (abstractName = coll.getQualifiedName() and concreteName = c.getQualifiedName())
+ else (abstractName = coll.getName() and concreteName = c.getName())
+select e, "$@ is cast to the concrete type $@, losing abstraction.",
+ coll.getSourceDeclaration(), abstractName,
+ c.getSourceDeclaration(), concreteName
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java
new file mode 100644
index 00000000000..c03324fb4ed
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.java
@@ -0,0 +1,18 @@
+public class Cart {
+ private Set- items;
+ // ...
+ // AVOID: Exposes representation
+ public Set
- getItems() {
+ return items;
+ }
+}
+....
+int countItems(Set carts) {
+ int result = 0;
+ for (Cart cart : carts) {
+ Set
- items = cart.getItems();
+ result += items.size();
+ items.clear(); // AVOID: Changes internal representation
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp
new file mode 100644
index 00000000000..59934307cb2
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.qhelp
@@ -0,0 +1,67 @@
+
+
+
+
+
+
A subtle type of defect is caused when an object accidentally exposes its internal
+representation to the code outside the object, and the internal representation is then (deliberately or accidentally)
+modified in ways that the object is not prepared to handle. Most commonly, this happens
+when a getter returns a direct reference to a mutable field within the object, or a setter just assigns
+a mutable argument to its field.
+
+
+
+There are three ways of addressing this problem:
+
+
+- Using immutable objects : The fields store objects that are immutable,
+which means that once constructed their value can never be changed. Examples from the
+standard library are
String, Integer or
+Float. Although such an object may be aliased, or shared between several contexts,
+there can be no unexpected changes to the internal state of the object because it cannot be modified.
+
+- Creating a read-only view : The
java.util.Collections.unmodifiable*
+methods can be used to create a read-only view of a collection without copying it.
+This tends to give better performance than creating copies of objects. Note that this
+technique is not suitable for every situation, because any changes to the underlying
+collection will spread to affect the view. This can lead to unexpected
+results, and is a particular danger when writing multi-threaded code.
+
+- Making defensive copies : Each setter (or constructor) makes a copy or clone of the
+incoming parameter. In this way, it constructs an instance known only internally,
+and no matter what happens with the object that was passed in, the state stays
+consistent. Conversely, each getter for a field must also construct a copy of the
+field's value to return.
+
+
+
+
+
+In the following example, the private field items is returned
+directly by the getter getItems. Thus, a caller obtains a reference to internal object state
+and can manipulate the collection of items in the cart. In the example, each of the
+carts is emptied when countItems is called.
+
+
+
+The solution is for getItems to return a copy of the
+actual field, for example return new HashSet<Item>(items);.
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition),
+ Items 15 and 39.
+ Addison-Wesley, 2008.
+
+
+ Java 7 API Documentation: Collections.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql
new file mode 100644
index 00000000000..f64bcb926b5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/ExposeRepresentation.ql
@@ -0,0 +1,118 @@
+/**
+ * @name Exposing internal representation
+ * @description An object that accidentally exposes its internal representation may allow the
+ * object's fields to be modified in ways that the object is not prepared to handle.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/internal-representation-exposure
+ * @tags reliability
+ * maintainability
+ * modularity
+ * external/cwe/cwe-485
+ */
+import java
+import semmle.code.java.dataflow.DefUse
+
+predicate relevantType(RefType t) {
+ t instanceof Array or
+ exists(RefType sup | sup = t.getASupertype*().getSourceDeclaration() |
+ sup.hasQualifiedName("java.util", "Map") or
+ sup.hasQualifiedName("java.util", "Collection")
+ )
+}
+
+predicate modifyMethod(Method m) {
+ relevantType(m.getDeclaringType()) and (
+ m.hasName("add") or m.hasName("addAll") or
+ m.hasName("put") or m.hasName("putAll") or
+ m.hasName("push") or m.hasName("pop") or
+ m.hasName("remove") or m.hasName("removeAll") or
+ m.hasName("clear") or m.hasName("set")
+ )
+}
+
+predicate storesArray(Callable c, int i, Field f) {
+ f.getDeclaringType() = c.getDeclaringType().getASupertype*().getSourceDeclaration() and
+ relevantType(f.getType()) and
+ exists(Parameter p | p = c.getParameter(i) | f.getAnAssignedValue() = p.getAnAccess()) and
+ not c.isStatic()
+}
+
+predicate returnsArray(Callable c, Field f) {
+ f.getDeclaringType() = c.getDeclaringType().getASupertype*().getSourceDeclaration() and
+ relevantType(f.getType()) and
+ exists(ReturnStmt rs | rs.getEnclosingCallable() = c and rs.getResult() = f.getAnAccess()) and
+ not c.isStatic()
+}
+
+predicate mayWriteToArray(Expr modified) {
+ writesToArray(modified) or
+
+ // x = __y__; x[0] = 1;
+ exists(AssignExpr e, LocalVariableDecl v | e.getDest() = v.getAnAccess() |
+ modified = e.getSource() and
+ mayWriteToArray(v.getAnAccess())
+ ) or
+
+ // int[] x = __y__; x[0] = 1;
+ exists(LocalVariableDeclExpr e, Variable v | e.getVariable() = v |
+ modified = e.getInit() and
+ mayWriteToArray(v.getAnAccess())
+ ) or
+
+ // return __array__; ... method()[1] = 0
+ exists(ReturnStmt rs | modified = rs.getResult() and relevantType(modified.getType()) |
+ exists(Callable enclosing, MethodAccess ma |
+ enclosing = rs.getEnclosingCallable() and ma.getMethod() = enclosing
+ |
+ mayWriteToArray(ma)
+ )
+ )
+}
+
+predicate writesToArray(Expr array) {
+ relevantType(array.getType()) and
+ (
+ exists(Assignment a, ArrayAccess access | a.getDest() = access | access.getArray() = array)) or
+ exists(MethodAccess ma | ma.getQualifier() = array | modifyMethod(ma.getMethod())
+ )
+}
+
+VarAccess modificationAfter(VarAccess v) {
+ mayWriteToArray(result) and
+ useUsePair(v, result)
+}
+
+VarAccess varPassedInto(Callable c, int i) {
+ exists(Call call | call.getCallee() = c |
+ call.getArgument(i) = result
+ )
+}
+
+predicate exposesByReturn(Callable c, Field f, Expr why, string whyText) {
+ returnsArray(c, f) and
+ exists(MethodAccess ma | ma.getMethod() = c and ma.getCompilationUnit() != c.getCompilationUnit() |
+ mayWriteToArray(ma) and
+ why = ma and
+ whyText = "after this call to " + c.getName()
+ )
+}
+
+predicate exposesByStore(Callable c, Field f, Expr why, string whyText) {
+ exists(VarAccess v, int i |
+ storesArray(c, i, f) and
+ v = varPassedInto(c, i) and
+ v.getCompilationUnit() != c.getCompilationUnit() and
+ why = modificationAfter(v) and
+ whyText = "through the variable " + v.getVariable().getName()
+ )
+}
+
+from Callable c, Field f, Expr why, string whyText
+where
+ exposesByReturn(c, f, why, whyText) or
+ exposesByStore(c, f, why, whyText)
+select c, c.getName() + " exposes the internal representation stored in field " + f.getName() +
+ ". The value may be modified $@.",
+ why.getLocation(), whyText
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java
new file mode 100644
index 00000000000..e4de1b693a6
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.java
@@ -0,0 +1,17 @@
+package framework;
+class Address {
+ public URL getPostalCodes() {
+ // AVOID: The call is made on the run-time type of 'this'.
+ return this.getClass().getResource("postal-codes.csv");
+ }
+}
+
+package client;
+class UKAddress extends Address {
+ public void convert() {
+ // Looks up "framework/postal-codes.csv"
+ new Address().getPostalCodes();
+ // Looks up "client/postal-codes.csv"
+ new UKAddress().getPostalCodes();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp
new file mode 100644
index 00000000000..fa3d66355bd
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.qhelp
@@ -0,0 +1,59 @@
+
+
+
+
+
+Using the Class.getResource method is a common way of including
+some non-code resources with an application.
+
+There are problems when this is called using x.getClass().getResource(),
+for some variable x. This is not a safe way to retrieve a resource. The method
+getClass returns the run-time class of x (that is, its actual, "most
+derived" class, rather than its declared type), which causes two potential problems:
+
+
+- If the run-time type of the receiving object is a subclass of the declared type and is in
+a different package, the resource path may be interpreted differently. According to
+its contract,
Class.getResource qualifies non-absolute paths with the
+current package name, thus potentially returning a different resource or failing to find
+the requested resource.
+Class.getResource delegates finding the resource to the class loader that loaded the class.
+At run time, there is no guarantee that all subclasses of
+a particular type are loaded by the same class loader, resulting in resource lookup
+failures that are difficult to diagnose.
+
+
+
+
+Rather than using the getClass method, which relies on dynamic dispatch
+and run-time types, use class literals instead. For example, instead of calling
+getClass().getResource() on an object of type Foo,
+call Foo.class.getResource(). Class literals always refer to the
+declared type they are used on, removing the dependency on run-time types.
+
+
+
+
+In the following example, the calls to getPostalCodes return different results,
+depending on which class the call is made on: the class Address is in the package
+framework and the class UKAddress is in the package client.
+
+
+
+In the following corrected example, the implementation of getPostalCodes is changed
+so that it always calls getResource on the same class.
+
+
+
+
+
+
+Java Platform, Standard Edition 7, API Specification:
+class.getResource().
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql
new file mode 100644
index 00000000000..979a14a40fe
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResource.ql
@@ -0,0 +1,34 @@
+/**
+ * @name Unsafe use of getResource
+ * @description Calling 'this.getClass().getResource()' may yield unexpected results if called from a
+ * subclass in another package.
+ * @kind problem
+ * @problem.severity warning
+ * @precision medium
+ * @id java/unsafe-get-resource
+ * @tags reliability
+ * maintainability
+ */
+import java
+
+/** Access to a method in `this` object. */
+class MethodAccessInThis extends MethodAccess {
+ MethodAccessInThis() {
+ not this.hasQualifier() or
+ this.getQualifier() instanceof ThisAccess
+ }
+}
+
+from Class c, MethodAccess getResource, MethodAccessInThis getClass
+where
+ getResource.getNumArgument() = 1 and
+ (
+ getResource.getMethod().hasName("getResource") or
+ getResource.getMethod().hasName("getResourceAsStream")
+ ) and
+ getResource.getQualifier() = getClass and
+ getClass.getNumArgument() = 0 and
+ getClass.getMethod().hasName("getClass") and
+ getResource.getEnclosingCallable().getDeclaringType() = c and
+ c.isPublic()
+select getResource, "The idiom getClass().getResource() is unsafe for classes that may be extended."
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java
new file mode 100644
index 00000000000..2945b3c61bc
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/GetClassGetResourceGood.java
@@ -0,0 +1,17 @@
+package framework;
+class Address {
+ public URL getPostalCodes() {
+ // GOOD: The call is always made on an object of the same type.
+ return Address.class.getResource("postal-codes.csv");
+ }
+}
+
+package client;
+class UKAddress extends Address {
+ public void convert() {
+ // Looks up "framework/postal-codes.csv"
+ new Address().getPostalCodes();
+ // Looks up "framework/postal-codes.csv"
+ new UKAddress().getPostalCodes();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java
new file mode 100644
index 00000000000..c841170728c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.java
@@ -0,0 +1,11 @@
+public class Display {
+ // AVOID: Array constant is vulnerable to mutation.
+ public static final String[] RGB = {
+ "FF0000", "00FF00", "0000FF"
+ };
+
+ void f() {
+ // Re-assigning the "constant" is legal.
+ RGB[0] = "00FFFF";
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp
new file mode 100644
index 00000000000..089e63c05a0
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.qhelp
@@ -0,0 +1,62 @@
+
+
+
+
+
+Constant values are typically represented by
+public, static, final
+fields. When defining several related constants, it is
+sometimes tempting to define a public, static, final
+field with an array type, and initialize it with a list
+of all the different constant values.
+
+However, the final keyword applies only
+to the field itself (that is, the array reference),
+and not to the contents of the array. This means that the field
+always refers to the same array instance, but each
+element of the array may be modified freely. This possibly
+invalidates important assumptions of client code.
+
+
+
+
+Where possible, avoid declaring array constants. If
+there are only a few constant values, consider using
+a named constant for each one, or defining them in an enum type.
+
+If you genuinely need to
+refer to a long list of constants with the same name and
+an index, consider replacing the array constant with a
+constant of type List to which you assign
+an unmodifiable collection. See the example for ways of
+achieving this.
+
+
+
+
+In the following example, public static final applies only to RGB itself,
+not the constants that it contains.
+
+
+
+The following example shows examples of ways to declare constants that avoid this problem.
+
+
+
+
+
+
+
+ J. Bloch,
+ Effective Java (second edition), p. 70.
+ Addison-Wesley, 2008.
+
+ Java Language Specification:
+ 4.12.4 final Variables.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql
new file mode 100644
index 00000000000..f5ce6800cae
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArray.ql
@@ -0,0 +1,45 @@
+/**
+ * @name Array constant vulnerable to change
+ * @description Array constants are mutable and can be changed by malicious code or by accident.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/static-array
+ * @tags maintainability
+ * modularity
+ * external/cwe/cwe-582
+ */
+import java
+
+predicate nonEmptyArrayLiteralOrNull(Expr e) {
+ exists(ArrayCreationExpr arr | arr = e |
+ // Array initializer expressions such as `{1, 2, 3}`.
+ // Array is empty if the initializer expression is empty.
+ exists(Expr arrayValue | arrayValue = arr.getInit().getAnInit())
+ or
+ // Array creation with dimensions (but without initializers).
+ // Empty if the first dimension is 0.
+ exists(Expr dim | dim = arr.getDimension(0) |
+ not (dim.(CompileTimeConstantExpr).getIntValue() = 0)
+ )
+ )
+ or
+ e instanceof NullLiteral
+ or
+ exists(ConditionalExpr cond | cond = e |
+ nonEmptyArrayLiteralOrNull(cond.getTrueExpr()) and
+ nonEmptyArrayLiteralOrNull(cond.getFalseExpr())
+ )
+}
+
+from Field f
+where
+ f.isPublic() and
+ f.isStatic() and
+ f.isFinal() and
+ f.getType() instanceof Array and
+ f.fromSource() and
+ forall(AssignExpr a | a.getDest() = f.getAnAccess() |
+ nonEmptyArrayLiteralOrNull(a.getSource())
+ )
+select f, "The array constant " + f.getName() + " is vulnerable to mutation."
diff --git a/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java
new file mode 100644
index 00000000000..a48cc7962f5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Implementation Hiding/StaticArrayGood.java
@@ -0,0 +1,42 @@
+// Solution 1: Extract to individual constants
+public class Display {
+ public static final String RED = "FF0000";
+ public static final String GREEN = "00FF00";
+ public static final String BLUE = "0000FF";
+}
+
+// Solution 2: Define constants using in an enum type
+public enum Display
+{
+ RED ("FF0000"), GREEN ("00FF00"), BLUE ("0000FF");
+
+ private String rgb;
+ private Display(int rgb) {
+ this.rgb = rgb;
+ }
+ public String getRGB(){
+ return rgb;
+ }
+}
+
+// Solution 3: Use an unmodifiable collection
+public class Display {
+ public static final List RGB =
+ Collections.unmodifiableList(
+ Arrays.asList("FF0000",
+ "00FF00",
+ "0000FF"));
+}
+
+// Solution 4: Use a utility method
+public class Utils {
+ public static List constList(T... values) {
+ return Collections.unmodifiableList(
+ Arrays.asList(values));
+ }
+}
+
+public class Display {
+ public static final List RGB =
+ Utils.constList("FF0000", "00FF00", "0000FF");
+}
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll
new file mode 100644
index 00000000000..abcc715c11d
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstants.qll
@@ -0,0 +1,347 @@
+import java
+
+/*
+ *
+ * Counting nontrivial literal occurrences
+ *
+ */
+
+private
+predicate trivialPositiveIntValue(string s) {
+ s="0" or s="1" or s="2" or s="3" or s="4" or s="5" or s="6" or s="7" or s="8" or
+ s="9" or s="10" or s="11" or s="12" or s="13" or s="14" or s="15" or s="16" or s="17" or
+ s="18" or s="19" or s="20"
+
+ or
+
+ s="16" or s="32" or s="64" or s="128" or s="256" or s="512" or s="1024" or
+ s="2048" or s="4096" or s="16384" or s="32768" or s="65536" or
+ s="1048576" or s="2147483648" or s="4294967296"
+
+ or
+
+ s="15" or s="31" or s="63" or s="127" or s="255" or s="511" or s="1023" or
+ s="2047" or s="4095" or s="16383" or s="32767" or s="65535" or
+ s="1048577" or s="2147483647" or s="4294967295"
+
+ or
+
+ s = "0x00000001" or s = "0x00000002" or s = "0x00000004" or s = "0x00000008" or
+ s = "0x00000010" or s = "0x00000020" or s = "0x00000040" or s = "0x00000080" or
+ s = "0x00000100" or s = "0x00000200" or s = "0x00000400" or s = "0x00000800" or
+ s = "0x00001000" or s = "0x00002000" or s = "0x00004000" or s = "0x00008000" or
+ s = "0x00010000" or s = "0x00020000" or s = "0x00040000" or s = "0x00080000" or
+ s = "0x00100000" or s = "0x00200000" or s = "0x00400000" or s = "0x00800000" or
+ s = "0x01000000" or s = "0x02000000" or s = "0x04000000" or s = "0x08000000" or
+ s = "0x10000000" or s = "0x20000000" or s = "0x40000000" or s = "0x80000000" or
+ s = "0x00000001" or s = "0x00000003" or s = "0x00000007" or s = "0x0000000f" or
+ s = "0x0000001f" or s = "0x0000003f" or s = "0x0000007f" or s = "0x000000ff" or
+ s = "0x000001ff" or s = "0x000003ff" or s = "0x000007ff" or s = "0x00000fff" or
+ s = "0x00001fff" or s = "0x00003fff" or s = "0x00007fff" or s = "0x0000ffff" or
+ s = "0x0001ffff" or s = "0x0003ffff" or s = "0x0007ffff" or s = "0x000fffff" or
+ s = "0x001fffff" or s = "0x003fffff" or s = "0x007fffff" or s = "0x00ffffff" or
+ s = "0x01ffffff" or s = "0x03ffffff" or s = "0x07ffffff" or s = "0x0fffffff" or
+ s = "0x1fffffff" or s = "0x3fffffff" or s = "0x7fffffff" or s = "0xffffffff" or
+ s = "0x0001" or s = "0x0002" or s = "0x0004" or s = "0x0008" or
+ s = "0x0010" or s = "0x0020" or s = "0x0040" or s = "0x0080" or
+ s = "0x0100" or s = "0x0200" or s = "0x0400" or s = "0x0800" or
+ s = "0x1000" or s = "0x2000" or s = "0x4000" or s = "0x8000" or
+ s = "0x0001" or s = "0x0003" or s = "0x0007" or s = "0x000f" or
+ s = "0x001f" or s = "0x003f" or s = "0x007f" or s = "0x00ff" or
+ s = "0x01ff" or s = "0x03ff" or s = "0x07ff" or s = "0x0fff" or
+ s = "0x1fff" or s = "0x3fff" or s = "0x7fff" or s = "0xffff" or
+ s = "0x01" or s = "0x02" or s = "0x04" or s = "0x08" or
+ s = "0x10" or s = "0x20" or s = "0x40" or s = "0x80" or
+ s = "0x01" or s = "0x03" or s = "0x07" or s = "0x0f" or
+ s = "0x1f" or s = "0x3f" or s = "0x7f" or s = "0xff" or
+ s = "0x00"
+
+ or
+
+ s = "10" or s = "100" or s = "1000" or
+ s = "10000" or s = "100000" or s = "1000000" or
+ s = "10000000" or s = "100000000" or s = "1000000000"
+}
+
+private
+predicate trivialIntValue(string s) {
+ trivialPositiveIntValue(s) or
+ exists(string pos | trivialPositiveIntValue(pos) and s = "-" + pos)
+}
+
+private
+predicate intTrivial(Literal lit) {
+ exists(string v | trivialIntValue(v) and v = lit.getLiteral())
+}
+
+private
+predicate longTrivial(Literal lit) {
+ exists(string v | trivialIntValue(v) and v + "L" = lit.getLiteral())
+}
+
+private
+predicate powerOfTen(float f) {
+ f = 10 or f = 100 or f = 1000 or
+ f = 10000 or f = 100000 or f = 1000000 or
+ f = 10000000 or f = 100000000 or f = 1000000000
+}
+
+private
+predicate floatTrivial(Literal lit) {
+ (lit instanceof FloatingPointLiteral or lit instanceof DoubleLiteral) and
+ exists(float f |
+ f = lit.getValue().toFloat() and
+ (f.abs() <= 20.0 or powerOfTen(f))
+ )
+}
+
+private
+predicate stringTrivial(StringLiteral lit) {
+ lit.getLiteral().length() < 8
+}
+
+private
+predicate small(Literal lit) {
+ lit.getLiteral().length() <= 1
+}
+
+private
+predicate trivial(Literal lit) {
+ lit instanceof CharacterLiteral or
+ lit instanceof BooleanLiteral or
+ lit instanceof NullLiteral or
+ intTrivial(lit) or
+ floatTrivial(lit) or
+ stringTrivial(lit) or
+ longTrivial(lit) or
+ small(lit) or
+ excludedLiteral(lit)
+}
+
+private
+predicate literalIsConstantInitializer(Literal literal, Field f) {
+ exists(AssignExpr e, VarAccess access |
+ access = e.getAChildExpr() and
+ f = access.getVariable() and
+ access.getIndex() = 0 and
+ f.isFinal() and
+ f.isStatic() and
+ literal = e.getAChildExpr() and
+ literal.getIndex() = 1) and
+ not trivial(literal)
+}
+
+private
+predicate nonTrivialValue(string value, Literal literal, string context) {
+ value = literal.getLiteral() and
+ not trivial(literal) and
+ not literalIsConstantInitializer(literal, _) and
+ not literal.getParent*() instanceof ArrayInit and
+ not literal.getParent+() instanceof Annotation and
+ exists(MethodAccess ma | literal = ma.getAnArgument() and ma.getMethod().getName() = context)
+}
+
+
+private
+predicate valueOccurrenceCount(string value, int n, string context) {
+ n = strictcount(Literal lit | nonTrivialValue(value, lit, context)) and
+ n > 20
+}
+
+private
+predicate occurenceCount(Literal lit, string value, int n, string context) {
+ valueOccurrenceCount(value, n, context) and
+ value = lit.getLiteral() and
+ nonTrivialValue(_, lit, context)
+}
+
+
+/*
+ *
+ * Literals repeated frequently
+ *
+ */
+
+private
+predicate check(Literal lit, string value, int n, string context, CompilationUnit f) {
+ // Check that the literal is nontrivial
+ not trivial(lit) and
+ // Check that it is repeated a number of times
+ occurenceCount(lit, value, n, context) and n > 20 and
+ f = lit.getCompilationUnit()
+}
+
+private
+predicate checkWithFileCount(string value, int overallCount, int fileCount, string context, CompilationUnit f) {
+ fileCount = strictcount(Literal lit | check(lit, value, overallCount, context, f))
+}
+
+private
+predicate firstOccurrence(Literal lit, string value, string context, int n) {
+ exists(CompilationUnit f, int fileCount |
+ checkWithFileCount(value, n, fileCount, context, f) and
+ fileCount < 100 and
+ check(lit, value, n, context, f) and
+ not exists(Literal lit2, int start1, int start2 |
+ check(lit2, value, n, context, f) and
+ lit.getLocation().getStartLine() = start1 and
+ lit2.getLocation().getStartLine() = start2 and
+ start2 < start1)
+ )
+}
+
+predicate isNumber(Literal lit) {
+ lit.getType().getName() = "char" or
+ lit.getType().getName() = "short" or
+ lit.getType().getName() = "int" or
+ lit.getType().getName() = "long" or
+ lit.getType().getName() = "float" or
+ lit.getType().getName() = "double"
+}
+
+predicate magicConstant(Literal e, string msg) {
+ exists(string value, int n, string context |
+ firstOccurrence(e, value, context, n) and
+ msg = "Magic constant: literal '" + value + "' is used " + n.toString() + " times in calls to " + context
+ )
+}
+
+/*
+ *
+ * Literals where there is a defined constant with the same value
+ *
+ */
+
+private
+predicate relevantField(Field f, string value) {
+ exists(Literal lit | not trivial(lit) and value = lit.getLiteral() and literalIsConstantInitializer(lit, f))
+}
+
+private
+predicate relevantType(RefType t, string value, Package p) {
+ exists(Literal lit | nonTrivialValue(value, lit, _) |
+ lit.getEnclosingCallable().getDeclaringType() = t and p = t.getPackage()
+ )
+}
+
+private
+predicate fieldUsedInContext(Field constField, string context) {
+ literalIsConstantInitializer(_, constField) and
+ exists(MethodAccess ma |
+ constField.getAnAccess() = ma.getAnArgument() and
+ ma.getMethod().getName() = context)
+}
+
+private
+predicate candidateConstantForLiteral(Field constField, RefType literalType, Literal magicLiteral, string context) {
+ exists(Literal initLiteral |
+ literalIsConstantInitializer(initLiteral, constField) and
+ exists(string value |
+ value = initLiteral.getLiteral() and
+ nonTrivialValue(value, magicLiteral, context) and
+ fieldUsedInContext(constField, context)
+ ) and
+ literalType = magicLiteral.getEnclosingCallable().getDeclaringType()
+ )
+}
+
+private
+RefType inheritsProtected(Field f) {
+ (f.isProtected() and result.getASupertype() = f.getDeclaringType()) or
+ exists(RefType mid | mid = inheritsProtected(f) and result.getASupertype() = mid)
+}
+
+private
+predicate constantForLiteral(Field field, string value, RefType fromType, Literal magicLiteral, string context) {
+ //public fields in public classes
+ (
+ candidateConstantForLiteral(field, fromType, magicLiteral, context) and
+ relevantField(field, value) and
+ field.getDeclaringType().isPublic() and
+ field.isPublic() and
+ relevantType(fromType, value, _)
+ )
+ or
+ //in same class
+ (
+ candidateConstantForLiteral(field, fromType, magicLiteral, context) and
+ relevantField(field, value) and
+ fromType = field.getDeclaringType() and
+ relevantType(fromType, value, _)
+ )
+ or
+ //in subclass and not private
+ (
+ candidateConstantForLiteral(field, fromType, magicLiteral, context) and
+ relevantField(field, value) and
+ field.isProtected() and
+ fromType = inheritsProtected(field) and
+ relevantType(fromType, value, _)
+ )
+ or
+ //not private and in same package
+ (
+ candidateConstantForLiteral(field, fromType, magicLiteral, context) and
+ relevantField(field, value) and
+ field.isPackageProtected() and
+ exists(Package p |
+ exists(CompilationUnit cu | cu = field.getCompilationUnit() and cu.getPackage() = p) and
+ relevantType(fromType, value, p)
+ )
+ )
+}
+
+private
+predicate canUseFieldInsteadOfLiteral(Field constField, Literal magicLiteral, string context) {
+ constantForLiteral(constField, _, _, magicLiteral, context)
+}
+
+predicate literalInsteadOfConstant(Literal magicLiteral, string message, Field constField, string linkText) {
+ exists(string context |
+ canUseFieldInsteadOfLiteral(constField, magicLiteral, context) and
+ message =
+ "Literal value '" + magicLiteral.getLiteral() + "' used " +
+ " in a call to " + context +
+ "; consider using the defined constant $@." and linkText = constField.getName()
+ and
+ (
+ constField.getCompilationUnit() = magicLiteral.getCompilationUnit() or
+ not almostPrivate(constField)
+ )
+ and
+ (
+ constField.getCompilationUnit().getPackage() = magicLiteral.getCompilationUnit().getPackage() or
+ not almostPackageProtected(constField)
+ )
+ )
+}
+
+private predicate almostPrivate(Field f) {
+ not exists(VarAccess va | va.getVariable() = f and va.getCompilationUnit() != f.getCompilationUnit())
+ or
+ exists(Interface i | i = f.getDeclaringType() |
+ forall(VarAccess va | va.getVariable() = f |
+ va.getEnclosingCallable().getDeclaringType().getASupertype*() = i
+ )
+ )
+}
+
+private predicate almostPackageProtected(Field f) {
+ not exists(VarAccess va | va.getVariable() = f |
+ va.getCompilationUnit().getPackage() != f.getCompilationUnit().getPackage()
+ )
+}
+
+/*
+ *
+ * Removing literals from uninteresting contexts
+ *
+ */
+
+private
+predicate excludedLiteral(Literal lit) {
+ // Remove test cases
+ lit.getEnclosingCallable().getDeclaringType() instanceof TestClass
+ or
+ exists(MethodAccess ma | lit = ma.getAnArgument() | ma.getMethod() instanceof TestMethod)
+}
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java
new file mode 100644
index 00000000000..12891b56e17
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.java
@@ -0,0 +1,36 @@
+// Problem version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+ int timeout = 60000; // AVOID: Magic number
+
+ new MagicConstants().serve(IP, PORT, USERNAME, timeout);
+ }
+}
+
+
+// Fixed version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+ final static public int TIMEOUT = 60000; // Magic number is replaced by named constant
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+
+ new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'TIMEOUT' constant
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp
new file mode 100644
index 00000000000..1c675e2b717
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.qhelp
@@ -0,0 +1,53 @@
+
+
+
+
+
+A magic number is a numeric literal (for example, 8080,
+2048) that is used in the middle of a block of code without
+explanation. It is considered bad practice to use magic numbers because:
+
+
+- A number in isolation can be difficult for other programmers to understand.
+
+- It can be difficult to update the code if the requirements change. For example, if the magic
+number represents the number of guests allowed, adding one more guest means that you must change
+every occurrence of the magic number.
+
+
+
+
+
+
+Assign the magic number to a new named constant, and use this instead. This overcomes the two problems
+with magic numbers:
+
+
+- A named constant (such as
MAX_GUESTS) is more easily understood by other programmers.
+
+- Using the same named constant in many places makes the code much easier to
+update if the requirements change, because you have to update the number in only one place.
+
+
+
+
+
+
+The following example shows a magic number timeout. This should be replaced by a new
+named constant, as shown in the fixed version.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql
new file mode 100644
index 00000000000..b60fd486f3c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsNumbers.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Magic numbers
+ * @description A magic number makes code less readable and maintainable.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/magic-number
+ * @tags maintainability
+ * readability
+ * statistical
+ * non-attributable
+ */
+import java
+import MagicConstants
+
+from Literal e, string msg
+where
+ magicConstant(e, msg) and
+ isNumber(e)
+select e, msg
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java
new file mode 100644
index 00000000000..e3cdaa5cb80
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.java
@@ -0,0 +1,36 @@
+// Problem version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+ String username = "test"; // AVOID: Magic string
+
+ new MagicConstants().serve(IP, PORT, username, TIMEOUT);
+ }
+}
+
+
+// Fixed version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public int USERNAME = "test"; // Magic string is replaced by named constant
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+
+ new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'USERNAME' constant
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp
new file mode 100644
index 00000000000..8d0a7913f5f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.qhelp
@@ -0,0 +1,53 @@
+
+
+
+
+
+A magic string is a string literal (for example, "SELECT",
+"127.0.0.1") that is used in the middle of a block of code without
+explanation. It is considered bad practice to use magic strings because:
+
+
+- A string in isolation can be difficult for other programmers to understand.
+
+- It can be difficult to update the code if the requirements change. For example, if the magic
+string represents a protocol, changing the protocol means that you must change
+every occurrence of the protocol.
+
+
+
+
+
+
+Assign the magic string to a new named constant, and use this instead. This overcomes the two problems
+with magic strings:
+
+
+- A named constant (such as
SMTP_HELO) is more easily understood by other programmers.
+
+- Using the same named constant in many places makes the code much easier to
+update if the requirements change, because you have to update the string in only one place.
+
+
+
+
+
+
+The following example shows a magic string username. This should be replaced by a new
+named constant, as shown in the fixed version.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql
new file mode 100644
index 00000000000..3e69696daa4
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicConstantsString.ql
@@ -0,0 +1,78 @@
+/**
+ * @name Magic strings
+ * @description A magic string makes code less readable and maintainable.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/magic-string
+ * @tags maintainability
+ * readability
+ * statistical
+ * non-attributable
+ */
+import java
+import MagicConstants
+
+/**
+ * Holds if the string is a standard system property as defined in:
+ *
+ * http://docs.oracle.com/javase/7/docs/api/java/lang/System.html#getProperties()
+ */
+predicate isSystemProperty(string e) {
+ e = "java.version" or
+ e = "java.vendor" or
+ e = "java.vendor.url" or
+ e = "java.home" or
+ e = "java.vm.specification.version" or
+ e = "java.vm.specification.vendor" or
+ e = "java.vm.specification.name" or
+ e = "java.vm.version" or
+ e = "java.vm.vendor" or
+ e = "java.vm.name" or
+ e = "java.specification.version" or
+ e = "java.specification.vendor" or
+ e = "java.specification.name" or
+ e = "java.class.version" or
+ e = "java.class.path" or
+ e = "java.library.path" or
+ e = "java.io.tmpdir" or
+ e = "java.compiler" or
+ e = "java.ext.dirs" or
+ e = "os.name" or
+ e = "os.arch" or
+ e = "os.version" or
+ e = "file.separator" or
+ e = "path.separator" or
+ e = "line.separator" or
+ e = "user.name" or
+ e = "user.home" or
+ e = "user.dir"
+}
+
+predicate trivialContext(Literal e) {
+ // String concatenation.
+ e.getParent() instanceof AddExpr or
+ e.getParent() instanceof AssignAddExpr or
+ exists(MethodAccess ma |
+ ma.getMethod().getName() = "append" and
+ (e = ma.getAnArgument() or e = ma.getQualifier())
+ ) or
+ // Standard property in a call to `System.getProperty()`.
+ exists(MethodAccess ma |
+ ma.getMethod().getName() = "getProperty" and
+ e = ma.getAnArgument() and
+ ma.getMethod().getDeclaringType() instanceof TypeSystem and
+ isSystemProperty(e.getValue())
+ ) or
+ // Message in an exception.
+ exists(ClassInstanceExpr constr |
+ constr.getType().(RefType).getASupertype+().hasName("Exception") and
+ e = constr.getArgument(0)
+ )
+}
+
+from StringLiteral e, string msg
+where
+ magicConstant(e, msg) and
+ not trivialContext(e)
+select e, msg
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java
new file mode 100644
index 00000000000..beb2916613b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.java
@@ -0,0 +1,37 @@
+// Problem version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+ int internal_port = 8080; // AVOID: Magic number
+
+ new MagicConstants().serve(IP, internal_port, USERNAME, TIMEOUT);
+ }
+}
+
+
+// Fixed version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+
+ new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); // Use 'PORT' constant
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp
new file mode 100644
index 00000000000..0dc6679535e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.qhelp
@@ -0,0 +1,53 @@
+
+
+
+
+
+A magic number is a numeric literal (for example, 8080,
+2048) that is used in the middle of a block of code without
+explanation. It is considered bad practice to use magic numbers because:
+
+
+- A number in isolation can be difficult for other programmers to understand.
+
+- It can be difficult to update the code if the requirements change. For example, if the magic
+number represents the number of guests allowed, adding one more guest means that you must change
+every occurrence of the magic number.
+
+
+
+
+
+
+Replace the magic number with the existing named constant. This overcomes the two problems
+with magic numbers:
+
+
+- A named constant (such as
MAX_GUESTS) is more easily understood by other programmers.
+
+- Using the same named constant in many places makes the code much easier to
+update if the requirements change, because you have to update the number in only one place.
+
+
+
+
+
+
+The following example shows a magic number internal_port. This should be replaced by
+the existing named constant, as shown in the fixed version.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql
new file mode 100644
index 00000000000..5f030dd7da8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicNumbersUseConstant.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Magic numbers: use defined constant
+ * @description A magic number, which is used instead of an existing named constant, makes code less
+ * readable and maintainable.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/use-number-constant
+ * @tags maintainability
+ * readability
+ */
+import java
+import MagicConstants
+
+from Literal magicLiteral, string message, Field field, string linkText
+where
+ isNumber(magicLiteral) and
+ literalInsteadOfConstant(magicLiteral, message, field, linkText)
+select magicLiteral, message, field, linkText
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java
new file mode 100644
index 00000000000..b4b7c116e73
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.java
@@ -0,0 +1,37 @@
+// Problem version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+ String internal_ip = "127.0.0.1"; // AVOID: Magic string
+
+ new MagicConstants().serve(internal_ip, PORT, USERNAME, TIMEOUT);
+ }
+}
+
+
+// Fixed version
+public class MagicConstants
+{
+ final static public String IP = "127.0.0.1";
+ final static public int PORT = 8080;
+ final static public String USERNAME = "test";
+ final static public int TIMEOUT = 60000;
+
+ public void serve(String ip, int port, String user, int timeout) {
+ // ...
+ }
+
+ public static void main(String[] args) {
+
+ new MagicConstants().serve(IP, PORT, USERNAME, TIMEOUT); //Use 'IP' constant
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp
new file mode 100644
index 00000000000..1a6aa84b134
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.qhelp
@@ -0,0 +1,53 @@
+
+
+
+
+
+A magic string is a string literal (for example, "SELECT",
+"127.0.0.1") that is used in the middle of a block of code without
+explanation. It is considered bad practice to use magic strings because:
+
+
+- A string in isolation can be difficult for other programmers to understand.
+
+- It can be difficult to update the code if the requirements change. For example, if the magic
+string represents a protocol, changing the protocol means that you must change
+every occurrence of the protocol.
+
+
+
+
+
+
+Replace the magic string with the existing named constant. This overcomes the two problems
+with magic strings:
+
+
+- A named constant (such as
SMTP_HELO) is more easily understood by other programmers.
+
+- Using the same named constant in many places makes the code much easier to
+update if the requirements change, because you have to update the string in only one place.
+
+
+
+
+
+
+The following example shows a magic string internal_ip. This should be replaced by
+the existing named constant, as shown in the fixed version.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.G25. Prentice Hall, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql
new file mode 100644
index 00000000000..e255165848f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Magic Constants/MagicStringsUseConstant.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Magic strings: use defined constant
+ * @description A magic string, which is used instead of an existing named constant, makes code less
+ * readable and maintainable.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/use-string-constant
+ * @tags maintainability
+ * readability
+ */
+import java
+import MagicConstants
+
+from StringLiteral magicLiteral, string message, Field field, string linkText
+where literalInsteadOfConstant(magicLiteral, message, field, linkText)
+select magicLiteral, message, field, linkText
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java
new file mode 100644
index 00000000000..7a1d1cc86e1
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.java
@@ -0,0 +1,24 @@
+public class Outer
+{
+ void printMessage() {
+ System.out.println("Outer");
+ }
+
+ class Inner extends Super
+ {
+ void ambiguous() {
+ printMessage(); // Ambiguous call
+ }
+ }
+
+ public static void main(String[] args) {
+ new Outer().new Inner().ambiguous();
+ }
+}
+
+class Super
+{
+ void printMessage() {
+ System.out.println("Super");
+ }
+}
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp
new file mode 100644
index 00000000000..ad4515d7021
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.qhelp
@@ -0,0 +1,48 @@
+
+
+
+
+
+If a call is made to a method from an inner class A, and a method of that name is
+defined in both a superclass of A and an outer class of A, it is not clear to a programmer
+which method is intended to be called.
+
+
+
+
+In the following example, it is not clear whether the call to printMessage calls the
+method that is defined in Outer or Super.
+
+
+
+Inherited methods take precedence over methods in outer classes, so the method in the superclass
+is called. However, such situations are a potential cause of confusion and defects.
+
+
+
+
+Resolve the ambiguity by explicitly qualifying the method call:
+
+
+- To specify the outer class, prefix the method with
Outer.this..
+- To specify the superclass, prefix the method with
super..
+
+
+ In the above example, the call to printMessage could be replaced by
+either Outer.this.printMessage or super.printMessage, depending on which
+method you intend to call. To preserve the behavior in the example, use super.printMessage.
+
+
+
+
+
+
+Inner Classes Specification:
+What are top-level classes and inner classes?.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql
new file mode 100644
index 00000000000..cb9791add95
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/AmbiguousOuterSuper.ql
@@ -0,0 +1,59 @@
+/**
+ * @name Subtle call to inherited method
+ * @description An unqualified call to a method that exists with the same signature in both a
+ * superclass and an outer class is ambiguous.
+ * @kind problem
+ * @problem.severity warning
+ * @precision very-high
+ * @id java/subtle-inherited-call
+ * @tags reliability
+ * readability
+ */
+import java
+
+RefType nestedSupertypePlus(RefType t) {
+ t.getASourceSupertype() = result and
+ t instanceof NestedType or
+ exists(RefType mid | mid = nestedSupertypePlus(t) |
+ mid.getASourceSupertype() = result
+ )
+}
+
+/**
+ * A call (without a qualifier) in a nested type
+ * to an inherited method with the specified `signature`.
+ */
+predicate callToInheritedMethod(RefType lexicalScope, MethodAccess ma, string signature) {
+ not ma.getMethod().isStatic() and
+ not ma.hasQualifier() and
+ ma.getEnclosingCallable().getDeclaringType() = lexicalScope and
+ nestedSupertypePlus(lexicalScope).getAMethod() = ma.getMethod() and
+ signature = ma.getMethod().getSignature()
+}
+
+/**
+ * Return accessible methods in an outer class of `nested`.
+ *
+ * Accessible means that if a method is virtual then none of the nested
+ * classes "on-route" can be static.
+ */
+Method methodInEnclosingType(NestedType nested, string signature) {
+ (result.isStatic() or not nested.isStatic())
+ and
+ result.getSignature() = signature
+ and
+ exists(RefType outer | outer = nested.getEnclosingType() |
+ result = outer.getAMethod() or
+ result = methodInEnclosingType(nested, signature)
+ )
+}
+
+from MethodAccess ma, Method m, NestedType nt, string signature
+where
+ callToInheritedMethod(nt, ma, signature) and
+ m = methodInEnclosingType(nt, signature) and
+ // There is actually scope for confusion.
+ not nt.getASourceSupertype+() = m.getDeclaringType()
+select ma, "A $@ is called instead of a $@.",
+ ma.getMethod(), "method declared in a superclass",
+ m, "method with the same signature in an enclosing class"
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java
new file mode 100644
index 00000000000..24bc7bdb7b5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.java
@@ -0,0 +1,18 @@
+public class InternetResource
+{
+ private String protocol;
+ private String host;
+ private String path;
+
+ // ...
+
+ public String toUri() {
+ return protocol + "://" + host + "/" + path;
+ }
+
+ // ...
+
+ public String toURI() {
+ return toUri();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp
new file mode 100644
index 00000000000..6479ef09840
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.qhelp
@@ -0,0 +1,35 @@
+
+
+
+
+
+It is bad practice to have methods in a class with names that differ
+only in their capitalization. This can be confusing and lead to mistakes.
+
+
+
+
+Name the methods to make the distinction between them
+clear.
+
+
+
+
+The following example shows a class that contains two methods: toUri and
+toURI. One or both of them should be renamed.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, 17.N4. Prentice Hall, 2008.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql
new file mode 100644
index 00000000000..27f5561be57
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingMethodNames.ql
@@ -0,0 +1,29 @@
+/**
+ * @name Confusing method names because of capitalization
+ * @description Methods in the same class whose names differ only in capitalization are
+ * confusing.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/confusing-method-name
+ * @tags maintainability
+ * readability
+ * naming
+ */
+import java
+
+predicate methodTypeAndLowerCaseName(Method m, RefType t, string name) {
+ t = m.getDeclaringType() and
+ name = m.getName().toLowerCase()
+}
+
+from Method m, Method n
+where
+ exists(RefType t, string name |
+ methodTypeAndLowerCaseName(m, t, name) and
+ methodTypeAndLowerCaseName(n, t, name)
+ ) and
+ not m.getAnAnnotation() instanceof DeprecatedAnnotation and
+ not n.getAnAnnotation() instanceof DeprecatedAnnotation and
+ m.getName() < n.getName()
+select m, "The method '" + m.getName() + "' may be confused with $@.", n, n.getName()
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp
new file mode 100644
index 00000000000..39a9b635744
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.qhelp
@@ -0,0 +1,51 @@
+
+
+
+
+
+Overloaded method declarations that have the same number of parameters may be confusing
+if none of the corresponding pairs of parameter types is substantially different. A pair of
+parameter types A and B is substantially different if A cannot be cast to B and B cannot be cast
+to A. If the parameter types are not substantially different then the programmer may assume that the method with parameter type A is called when in fact
+the method with parameter type B is called.
+
+
+
+
+It is generally best to avoid
+declaring overloaded methods with the same number of parameters, unless at least one of
+the corresponding parameter pairs is substantially different.
+
+
+
+
+
+
+Declaring overloaded methods process(Object obj) and process(String s)
+is confusing because the parameter types are not substantially different. It is clearer to declare
+methods with different names: processObject(Object obj) and
+processString(String s).
+
+In contrast, declaring overloaded methods process(Object obj, String s) and
+process(String s, int i) is not as confusing because the second parameters of each
+method are substantially different.
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition),
+ Item 41.
+ Addison-Wesley, 2008.
+
+
+ Java Language Specification:
+ 15.12 Method Invocation Expressions.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
new file mode 100644
index 00000000000..cd754096245
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
@@ -0,0 +1,123 @@
+/**
+ * @name Confusing overloading of methods
+ * @description Overloaded methods that have the same number of parameters, where each pair of
+ * corresponding parameter types is convertible by casting or autoboxing, may be
+ * confusing.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/confusing-method-signature
+ * @tags maintainability
+ * readability
+ * naming
+ */
+import java
+
+private pragma[nomagic]
+predicate confusingPrimitiveBoxedTypes(Type t, Type u) {
+ t.(PrimitiveType).getBoxedType() = u or
+ u.(PrimitiveType).getBoxedType() = t
+}
+
+private
+predicate overloadedMethods(Method n, Method m) {
+ n.fromSource() and
+ exists(RefType rt, string name, int numParams |
+ candidateMethod(rt, m, name, numParams) and
+ candidateMethod(rt, n, name, numParams)
+ ) and
+ n != m and
+ n.getSourceDeclaration().getSignature() < m.getSourceDeclaration().getSignature()
+}
+
+private
+predicate overloadedMethodsMostSpecific(Method n, Method m) {
+ overloadedMethods(n, m) and
+ not exists(Method nSup, Method mSup |
+ n.overridesOrInstantiates*(nSup) and m.overridesOrInstantiates*(mSup)
+ |
+ overloadedMethods(nSup, mSup) and
+ (n != nSup or m != mSup)
+ )
+}
+
+/**
+ * A whitelist of names that are commonly overloaded in odd ways and should
+ * not be reported by this query.
+ */
+private predicate whitelist(string name) {
+ name = "visit"
+}
+
+/**
+ * Method `m` has name `name`, number of parameters `numParams`
+ * and is declared in `t` or inherited from a supertype of `t`.
+ */
+private
+predicate candidateMethod(RefType t, Method m, string name, int numParam) {
+ exists(Method n | n.getSourceDeclaration() = m | t.inherits(n)) and
+ m.getName() = name and
+ m.getNumberOfParameters() = numParam and
+ m = m.getSourceDeclaration() and
+ not m.getAnAnnotation() instanceof DeprecatedAnnotation and
+ not whitelist(name)
+}
+
+private pragma[inline]
+predicate potentiallyConfusingTypes(Type a, Type b) {
+ exists(RefType commonSubtype | hasSubtypeOrInstantiation*(a, commonSubtype) |
+ hasSubtypeOrInstantiation*(b, commonSubtype)
+ ) or
+ confusingPrimitiveBoxedTypes(a, b)
+}
+
+private
+predicate hasSubtypeOrInstantiation(RefType t, RefType sub) {
+ hasSubtype(t, sub) or
+ sub.getSourceDeclaration() = t
+}
+
+private
+predicate confusinglyOverloaded(Method m, Method n) {
+ overloadedMethodsMostSpecific(n, m) and
+ forall(int i, Parameter p, Parameter q | p = n.getParameter(i) and q = m.getParameter(i) |
+ potentiallyConfusingTypes(p.getType(), q.getType())
+ ) and
+ // There is no possibility for confusion between two methods with identical behavior.
+ not exists(Method target | delegate*(m, target) and delegate*(n, target))
+}
+
+private
+predicate wrappedAccess(Expr e, MethodAccess ma) {
+ e = ma or
+ wrappedAccess(e.(ParExpr).getExpr(), ma) or
+ wrappedAccess(e.(CastExpr).getExpr(), ma)
+}
+
+private
+predicate delegate(Method caller, Method callee) {
+ exists(MethodAccess ma | ma.getMethod() = callee |
+ exists(Stmt stmt | stmt = caller.getBody().(SingletonBlock).getStmt() |
+ wrappedAccess(stmt.(ExprStmt).getExpr(), ma) or
+ wrappedAccess(stmt.(ReturnStmt).getResult(), ma)
+ ) and
+ forex(Parameter p, int i, Expr arg | p = caller.getParameter(i) and ma.getArgument(i) = arg |
+ // The parameter is propagated without modification.
+ arg = p.getAnAccess() or
+ // The parameter is cast to a supertype.
+ arg.(CastExpr).getExpr() = p.getAnAccess() and
+ arg.getType().(RefType).getASubtype() = p.getType()
+ )
+ )
+}
+
+from Method m, Method n, string messageQualifier
+where
+ confusinglyOverloaded(m, n) and
+ (if m.getDeclaringType() = n.getDeclaringType()
+ then messageQualifier = ""
+ else messageQualifier = m.getDeclaringType().getName() + ".")
+select n, "Method " + n.getDeclaringType() + "." + n +
+ "(..) could be confused with overloaded method $@, since dispatch depends on static types.",
+ m.getSourceDeclaration(), messageQualifier + m.getName()
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java
new file mode 100644
index 00000000000..618bb53090b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.java
@@ -0,0 +1,12 @@
+public class Customer
+{
+ private String title;
+ private String forename;
+ private String surname;
+
+ // ...
+
+ public String tostring() { // Incorrect capitalization of 'toString'
+ return title + " " + forename + " " + surname;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp
new file mode 100644
index 00000000000..248b3d88b89
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.qhelp
@@ -0,0 +1,45 @@
+
+
+
+
+
+If a method that would override another method but does not
+because the name is capitalized differently, there are two possibilities:
+
+
+ - The programmer intends the method to override the other method, and the
+ difference in capitalization is a typographical error.
+ - The programmer does not intend the method to override the other method,
+ in which case the similarity of the names is very confusing.
+
+
+
+
+
+If overriding is intended, make the capitalization of the
+two methods the same.
+
+If overriding is not intended, consider naming the methods to make
+the distinction between them clear.
+
+
+
+
+In the following example, toString has been
+wrongly capitalized as tostring. This means that objects of type
+Customer do not print correctly.
+
+
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.N4. Prentice Hall, 2008.
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql
new file mode 100644
index 00000000000..caeca946f07
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverridesNames.ql
@@ -0,0 +1,75 @@
+/**
+ * @name Confusing method names because of overriding
+ * @description A method that would override another method but does not, because the name is
+ * capitalized differently, is confusing and may be a mistake.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/confusing-override-name
+ * @tags maintainability
+ * readability
+ * naming
+ */
+import java
+
+/**
+ * For each class, get all methods from this class or its
+ * superclasses, with their names in lowercase.
+ */
+predicate methodNames(RefType t, Method m, string lowercase, string name) {
+ exists(RefType t2 |
+ m.getDeclaringType() = t2 and
+ hasSubtype*(t2, t)
+ ) and
+ name = m.getName() and
+ lowercase = name.toLowerCase() and
+ lowercase.length() > 1
+}
+
+/**
+ * For each class, find the pairs of methods that
+ * are candidates for being confusing in this class.
+ */
+predicate confusing(Method m1, Method m2) {
+ exists(RefType t, string lower, string name1, string name2 |
+ methodNames(t, m1, lower, name1) and
+ methodNames(t, m2, lower, name2) and
+ name1 != name2
+ )
+}
+
+/*
+ * Two methods are considered confusing if all of the following conditions hold:
+ *
+ * - They are both static methods or both instance methods.
+ * - They are not declared in the same class, and the superclass method is
+ * not overridden in an intermediate class.
+ * - They have different names.
+ * - They have the same names if case is ignored.
+ * - There is no method in the subclass that has the same name as
+ * the superclass method.
+ *
+ * There is an additional check that only methods with names longer than one character
+ * can be considered confusing.
+ */
+
+from Method m1, Method m2
+where
+ confusing(m1, m2) and
+ m1.getDeclaringType() != m2.getDeclaringType() and
+ (
+ m1.isStatic() and m2.isStatic()
+ or
+ not m1.isStatic() and not m2.isStatic()
+ ) and
+ not exists(Method mid |
+ confusing(m1, mid) and
+ mid.getDeclaringType().getASupertype+() = m2.getDeclaringType()
+ ) and
+ not exists(Method notConfusing |
+ notConfusing.getDeclaringType() = m1.getDeclaringType() and
+ notConfusing.getName() = m2.getName()
+ )
+select m1,
+ "It is confusing to have methods " + m1.getName() + " in " + m1.getDeclaringType().getName() + " and "
+ + m2.getName() + " in " + m2.getDeclaringType().getName() + "."
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java
new file mode 100644
index 00000000000..9909f06de27
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.java
@@ -0,0 +1,24 @@
+public class FieldMasksSuperField {
+ static class Person {
+ protected int age;
+ public Person(int age)
+ {
+ this.age = age;
+ }
+ }
+
+ static class Employee extends Person {
+ protected int age; // This field hides 'Person.age'.
+ protected int numberOfYearsEmployed;
+ public Employee(int age, int numberOfYearsEmployed)
+ {
+ super(age);
+ this.numberOfYearsEmployed = numberOfYearsEmployed;
+ }
+ }
+
+ public static void main(String[] args) {
+ Employee e = new Employee(20, 2);
+ System.out.println(e.age);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp
new file mode 100644
index 00000000000..15a1acf843f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.qhelp
@@ -0,0 +1,41 @@
+
+
+
+
+
+A field that has the same name as a field in a superclass hides the field in the superclass.
+Such hiding might be unintentional, especially if there are no references to the hidden field using
+the super qualifier. In any case, it makes code more difficult to read.
+
+
+
+
+Ensure that any hiding is intentional. For clarity, it may be better to rename the field in the
+subclass.
+
+
+
+In the following example, the programmer unintentionally added an age field to Employee, which
+hides the age field in Person. The constructor in Person sets the age
+ field in Person to 20 but the age field in Employee
+is still 0. This means that the program outputs 0, which is probably not what was intended.
+
+To fix this, delete the declaration of age on line 11.
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+The Java Tutorials:
+Hiding Fields.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql
new file mode 100644
index 00000000000..37d745a3410
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/FieldMasksSuperField.ql
@@ -0,0 +1,35 @@
+/**
+ * @name Field masks field in super class
+ * @description Hiding a field in a superclass by redeclaring it in a subclass might be
+ * unintentional, especially if references to the hidden field are not qualified using
+ * 'super'.
+ * @kind problem
+ * @problem.severity warning
+ * @precision medium
+ * @id java/field-masks-super-field
+ * @tags maintainability
+ * readability
+ */
+import java
+
+class VisibleInstanceField extends Field {
+ VisibleInstanceField() {
+ not this.isPrivate() and
+ not this.isStatic()
+ }
+}
+
+from RefType type, RefType supertype,
+ VisibleInstanceField masked, VisibleInstanceField masking
+where
+ type.getASourceSupertype+() = supertype and
+ masking.getDeclaringType() = type and
+ masked.getDeclaringType() = supertype and
+ masked.getName() = masking.getName() and
+ // Exclude intentional masking.
+ not exists(VarAccess va | va.getVariable() = masked |
+ va.getQualifier() instanceof SuperAccess
+ ) and
+ type.fromSource()
+select masking, "This field shadows another field called $@ in a superclass.",
+ masked, masked.getName()
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp
new file mode 100644
index 00000000000..a4b9cbea1a1
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.qhelp
@@ -0,0 +1,23 @@
+
+
+
+
+
+This query finds local variables that shadow like-named field declarations. This is confusing since it might easily lead to assignments
+to the local variable that should have been to the corresponding field.
+
+
+
+For clarity, it may be better to rename the variable to avoid shadowing.
+
+
+
+
+
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql
new file mode 100644
index 00000000000..bea931366cb
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsField.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Local variable shadows field
+ * @description If a local variable shadows a field of the same name, each use of
+ * the name is harder to read.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/local-shadows-field-unused
+ * @tags maintainability
+ */
+import java
+import Shadowing
+
+from LocalVariableDecl d, Class c, Field f, Callable callable, string callableType
+where
+ shadows(d, c, f, callable) and
+ not assignmentToShadowingLocal(d, f) and
+ not assignmentFromShadowingLocal(d, f) and
+ not thisAccess(d, f) and not confusingAccess(d, f) and
+ (if callable instanceof Constructor then callableType = "" else callableType = "method ")
+select
+ d, "This local variable shadows field $@, which is not used in " + callableType + "$@.",
+ f, f.getName(), callable, callable.getName()
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java
new file mode 100644
index 00000000000..6dac73cc35b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.java
@@ -0,0 +1,15 @@
+public class Container
+{
+ private int[] values; // Field called 'values'
+
+ public Container (int... values) {
+ this.values = values;
+ }
+
+ public Container dup() {
+ int length = values.length;
+ int[] values = new int[length]; // Local variable called 'values'
+ Container result = new Container(values);
+ return result;
+ }
+}
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp
new file mode 100644
index 00000000000..29d419f72e1
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.qhelp
@@ -0,0 +1,41 @@
+
+
+
+
+
+If a method declares a local variable with the same name as a field,
+then it is very easy to mix up the two when reading or modifying
+the program.
+
+
+
+
+Consider using different names for the field and local variable to make the difference
+between them clear.
+
+
+
+
+The following example shows a local variable values that has the same name as a field.
+
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+Java Language Specification:
+
+6.4 Shadowing and Obscuring.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql
new file mode 100644
index 00000000000..75b99c9ae4b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/LocalShadowsFieldConfusing.ql
@@ -0,0 +1,32 @@
+/**
+ * @name Possible confusion of local and field
+ * @description A method in which a variable is declared with the same name as a field is difficult
+ * to understand.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/local-shadows-field
+ * @tags maintainability
+ * readability
+ */
+import java
+import Shadowing
+
+from LocalVariableDecl d, Class c, Field f, Callable callable, string callableType, string message
+where
+ shadows(d, c, f, callable) and
+ not assignmentToShadowingLocal(d, f) and
+ not assignmentFromShadowingLocal(d, f) and
+ (if callable instanceof Constructor
+ then callableType = ""
+ else callableType = "method ") and
+ (
+ confusingAccess(d, f) and
+ message = "Confusing name: " + callableType +
+ "$@ also refers to field $@ (without qualifying it with 'this')."
+ or
+ thisAccess(d, f) and not confusingAccess(d, f) and
+ message = "Potentially confusing name: " + callableType +
+ "$@ also refers to field $@ (as this." + f.getName() + ")."
+ )
+select d, message, callable, callable.getName(), f, f.getName()
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java
new file mode 100644
index 00000000000..e2c5a976e86
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.java
@@ -0,0 +1,16 @@
+import com.company.util.Attendees;
+
+public class Meeting
+{
+ private Attendees attendees;
+
+ // ...
+ // Many lines
+ // ...
+
+ // AVOID: This class has the same name as its superclass.
+ private static class Attendees extends com.company.util.Attendees
+ {
+ // ...
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp
new file mode 100644
index 00000000000..580f88cb6c6
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.qhelp
@@ -0,0 +1,38 @@
+
+
+
+
+
+A class that has the same name as its superclass may be confusing.
+
+
+
+
+Clarify the difference between the subclass and the superclass by
+using different names.
+
+
+
+
+In the following example, it is not clear that the attendees field refers to the
+inner class Attendees and not the class com.company.util.Attendees.
+
+
+
+To fix this, the inner class should be renamed.
+
+
+
+
+
+
+R. C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship, §17.N4. Prentice
+Hall, 2008.
+
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql
new file mode 100644
index 00000000000..4ac08aeb8ca
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/SameNameAsSuper.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Class has same name as super class
+ * @description A class that has the same name as its superclass may be confusing.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/class-name-matches-super-class
+ * @tags maintainability
+ * readability
+ * naming
+ */
+import java
+
+from RefType sub, RefType sup
+where
+ sub.fromSource() and
+ sup = sub.getASupertype() and
+ sub.getName() = sup.getName()
+select
+ sub, sub.getName() + " has the same name as its supertype $@.",
+ sup, sup.getQualifiedName()
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll b/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll
new file mode 100644
index 00000000000..7488ac84590
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/Shadowing.qll
@@ -0,0 +1,83 @@
+import java
+
+predicate initializedToField(LocalVariableDecl d, Field f) {
+ exists(LocalVariableDeclExpr e | e.getVariable() = d and f.getAnAccess() = e.getInit())
+}
+
+predicate getterFor(Method m, Field f) {
+ m.getName().matches("get%") and
+ m.getDeclaringType() = f.getDeclaringType() and
+ exists(ReturnStmt ret | ret.getEnclosingCallable() = m and ret.getResult() = f.getAnAccess())
+}
+
+predicate setterFor(Method m, Field f) {
+ m.getName().matches("set%") and
+ m.getDeclaringType() = f.getDeclaringType() and
+ f.getAnAssignedValue() = m.getAParameter().getAnAccess() and
+ m.getNumberOfParameters() = 1
+}
+
+predicate shadows(LocalVariableDecl d, Class c, Field f, Callable method) {
+ d.getCallable() = method and
+ method.getDeclaringType() = c and
+ c.getAField() = f and
+ f.getName() = d.getName() and
+ f.getType() = d.getType() and
+ not d.getCallable().isStatic() and
+ not f.isStatic()
+}
+
+predicate thisAccess(LocalVariableDecl d, Field f) {
+ shadows(d, _, f, _) and
+ exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f |
+ va.getQualifier() instanceof ThisAccess and
+ va.getEnclosingCallable() = d.getCallable()
+ )
+}
+
+predicate confusingAccess(LocalVariableDecl d, Field f) {
+ shadows(d, _, f, _) and
+ exists(VarAccess va | va.getVariable().(Field).getSourceDeclaration() = f |
+ not exists(va.getQualifier()) and
+ va.getEnclosingCallable() = d.getCallable()
+ )
+}
+
+predicate assignmentToShadowingLocal(LocalVariableDecl d, Field f) {
+ shadows(d, _, _, _) and
+ exists(Expr assignedValue, Expr use |
+ d.getAnAssignedValue() = assignedValue and getARelevantChild(assignedValue) = use
+ |
+ exists(FieldAccess access, Field ff | access = assignedValue |
+ ff = access.getField() and
+ ff.getSourceDeclaration() = f
+ ) or
+ exists(MethodAccess get, Method getter | get = assignedValue and getter = get.getMethod() |
+ getterFor(getter, f)
+ )
+ )
+}
+
+predicate assignmentFromShadowingLocal(LocalVariableDecl d, Field f) {
+ shadows(d, _, _, _) and
+ exists(VarAccess access | access = d.getAnAccess() |
+ exists(MethodAccess set, Expr arg, Method setter |
+ access = getARelevantChild(arg) and
+ arg = set.getAnArgument() and
+ setter = set.getMethod() and
+ setterFor(setter, f)
+ ) or
+ exists(Field instance, Expr assignedValue |
+ access = getARelevantChild(assignedValue) and
+ assignedValue = instance.getAnAssignedValue() and
+ instance.getSourceDeclaration() = f
+ )
+ )
+}
+
+private
+Expr getARelevantChild(Expr parent) {
+ exists(MethodAccess ma | parent = ma.getAnArgument() and result = parent) or
+ exists(Variable v | parent = v.getAnAccess() and result = parent) or
+ exists(Expr mid | mid = getARelevantChild(parent) and result = mid.getAChildExpr())
+}
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java
new file mode 100644
index 00000000000..06655aa8c1f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.java
@@ -0,0 +1,8 @@
+void main() {
+ // ...
+ // BAD: Call to 'runFinalizersOnExit' forces execution of all finalizers on termination of
+ // the runtime, which can cause live objects to transition to an invalid state.
+ // Avoid using this method (and finalizers in general).
+ System.runFinalizersOnExit(true);
+ // ...
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp
new file mode 100644
index 00000000000..0b5ea2c768a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.qhelp
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+Avoid calling System.runFinalizersOnExit or Runtime.runFinalizersOnExit,
+which are considered to be dangerous methods.
+
+
+
+The Java Development Kit documentation for System.runFinalizersOnExit states:
+
+
+
+This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects,
+resulting in erratic behavior or deadlock.
+
+
+
+
+Object finalizers are normally only called when the object is about to be collected by the garbage collector. Using runFinalizersOnExit sets a Java Virtual Machine-wide
+flag that executes finalizers on all objects with a finalize method before the runtime exits. This would require all objects with finalizers
+to defend against the possibility of finalize being called when the object is still in use, which is not practical for most
+applications.
+
+
+
+
+
+Ensure that the code does not rely on the execution of finalizers. If the code is dependent on the garbage collection behavior of the Java Virtual Machine,
+there is no guarantee that finalizers will be executed in a timely manner, or at all. This may become a problem if finalizers are used to
+dispose of limited system resources, such as file handles.
+
+
+Instead of finalizers, use explicit dispose methods in finally blocks, to make sure that an object's resources are released.
+
+
+
+
+
+The following example shows a program that calls runFinalizersOnExit, which is not
+recommended.
+
+
+
+The following example shows the recommended approach: a program that calls a dispose method in a finally block.
+
+
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition), Item 7. Addison-Wesley, 2008.
+
+
+ Java 6 API Documentation:
+ System.runFinalizersOnExit(),
+ Object.finalize().
+
+
+ Java SE Documentation:
+ Java Thread Primitive Deprecation.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql
new file mode 100644
index 00000000000..53d484bec6a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExit.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Dangerous runFinalizersOnExit
+ * @description Calling 'System.runFinalizersOnExit' or 'Runtime.runFinalizersOnExit'
+ * may cause finalizers to be run on live objects, leading to erratic behavior or
+ * deadlock.
+ * @kind problem
+ * @problem.severity error
+ * @precision medium
+ * @id java/run-finalizers-on-exit
+ * @tags reliability
+ * maintainability
+ */
+import java
+
+from MethodAccess ma, Method runfinalizers, Class c
+where
+ ma.getMethod() = runfinalizers and
+ runfinalizers.hasName("runFinalizersOnExit") and
+ runfinalizers.getDeclaringType() = c and
+ (
+ c.hasName("Runtime") or
+ c.hasName("System")
+ ) and
+ c.getPackage().hasName("java.lang")
+select ma, "Call to runFinalizersOnExit."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java
new file mode 100644
index 00000000000..8cda5c7ab03
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToRunFinalizersOnExitGood.java
@@ -0,0 +1,23 @@
+// Instead of using finalizers, define explicit termination methods
+// and call them in 'finally' blocks.
+class LocalCache {
+ private Collection cacheFiles = ...;
+
+ // Explicit method to close all cacheFiles
+ public void dispose() {
+ for (File cacheFile : cacheFiles) {
+ disposeCacheFile(cacheFile);
+ }
+ }
+}
+
+void main() {
+ LocalCache cache = new LocalCache();
+ try {
+ // Use the cache
+ } finally {
+ // Call the termination method in a 'finally' block, to ensure that
+ // the cache's resources are freed.
+ cache.dispose();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java
new file mode 100644
index 00000000000..2fb4df11283
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.java
@@ -0,0 +1,9 @@
+public static void main(String args[]) {
+ String name = "John Doe";
+
+ // BAD: Unnecessary call to 'toString' on 'name'
+ System.out.println("Hi, my name is " + name.toString());
+
+ // GOOD: No call to 'toString' on 'name'
+ System.out.println("Hi, my name is " + name);
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp
new file mode 100644
index 00000000000..410f2387a9c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.qhelp
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+There is no need to call toString on a String because it just returns the object itself. From the Java API
+Specification entry for String.toString():
+
+
+public String toString()
+This object (which is already a string!) is itself returned.
+
+
+
+
+
+
+Do not call toString on a String object.
+
+
+
+
+
+The following example shows an unnecessary call to toString on the string name.
+
+
+
+
+
+
+
+
+ Java 6 API Specification:
+ String.toString().
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql
new file mode 100644
index 00000000000..dda46d4f30d
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToStringToString.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Useless toString on String
+ * @description Calling 'toString' on a string is redundant.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/useless-tostring-call
+ * @tags maintainability
+ */
+import java
+
+from MethodAccess ma, Method tostring
+where
+ tostring.hasName("toString") and
+ tostring.getDeclaringType() instanceof TypeString and
+ ma.getMethod() = tostring
+select ma, "Redundant call to 'toString' on a String object."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java
new file mode 100644
index 00000000000..da7277aa25c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.java
@@ -0,0 +1,24 @@
+// Problem 1: Miss out cleanup code
+class FileOutput {
+ boolean write(String[] s) {
+ try {
+ output.write(s.getBytes());
+ } catch (IOException e) {
+ System.exit(1);
+ }
+ return true;
+ }
+}
+
+// Problem 2: Make code reuse difficult
+class Action {
+ public void run() {
+ // ...
+ // Perform tasks ...
+ // ...
+ System.exit(0);
+ }
+ public static void main(String[] args) {
+ new Action(args).run();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp
new file mode 100644
index 00000000000..bb9b2b47e17
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.qhelp
@@ -0,0 +1,66 @@
+
+
+
+Calling one of the methods System.exit, Runtime.halt, and Runtime.exit
+immediately terminates the Java Virtual Machine (JVM),
+effectively killing all threads without giving any of them a chance to
+perform cleanup actions or recover. As such, it is a dangerous thing to
+do: firstly, it can terminate the entire program inadvertently, and
+secondly, it can prevent important resources from being released or
+program state from being written to disk consistently.
+
+It is sometimes considered acceptable to call System.exit
+from a program's main method in order to indicate the overall exit status
+of the program. Such calls are an exception to this rule.
+
+
+
+
+It is usually preferable to use a different mechanism for reporting
+failure conditions. Consider returning a special value (perhaps
+null) that users of the current method check for and
+recover from appropriately. Alternatively, throw a suitable exception, which
+unwinds the stack and allows properly written code to clean up after itself,
+while leaving other threads undisturbed.
+
+
+
+
+In the following example, problem 1 shows that FileOutput.write tries
+to write some data to disk and terminates the JVM if this fails. This
+leaves the partially-written file on disk without any cleanup
+code running. It would be better to either return false to
+indicate the failure, or let the IOException propagate
+upwards and be handled by a method that knows how to recover.
+
+Problem 2 is more subtle. In this example, there is just one entry point to
+the program (the main method), which constructs an
+Action and performs it. Action.run calls
+System.exit to indicate successful completion. Consider,
+however, how this code might be integrated in an application server that
+constructs Action instances and calls
+run on them without going through main.
+The fact that run terminates the JVM instead of returning its
+exit code as an integer makes that use-case impossible.
+
+
+
+
+
+
+
+
+ J. Bloch,
+ Effective Java (second edition), p. 232.
+ Addison-Wesley, 2008.
+
+Java Platform, Standard Edition 7, API Specification:
+System.exit(int),
+Runtime.halt(int),
+Runtime.exit(int).
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql
new file mode 100644
index 00000000000..0921436ddcf
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/CallsToSystemExit.ql
@@ -0,0 +1,27 @@
+/**
+ * @name Forcible JVM termination
+ * @description Calling 'System.exit', 'Runtime.halt', or 'Runtime.exit' may make code harder to
+ * reuse and prevent important cleanup steps from running.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/jvm-exit
+ * @tags reliability
+ * maintainability
+ * external/cwe/cwe-382
+ */
+import java
+
+from Method m, MethodAccess sysexitCall, Method sysexit, Class system
+where
+ sysexitCall = m.getACallSite(sysexit) and
+ (sysexit.hasName("exit") or sysexit.hasName("halt")) and
+ sysexit.getDeclaringType() = system and
+ ( system.hasQualifiedName("java.lang", "System") or
+ system.hasQualifiedName("java.lang", "Runtime")
+ ) and
+ m.fromSource() and
+ not m instanceof MainMethod
+select sysexitCall,
+ "Avoid calls to " + sysexit.getDeclaringType().getName() + "." + sysexit.getName() +
+ "() as this makes code harder to reuse."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java
new file mode 100644
index 00000000000..2cff927b157
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.java
@@ -0,0 +1,21 @@
+// This class does not have a 'toString' method, so 'java.lang.Object.toString'
+// is used when the class is converted to a string.
+class WrongPerson {
+ private String name;
+ private Date birthDate;
+
+ public WrongPerson(String name, Date birthDate) {
+ this.name =name;
+ this.birthDate = birthDate;
+ }
+}
+
+public static void main(String args[]) throws Exception {
+ DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
+ WrongPerson wp = new WrongPerson("Robert Van Winkle", dateFormatter.parse("1967-10-31"));
+
+ // BAD: The following statement implicitly calls 'Object.toString',
+ // which returns something similar to:
+ // WrongPerson@4383f74d
+ System.out.println(wp);
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp
new file mode 100644
index 00000000000..d2a9a457107
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.qhelp
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+In most cases, calling the default implementation of toString in java.lang.Object is not what is intended when
+a string representation of an object is required. The output of the default toString method consists of the class name of the object
+as well as the object's hashcode, which is usually not what was intended.
+
+
+This rule includes explicit and implicit calls to toString that resolve to java.lang.Object.toString, particularly
+calls that are used in print or log statements.
+
+
+
+
+
+For objects that are printed, define a toString method for the object that returns a human-readable string.
+
+
+
+
+
+The following example shows that printing an object makes an implicit call to toString.
+Because the class WrongPerson does not have a toString method,
+Object.toString is called instead, which returns the class name and the wp object's hashcode.
+
+
+
+In contrast, in the following modification of the example, the class Person does have a toString method, which returns a
+string containing the arguments that were passed when the object p was created.
+
+
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition), Item 10. Addison-Wesley, 2008.
+
+
+ Java 6 API Specification: Object.toString().
+
+
+ Java Language Specification, 3rd ed: 5.4 String Conversion.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql
new file mode 100644
index 00000000000..826fc9df913
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToString.ql
@@ -0,0 +1,62 @@
+/**
+ * @name Use of default toString()
+ * @description Calling the default implementation of 'toString' returns a value that is unlikely to
+ * be what you expect.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/call-to-object-tostring
+ * @tags reliability
+ * maintainability
+ */
+import java
+import semmle.code.java.StringFormat
+
+predicate explicitToStringCall(Expr e) {
+ exists(MethodAccess ma, Method toString | toString = ma.getMethod() |
+ e = ma.getQualifier() and
+ toString.getName() = "toString" and
+ toString.getNumberOfParameters() = 0 and
+ not toString.isStatic()
+ )
+}
+
+predicate directlyDeclaresToString(Class c) {
+ exists(Method m | m.getDeclaringType() = c |
+ m.getName() = "toString" and
+ m.getNumberOfParameters() = 0
+ )
+}
+
+predicate inheritsObjectToString(Class t) {
+ not directlyDeclaresToString(t.getSourceDeclaration()) and
+ (
+ t.getASupertype().hasQualifiedName("java.lang", "Object")
+ or
+ not t.getASupertype().hasQualifiedName("java.lang", "Object") and
+ inheritsObjectToString(t.getASupertype())
+ )
+}
+
+Class getAnImplementation(RefType parent) {
+ result = parent.getASubtype*() and
+ not result.isAbstract()
+}
+
+predicate bad(RefType t) {
+ forex(Class sub | sub = getAnImplementation(t) | inheritsObjectToString(sub)) and
+ not t instanceof Array and
+ not t instanceof GenericType and
+ not t instanceof BoundedType and
+ t.fromSource()
+}
+
+from Expr e, RefType sourceType
+where
+ (implicitToStringCall(e) or explicitToStringCall(e)) and
+ sourceType = e.getType().(RefType).getSourceDeclaration() and
+ bad(sourceType) and
+ not sourceType.isAbstract() and
+ sourceType.fromSource()
+select e, "Default toString(): " + e.getType().getName() +
+ " inherits toString() from Object, and so is not suitable for printing."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java
new file mode 100644
index 00000000000..32b6b70e209
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/DefaultToStringGood.java
@@ -0,0 +1,26 @@
+// This class does have a 'toString' method, which is used when the object is
+// converted to a string.
+class Person {
+ private String name;
+ private Date birthDate;
+
+ public String toString() {
+ DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
+ return "(Name: " + name + ", Birthdate: " + dateFormatter.format(birthDate) + ")";
+ }
+
+ public Person(String name, Date birthDate) {
+ this.name =name;
+ this.birthDate = birthDate;
+ }
+}
+
+public static void main(String args[]) throws Exception {
+ DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
+ Person p = new Person("Eric Arthur Blair", dateFormatter.parse("1903-06-25"));
+
+ // GOOD: The following statement implicitly calls 'Person.toString',
+ // which correctly returns a human-readable string:
+ // (Name: Eric Arthur Blair, Birthdate: 1903-06-25)
+ System.out.println(p);
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java
new file mode 100644
index 00000000000..47c881dbf20
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.java
@@ -0,0 +1,15 @@
+class RequestHandler extends Thread {
+ private boolean isRunning;
+ private Connection conn = new Connection();
+
+ public void run() {
+ while (isRunning) {
+ Request req = conn.getRequest();
+ // Process the request ...
+
+ System.gc(); // This call may cause a garbage collection after each request.
+ // This will likely reduce the throughput of the RequestHandler
+ // because the JVM spends time on unnecessary garbage collection passes.
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp
new file mode 100644
index 00000000000..c65c984286c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.qhelp
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+You should avoid making calls to explicit garbage collection methods (Runtime.gc and System.gc). The calls are not
+guaranteed to trigger garbage collection, and they may also trigger unnecessary garbage collection passes that lead to decreased
+performance.
+
+
+
+
+
+It is better to let the Java Virtual Machine (JVM) handle garbage collection. If it becomes necessary to control how the JVM handles memory, it is better to use the
+JVM's memory and garbage collection options (for example, -Xmx, -XX:NewRatio, -XX:Use*GC) than to trigger
+garbage collection in the application.
+
+The memory management classes that are used by Real-Time Java are an exception to this rule,
+because they are designed to handle garbage collection differently from the JVM default.
+
+
+
+
+The following example shows code that makes connection requests, and tries to trigger garbage
+collection after it has processed each request.
+
+
+
+It is better to remove the call to System.gc and rely on the JVM to dispose of the
+connection.
+
+
+
+
+
+
+ Java 6 API Documentation:
+ System.gc().
+
+
+ Oracle Technology Network:
+ Java SE 6 HotSpot Virtual Machine Garbage Collection Tuning.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql
new file mode 100644
index 00000000000..f48e6d00b1e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/GarbageCollection.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Explicit garbage collection
+ * @description Triggering garbage collection explicitly may either have no effect or may trigger
+ * unnecessary garbage collection.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/garbage-collection
+ * @tags reliability
+ * maintainability
+ */
+import java
+
+from MethodAccess mc, Method m
+where
+ (
+ m.getDeclaringType().hasQualifiedName("java.lang", "Runtime") or
+ m.getDeclaringType().hasQualifiedName("java.lang", "System")
+ ) and
+ m.hasName("gc") and
+ mc.getMethod() = m
+select mc, "Explicit garbage collection. This should only be used in benchmarking code."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java
new file mode 100644
index 00000000000..14bbaf446ad
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.java
@@ -0,0 +1,26 @@
+public class NextFromIterator implements Iterator {
+ private int position = -1;
+ private List list = new ArrayList() {{
+ add("alpha"); add("bravo"); add("charlie"); add("delta"); add("echo"); add("foxtrot");
+ }};
+
+ public boolean hasNext() {
+ return next() != null; // BAD: Call to 'next'
+ }
+
+ public String next() {
+ position++;
+ return position < list.size() ? list.get(position) : null;
+ }
+
+ public void remove() {
+ // ...
+ }
+
+ public static void main(String[] args) {
+ NextFromIterator x = new NextFromIterator();
+ while(x.hasNext()) {
+ System.out.println(x.next());
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp
new file mode 100644
index 00000000000..a86432566e8
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.qhelp
@@ -0,0 +1,49 @@
+
+
+
+
+
+Iterator implementations with a hasNext method that
+calls the next method are most likely incorrect. This is because next
+changes the iterator's position to the next element and returns that element, which is unlikely to
+be desirable in the implementation of hasNext.
+
+
+
+
+Ensure that any calls to next from
+within hasNext are legitimate.
+The hasNext method should indicate whether there are
+further elements remaining in the iteration without changing the iterator's state by calling
+next.
+
+
+
+
+
+In the following example, which outputs the contents of a string, hasNext calls
+next, which has the effect of changing the iterator's position. Given that
+main also calls next when it outputs an item, some items are skipped and
+only half the items are output.
+
+
+
+Instead, the implementation of hasNext should use another way of indicating whether
+there are further elements in the string without calling next. For example, hasNext
+could check the underlying array directly to see if there is an element at the next position.
+
+
+
+
+
+
+ Java API Documentation:
+ Iterator.hasNext(),
+ Iterator.next().
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql
new file mode 100644
index 00000000000..1bc74ae7724
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/NextFromIterator.ql
@@ -0,0 +1,28 @@
+/**
+ * @name Next in hasNext implementation
+ * @description Iterator implementations whose 'hasNext' method calls 'next' are most likely
+ * incorrect.
+ * @kind problem
+ * @problem.severity warning
+ * @precision medium
+ * @id java/iterator-hasnext-calls-next
+ * @tags reliability
+ * correctness
+ */
+import java
+
+from MethodAccess m
+where
+ m.getMethod().hasName("next") and
+ m.getMethod().getNumberOfParameters() = 0 and
+ (
+ not m.hasQualifier() or
+ m.getQualifier() instanceof ThisAccess
+ ) and
+ exists(Interface i, Method hasNext |
+ i.getSourceDeclaration().hasQualifiedName("java.util", "Iterator") and
+ m.getEnclosingCallable() = hasNext and
+ hasNext.getDeclaringType().getSourceDeclaration().getASupertype*() = i and
+ hasNext.hasName("hasNext")
+ )
+select m, "next() called from within an Iterator method."
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java
new file mode 100644
index 00000000000..78f1221829a
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.java
@@ -0,0 +1,24 @@
+public static void main(String args[]) {
+ String[] words = {"Who", "is", "John", "Galt"};
+ String[][] wordMatrix = {{"There", "is"}, {"no", "spoon"}};
+
+ // BAD: This implicitly uses 'Object.toString' to convert the contents
+ // of 'words[]', and prints out something similar to:
+ // [Ljava.lang.String;@459189e1
+ System.out.println(words);
+
+ // GOOD: 'Arrays.toString' calls 'toString' on
+ // each of the array's elements. The statement prints out:
+ // [Who, is, John, Galt]
+ System.out.println(Arrays.toString(words));
+
+ // ALMOST RIGHT: This calls 'toString' on each of the multi-dimensional
+ // array's elements. However, because the elements are arrays, the statement
+ // prints out something similar to:
+ // [[Ljava.lang.String;@55f33675, [Ljava.lang.String;@527c6768]]
+ System.out.println(Arrays.toString(wordMatrix));
+
+ // GOOD: This properly prints out the contents of the multi-dimensional array:
+ // [[There, is], [no, spoon]]
+ System.out.println(Arrays.deepToString(wordMatrix));
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp
new file mode 100644
index 00000000000..f8a6dabfe2c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.qhelp
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+Printing an array is likely to produce unintended results. That is, the result does not contain the contents of the array.
+This is because the array is implicitly converted to a String using Object.toString, which just returns the following value:
+
+
+
+getClass().getName() + '@' + Integer.toHexString(hashCode())
+
+
+
+
+
+
+When converting an array to a readable string, use Arrays.toString for one-dimensional arrays, or
+Arrays.deepToString for multi-dimensional arrays. These functions iterate over the contents of the array
+and produce human-readable output.
+
+
+
+
+
+In the following example, the contents of the array words are printed out only if
+Arrays.toString is called on the array first. Similarly, the contents of the multi-dimensional
+array wordMatrix are printed out only if Arrays.deepToString is called
+on the array first.
+
+
+
+
+
+
+
+
+Java 6 API Documentation:
+Arrays.toString(),
+Arrays.deepToString(),
+Object.toString().
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql
new file mode 100644
index 00000000000..077de8b19e6
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/Undesirable Calls/PrintLnArray.ql
@@ -0,0 +1,32 @@
+/**
+ * @name Implicit conversion from array to string
+ * @description Directly printing an array, without first converting the array to a string,
+ * produces unreadable results.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision very-high
+ * @id java/print-array
+ * @tags maintainability
+ */
+import java
+import semmle.code.java.StringFormat
+
+/**
+ * Holds if `e` is an argument of `Arrays.toString(..)`.
+ */
+predicate arraysToStringArgument(Expr e) {
+ exists(MethodAccess ma, Method m |
+ ma.getAnArgument() = e and
+ ma.getMethod() = m and
+ m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
+ m.hasName("toString")
+ )
+}
+from Expr arr
+where
+ arr.getType() instanceof Array and
+ implicitToStringCall(arr)
+ or
+ arr.getType().(Array).getComponentType() instanceof Array and
+ arraysToStringArgument(arr)
+select arr, "Implicit conversion from Array to String."
diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java
new file mode 100644
index 00000000000..a15bff3d039
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.java
@@ -0,0 +1,4 @@
+Long sum = 0L;
+for (long k = 0; k < Integer.MAX_VALUE; k++) {
+ sum += k; // AVOID: Inefficient unboxing and reboxing of 'sum'
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp
new file mode 100644
index 00000000000..4d826204168
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.qhelp
@@ -0,0 +1,72 @@
+
+
+
+
+
+For each primitive type, such as int or double,
+there is a corresponding boxed reference type, such as Integer or
+Double. These boxed versions differ from their primitive equivalents
+because they can hold an undefined null element in addition to numeric (or other) values,
+and there can be more than one instance of a boxed type representing the same value.
+
+
+In Java 5 and later, automated boxing and unboxing conversions have been added to the language.
+Although these automated conversions reduce the verbosity of the code, they can hide potential problems.
+Such problems include performance issues because of unnecessary object creation, and confusion of boxed
+types with their primitive equivalents.
+
+
+
+
+
+
+Generally, you should use primitive types (boolean, byte, char, short, int, long, float, double)
+in preference to boxed types (Boolean, Byte, Character, Short, Integer, Long, Float,
+Double), whenever there is a choice. Exceptions are when a primitive value is used in
+collections and other parameterized types, or
+when a null value is explicitly used to represent an undefined value.
+
+Where they cannot be avoided, perform boxing and unboxing conversions explicitly to avoid
+possible confusion of boxed types and their primitive equivalents. In cases where boxing conversions cause
+performance issues, use primitive types instead.
+
+
+
+
+
+In the following example, declaring the variable sum to have boxed type
+Long causes it to be unboxed and reboxed during execution of the statement inside the
+loop.
+
+
+
+To avoid this inefficiency, declare sum to have primitive type long
+instead.
+
+
+
+
+
+
+ J. Bloch, Effective Java (second edition),
+ Item 49.
+ Addison-Wesley, 2008.
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+ Java Language Specification:
+ 5.1.7 Boxing Conversion.
+
+
+ Java SE Documentation:
+ Autoboxing.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql
new file mode 100644
index 00000000000..5cb5a443e24
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/AutoBoxing.ql
@@ -0,0 +1,93 @@
+/**
+ * @name Auto boxing or unboxing
+ * @description Implicit boxing or unboxing of primitive types, such as 'int' and 'double',
+ * may cause confusion and subtle performance problems.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/implicit-auto-boxing
+ * @tags efficiency
+ */
+
+import java
+
+/** An expression of primitive type. */
+class PrimitiveExpr extends Expr {
+ PrimitiveExpr() {
+ this.getType() instanceof PrimitiveType
+ }
+}
+
+/** An expression of boxed type. */
+class BoxedExpr extends Expr {
+ BoxedExpr() {
+ this.getType() instanceof BoxedType
+ }
+}
+
+/**
+ * Relate expressions and the variables they flow into in one step,
+ * either by assignment or parameter passing.
+ */
+Variable flowTarget(Expr arg) {
+ arg = result.getAnAssignedValue() or
+ exists(Call c, int i |
+ c.getArgument(i) = arg and result = c.getCallee().getParameter(i)
+ )
+}
+
+/**
+ * Holds if `e` is in a syntactic position where it is implicitly unboxed.
+ */
+predicate unboxed(BoxedExpr e) {
+ exists(BinaryExpr bin | e = bin.getAnOperand() |
+ if (bin instanceof EqualityTest or bin instanceof ComparisonExpr) then
+ bin.getAnOperand() instanceof PrimitiveExpr
+ else
+ bin instanceof PrimitiveExpr
+ )
+ or
+ exists(Assignment assign | assign.getDest() instanceof PrimitiveExpr |
+ assign.getSource() = e
+ )
+ or
+ flowTarget(e).getType() instanceof PrimitiveType
+ or
+ exists(ConditionalExpr cond | cond instanceof PrimitiveExpr |
+ cond.getTrueExpr() = e or cond.getFalseExpr() = e
+ )
+}
+
+/**
+ * Holds if `e` is in a syntactic position where it is implicitly boxed.
+ */
+predicate boxed(PrimitiveExpr e) {
+ exists(AssignExpr assign | assign.getDest() instanceof BoxedExpr |
+ assign.getSource() = e)
+ or
+ flowTarget(e).getType() instanceof BoxedType
+ or
+ exists(ConditionalExpr cond | cond instanceof BoxedExpr |
+ cond.getTrueExpr() = e or cond.getFalseExpr() = e
+ )
+}
+
+/**
+ * Holds if `e` is an assignment that unboxes, updates and reboxes `v`.
+ */
+predicate rebox(Assignment e, Variable v) {
+ v.getType() instanceof BoxedType and
+ not e instanceof AssignExpr and
+ e.getDest() = v.getAnAccess()
+}
+
+from Expr e, string conv
+where
+ boxed(e) and conv = "This expression is implicitly boxed."
+ or
+ unboxed(e) and conv = "This expression is implicitly unboxed."
+ or
+ exists(Variable v | rebox(e, v) |
+ conv = "This expression implicitly unboxes, updates, and reboxes the value of '" + v.getName() + "'."
+ )
+select e, conv
diff --git a/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp
new file mode 100644
index 00000000000..647ab37a465
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.qhelp
@@ -0,0 +1,50 @@
+
+
+
+
+
+A finally block that does not complete normally
+suppresses any exceptions that may have been thrown in the corresponding
+try block. This can happen if the finally block
+contains any return or throw statements, or if it
+contains any break or continue statements whose
+jump target lies outside of the finally block.
+
+
+
+
+
+To avoid suppressing exceptions that are thrown in a try block,
+design the code so that the corresponding finally block
+always completes normally. Remove any of the following statements that
+may cause it to terminate abnormally:
+
+
+
+ return
+ throw
+ break
+ continue
+
+
+
+
+
+
+
+ J. Bloch and N. Gafter, Java Puzzlers: Traps, Pitfalls, and Corner Cases, Puzzle 36. Addison-Wesley, 2005.
+
+
+ The Java Language Specification:
+ Execution of try-finally and try-catch-finally.
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql
new file mode 100644
index 00000000000..3e04d76e2be
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/FinallyMayNotComplete.ql
@@ -0,0 +1,37 @@
+/**
+ * @name Finally block may not complete normally
+ * @description A 'finally' block that runs because an exception has been thrown, and that does not
+ * complete normally, causes the exception to disappear silently.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/abnormal-finally-completion
+ * @tags reliability
+ * correctness
+ * exceptions
+ * external/cwe/cwe-584
+ */
+
+import java
+
+Block finallyBlock() {
+ exists(TryStmt try | try.getFinally() = result)
+}
+
+Stmt statementIn(Block finally) {
+ finallyBlock() = finally and
+ result.getParent+() = finally
+}
+
+predicate banned(Stmt s, Block finally) {
+ s = statementIn(finally) and
+ (
+ s instanceof ReturnStmt or
+ exists(ThrowStmt throw | s = throw and not throw.getLexicalCatchIfAny() = statementIn(finally)) or
+ exists(JumpStmt jump | s = jump and not jump.getTarget() = statementIn(finally))
+ )
+}
+
+from Stmt s, Block finally
+where banned(s, finally)
+select s, "Leaving a finally-block with this statement can cause exceptions to silently disappear."
diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java
new file mode 100644
index 00000000000..8402008007e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.java
@@ -0,0 +1,13 @@
+class InexactVarArg
+{
+ private static void length(Object... objects) {
+ System.out.println(objects.length);
+ }
+
+ public static void main(String[] args) {
+ String[] words = { "apple", "banana", "cherry" };
+ String[][] lists = { words, words };
+ length(words); // BAD: Argument does not clarify
+ length(lists); // which parameter type is used.
+ }
+}
diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp
new file mode 100644
index 00000000000..c7c45cb0d4c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.qhelp
@@ -0,0 +1,81 @@
+
+
+
+
+
+A variable arity method, commonly known as a varargs method, may be called
+with different numbers of arguments. For example, the method sum(int... values)
+may be called in all of the following ways:
+
+
+sum()
+sum(1)
+sum(1,2,3)
+sum(new int[] { 1, 2, 3 })
+
+
+When a method foo(T... x) is called with an argument that is neither
+T nor T[], but the argument can be cast as either, the choice of which
+type the argument is cast as is compiler-dependent.
+
+
+
+
+When a variable arity method, for example m(T... ts), is called with a
+single argument (for example m(arg)), the type of the argument should be
+either T or T[] (insert a cast if necessary).
+
+
+
+
+In the following example, the calls to length do not pass an argument of the same
+type as the parameter of length, which is Object or an array of
+Object. Therefore, when the program is compiled with javac, the output is:
+
+
+3
+2
+
+
+When the program is compiled with a different compiler, for example the default compiler for some
+versions of Eclipse, the output may be:
+
+
+3
+1
+
+
+
+
+To fix the code, length(words) should be replaced by either of the following:
+
+
+length((Object) words)
+length((Object[]) words)
+
+
+Similarly, length(lists) should be replaced by one of the following:
+
+
+length((Object) lists)
+length((Object[]) lists)
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+Java Language Specification:
+8.4.1 Formal Parameters,
+15.12.4.2 Evaluate Arguments.
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql
new file mode 100644
index 00000000000..1a14d9ed95b
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/InexactVarArg.ql
@@ -0,0 +1,42 @@
+/**
+ * @name Inexact type match for varargs argument
+ * @description Calling a varargs method where it is unclear whether the arguments
+ * should be interpreted as a list of arguments or as a single argument, may lead
+ * to compiler-dependent behavior.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id java/inexact-varargs
+ * @tags reliability
+ */
+import java
+
+predicate varArgsMethod(Method method, Array varargsType, int arity) {
+ exists(MethodAccess access | access.getMethod() = method and
+ arity = method.getNumberOfParameters() and
+ not access.getNumArgument() = arity and
+ method.getParameterType(arity-1) = varargsType
+ )
+}
+
+RefType normalised(Type type) {
+ type.(RawType).getErasure() = result or
+ type.(ParameterizedType).getErasure() = result or
+ type.(BoundedType).getUpperBoundType() = result or
+ (not type instanceof RawType and not type instanceof ParameterizedType and type = result)
+}
+
+predicate equivalent(Array declared, Array used) {
+ normalised(declared.getElementType()) = normalised(used.getElementType()) and
+ declared.getDimension() = used.getDimension()
+}
+
+from Method target, MethodAccess access, Array declaredType, Array usedType, int params
+where
+ varArgsMethod(target, declaredType, params) and
+ target = access.getMethod() and
+ access.getNumArgument() = params and
+ usedType = access.getArgument(params - 1).getType() and
+ not equivalent(declaredType, usedType) and
+ declaredType.getDimension() != usedType.getDimension()+1
+select access.getArgument(params - 1), "Call to varargs method $@ with inexact argument type (compiler dependent).", target, target.getName()
diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java
new file mode 100644
index 00000000000..a7f4ab31137
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.java
@@ -0,0 +1,18 @@
+final private static double KM_PER_MILE = 1.609344;
+
+// AVOID: Example that assigns to a parameter
+public double milesToKM(double miles) {
+ miles *= KM_PER_MILE;
+ return miles;
+}
+
+// GOOD: Example of using an expression instead
+public double milesToKM(double miles) {
+ return miles * KM_PER_MILE;
+}
+
+// GOOD: Example of using a local variable
+public double milesToKM(double miles) {
+ double kilometres = miles * KM_PER_MILE;
+ return kilometres;
+}
diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp
new file mode 100644
index 00000000000..aaaf9688c3e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.qhelp
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+Programmers usually assume that the value of a parameter is the value that was
+passed in to the method or constructor. Assigning a different value to a parameter in a
+method or constructor invalidates that assumption.
+
+
+
+
+Avoid assignment to parameters by doing one of the following:
+
+ - Introduce a local variable and assign to that instead.
+ - Use an expression directly rather than assigning it to a parameter.
+
+
+
+
+
+
+In the following example, the first method shows assignment to the parameter miles.
+The second method shows how to avoid this by using the expression miles * KM_PER_MILE.
+The third method shows how to avoid the assignment by declaring a local variable kilometres
+and assigning to that.
+
+
+
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+Java Basics:
+Methods 4 - Local variables.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql
new file mode 100644
index 00000000000..4225776a53f
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/ParameterAssignment.ql
@@ -0,0 +1,16 @@
+/**
+ * @name Assignment to parameter
+ * @description Changing a parameter's value in a method or constructor may decrease code
+ * readability.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/assignment-to-parameter
+ * @tags maintainability
+ */
+
+import java
+
+from Assignment a, Parameter p
+where a.getDest() = p.getAnAccess()
+select a, "Assignment to parameters may decrease code readability."
diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java
new file mode 100644
index 00000000000..b3b6764eba5
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.java
@@ -0,0 +1,6 @@
+public class UnnecessaryCast {
+ public static void main(String[] args) {
+ Integer i = 23;
+ Integer j = (Integer)i; // AVOID: Redundant cast
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp
new file mode 100644
index 00000000000..b6a631f226c
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.qhelp
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+A cast is unnecessary if the type of the operand is already the same as the type that is being cast to.
+
+
+
+
+
+
+Avoid including unnecessary casts.
+
+
+
+
+In the following example, casting i to an Integer is not necessary. It
+is already an Integer.
+
+
+To fix the code, delete (Integer) on the right-hand side of the assignment on line 4.
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql
new file mode 100644
index 00000000000..2d760a374b3
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryCast.ql
@@ -0,0 +1,18 @@
+/**
+ * @name Unnecessary cast
+ * @description Casting an object to its own type is unnecessary.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/redundant-cast
+ * @tags maintainability
+ * external/cwe/cwe-561
+ */
+
+import java
+
+from CastExpr redundant, Type type
+where
+ redundant.getType() = type and
+ type = redundant.getExpr().getType()
+select redundant, "This cast is redundant - the expression is already of type '" + type + "'."
diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp
new file mode 100644
index 00000000000..58e8beefb8e
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.qhelp
@@ -0,0 +1,35 @@
+
+
+
+An import statement that is not necessary (because no part
+of the file that it is in uses any imported type) should be avoided. Although
+importing too many types does not affect performance, redundant import statements
+introduce unnecessary and undesirable dependencies in the code. If an imported type is
+renamed or deleted, the source code cannot be compiled because the
+import statement cannot be resolved.
+
+Unnecessary import statements are often an indication of incomplete refactoring.
+
+
+
+
+Avoid including an import statement that is not needed. Many modern IDEs have
+automated support for doing this, typically under the
+name 'Organize imports'. This sorts the import statements and
+removes any that are not used, and it is good practice to run such a
+command before every commit.
+
+
+
+
+
+
+Help - Eclipse Platform:
+Java Compiler Errors/Warnings Preferences.
+
+
+
+
+
diff --git a/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql
new file mode 100644
index 00000000000..2be3595fcdf
--- /dev/null
+++ b/java/ql/src/Violations of Best Practice/legacy/UnnecessaryImport.ql
@@ -0,0 +1,77 @@
+/**
+ * @name Unnecessary import
+ * @description A redundant 'import' statement introduces unnecessary and undesirable
+ * dependencies.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/unused-import
+ * @tags maintainability
+ * external/cwe/cwe-561
+ */
+
+import java
+
+string neededByJavadoc(JavadocElement c) {
+ result = c.getText().regexpCapture(".*\\{@link(?:plain)?\\s+(\\w+)\\b.*\\}.*", 1) or
+ result = c.(ThrowsTag).getExceptionName() or
+ result = c.(SeeTag).getReference()
+}
+
+Annotation nestedAnnotation(Annotation a) {
+ result.getAnnotatedElement().(Expr).getParent+() = a
+}
+
+RefType neededByAnnotation(Annotation a) {
+ exists(TypeAccess t | t.getParent+() = a |
+ result = t.getType().(RefType).getSourceDeclaration()
+ ) or
+ exists(ArrayTypeAccess at | at.getParent+() = a |
+ result = at.getType().(Array).getElementType().(RefType).getSourceDeclaration()
+ ) or
+ exists(VarAccess va | va.getParent+() = a |
+ result = va.getVariable().(Field).getDeclaringType()
+ ) or
+ result = a.getType() or
+ result = a.getType().(NestedType).getEnclosingType+() or
+ result = neededByAnnotation(nestedAnnotation(a))
+}
+
+RefType neededType(CompilationUnit cu) {
+ // Annotations
+ exists(Annotation a | a.getAnnotatedElement().getCompilationUnit() = cu |
+ result = neededByAnnotation(a)
+ ) or
+ // type accesses
+ exists(TypeAccess t | t.getCompilationUnit() = cu |
+ result = t.getType().(RefType).getSourceDeclaration()
+ ) or
+ exists(ArrayTypeAccess at | at.getCompilationUnit() = cu |
+ result = at.getType().(Array).getElementType().(RefType).getSourceDeclaration()
+ ) or
+ // throws clauses
+ exists(Callable c | c.getCompilationUnit() = cu |
+ result = c.getAnException().getType()
+ ) or
+ // Javadoc
+ exists(JavadocElement j | cu.getFile() = j.getFile() |
+ result.getName() = neededByJavadoc(j)
+ )
+}
+
+RefType importedType(Import i) {
+ result = i.(ImportOnDemandFromPackage).getAnImport() or
+ result = i.(ImportOnDemandFromType).getAnImport() or
+ result = i.(ImportType).getImportedType()
+}
+
+predicate neededImport(Import i) {
+ importedType(i) = neededType(i.getCompilationUnit())
+}
+
+from Import i
+where
+ not neededImport(i) and
+ not i instanceof ImportStaticOnDemand and
+ not i instanceof ImportStaticTypeMember
+select i, "The statement '" + i + "' is unnecessary."
diff --git a/java/ql/src/config/semmlecode.dbscheme b/java/ql/src/config/semmlecode.dbscheme
new file mode 100755
index 00000000000..87750e19e59
--- /dev/null
+++ b/java/ql/src/config/semmlecode.dbscheme
@@ -0,0 +1,821 @@
+/*
+ * External artifacts
+ */
+
+externalData(
+ int id : @externalDataElement,
+ string path : string ref,
+ int column: int ref,
+ string value : string ref
+);
+
+snapshotDate(
+ unique date snapshotDate : date ref
+);
+
+sourceLocationPrefix(
+ string prefix : string ref
+);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id : @duplication,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+similarCode(
+ unique int id : @similarity,
+ string relativePath : string ref,
+ int equivClass : int ref
+);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref
+);
+
+/*
+ * Version history
+ */
+
+svnentries(
+ int id : @svnentry,
+ string revision : string ref,
+ string author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+)
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ string action : string ref
+)
+
+svnentrymsg(
+ int id : @svnentry ref,
+ string message : string ref
+)
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+)
+
+/*
+ * Locations and files
+ */
+
+@location = @location_default ;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+);
+
+hasLocation(
+ int locatableid: @locatable ref,
+ int id: @location ref
+);
+
+@sourceline = @locatable ;
+
+#keyset[element_id]
+numlines(
+ int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+);
+
+files(
+ unique int id: @file,
+ string name: string ref,
+ string simple: string ref,
+ string ext: string ref,
+ int fromSource: int ref // deprecated
+);
+
+folders(
+ unique int id: @folder,
+ string name: string ref,
+ string simple: string ref
+);
+
+@container = @folder | @file
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref
+);
+
+/*
+ * Java
+ */
+
+cupackage(
+ unique int id: @file ref,
+ int packageid: @package ref
+);
+
+#keyset[fileid,keyName]
+jarManifestMain(
+ int fileid: @file ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+#keyset[fileid,entryName,keyName]
+jarManifestEntries(
+ int fileid: @file ref,
+ string entryName: string ref,
+ string keyName: string ref,
+ string value: string ref
+);
+
+packages(
+ unique int id: @package,
+ string nodeName: string ref
+);
+
+primitives(
+ unique int id: @primitive,
+ string nodeName: string ref
+);
+
+modifiers(
+ unique int id: @modifier,
+ string nodeName: string ref
+);
+
+classes(
+ unique int id: @class,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @class ref
+);
+
+interfaces(
+ unique int id: @interface,
+ string nodeName: string ref,
+ int parentid: @package ref,
+ int sourceid: @interface ref
+);
+
+fielddecls(
+ unique int id: @fielddecl,
+ int parentid: @reftype ref
+);
+
+#keyset[fieldId] #keyset[fieldDeclId,pos]
+fieldDeclaredIn(
+ int fieldId: @field ref,
+ int fieldDeclId: @fielddecl ref,
+ int pos: int ref
+);
+
+fields(
+ unique int id: @field,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @field ref
+);
+
+constrs(
+ unique int id: @constructor,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @constructor ref
+);
+
+methods(
+ unique int id: @method,
+ string nodeName: string ref,
+ string signature: string ref,
+ int typeid: @type ref,
+ int parentid: @reftype ref,
+ int sourceid: @method ref
+);
+
+#keyset[parentid,pos]
+params(
+ unique int id: @param,
+ int typeid: @type ref,
+ int pos: int ref,
+ int parentid: @callable ref,
+ int sourceid: @param ref
+);
+
+paramName(
+ unique int id: @param ref,
+ string nodeName: string ref
+);
+
+isVarargsParam(
+ int param: @param ref
+);
+
+exceptions(
+ unique int id: @exception,
+ int typeid: @type ref,
+ int parentid: @callable ref
+);
+
+isAnnotType(
+ int interfaceid: @interface ref
+);
+
+isAnnotElem(
+ int methodid: @method ref
+);
+
+annotValue(
+ int parentid: @annotation ref,
+ int id2: @method ref,
+ unique int value: @expr ref
+);
+
+isEnumType(
+ int classid: @class ref
+);
+
+isEnumConst(
+ int fieldid: @field ref
+);
+
+#keyset[parentid,pos]
+typeVars(
+ unique int id: @typevariable,
+ string nodeName: string ref,
+ int pos: int ref,
+ int kind: int ref, // deprecated
+ int parentid: @typeorcallable ref
+);
+
+wildcards(
+ unique int id: @wildcard,
+ string nodeName: string ref,
+ int kind: int ref
+);
+
+#keyset[parentid,pos]
+typeBounds(
+ unique int id: @typebound,
+ int typeid: @reftype ref,
+ int pos: int ref,
+ int parentid: @boundedtype ref
+);
+
+#keyset[parentid,pos]
+typeArgs(
+ int argumentid: @reftype ref,
+ int pos: int ref,
+ int parentid: @typeorcallable ref
+);
+
+isParameterized(
+ int memberid: @member ref
+);
+
+isRaw(
+ int memberid: @member ref
+);
+
+erasure(
+ unique int memberid: @member ref,
+ int erasureid: @member ref
+);
+
+#keyset[classid] #keyset[parent]
+isAnonymClass(
+ int classid: @class ref,
+ int parent: @classinstancexpr ref
+);
+
+#keyset[classid] #keyset[parent]
+isLocalClass(
+ int classid: @class ref,
+ int parent: @localclassdeclstmt ref
+);
+
+isDefConstr(
+ int constructorid: @constructor ref
+);
+
+#keyset[exprId]
+lambdaKind(
+ int exprId: @lambdaexpr ref,
+ int bodyKind: int ref
+);
+
+arrays(
+ unique int id: @array,
+ string nodeName: string ref,
+ int elementtypeid: @type ref,
+ int dimension: int ref,
+ int componenttypeid: @type ref
+);
+
+enclInReftype(
+ unique int child: @reftype ref,
+ int parent: @reftype ref
+);
+
+extendsReftype(
+ int id1: @reftype ref,
+ int id2: @classorinterface ref
+);
+
+implInterface(
+ int id1: @classorarray ref,
+ int id2: @interface ref
+);
+
+hasModifier(
+ int id1: @modifiable ref,
+ int id2: @modifier ref
+);
+
+imports(
+ unique int id: @import,
+ int holder: @typeorpackage ref,
+ string name: string ref,
+ int kind: int ref
+);
+
+#keyset[parent,idx]
+stmts(
+ unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ int bodydecl: @callable ref
+);
+
+@stmtparent = @callable | @stmt;
+
+case @stmt.kind of
+ 0 = @block
+| 1 = @ifstmt
+| 2 = @forstmt
+| 3 = @enhancedforstmt
+| 4 = @whilestmt
+| 5 = @dostmt
+| 6 = @trystmt
+| 7 = @switchstmt
+| 8 = @synchronizedstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @breakstmt
+| 12 = @continuestmt
+| 13 = @emptystmt
+| 14 = @exprstmt
+| 15 = @labeledstmt
+| 16 = @assertstmt
+| 17 = @localvariabledeclstmt
+| 18 = @localclassdeclstmt
+| 19 = @constructorinvocationstmt
+| 20 = @superconstructorinvocationstmt
+| 21 = @case
+| 22 = @catchclause
+;
+
+#keyset[parent,idx]
+exprs(
+ unique int id: @expr,
+ int kind: int ref,
+ int typeid: @type ref,
+ int parent: @element ref,
+ int idx: int ref
+);
+
+callableEnclosingExpr(
+ unique int id: @expr ref,
+ int callable_id: @callable ref
+);
+
+statementEnclosingExpr(
+ unique int id: @expr ref,
+ int statement_id: @stmt ref
+);
+
+case @expr.kind of
+ 1 = @arrayaccess
+| 2 = @arraycreationexpr
+| 3 = @arrayinit
+| 4 = @assignexpr
+| 5 = @assignaddexpr
+| 6 = @assignsubexpr
+| 7 = @assignmulexpr
+| 8 = @assigndivexpr
+| 9 = @assignremexpr
+| 10 = @assignandexpr
+| 11 = @assignorexpr
+| 12 = @assignxorexpr
+| 13 = @assignlshiftexpr
+| 14 = @assignrshiftexpr
+| 15 = @assignurshiftexpr
+| 16 = @booleanliteral
+| 17 = @integerliteral
+| 18 = @longliteral
+| 19 = @floatingpointliteral
+| 20 = @doubleliteral
+| 21 = @characterliteral
+| 22 = @stringliteral
+| 23 = @nullliteral
+| 24 = @mulexpr
+| 25 = @divexpr
+| 26 = @remexpr
+| 27 = @addexpr
+| 28 = @subexpr
+| 29 = @lshiftexpr
+| 30 = @rshiftexpr
+| 31 = @urshiftexpr
+| 32 = @andbitexpr
+| 33 = @orbitexpr
+| 34 = @xorbitexpr
+| 35 = @andlogicalexpr
+| 36 = @orlogicalexpr
+| 37 = @ltexpr
+| 38 = @gtexpr
+| 39 = @leexpr
+| 40 = @geexpr
+| 41 = @eqexpr
+| 42 = @neexpr
+| 43 = @postincexpr
+| 44 = @postdecexpr
+| 45 = @preincexpr
+| 46 = @predecexpr
+| 47 = @minusexpr
+| 48 = @plusexpr
+| 49 = @bitnotexpr
+| 50 = @lognotexpr
+| 51 = @castexpr
+| 52 = @newexpr
+| 53 = @conditionalexpr
+| 54 = @parexpr
+| 55 = @instanceofexpr
+| 56 = @localvariabledeclexpr
+| 57 = @typeliteral
+| 58 = @thisaccess
+| 59 = @superaccess
+| 60 = @varaccess
+| 61 = @methodaccess
+| 62 = @unannotatedtypeaccess
+| 63 = @arraytypeaccess
+| 64 = @packageaccess
+| 65 = @wildcardtypeaccess
+| 66 = @declannotation
+| 67 = @uniontypeaccess
+| 68 = @lambdaexpr
+| 69 = @memberref
+| 70 = @annotatedtypeaccess
+| 71 = @typeannotation
+| 72 = @intersectiontypeaccess
+;
+
+@classinstancexpr = @newexpr | @lambdaexpr | @memberref
+
+@annotation = @declannotation | @typeannotation
+@typeaccess = @unannotatedtypeaccess | @annotatedtypeaccess
+
+@assignment = @assignexpr
+ | @assignop;
+
+@unaryassignment = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr;
+
+@assignop = @assignaddexpr
+ | @assignsubexpr
+ | @assignmulexpr
+ | @assigndivexpr
+ | @assignremexpr
+ | @assignandexpr
+ | @assignorexpr
+ | @assignxorexpr
+ | @assignlshiftexpr
+ | @assignrshiftexpr
+ | @assignurshiftexpr;
+
+@literal = @booleanliteral
+ | @integerliteral
+ | @longliteral
+ | @floatingpointliteral
+ | @doubleliteral
+ | @characterliteral
+ | @stringliteral
+ | @nullliteral;
+
+@binaryexpr = @mulexpr
+ | @divexpr
+ | @remexpr
+ | @addexpr
+ | @subexpr
+ | @lshiftexpr
+ | @rshiftexpr
+ | @urshiftexpr
+ | @andbitexpr
+ | @orbitexpr
+ | @xorbitexpr
+ | @andlogicalexpr
+ | @orlogicalexpr
+ | @ltexpr
+ | @gtexpr
+ | @leexpr
+ | @geexpr
+ | @eqexpr
+ | @neexpr;
+
+@unaryexpr = @postincexpr
+ | @postdecexpr
+ | @preincexpr
+ | @predecexpr
+ | @minusexpr
+ | @plusexpr
+ | @bitnotexpr
+ | @lognotexpr;
+
+@caller = @classinstancexpr
+ | @methodaccess
+ | @constructorinvocationstmt
+ | @superconstructorinvocationstmt;
+
+callableBinding(
+ unique int callerid: @caller ref,
+ int callee: @callable ref
+);
+
+memberRefBinding(
+ unique int id: @expr ref,
+ int callable: @callable ref
+);
+
+@exprparent = @stmt | @expr | @callable | @field | @fielddecl | @class | @interface | @param | @localvar | @typevariable;
+
+variableBinding(
+ unique int expr: @varaccess ref,
+ int variable: @variable ref
+);
+
+@variable = @localscopevariable | @field;
+
+@localscopevariable = @localvar | @param;
+
+localvars(
+ unique int id: @localvar,
+ string nodeName: string ref,
+ int typeid: @type ref,
+ int parentid: @localvariabledeclexpr ref
+);
+
+@namedexprorstmt = @breakstmt
+ | @continuestmt
+ | @labeledstmt
+ | @literal;
+
+namestrings(
+ string name: string ref,
+ string value: string ref,
+ unique int parent: @namedexprorstmt ref
+);
+
+/*
+ * Modules
+ */
+
+#keyset[name]
+modules(
+ unique int id: @module,
+ string name: string ref
+);
+
+isOpen(
+ int id: @module ref
+);
+
+#keyset[fileId]
+cumodule(
+ int fileId: @file ref,
+ int moduleId: @module ref
+);
+
+@directive = @requires
+ | @exports
+ | @opens
+ | @uses
+ | @provides
+
+#keyset[directive]
+directives(
+ int id: @module ref,
+ int directive: @directive ref
+);
+
+requires(
+ unique int id: @requires,
+ int target: @module ref
+);
+
+isTransitive(
+ int id: @requires ref
+);
+
+isStatic(
+ int id: @requires ref
+);
+
+exports(
+ unique int id: @exports,
+ int target: @package ref
+);
+
+exportsTo(
+ int id: @exports ref,
+ int target: @module ref
+);
+
+opens(
+ unique int id: @opens,
+ int target: @package ref
+);
+
+opensTo(
+ int id: @opens ref,
+ int target: @module ref
+);
+
+uses(
+ unique int id: @uses,
+ string serviceInterface: string ref
+);
+
+provides(
+ unique int id: @provides,
+ string serviceInterface: string ref
+);
+
+providesWith(
+ int id: @provides ref,
+ string serviceImpl: string ref
+);
+
+/*
+ * Javadoc
+ */
+
+javadoc(
+ unique int id: @javadoc
+);
+
+isNormalComment(
+ int commentid : @javadoc ref
+);
+
+isEolComment(
+ int commentid : @javadoc ref
+);
+
+hasJavadoc(
+ int documentableid: @member ref,
+ int javadocid: @javadoc ref
+);
+
+#keyset[parentid,idx]
+javadocTag(
+ unique int id: @javadocTag,
+ string name: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+#keyset[parentid,idx]
+javadocText(
+ unique int id: @javadocText,
+ string text: string ref,
+ int parentid: @javadocParent ref,
+ int idx: int ref
+);
+
+@javadocParent = @javadoc | @javadocTag;
+@javadocElement = @javadocTag | @javadocText;
+
+@typeorpackage = @type | @package;
+
+@typeorcallable = @type | @callable;
+@classorinterface = @interface | @class;
+@boundedtype = @typevariable | @wildcard;
+@reftype = @classorinterface | @array | @boundedtype;
+@classorarray = @class | @array;
+@type = @primitive | @reftype;
+@callable = @method | @constructor;
+@element = @file | @package | @primitive | @class | @interface | @method | @constructor | @modifier | @param | @exception | @field |
+ @annotation | @boundedtype | @array | @localvar | @expr | @stmt | @import | @fielddecl;
+
+@modifiable = @member_modifiable| @param | @localvar ;
+
+@member_modifiable = @class | @interface | @method | @constructor | @field ;
+
+@member = @method | @constructor | @field | @reftype ;
+
+@locatable = @file | @class | @interface | @fielddecl | @field | @constructor | @method | @param | @exception
+ | @boundedtype | @typebound | @array | @primitive
+ | @import | @stmt | @expr | @localvar | @javadoc | @javadocTag | @javadocText
+ | @xmllocatable;
+
+@top = @element | @locatable | @folder;
+
+/*
+ * XML Files
+ */
+
+xmlEncoding(
+ unique int id: @file ref,
+ string encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ string root: string ref,
+ string publicId: string ref,
+ string systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ string name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ string name: string ref,
+ string value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ string prefixName: string ref,
+ string URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ string text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+
diff --git a/java/ql/src/config/semmlecode.dbscheme.stats b/java/ql/src/config/semmlecode.dbscheme.stats
new file mode 100644
index 00000000000..3f7eacf917f
--- /dev/null
+++ b/java/ql/src/config/semmlecode.dbscheme.stats
@@ -0,0 +1,23441 @@
+
+
+@externalDataElement
+1742
+
+
+@duplication
+38760
+
+
+@similarity
+125508
+
+
+@svnentry
+575525
+
+
+@location_default
+15321821
+
+
+@file
+176620
+
+
+@folder
+19172
+
+
+@package
+10167
+
+
+@primitive
+2904
+
+
+@modifier
+2904
+
+
+@class
+232016
+
+
+@interface
+249736
+
+
+@fielddecl
+99310
+
+
+@field
+299208
+
+
+@constructor
+262025
+
+
+@method
+2355907
+
+
+@param
+2259753
+
+
+@exception
+510688
+
+
+@typevariable
+58098
+
+
+@wildcard
+39393
+
+
+@typebound
+45015
+
+
+@array
+31082
+
+
+@import
+157518
+
+
+@block
+682661
+
+
+@ifstmt
+214215
+
+
+@forstmt
+24683
+
+
+@enhancedforstmt
+14815
+
+
+@whilestmt
+14815
+
+
+@dostmt
+3077
+
+
+@trystmt
+28308
+
+
+@switchstmt
+12518
+
+
+@synchronizedstmt
+5693
+
+
+@returnstmt
+216708
+
+
+@throwstmt
+81919
+
+
+@breakstmt
+47506
+
+
+@continuestmt
+2817
+
+
+@emptystmt
+523
+
+
+@exprstmt
+785297
+
+
+@labeledstmt
+3251
+
+
+@assertstmt
+9478
+
+
+@localvariabledeclstmt
+223232
+
+
+@localclassdeclstmt
+349
+
+
+@constructorinvocationstmt
+28177
+
+
+@superconstructorinvocationstmt
+54771
+
+
+@case
+82664
+
+
+@catchclause
+28672
+
+
+@arrayaccess
+53561
+
+
+@arraycreationexpr
+35027
+
+
+@arrayinit
+215504
+
+
+@assignexpr
+385011
+
+
+@assignaddexpr
+14076
+
+
+@assignsubexpr
+6390
+
+
+@assignmulexpr
+448
+
+
+@assigndivexpr
+95
+
+
+@assignremexpr
+39
+
+
+@assignandexpr
+441
+
+
+@assignorexpr
+11942
+
+
+@assignxorexpr
+586
+
+
+@assignlshiftexpr
+134
+
+
+@assignrshiftexpr
+113
+
+
+@assignurshiftexpr
+135
+
+
+@booleanliteral
+1334750
+
+
+@integerliteral
+345917
+
+
+@longliteral
+56227
+
+
+@floatingpointliteral
+16835
+
+
+@doubleliteral
+5029
+
+
+@characterliteral
+23950
+
+
+@stringliteral
+584002
+
+
+@nullliteral
+294342
+
+
+@mulexpr
+17168
+
+
+@divexpr
+3929
+
+
+@remexpr
+1458
+
+
+@addexpr
+123025
+
+
+@subexpr
+22368
+
+
+@lshiftexpr
+13943
+
+
+@rshiftexpr
+12781
+
+
+@urshiftexpr
+7862
+
+
+@andbitexpr
+24111
+
+
+@orbitexpr
+7136
+
+
+@xorbitexpr
+1270
+
+
+@andlogicalexpr
+29030
+
+
+@orlogicalexpr
+40849
+
+
+@ltexpr
+29652
+
+
+@gtexpr
+30501
+
+
+@leexpr
+7180
+
+
+@geexpr
+10167
+
+
+@eqexpr
+136556
+
+
+@neexpr
+52986
+
+
+@postincexpr
+36484
+
+
+@postdecexpr
+15952
+
+
+@preincexpr
+8700
+
+
+@predecexpr
+3128
+
+
+@minusexpr
+21600
+
+
+@plusexpr
+165
+
+
+@bitnotexpr
+6729
+
+
+@lognotexpr
+31373
+
+
+@castexpr
+123977
+
+
+@newexpr
+177501
+
+
+@conditionalexpr
+28468
+
+
+@parexpr
+76040
+
+
+@instanceofexpr
+24438
+
+
+@localvariabledeclexpr
+269506
+
+
+@typeliteral
+47201
+
+
+@thisaccess
+81047
+
+
+@superaccess
+10167
+
+
+@varaccess
+2012792
+
+
+@methodaccess
+1202564
+
+
+@unannotatedtypeaccess
+1477426
+
+
+@arraytypeaccess
+70880
+
+
+@wildcardtypeaccess
+39865
+
+
+@declannotation
+277067
+
+
+@uniontypeaccess
+158
+
+
+@lambdaexpr
+267
+
+
+@memberref
+116
+
+
+@intersectiontypeaccess
+15
+
+
+@packageaccess
+1
+
+
+@annotatedtypeaccess
+3
+
+
+@typeannotation
+5
+
+
+@localvar
+269506
+
+
+@module
+72
+
+
+@requires
+204
+
+
+@exports
+495
+
+
+@opens
+15
+
+
+@uses
+108
+
+
+@provides
+68
+
+
+@javadoc
+596965
+
+
+@javadocTag
+1048974
+
+
+@javadocText
+3965535
+
+
+@xmldtd
+1
+
+
+@xmlelement
+1270313
+
+
+@xmlattribute
+1202020
+
+
+@xmlnamespace
+4185
+
+
+@xmlcomment
+26812
+
+
+@xmlcharacters
+439958
+
+
+
+externalData
+3485
+
+
+id
+1742
+
+
+path
+290
+
+
+column
+580
+
+
+value
+3485
+
+
+
+
+id
+path
+
+
+12
+
+
+1
+2
+1742
+
+
+
+
+
+
+id
+column
+
+
+12
+
+
+2
+3
+1742
+
+
+
+
+
+
+id
+value
+
+
+12
+
+
+2
+3
+1742
+
+
+
+
+
+
+path
+id
+
+
+12
+
+
+6
+7
+290
+
+
+
+
+
+
+path
+column
+
+
+12
+
+
+2
+3
+290
+
+
+
+
+
+
+path
+value
+
+
+12
+
+
+12
+13
+290
+
+
+
+
+
+
+column
+id
+
+
+12
+
+
+6
+7
+580
+
+
+
+
+
+
+column
+path
+
+
+12
+
+
+1
+2
+580
+
+
+
+
+
+
+column
+value
+
+
+12
+
+
+6
+7
+580
+
+
+
+
+
+
+value
+id
+
+
+12
+
+
+1
+2
+3485
+
+
+
+
+
+
+value
+path
+
+
+12
+
+
+1
+2
+3485
+
+
+
+
+
+
+value
+column
+
+
+12
+
+
+1
+2
+3485
+
+
+
+
+
+
+
+
+snapshotDate
+290
+
+
+snapshotDate
+290
+
+
+
+
+
+sourceLocationPrefix
+290
+
+
+prefix
+290
+
+
+
+
+
+duplicateCode
+38760
+
+
+id
+38760
+
+
+relativePath
+977
+
+
+equivClass
+9189
+
+
+
+
+id
+relativePath
+
+
+12
+
+
+1
+2
+38760
+
+
+
+
+
+
+id
+equivClass
+
+
+12
+
+
+1
+2
+38760
+
+
+
+
+
+
+relativePath
+id
+
+
+12
+
+
+1
+2
+195
+
+
+3
+4
+195
+
+
+4
+5
+48
+
+
+5
+6
+48
+
+
+8
+9
+146
+
+
+9
+10
+97
+
+
+10
+11
+48
+
+
+15
+16
+48
+
+
+24
+25
+97
+
+
+653
+654
+48
+
+
+
+
+
+
+relativePath
+equivClass
+
+
+12
+
+
+1
+2
+195
+
+
+3
+4
+244
+
+
+4
+5
+48
+
+
+5
+6
+48
+
+
+7
+8
+48
+
+
+8
+9
+146
+
+
+9
+10
+97
+
+
+24
+25
+97
+
+
+136
+137
+48
+
+
+
+
+
+
+equivClass
+id
+
+
+12
+
+
+1
+2
+439
+
+
+2
+3
+3568
+
+
+3
+4
+830
+
+
+4
+5
+782
+
+
+5
+6
+586
+
+
+6
+7
+830
+
+
+7
+8
+733
+
+
+8
+9
+488
+
+
+9
+10
+684
+
+
+10
+11
+244
+
+
+
+
+
+
+equivClass
+relativePath
+
+
+12
+
+
+1
+2
+6696
+
+
+2
+3
+1808
+
+
+3
+7
+684
+
+
+
+
+
+
+
+
+similarCode
+125508
+
+
+id
+125508
+
+
+relativePath
+3504
+
+
+equivClass
+31482
+
+
+
+
+id
+relativePath
+
+
+12
+
+
+1
+2
+125508
+
+
+
+
+
+
+id
+equivClass
+
+
+12
+
+
+1
+2
+125508
+
+
+
+
+
+
+relativePath
+id
+
+
+12
+
+
+1
+2
+634
+
+
+2
+3
+896
+
+
+3
+4
+271
+
+
+4
+5
+241
+
+
+5
+6
+211
+
+
+6
+7
+241
+
+
+7
+11
+312
+
+
+11
+16
+281
+
+
+16
+42
+271
+
+
+55
+3207
+140
+
+
+
+
+
+
+relativePath
+equivClass
+
+
+12
+
+
+1
+2
+976
+
+
+2
+3
+715
+
+
+3
+4
+332
+
+
+4
+5
+332
+
+
+5
+6
+231
+
+
+6
+7
+302
+
+
+7
+11
+312
+
+
+11
+777
+271
+
+
+783
+963
+30
+
+
+
+
+
+
+equivClass
+id
+
+
+12
+
+
+2
+3
+11853
+
+
+3
+4
+5569
+
+
+4
+5
+4109
+
+
+5
+6
+2477
+
+
+6
+7
+2336
+
+
+7
+8
+1712
+
+
+8
+10
+2537
+
+
+10
+11
+886
+
+
+
+
+
+
+equivClass
+relativePath
+
+
+12
+
+
+1
+2
+17957
+
+
+2
+3
+6506
+
+
+3
+4
+3112
+
+
+4
+5
+2125
+
+
+5
+11
+1782
+
+
+
+
+
+
+
+
+tokens
+17070956
+
+
+id
+139325
+
+
+offset
+25832
+
+
+beginLine
+243624
+
+
+beginColumn
+10171
+
+
+endLine
+243624
+
+
+endColumn
+10141
+
+
+
+
+id
+offset
+
+
+12
+
+
+100
+101
+13284
+
+
+101
+102
+16426
+
+
+102
+103
+7442
+
+
+103
+105
+12176
+
+
+105
+108
+11763
+
+
+108
+112
+12276
+
+
+112
+116
+12810
+
+
+116
+120
+9668
+
+
+120
+128
+11531
+
+
+128
+143
+11088
+
+
+143
+169
+10846
+
+
+169
+2566
+10010
+
+
+
+
+
+
+id
+beginLine
+
+
+12
+
+
+4
+7
+9094
+
+
+7
+11
+11259
+
+
+11
+13
+7956
+
+
+13
+15
+12387
+
+
+15
+16
+6586
+
+
+16
+17
+9054
+
+
+17
+18
+8036
+
+
+18
+19
+9245
+
+
+19
+20
+9195
+
+
+20
+22
+12246
+
+
+22
+24
+12367
+
+
+24
+27
+12145
+
+
+27
+34
+11330
+
+
+34
+478
+8419
+
+
+
+
+
+
+id
+beginColumn
+
+
+12
+
+
+4
+32
+10786
+
+
+32
+41
+10705
+
+
+41
+44
+9426
+
+
+44
+46
+8751
+
+
+46
+48
+8701
+
+
+48
+50
+11138
+
+
+50
+52
+11964
+
+
+52
+54
+12337
+
+
+54
+56
+10323
+
+
+56
+59
+12166
+
+
+59
+63
+11662
+
+
+63
+70
+10897
+
+
+70
+144
+10464
+
+
+
+
+
+
+id
+endLine
+
+
+12
+
+
+4
+7
+9094
+
+
+7
+11
+11259
+
+
+11
+13
+7956
+
+
+13
+15
+12387
+
+
+15
+16
+6586
+
+
+16
+17
+9054
+
+
+17
+18
+8036
+
+
+18
+19
+9245
+
+
+19
+20
+9195
+
+
+20
+22
+12246
+
+
+22
+24
+12367
+
+
+24
+27
+12145
+
+
+27
+34
+11330
+
+
+34
+478
+8419
+
+
+
+
+
+
+id
+endColumn
+
+
+12
+
+
+4
+35
+10725
+
+
+35
+44
+12861
+
+
+44
+48
+11350
+
+
+48
+50
+9738
+
+
+50
+52
+10413
+
+
+52
+54
+11521
+
+
+54
+56
+12135
+
+
+56
+58
+9960
+
+
+58
+60
+9446
+
+
+60
+63
+10856
+
+
+63
+67
+11017
+
+
+67
+76
+10645
+
+
+76
+150
+8651
+
+
+
+
+
+
+offset
+id
+
+
+12
+
+
+4
+5
+14784
+
+
+10
+11
+2487
+
+
+14
+17
+1923
+
+
+18
+30
+2064
+
+
+31
+151
+1943
+
+
+152
+12516
+1621
+
+
+13834
+13835
+1007
+
+
+
+
+
+
+offset
+beginLine
+
+
+12
+
+
+1
+2
+14784
+
+
+7
+8
+2487
+
+
+8
+13
+2044
+
+
+13
+22
+1943
+
+
+22
+110
+1943
+
+
+112
+7742
+1943
+
+
+7742
+7961
+684
+
+
+
+
+
+
+offset
+beginColumn
+
+
+12
+
+
+1
+2
+14845
+
+
+2
+3
+2517
+
+
+3
+5
+1973
+
+
+5
+9
+2044
+
+
+9
+43
+1963
+
+
+43
+238
+1953
+
+
+238
+338
+533
+
+
+
+
+
+
+offset
+endLine
+
+
+12
+
+
+1
+2
+14784
+
+
+7
+8
+2487
+
+
+8
+13
+2044
+
+
+13
+22
+1943
+
+
+22
+110
+1943
+
+
+112
+7742
+1943
+
+
+7742
+7961
+684
+
+
+
+
+
+
+offset
+endColumn
+
+
+12
+
+
+1
+2
+14845
+
+
+2
+3
+2507
+
+
+3
+5
+2034
+
+
+5
+10
+2104
+
+
+10
+46
+1953
+
+
+46
+244
+1943
+
+
+244
+341
+443
+
+
+
+
+
+
+beginLine
+id
+
+
+12
+
+
+1
+2
+12830
+
+
+2
+3
+15499
+
+
+3
+4
+17463
+
+
+4
+5
+18762
+
+
+5
+6
+16788
+
+
+6
+7
+18309
+
+
+7
+8
+17604
+
+
+8
+9
+16728
+
+
+9
+10
+15680
+
+
+10
+11
+14824
+
+
+11
+12
+12720
+
+
+12
+14
+20988
+
+
+14
+18
+22438
+
+
+18
+44
+18380
+
+
+44
+418
+4602
+
+
+
+
+
+
+beginLine
+offset
+
+
+12
+
+
+1
+6
+18521
+
+
+6
+11
+20686
+
+
+11
+17
+21703
+
+
+17
+22
+18369
+
+
+22
+27
+18631
+
+
+27
+33
+21139
+
+
+33
+39
+19669
+
+
+39
+46
+19538
+
+
+46
+55
+20182
+
+
+55
+66
+19518
+
+
+66
+82
+18329
+
+
+82
+114
+18631
+
+
+114
+348
+8701
+
+
+
+
+
+
+beginLine
+beginColumn
+
+
+12
+
+
+1
+3
+20847
+
+
+3
+5
+15449
+
+
+5
+7
+21482
+
+
+7
+9
+15197
+
+
+9
+11
+19971
+
+
+11
+13
+18621
+
+
+13
+15
+17695
+
+
+15
+18
+21723
+
+
+18
+20
+16758
+
+
+20
+23
+22126
+
+
+23
+28
+20505
+
+
+28
+39
+19316
+
+
+39
+274
+13928
+
+
+
+
+
+
+beginLine
+endLine
+
+
+12
+
+
+1
+2
+243624
+
+
+
+
+
+
+beginLine
+endColumn
+
+
+12
+
+
+1
+3
+20495
+
+
+3
+5
+15207
+
+
+5
+7
+21069
+
+
+7
+9
+14965
+
+
+9
+11
+19558
+
+
+11
+13
+18813
+
+
+13
+15
+17262
+
+
+15
+18
+22076
+
+
+18
+20
+16980
+
+
+20
+23
+21995
+
+
+23
+28
+20938
+
+
+28
+38
+18772
+
+
+38
+274
+15489
+
+
+
+
+
+
+beginColumn
+id
+
+
+12
+
+
+1
+2
+674
+
+
+2
+3
+463
+
+
+3
+5
+896
+
+
+5
+8
+906
+
+
+8
+9
+161
+
+
+9
+10
+1057
+
+
+10
+20
+825
+
+
+20
+30
+815
+
+
+30
+44
+785
+
+
+44
+65
+765
+
+
+65
+102
+765
+
+
+102
+1024
+765
+
+
+1040
+5993
+765
+
+
+6023
+12021
+523
+
+
+
+
+
+
+beginColumn
+offset
+
+
+12
+
+
+1
+2
+956
+
+
+2
+3
+997
+
+
+3
+4
+1530
+
+
+4
+8
+845
+
+
+8
+13
+886
+
+
+13
+18
+856
+
+
+18
+24
+805
+
+
+24
+35
+825
+
+
+35
+62
+765
+
+
+66
+177
+765
+
+
+179
+380
+765
+
+
+382
+565
+171
+
+
+
+
+
+
+beginColumn
+beginLine
+
+
+12
+
+
+1
+2
+1057
+
+
+2
+3
+553
+
+
+3
+4
+715
+
+
+4
+5
+654
+
+
+5
+6
+1430
+
+
+6
+10
+835
+
+
+10
+15
+795
+
+
+15
+21
+785
+
+
+21
+30
+775
+
+
+30
+49
+765
+
+
+49
+658
+765
+
+
+681
+5371
+765
+
+
+5501
+12832
+271
+
+
+
+
+
+
+beginColumn
+endLine
+
+
+12
+
+
+1
+2
+1057
+
+
+2
+3
+553
+
+
+3
+4
+715
+
+
+4
+5
+654
+
+
+5
+6
+1430
+
+
+6
+10
+835
+
+
+10
+15
+795
+
+
+15
+21
+785
+
+
+21
+30
+775
+
+
+30
+49
+765
+
+
+49
+658
+765
+
+
+681
+5371
+765
+
+
+5501
+12832
+271
+
+
+
+
+
+
+beginColumn
+endColumn
+
+
+12
+
+
+1
+2
+3001
+
+
+2
+3
+1712
+
+
+3
+4
+1309
+
+
+4
+5
+1077
+
+
+5
+6
+674
+
+
+6
+9
+795
+
+
+9
+21
+785
+
+
+21
+51
+765
+
+
+51
+74
+50
+
+
+
+
+
+
+endLine
+id
+
+
+12
+
+
+1
+2
+12830
+
+
+2
+3
+15499
+
+
+3
+4
+17463
+
+
+4
+5
+18762
+
+
+5
+6
+16788
+
+
+6
+7
+18309
+
+
+7
+8
+17604
+
+
+8
+9
+16728
+
+
+9
+10
+15680
+
+
+10
+11
+14824
+
+
+11
+12
+12720
+
+
+12
+14
+20988
+
+
+14
+18
+22438
+
+
+18
+44
+18380
+
+
+44
+418
+4602
+
+
+
+
+
+
+endLine
+offset
+
+
+12
+
+
+1
+6
+18521
+
+
+6
+11
+20686
+
+
+11
+17
+21703
+
+
+17
+22
+18369
+
+
+22
+27
+18631
+
+
+27
+33
+21139
+
+
+33
+39
+19669
+
+
+39
+46
+19538
+
+
+46
+55
+20182
+
+
+55
+66
+19518
+
+
+66
+82
+18329
+
+
+82
+114
+18631
+
+
+114
+348
+8701
+
+
+
+
+
+
+endLine
+beginLine
+
+
+12
+
+
+1
+2
+243624
+
+
+
+
+
+
+endLine
+beginColumn
+
+
+12
+
+
+1
+3
+20847
+
+
+3
+5
+15449
+
+
+5
+7
+21482
+
+
+7
+9
+15197
+
+
+9
+11
+19971
+
+
+11
+13
+18621
+
+
+13
+15
+17695
+
+
+15
+18
+21723
+
+
+18
+20
+16758
+
+
+20
+23
+22126
+
+
+23
+28
+20505
+
+
+28
+39
+19316
+
+
+39
+274
+13928
+
+
+
+
+
+
+endLine
+endColumn
+
+
+12
+
+
+1
+3
+20495
+
+
+3
+5
+15207
+
+
+5
+7
+21069
+
+
+7
+9
+14965
+
+
+9
+11
+19558
+
+
+11
+13
+18813
+
+
+13
+15
+17262
+
+
+15
+18
+22076
+
+
+18
+20
+16980
+
+
+20
+23
+21995
+
+
+23
+28
+20938
+
+
+28
+38
+18772
+
+
+38
+274
+15489
+
+
+
+
+
+
+endColumn
+id
+
+
+12
+
+
+1
+2
+654
+
+
+2
+4
+896
+
+
+4
+6
+755
+
+
+6
+9
+604
+
+
+9
+10
+1047
+
+
+10
+19
+775
+
+
+19
+27
+785
+
+
+27
+38
+785
+
+
+38
+56
+765
+
+
+56
+90
+765
+
+
+91
+428
+765
+
+
+449
+3654
+765
+
+
+3777
+11083
+765
+
+
+12440
+12441
+10
+
+
+
+
+
+
+endColumn
+offset
+
+
+12
+
+
+1
+2
+825
+
+
+2
+3
+1117
+
+
+3
+4
+1470
+
+
+4
+9
+815
+
+
+9
+13
+845
+
+
+13
+17
+886
+
+
+17
+23
+805
+
+
+23
+34
+805
+
+
+34
+55
+765
+
+
+55
+166
+775
+
+
+166
+366
+765
+
+
+369
+520
+261
+
+
+
+
+
+
+endColumn
+beginLine
+
+
+12
+
+
+1
+2
+997
+
+
+2
+3
+382
+
+
+3
+4
+694
+
+
+4
+5
+654
+
+
+5
+6
+1379
+
+
+6
+9
+805
+
+
+9
+13
+785
+
+
+13
+18
+765
+
+
+18
+25
+775
+
+
+25
+38
+785
+
+
+38
+230
+765
+
+
+230
+2563
+765
+
+
+2595
+10410
+584
+
+
+
+
+
+
+endColumn
+beginColumn
+
+
+12
+
+
+1
+2
+2890
+
+
+2
+3
+1691
+
+
+3
+4
+1490
+
+
+4
+5
+1017
+
+
+5
+7
+926
+
+
+7
+12
+795
+
+
+12
+31
+785
+
+
+31
+49
+543
+
+
+
+
+
+
+endColumn
+endLine
+
+
+12
+
+
+1
+2
+997
+
+
+2
+3
+382
+
+
+3
+4
+694
+
+
+4
+5
+654
+
+
+5
+6
+1379
+
+
+6
+9
+805
+
+
+9
+13
+785
+
+
+13
+18
+765
+
+
+18
+25
+775
+
+
+25
+38
+785
+
+
+38
+230
+765
+
+
+230
+2563
+765
+
+
+2595
+10410
+584
+
+
+
+
+
+
+
+
+svnentries
+575525
+
+
+id
+575525
+
+
+revision
+575525
+
+
+author
+19539
+
+
+revisionDate
+547759
+
+
+changeSize
+1
+
+
+
+
+id
+revision
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+id
+author
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+id
+revisionDate
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+id
+changeSize
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+revision
+id
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+revision
+author
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+revision
+revisionDate
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+revision
+changeSize
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+author
+id
+
+
+12
+
+
+1
+2
+7913
+
+
+2
+3
+2531
+
+
+3
+4
+1388
+
+
+4
+6
+1523
+
+
+6
+10
+1529
+
+
+10
+20
+1509
+
+
+20
+52
+1488
+
+
+52
+568
+1466
+
+
+569
+16582
+192
+
+
+
+
+
+
+author
+revision
+
+
+12
+
+
+1
+2
+7913
+
+
+2
+3
+2531
+
+
+3
+4
+1388
+
+
+4
+6
+1523
+
+
+6
+10
+1529
+
+
+10
+20
+1509
+
+
+20
+52
+1488
+
+
+52
+568
+1466
+
+
+569
+16582
+192
+
+
+
+
+
+
+author
+revisionDate
+
+
+12
+
+
+1
+2
+7996
+
+
+2
+3
+2509
+
+
+3
+4
+1379
+
+
+4
+6
+1520
+
+
+6
+10
+1529
+
+
+10
+20
+1507
+
+
+20
+52
+1474
+
+
+52
+662
+1466
+
+
+663
+16573
+159
+
+
+
+
+
+
+author
+changeSize
+
+
+12
+
+
+1
+2
+19539
+
+
+
+
+
+
+revisionDate
+id
+
+
+12
+
+
+1
+2
+531878
+
+
+2
+100
+15881
+
+
+
+
+
+
+revisionDate
+revision
+
+
+12
+
+
+1
+2
+531878
+
+
+2
+100
+15881
+
+
+
+
+
+
+revisionDate
+author
+
+
+12
+
+
+1
+2
+542505
+
+
+2
+17
+5254
+
+
+
+
+
+
+revisionDate
+changeSize
+
+
+12
+
+
+1
+2
+547759
+
+
+
+
+
+
+changeSize
+id
+
+
+12
+
+
+575525
+575526
+1
+
+
+
+
+
+
+changeSize
+revision
+
+
+12
+
+
+575525
+575526
+1
+
+
+
+
+
+
+changeSize
+author
+
+
+12
+
+
+19539
+19540
+1
+
+
+
+
+
+
+changeSize
+revisionDate
+
+
+12
+
+
+547759
+547760
+1
+
+
+
+
+
+
+
+
+svnaffectedfiles
+1314068
+
+
+id
+531628
+
+
+file
+90924
+
+
+action
+1
+
+
+
+
+id
+file
+
+
+12
+
+
+1
+2
+337698
+
+
+2
+3
+77525
+
+
+3
+4
+43024
+
+
+4
+7
+46689
+
+
+7
+16635
+26692
+
+
+
+
+
+
+id
+action
+
+
+12
+
+
+1
+2
+531628
+
+
+
+
+
+
+file
+id
+
+
+12
+
+
+1
+2
+11819
+
+
+2
+3
+18230
+
+
+3
+4
+9501
+
+
+4
+5
+6656
+
+
+5
+6
+5012
+
+
+6
+8
+7103
+
+
+8
+11
+6788
+
+
+11
+16
+6996
+
+
+16
+26
+7180
+
+
+26
+54
+6824
+
+
+54
+3572
+4815
+
+
+
+
+
+
+file
+action
+
+
+12
+
+
+1
+2
+90924
+
+
+
+
+
+
+action
+id
+
+
+12
+
+
+531628
+531629
+1
+
+
+
+
+
+
+action
+file
+
+
+12
+
+
+90924
+90925
+1
+
+
+
+
+
+
+
+
+svnentrymsg
+575525
+
+
+id
+575525
+
+
+message
+568305
+
+
+
+
+id
+message
+
+
+12
+
+
+1
+2
+575525
+
+
+
+
+
+
+message
+id
+
+
+12
+
+
+1
+2
+565381
+
+
+2
+142
+2924
+
+
+
+
+
+
+
+
+svnchurn
+46790
+
+
+commit
+22361
+
+
+file
+16124
+
+
+addedLines
+910
+
+
+deletedLines
+787
+
+
+
+
+commit
+file
+
+
+12
+
+
+1
+2
+15208
+
+
+2
+3
+3101
+
+
+3
+4
+1746
+
+
+4
+8
+1774
+
+
+8
+246
+532
+
+
+
+
+
+
+commit
+addedLines
+
+
+12
+
+
+1
+2
+16074
+
+
+2
+3
+3323
+
+
+3
+4
+1561
+
+
+4
+118
+1403
+
+
+
+
+
+
+commit
+deletedLines
+
+
+12
+
+
+1
+2
+16799
+
+
+2
+3
+3286
+
+
+3
+5
+1763
+
+
+5
+113
+513
+
+
+
+
+
+
+file
+commit
+
+
+12
+
+
+1
+2
+8618
+
+
+2
+3
+2956
+
+
+3
+4
+1426
+
+
+4
+6
+1364
+
+
+6
+12
+1210
+
+
+12
+448
+550
+
+
+
+
+
+
+file
+addedLines
+
+
+12
+
+
+1
+2
+9240
+
+
+2
+3
+3129
+
+
+3
+4
+1393
+
+
+4
+6
+1239
+
+
+6
+59
+1123
+
+
+
+
+
+
+file
+deletedLines
+
+
+12
+
+
+1
+2
+9525
+
+
+2
+3
+3192
+
+
+3
+4
+1401
+
+
+4
+7
+1387
+
+
+7
+70
+619
+
+
+
+
+
+
+addedLines
+commit
+
+
+12
+
+
+1
+2
+446
+
+
+2
+3
+133
+
+
+3
+4
+70
+
+
+4
+6
+68
+
+
+6
+12
+70
+
+
+12
+57
+69
+
+
+57
+6874
+54
+
+
+
+
+
+
+addedLines
+file
+
+
+12
+
+
+1
+2
+445
+
+
+2
+3
+132
+
+
+3
+4
+69
+
+
+4
+6
+68
+
+
+6
+12
+73
+
+
+12
+58
+69
+
+
+58
+6663
+54
+
+
+
+
+
+
+addedLines
+deletedLines
+
+
+12
+
+
+1
+2
+621
+
+
+2
+3
+96
+
+
+3
+7
+81
+
+
+7
+34
+70
+
+
+34
+727
+42
+
+
+
+
+
+
+deletedLines
+commit
+
+
+12
+
+
+1
+2
+439
+
+
+2
+3
+116
+
+
+3
+4
+48
+
+
+4
+8
+67
+
+
+8
+28
+60
+
+
+28
+6794
+57
+
+
+
+
+
+
+deletedLines
+file
+
+
+12
+
+
+1
+2
+437
+
+
+2
+3
+113
+
+
+3
+4
+49
+
+
+4
+7
+61
+
+
+7
+19
+60
+
+
+19
+770
+60
+
+
+985
+7318
+7
+
+
+
+
+
+
+deletedLines
+addedLines
+
+
+12
+
+
+1
+2
+545
+
+
+2
+3
+72
+
+
+3
+7
+69
+
+
+7
+30
+60
+
+
+30
+871
+41
+
+
+
+
+
+
+
+
+locations_default
+15321821
+
+
+id
+15321821
+
+
+file
+176620
+
+
+beginLine
+865672
+
+
+beginColumn
+42121
+
+
+endLine
+883102
+
+
+endColumn
+43574
+
+
+
+
+id
+file
+
+
+12
+
+
+1
+2
+15321821
+
+
+
+
+
+
+id
+beginLine
+
+
+12
+
+
+1
+2
+15321821
+
+
+
+
+
+
+id
+beginColumn
+
+
+12
+
+
+1
+2
+15321821
+
+
+
+
+
+
+id
+endLine
+
+
+12
+
+
+1
+2
+15321821
+
+
+
+
+
+
+id
+endColumn
+
+
+12
+
+
+1
+2
+15321821
+
+
+
+
+
+
+file
+id
+
+
+12
+
+
+1
+2
+146118
+
+
+51
+203
+13653
+
+
+211
+790
+13362
+
+
+926
+7096
+3485
+
+
+
+
+
+
+file
+beginLine
+
+
+12
+
+
+1
+2
+146118
+
+
+35
+89
+13362
+
+
+89
+277
+13362
+
+
+292
+2423
+3776
+
+
+
+
+
+
+file
+beginColumn
+
+
+12
+
+
+1
+2
+146118
+
+
+14
+42
+13943
+
+
+42
+84
+13362
+
+
+87
+136
+3195
+
+
+
+
+
+
+file
+endLine
+
+
+12
+
+
+1
+2
+146118
+
+
+36
+94
+13362
+
+
+95
+306
+13362
+
+
+329
+2637
+3776
+
+
+
+
+
+
+file
+endColumn
+
+
+12
+
+
+1
+2
+146118
+
+
+32
+67
+13653
+
+
+68
+98
+13362
+
+
+99
+138
+3485
+
+
+
+
+
+
+beginLine
+id
+
+
+12
+
+
+1
+2
+73204
+
+
+2
+3
+79014
+
+
+3
+4
+88310
+
+
+4
+5
+87729
+
+
+5
+6
+63037
+
+
+6
+7
+59841
+
+
+7
+8
+52288
+
+
+8
+10
+70009
+
+
+10
+13
+71171
+
+
+13
+20
+70590
+
+
+20
+49
+65651
+
+
+49
+137
+65361
+
+
+137
+609
+19463
+
+
+
+
+
+
+beginLine
+file
+
+
+12
+
+
+1
+2
+201893
+
+
+2
+3
+355564
+
+
+3
+4
+103125
+
+
+4
+8
+74947
+
+
+8
+24
+66813
+
+
+24
+609
+63327
+
+
+
+
+
+
+beginLine
+beginColumn
+
+
+12
+
+
+1
+2
+108935
+
+
+2
+3
+88891
+
+
+3
+4
+139437
+
+
+4
+5
+82209
+
+
+5
+6
+70880
+
+
+6
+7
+50836
+
+
+7
+9
+78433
+
+
+9
+12
+74075
+
+
+12
+19
+67685
+
+
+19
+35
+68847
+
+
+35
+57
+35440
+
+
+
+
+
+
+beginLine
+endLine
+
+
+12
+
+
+1
+2
+487158
+
+
+2
+3
+227747
+
+
+3
+4
+60132
+
+
+4
+8
+72623
+
+
+8
+15
+18010
+
+
+
+
+
+
+beginLine
+endColumn
+
+
+12
+
+
+1
+2
+83662
+
+
+2
+3
+83081
+
+
+3
+4
+94701
+
+
+4
+5
+108063
+
+
+5
+6
+74947
+
+
+6
+7
+55484
+
+
+7
+8
+52869
+
+
+8
+10
+61875
+
+
+10
+13
+65651
+
+
+13
+21
+65651
+
+
+21
+43
+65070
+
+
+43
+76
+54612
+
+
+
+
+
+
+beginColumn
+id
+
+
+12
+
+
+1
+3
+3776
+
+
+3
+5
+2614
+
+
+5
+10
+3195
+
+
+10
+19
+3485
+
+
+19
+28
+3195
+
+
+30
+54
+3195
+
+
+54
+79
+3195
+
+
+86
+131
+3195
+
+
+149
+198
+3195
+
+
+198
+383
+3195
+
+
+392
+550
+3195
+
+
+596
+917
+3195
+
+
+918
+4396
+3195
+
+
+7284
+7285
+290
+
+
+
+
+
+
+beginColumn
+file
+
+
+12
+
+
+1
+2
+2323
+
+
+2
+3
+2904
+
+
+3
+5
+2614
+
+
+5
+7
+2904
+
+
+7
+10
+3485
+
+
+10
+16
+3776
+
+
+16
+24
+3195
+
+
+25
+38
+3485
+
+
+39
+49
+3195
+
+
+51
+61
+3485
+
+
+64
+72
+3195
+
+
+72
+85
+3195
+
+
+85
+105
+2614
+
+
+105
+609
+1742
+
+
+
+
+
+
+beginColumn
+beginLine
+
+
+12
+
+
+1
+2
+1742
+
+
+2
+3
+2323
+
+
+3
+5
+2614
+
+
+5
+10
+3195
+
+
+10
+19
+3776
+
+
+20
+28
+3195
+
+
+29
+49
+3195
+
+
+49
+71
+3195
+
+
+73
+111
+3195
+
+
+126
+158
+3195
+
+
+159
+247
+3195
+
+
+256
+348
+3195
+
+
+353
+577
+3195
+
+
+580
+2365
+2904
+
+
+
+
+
+
+beginColumn
+endLine
+
+
+12
+
+
+1
+2
+1742
+
+
+2
+3
+2323
+
+
+3
+5
+2614
+
+
+5
+10
+3195
+
+
+10
+19
+3776
+
+
+20
+28
+3195
+
+
+29
+50
+3195
+
+
+51
+71
+3195
+
+
+74
+111
+3195
+
+
+126
+157
+3195
+
+
+160
+250
+3195
+
+
+262
+342
+3195
+
+
+357
+579
+3195
+
+
+579
+2365
+2904
+
+
+
+
+
+
+beginColumn
+endColumn
+
+
+12
+
+
+1
+2
+3485
+
+
+2
+3
+4357
+
+
+3
+6
+3195
+
+
+6
+9
+3776
+
+
+9
+14
+3195
+
+
+14
+21
+3195
+
+
+21
+29
+3485
+
+
+29
+34
+3195
+
+
+34
+42
+3195
+
+
+43
+55
+3485
+
+
+55
+67
+3195
+
+
+67
+105
+3776
+
+
+107
+112
+580
+
+
+
+
+
+
+endLine
+id
+
+
+12
+
+
+1
+2
+56355
+
+
+2
+3
+95863
+
+
+3
+4
+91215
+
+
+4
+5
+101091
+
+
+5
+6
+64780
+
+
+6
+7
+62746
+
+
+7
+8
+52869
+
+
+8
+10
+74075
+
+
+10
+13
+70299
+
+
+13
+21
+69718
+
+
+21
+55
+67104
+
+
+55
+157
+66523
+
+
+157
+609
+10457
+
+
+
+
+
+
+endLine
+file
+
+
+12
+
+
+1
+2
+131884
+
+
+2
+3
+406982
+
+
+3
+4
+125202
+
+
+4
+7
+70299
+
+
+7
+18
+66523
+
+
+18
+75
+66813
+
+
+76
+609
+15396
+
+
+
+
+
+
+endLine
+beginLine
+
+
+12
+
+
+1
+2
+514755
+
+
+2
+3
+225713
+
+
+3
+5
+79304
+
+
+5
+12
+63327
+
+
+
+
+
+
+endLine
+beginColumn
+
+
+12
+
+
+1
+2
+89472
+
+
+2
+3
+107773
+
+
+3
+4
+140889
+
+
+4
+5
+102834
+
+
+5
+6
+71461
+
+
+6
+7
+56646
+
+
+7
+9
+80176
+
+
+9
+12
+69137
+
+
+12
+20
+66232
+
+
+20
+37
+68847
+
+
+37
+58
+29630
+
+
+
+
+
+
+endLine
+endColumn
+
+
+12
+
+
+1
+2
+68556
+
+
+2
+3
+101091
+
+
+3
+4
+97606
+
+
+4
+5
+124912
+
+
+5
+6
+69428
+
+
+6
+7
+63618
+
+
+7
+8
+50255
+
+
+8
+10
+67104
+
+
+10
+14
+74947
+
+
+14
+26
+66232
+
+
+26
+52
+66523
+
+
+52
+78
+32825
+
+
+
+
+
+
+endColumn
+id
+
+
+12
+
+
+1
+4
+3776
+
+
+4
+7
+3485
+
+
+7
+30
+3485
+
+
+31
+58
+3485
+
+
+58
+104
+3485
+
+
+105
+176
+3485
+
+
+192
+398
+3485
+
+
+402
+472
+3485
+
+
+474
+579
+3485
+
+
+580
+657
+3485
+
+
+677
+783
+3485
+
+
+797
+1060
+3485
+
+
+1109
+2048
+1452
+
+
+
+
+
+
+endColumn
+file
+
+
+12
+
+
+1
+2
+3195
+
+
+2
+5
+3485
+
+
+5
+10
+3485
+
+
+10
+16
+3485
+
+
+16
+25
+3485
+
+
+25
+40
+3485
+
+
+42
+67
+3485
+
+
+67
+78
+3485
+
+
+78
+82
+3485
+
+
+82
+87
+3485
+
+
+87
+100
+3485
+
+
+101
+105
+1452
+
+
+105
+106
+3776
+
+
+608
+609
+290
+
+
+
+
+
+
+endColumn
+beginLine
+
+
+12
+
+
+1
+3
+3776
+
+
+3
+7
+3776
+
+
+7
+18
+3485
+
+
+18
+40
+3485
+
+
+40
+72
+3485
+
+
+72
+126
+3485
+
+
+135
+266
+3485
+
+
+268
+312
+3485
+
+
+313
+359
+3485
+
+
+364
+406
+3485
+
+
+419
+477
+3485
+
+
+485
+586
+3485
+
+
+597
+1097
+1161
+
+
+
+
+
+
+endColumn
+beginColumn
+
+
+12
+
+
+1
+2
+3776
+
+
+2
+4
+2614
+
+
+4
+6
+3485
+
+
+6
+10
+3485
+
+
+10
+19
+3485
+
+
+19
+26
+3485
+
+
+26
+32
+3485
+
+
+32
+36
+3195
+
+
+36
+40
+2904
+
+
+40
+47
+3776
+
+
+47
+52
+3776
+
+
+52
+56
+3776
+
+
+56
+121
+2323
+
+
+
+
+
+
+endColumn
+endLine
+
+
+12
+
+
+1
+3
+3776
+
+
+3
+7
+3776
+
+
+7
+18
+3485
+
+
+21
+41
+3485
+
+
+43
+71
+3485
+
+
+72
+124
+3485
+
+
+135
+266
+3485
+
+
+267
+311
+3485
+
+
+312
+358
+3485
+
+
+359
+406
+3485
+
+
+419
+477
+3485
+
+
+486
+594
+3485
+
+
+605
+1097
+1161
+
+
+
+
+
+
+
+
+hasLocation
+18893736
+
+
+locatableid
+18342088
+
+
+id
+15321821
+
+
+
+
+locatableid
+id
+
+
+12
+
+
+1
+2
+17790439
+
+
+2
+3
+551648
+
+
+
+
+
+
+id
+locatableid
+
+
+12
+
+
+1
+2
+15155948
+
+
+2
+581
+165872
+
+
+
+
+
+
+
+
+numlines
+8182637
+
+
+element_id
+8182637
+
+
+num_lines
+55193
+
+
+num_code
+33406
+
+
+num_comment
+38345
+
+
+
+
+element_id
+num_lines
+
+
+12
+
+
+1
+2
+8182637
+
+
+
+
+
+
+element_id
+num_code
+
+
+12
+
+
+1
+2
+8182637
+
+
+
+
+
+
+element_id
+num_comment
+
+
+12
+
+
+1
+2
+8182637
+
+
+
+
+
+
+num_lines
+element_id
+
+
+12
+
+
+1
+2
+30211
+
+
+2
+3
+10748
+
+
+3
+6
+4938
+
+
+6
+17
+4357
+
+
+19
+327
+4357
+
+
+1772
+24169
+580
+
+
+
+
+
+
+num_lines
+num_code
+
+
+12
+
+
+1
+2
+32535
+
+
+2
+3
+10457
+
+
+3
+4
+4647
+
+
+4
+5
+3485
+
+
+5
+9
+4066
+
+
+
+
+
+
+num_lines
+num_comment
+
+
+12
+
+
+1
+2
+32825
+
+
+2
+3
+9876
+
+
+3
+4
+5519
+
+
+4
+5
+4357
+
+
+5
+7
+2614
+
+
+
+
+
+
+num_code
+element_id
+
+
+12
+
+
+1
+2
+13362
+
+
+2
+3
+5519
+
+
+3
+4
+1742
+
+
+4
+5
+2614
+
+
+5
+10
+2614
+
+
+10
+21
+2614
+
+
+26
+111
+2614
+
+
+136
+24169
+2323
+
+
+
+
+
+
+num_code
+num_lines
+
+
+12
+
+
+1
+2
+13943
+
+
+2
+3
+6390
+
+
+3
+4
+3485
+
+
+4
+5
+1742
+
+
+5
+6
+1742
+
+
+6
+8
+2614
+
+
+8
+12
+2614
+
+
+12
+15
+871
+
+
+
+
+
+
+num_code
+num_comment
+
+
+12
+
+
+1
+2
+13943
+
+
+2
+3
+6390
+
+
+3
+4
+3195
+
+
+4
+5
+2614
+
+
+5
+7
+2904
+
+
+7
+10
+2904
+
+
+10
+15
+1452
+
+
+
+
+
+
+num_comment
+element_id
+
+
+12
+
+
+1
+2
+20334
+
+
+2
+3
+7552
+
+
+3
+4
+3195
+
+
+4
+6
+3195
+
+
+6
+16
+2904
+
+
+33
+27486
+1161
+
+
+
+
+
+
+num_comment
+num_lines
+
+
+12
+
+
+1
+2
+20915
+
+
+2
+3
+8133
+
+
+3
+4
+2904
+
+
+4
+5
+3195
+
+
+5
+31
+2904
+
+
+31
+32
+290
+
+
+
+
+
+
+num_comment
+num_code
+
+
+12
+
+
+1
+2
+21206
+
+
+2
+3
+7552
+
+
+3
+4
+3485
+
+
+4
+5
+2904
+
+
+5
+31
+3195
+
+
+
+
+
+
+
+
+files
+176620
+
+
+id
+176620
+
+
+name
+176620
+
+
+simple
+145537
+
+
+ext
+1161
+
+
+fromSource
+580
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+id
+simple
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+id
+ext
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+id
+fromSource
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+name
+simple
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+name
+ext
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+name
+fromSource
+
+
+12
+
+
+1
+2
+176620
+
+
+
+
+
+
+simple
+id
+
+
+12
+
+
+1
+2
+114745
+
+
+2
+3
+30501
+
+
+3
+4
+290
+
+
+
+
+
+
+simple
+name
+
+
+12
+
+
+1
+2
+114745
+
+
+2
+3
+30501
+
+
+3
+4
+290
+
+
+
+
+
+
+simple
+ext
+
+
+12
+
+
+1
+2
+115035
+
+
+2
+3
+30501
+
+
+
+
+
+
+simple
+fromSource
+
+
+12
+
+
+1
+2
+145247
+
+
+2
+3
+290
+
+
+
+
+
+
+ext
+id
+
+
+12
+
+
+1
+2
+580
+
+
+105
+106
+290
+
+
+501
+502
+290
+
+
+
+
+
+
+ext
+name
+
+
+12
+
+
+1
+2
+580
+
+
+105
+106
+290
+
+
+501
+502
+290
+
+
+
+
+
+
+ext
+simple
+
+
+12
+
+
+1
+2
+580
+
+
+105
+106
+290
+
+
+499
+500
+290
+
+
+
+
+
+
+ext
+fromSource
+
+
+12
+
+
+1
+2
+871
+
+
+2
+3
+290
+
+
+
+
+
+
+fromSource
+id
+
+
+12
+
+
+218
+219
+290
+
+
+390
+391
+290
+
+
+
+
+
+
+fromSource
+name
+
+
+12
+
+
+218
+219
+290
+
+
+390
+391
+290
+
+
+
+
+
+
+fromSource
+simple
+
+
+12
+
+
+113
+114
+290
+
+
+389
+390
+290
+
+
+
+
+
+
+fromSource
+ext
+
+
+12
+
+
+1
+2
+290
+
+
+4
+5
+290
+
+
+
+
+
+
+
+
+folders
+19172
+
+
+id
+19172
+
+
+name
+19172
+
+
+simple
+13362
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+19172
+
+
+
+
+
+
+id
+simple
+
+
+12
+
+
+1
+2
+19172
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+19172
+
+
+
+
+
+
+name
+simple
+
+
+12
+
+
+1
+2
+19172
+
+
+
+
+
+
+simple
+id
+
+
+12
+
+
+1
+2
+8133
+
+
+2
+3
+4647
+
+
+3
+4
+580
+
+
+
+
+
+
+simple
+name
+
+
+12
+
+
+1
+2
+8133
+
+
+2
+3
+4647
+
+
+3
+4
+580
+
+
+
+
+
+
+
+
+containerparent
+195212
+
+
+parent
+19463
+
+
+child
+195212
+
+
+
+
+parent
+child
+
+
+12
+
+
+1
+2
+7552
+
+
+2
+4
+1742
+
+
+4
+5
+871
+
+
+5
+7
+1742
+
+
+7
+10
+1452
+
+
+10
+14
+1742
+
+
+18
+25
+1452
+
+
+25
+30
+1742
+
+
+34
+73
+1161
+
+
+
+
+
+
+child
+parent
+
+
+12
+
+
+1
+2
+195212
+
+
+
+
+
+
+
+
+cupackage
+176039
+
+
+id
+176039
+
+
+packageid
+10167
+
+
+
+
+id
+packageid
+
+
+12
+
+
+1
+2
+176039
+
+
+
+
+
+
+packageid
+id
+
+
+12
+
+
+1
+2
+1452
+
+
+3
+4
+871
+
+
+4
+5
+871
+
+
+5
+7
+871
+
+
+7
+8
+580
+
+
+9
+10
+580
+
+
+10
+11
+1161
+
+
+12
+21
+871
+
+
+21
+35
+871
+
+
+42
+48
+871
+
+
+50
+59
+871
+
+
+69
+70
+290
+
+
+
+
+
+
+
+
+jarManifestMain
+6737
+
+
+fileid
+453
+
+
+keyName
+533
+
+
+value
+3081
+
+
+
+
+fileid
+keyName
+
+
+12
+
+
+3
+5
+40
+
+
+5
+6
+30
+
+
+6
+11
+40
+
+
+12
+13
+110
+
+
+13
+14
+40
+
+
+15
+18
+30
+
+
+19
+21
+40
+
+
+21
+25
+40
+
+
+25
+26
+10
+
+
+26
+27
+50
+
+
+27
+29
+20
+
+
+
+
+
+
+fileid
+value
+
+
+12
+
+
+3
+5
+40
+
+
+5
+6
+30
+
+
+6
+9
+30
+
+
+9
+10
+100
+
+
+10
+11
+50
+
+
+11
+16
+40
+
+
+16
+17
+30
+
+
+18
+19
+50
+
+
+19
+21
+40
+
+
+21
+23
+40
+
+
+
+
+
+
+keyName
+fileid
+
+
+12
+
+
+1
+2
+140
+
+
+2
+3
+50
+
+
+3
+5
+30
+
+
+5
+7
+40
+
+
+7
+14
+40
+
+
+15
+16
+20
+
+
+16
+18
+40
+
+
+19
+20
+50
+
+
+24
+29
+40
+
+
+30
+31
+20
+
+
+31
+38
+40
+
+
+45
+46
+20
+
+
+
+
+
+
+keyName
+value
+
+
+12
+
+
+1
+2
+211
+
+
+2
+4
+40
+
+
+4
+5
+40
+
+
+5
+7
+40
+
+
+7
+9
+40
+
+
+10
+16
+40
+
+
+16
+18
+40
+
+
+19
+20
+40
+
+
+24
+30
+40
+
+
+
+
+
+
+value
+fileid
+
+
+12
+
+
+1
+2
+2447
+
+
+2
+3
+312
+
+
+3
+6
+231
+
+
+6
+46
+90
+
+
+
+
+
+
+value
+keyName
+
+
+12
+
+
+1
+2
+2457
+
+
+2
+3
+412
+
+
+3
+6
+211
+
+
+
+
+
+
+
+
+jarManifestEntries
+10748
+
+
+fileid
+290
+
+
+entryName
+10748
+
+
+keyName
+290
+
+
+value
+290
+
+
+
+
+fileid
+entryName
+
+
+12
+
+
+37
+38
+290
+
+
+
+
+
+
+fileid
+keyName
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+fileid
+value
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+entryName
+fileid
+
+
+12
+
+
+1
+2
+10748
+
+
+
+
+
+
+entryName
+keyName
+
+
+12
+
+
+1
+2
+10748
+
+
+
+
+
+
+entryName
+value
+
+
+12
+
+
+1
+2
+10748
+
+
+
+
+
+
+keyName
+fileid
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+keyName
+entryName
+
+
+12
+
+
+37
+38
+290
+
+
+
+
+
+
+keyName
+value
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+value
+fileid
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+value
+entryName
+
+
+12
+
+
+37
+38
+290
+
+
+
+
+
+
+value
+keyName
+
+
+12
+
+
+1
+2
+290
+
+
+
+
+
+
+
+
+packages
+10167
+
+
+id
+10167
+
+
+nodeName
+10167
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+10167
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+10167
+
+
+
+
+
+
+
+
+primitives
+2904
+
+
+id
+2904
+
+
+nodeName
+2904
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+2904
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+2904
+
+
+
+
+
+
+
+
+modifiers
+2904
+
+
+id
+2904
+
+
+nodeName
+2904
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+2904
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+2904
+
+
+
+
+
+
+
+
+classes
+232016
+
+
+id
+232016
+
+
+nodeName
+124693
+
+
+parentid
+1143
+
+
+sourceid
+54791
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+232016
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+232016
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+232016
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+110648
+
+
+2
+5
+10125
+
+
+5
+2207
+3919
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+123244
+
+
+2
+20
+1449
+
+
+
+
+
+
+nodeName
+sourceid
+
+
+12
+
+
+1
+2
+121778
+
+
+2
+2207
+2915
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+218
+
+
+2
+3
+87
+
+
+3
+4
+87
+
+
+4
+5
+69
+
+
+5
+7
+96
+
+
+7
+10
+96
+
+
+10
+12
+104
+
+
+12
+23
+87
+
+
+25
+44
+96
+
+
+47
+189
+87
+
+
+211
+2179
+87
+
+
+3173
+9683
+26
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+218
+
+
+2
+3
+87
+
+
+3
+4
+87
+
+
+4
+5
+69
+
+
+5
+7
+104
+
+
+7
+10
+87
+
+
+10
+12
+104
+
+
+12
+18
+87
+
+
+18
+29
+87
+
+
+39
+114
+87
+
+
+124
+585
+87
+
+
+1269
+4602
+34
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+218
+
+
+2
+3
+87
+
+
+3
+4
+96
+
+
+4
+5
+78
+
+
+5
+7
+104
+
+
+7
+9
+104
+
+
+9
+12
+104
+
+
+12
+23
+96
+
+
+23
+44
+87
+
+
+47
+123
+87
+
+
+169
+1844
+78
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+46516
+
+
+2
+5
+4879
+
+
+5
+1831
+3395
+
+
+
+
+
+
+sourceid
+nodeName
+
+
+12
+
+
+1
+2
+46516
+
+
+2
+4
+4827
+
+
+4
+1410
+3447
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+54791
+
+
+
+
+
+
+
+
+interfaces
+249736
+
+
+id
+249736
+
+
+nodeName
+69229
+
+
+parentid
+925
+
+
+sourceid
+5438
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+249736
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+249736
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+249736
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+57960
+
+
+2
+4
+5787
+
+
+4
+126
+5193
+
+
+130
+640
+288
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+68863
+
+
+2
+4
+366
+
+
+
+
+
+
+nodeName
+sourceid
+
+
+12
+
+
+1
+2
+68836
+
+
+2
+10
+392
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+253
+
+
+2
+3
+104
+
+
+3
+4
+96
+
+
+4
+5
+52
+
+
+5
+6
+69
+
+
+6
+9
+78
+
+
+10
+19
+69
+
+
+19
+38
+69
+
+
+42
+235
+69
+
+
+244
+15903
+61
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+253
+
+
+2
+3
+104
+
+
+3
+4
+96
+
+
+4
+5
+61
+
+
+5
+7
+78
+
+
+7
+11
+69
+
+
+12
+17
+78
+
+
+17
+38
+69
+
+
+38
+183
+69
+
+
+227
+4122
+43
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+270
+
+
+2
+3
+96
+
+
+3
+4
+104
+
+
+4
+5
+61
+
+
+5
+6
+87
+
+
+6
+8
+78
+
+
+8
+11
+69
+
+
+12
+15
+61
+
+
+15
+23
+69
+
+
+24
+38
+26
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+4242
+
+
+2
+9
+410
+
+
+9
+52
+410
+
+
+66
+5544
+375
+
+
+
+
+
+
+sourceid
+nodeName
+
+
+12
+
+
+1
+2
+4242
+
+
+2
+6
+445
+
+
+6
+20
+418
+
+
+21
+1202
+331
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+5438
+
+
+
+
+
+
+
+
+fielddecls
+99310
+
+
+id
+99310
+
+
+parentid
+15500
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+99310
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+4363
+
+
+2
+3
+2837
+
+
+3
+4
+1952
+
+
+4
+5
+1413
+
+
+5
+6
+962
+
+
+6
+8
+1179
+
+
+8
+13
+1335
+
+
+13
+41
+1173
+
+
+41
+1438
+286
+
+
+
+
+
+
+
+
+fieldDeclaredIn
+101480
+
+
+fieldId
+101480
+
+
+fieldDeclId
+99310
+
+
+pos
+205
+
+
+
+
+fieldId
+fieldDeclId
+
+
+12
+
+
+1
+2
+101480
+
+
+
+
+
+
+fieldId
+pos
+
+
+12
+
+
+1
+2
+101480
+
+
+
+
+
+
+fieldDeclId
+fieldId
+
+
+12
+
+
+1
+2
+98741
+
+
+2
+206
+569
+
+
+
+
+
+
+fieldDeclId
+pos
+
+
+12
+
+
+1
+2
+98741
+
+
+2
+206
+569
+
+
+
+
+
+
+pos
+fieldId
+
+
+12
+
+
+1
+2
+2
+
+
+2
+3
+78
+
+
+3
+4
+43
+
+
+4
+7
+15
+
+
+7
+8
+18
+
+
+8
+9
+18
+
+
+9
+20
+16
+
+
+23
+99311
+15
+
+
+
+
+
+
+pos
+fieldDeclId
+
+
+12
+
+
+1
+2
+2
+
+
+2
+3
+78
+
+
+3
+4
+43
+
+
+4
+7
+15
+
+
+7
+8
+18
+
+
+8
+9
+18
+
+
+9
+20
+16
+
+
+23
+99311
+15
+
+
+
+
+
+
+
+
+fields
+299208
+
+
+id
+299208
+
+
+nodeName
+210027
+
+
+typeid
+47060
+
+
+parentid
+93539
+
+
+sourceid
+299208
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+185044
+
+
+2
+4
+18591
+
+
+4
+110
+6390
+
+
+
+
+
+
+nodeName
+typeid
+
+
+12
+
+
+1
+2
+196374
+
+
+2
+8
+13653
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+185044
+
+
+2
+4
+18591
+
+
+4
+110
+6390
+
+
+
+
+
+
+nodeName
+sourceid
+
+
+12
+
+
+1
+2
+185044
+
+
+2
+4
+18591
+
+
+4
+110
+6390
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+28177
+
+
+2
+3
+5228
+
+
+3
+4
+4357
+
+
+4
+8
+3776
+
+
+8
+23
+3776
+
+
+30
+326
+1742
+
+
+
+
+
+
+typeid
+nodeName
+
+
+12
+
+
+1
+2
+29630
+
+
+2
+3
+5519
+
+
+3
+4
+3776
+
+
+4
+9
+4066
+
+
+9
+73
+3776
+
+
+179
+180
+290
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+35440
+
+
+2
+3
+3776
+
+
+3
+6
+4066
+
+
+6
+177
+3776
+
+
+
+
+
+
+typeid
+sourceid
+
+
+12
+
+
+1
+2
+28177
+
+
+2
+3
+5228
+
+
+3
+4
+4357
+
+
+4
+8
+3776
+
+
+8
+23
+3776
+
+
+30
+326
+1742
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+51417
+
+
+2
+3
+12781
+
+
+3
+4
+9876
+
+
+4
+7
+8424
+
+
+7
+12
+7552
+
+
+12
+67
+3485
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+51417
+
+
+2
+3
+12781
+
+
+3
+4
+9876
+
+
+4
+7
+8424
+
+
+7
+12
+7552
+
+
+12
+67
+3485
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+61875
+
+
+2
+3
+13943
+
+
+3
+4
+7843
+
+
+4
+6
+7552
+
+
+6
+9
+2323
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+51417
+
+
+2
+3
+12781
+
+
+3
+4
+9876
+
+
+4
+7
+8424
+
+
+7
+12
+7552
+
+
+12
+67
+3485
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+sourceid
+nodeName
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+sourceid
+typeid
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+299208
+
+
+
+
+
+
+
+
+constrs
+262025
+
+
+id
+262025
+
+
+nodeName
+120845
+
+
+signature
+257087
+
+
+typeid
+290
+
+
+parentid
+126945
+
+
+sourceid
+225713
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+262025
+
+
+
+
+
+
+id
+signature
+
+
+12
+
+
+1
+2
+262025
+
+
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+262025
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+262025
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+262025
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+60132
+
+
+2
+3
+30501
+
+
+3
+4
+8714
+
+
+4
+5
+11038
+
+
+5
+10
+9586
+
+
+10
+18
+871
+
+
+
+
+
+
+nodeName
+signature
+
+
+12
+
+
+1
+2
+61003
+
+
+2
+3
+31082
+
+
+3
+4
+8424
+
+
+4
+5
+10457
+
+
+5
+11
+9295
+
+
+12
+18
+580
+
+
+
+
+
+
+nodeName
+typeid
+
+
+12
+
+
+1
+2
+120845
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+118231
+
+
+2
+9
+2614
+
+
+
+
+
+
+nodeName
+sourceid
+
+
+12
+
+
+1
+2
+61003
+
+
+2
+3
+30792
+
+
+3
+4
+8424
+
+
+4
+5
+10748
+
+
+5
+11
+9295
+
+
+12
+18
+580
+
+
+
+
+
+
+signature
+id
+
+
+12
+
+
+1
+2
+255053
+
+
+2
+9
+2033
+
+
+
+
+
+
+signature
+nodeName
+
+
+12
+
+
+1
+2
+257087
+
+
+
+
+
+
+signature
+typeid
+
+
+12
+
+
+1
+2
+257087
+
+
+
+
+
+
+signature
+parentid
+
+
+12
+
+
+1
+2
+255053
+
+
+2
+9
+2033
+
+
+
+
+
+
+signature
+sourceid
+
+
+12
+
+
+1
+2
+256506
+
+
+2
+3
+580
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+902
+903
+290
+
+
+
+
+
+
+typeid
+nodeName
+
+
+12
+
+
+416
+417
+290
+
+
+
+
+
+
+typeid
+signature
+
+
+12
+
+
+885
+886
+290
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+437
+438
+290
+
+
+
+
+
+
+typeid
+sourceid
+
+
+12
+
+
+777
+778
+290
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+67394
+
+
+2
+3
+31082
+
+
+3
+4
+8424
+
+
+4
+5
+10457
+
+
+5
+18
+9586
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+126945
+
+
+
+
+
+
+parentid
+signature
+
+
+12
+
+
+1
+2
+67394
+
+
+2
+3
+31082
+
+
+3
+4
+8424
+
+
+4
+5
+10457
+
+
+5
+18
+9586
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+126945
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+67394
+
+
+2
+3
+31082
+
+
+3
+4
+8424
+
+
+4
+5
+10457
+
+
+5
+18
+9586
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+212641
+
+
+2
+18
+13072
+
+
+
+
+
+
+sourceid
+nodeName
+
+
+12
+
+
+1
+2
+212641
+
+
+2
+11
+13072
+
+
+
+
+
+
+sourceid
+signature
+
+
+12
+
+
+1
+2
+212641
+
+
+2
+11
+13072
+
+
+
+
+
+
+sourceid
+typeid
+
+
+12
+
+
+1
+2
+225713
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+212641
+
+
+2
+18
+13072
+
+
+
+
+
+
+
+
+methods
+2355907
+
+
+id
+2355907
+
+
+nodeName
+556296
+
+
+signature
+870610
+
+
+typeid
+189402
+
+
+parentid
+220485
+
+
+sourceid
+1516379
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+2355907
+
+
+
+
+
+
+id
+signature
+
+
+12
+
+
+1
+2
+2355907
+
+
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+2355907
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+2355907
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+2355907
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+325643
+
+
+2
+3
+97606
+
+
+3
+4
+31663
+
+
+4
+7
+46188
+
+
+7
+22
+41831
+
+
+22
+194
+13362
+
+
+
+
+
+
+nodeName
+signature
+
+
+12
+
+
+1
+2
+442713
+
+
+2
+3
+71171
+
+
+3
+34
+41831
+
+
+41
+51
+580
+
+
+
+
+
+
+nodeName
+typeid
+
+
+12
+
+
+1
+2
+480186
+
+
+2
+3
+36021
+
+
+3
+109
+40088
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+368637
+
+
+2
+3
+72914
+
+
+3
+5
+47641
+
+
+5
+9
+43864
+
+
+9
+177
+23239
+
+
+
+
+
+
+nodeName
+sourceid
+
+
+12
+
+
+1
+2
+350335
+
+
+2
+3
+105449
+
+
+3
+5
+50545
+
+
+5
+20
+42121
+
+
+20
+151
+7843
+
+
+
+
+
+
+signature
+id
+
+
+12
+
+
+1
+2
+640539
+
+
+2
+3
+102253
+
+
+3
+6
+73204
+
+
+6
+177
+54612
+
+
+
+
+
+
+signature
+nodeName
+
+
+12
+
+
+1
+2
+870610
+
+
+
+
+
+
+signature
+typeid
+
+
+12
+
+
+1
+2
+803797
+
+
+2
+19
+65651
+
+
+21
+109
+1161
+
+
+
+
+
+
+signature
+parentid
+
+
+12
+
+
+1
+2
+640539
+
+
+2
+3
+102253
+
+
+3
+6
+73204
+
+
+6
+177
+54612
+
+
+
+
+
+
+signature
+sourceid
+
+
+12
+
+
+1
+2
+665521
+
+
+2
+3
+113292
+
+
+3
+7
+69137
+
+
+7
+151
+22658
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+72042
+
+
+2
+3
+35440
+
+
+3
+4
+21496
+
+
+4
+5
+12491
+
+
+5
+7
+15396
+
+
+7
+12
+14524
+
+
+12
+39
+14234
+
+
+43
+1655
+3776
+
+
+
+
+
+
+typeid
+nodeName
+
+
+12
+
+
+1
+2
+109225
+
+
+2
+3
+33987
+
+
+3
+4
+17720
+
+
+4
+8
+14815
+
+
+8
+452
+13653
+
+
+
+
+
+
+typeid
+signature
+
+
+12
+
+
+1
+2
+98477
+
+
+2
+3
+38926
+
+
+3
+4
+18010
+
+
+4
+7
+14234
+
+
+7
+21
+14234
+
+
+23
+719
+5519
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+93248
+
+
+2
+3
+43574
+
+
+3
+4
+22658
+
+
+4
+7
+14234
+
+
+7
+88
+14234
+
+
+94
+435
+1452
+
+
+
+
+
+
+typeid
+sourceid
+
+
+12
+
+
+1
+2
+75237
+
+
+2
+3
+36602
+
+
+3
+4
+22658
+
+
+4
+6
+17139
+
+
+6
+9
+17429
+
+
+9
+22
+14234
+
+
+25
+1341
+6100
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+66523
+
+
+2
+3
+12491
+
+
+3
+4
+14815
+
+
+4
+6
+16558
+
+
+6
+9
+18882
+
+
+9
+12
+18591
+
+
+12
+15
+15977
+
+
+15
+17
+19753
+
+
+17
+28
+17429
+
+
+28
+68
+18010
+
+
+70
+125
+1452
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+68847
+
+
+2
+3
+15396
+
+
+3
+4
+14524
+
+
+4
+6
+16848
+
+
+6
+8
+15105
+
+
+8
+10
+12781
+
+
+10
+14
+19172
+
+
+14
+15
+19753
+
+
+15
+22
+16558
+
+
+22
+43
+16558
+
+
+45
+74
+4938
+
+
+
+
+
+
+parentid
+signature
+
+
+12
+
+
+1
+2
+66523
+
+
+2
+3
+12491
+
+
+3
+4
+14815
+
+
+4
+6
+16558
+
+
+6
+9
+18882
+
+
+9
+12
+18591
+
+
+12
+15
+15977
+
+
+15
+17
+19753
+
+
+17
+28
+17429
+
+
+28
+68
+18010
+
+
+70
+125
+1452
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+76980
+
+
+2
+3
+21787
+
+
+3
+4
+20334
+
+
+4
+5
+15105
+
+
+5
+6
+13943
+
+
+6
+7
+19172
+
+
+7
+8
+21787
+
+
+8
+10
+13653
+
+
+10
+33
+17429
+
+
+46
+47
+290
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+66523
+
+
+2
+3
+12491
+
+
+3
+4
+14815
+
+
+4
+6
+16558
+
+
+6
+9
+18882
+
+
+9
+12
+18591
+
+
+12
+15
+15977
+
+
+15
+17
+19753
+
+
+17
+28
+17429
+
+
+28
+68
+18010
+
+
+70
+125
+1452
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+1349054
+
+
+2
+8
+139437
+
+
+10
+35
+27887
+
+
+
+
+
+
+sourceid
+nodeName
+
+
+12
+
+
+1
+2
+1516379
+
+
+
+
+
+
+sourceid
+signature
+
+
+12
+
+
+1
+2
+1488201
+
+
+2
+34
+28177
+
+
+
+
+
+
+sourceid
+typeid
+
+
+12
+
+
+1
+2
+1447822
+
+
+2
+32
+68556
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+1349054
+
+
+2
+8
+139437
+
+
+10
+35
+27887
+
+
+
+
+
+
+
+
+params
+2259753
+
+
+id
+2259753
+
+
+typeid
+144956
+
+
+pos
+3195
+
+
+parentid
+1514636
+
+
+sourceid
+1680508
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+2259753
+
+
+
+
+
+
+id
+pos
+
+
+12
+
+
+1
+2
+2259753
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+2259753
+
+
+
+
+
+
+id
+sourceid
+
+
+12
+
+
+1
+2
+2259753
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+63618
+
+
+2
+3
+20915
+
+
+3
+4
+13072
+
+
+4
+5
+8133
+
+
+5
+8
+12491
+
+
+8
+15
+11619
+
+
+15
+72
+11038
+
+
+72
+1788
+4066
+
+
+
+
+
+
+typeid
+pos
+
+
+12
+
+
+1
+2
+105158
+
+
+2
+3
+26434
+
+
+3
+5
+11329
+
+
+5
+11
+2033
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+64489
+
+
+2
+3
+20334
+
+
+3
+4
+13072
+
+
+4
+5
+8424
+
+
+5
+8
+12491
+
+
+8
+15
+11038
+
+
+15
+65
+11038
+
+
+71
+1305
+4066
+
+
+
+
+
+
+typeid
+sourceid
+
+
+12
+
+
+1
+2
+68847
+
+
+2
+3
+19172
+
+
+3
+4
+13072
+
+
+4
+6
+13362
+
+
+6
+11
+12491
+
+
+11
+37
+11038
+
+
+40
+1331
+6971
+
+
+
+
+
+
+pos
+id
+
+
+12
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+10
+11
+290
+
+
+18
+19
+290
+
+
+32
+33
+290
+
+
+61
+62
+290
+
+
+176
+177
+290
+
+
+562
+563
+290
+
+
+1695
+1696
+290
+
+
+5214
+5215
+290
+
+
+
+
+
+
+pos
+typeid
+
+
+12
+
+
+1
+2
+580
+
+
+3
+4
+871
+
+
+4
+5
+290
+
+
+22
+23
+290
+
+
+31
+32
+290
+
+
+67
+68
+290
+
+
+198
+199
+290
+
+
+386
+387
+290
+
+
+
+
+
+
+pos
+parentid
+
+
+12
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+10
+11
+290
+
+
+18
+19
+290
+
+
+32
+33
+290
+
+
+61
+62
+290
+
+
+176
+177
+290
+
+
+562
+563
+290
+
+
+1695
+1696
+290
+
+
+5214
+5215
+290
+
+
+
+
+
+
+pos
+sourceid
+
+
+12
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+10
+11
+290
+
+
+18
+19
+290
+
+
+32
+33
+290
+
+
+61
+62
+290
+
+
+173
+174
+290
+
+
+544
+545
+290
+
+
+1392
+1393
+290
+
+
+3544
+3545
+290
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+1022248
+
+
+2
+3
+329129
+
+
+3
+4
+112130
+
+
+4
+12
+51126
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+1107944
+
+
+2
+3
+332034
+
+
+3
+7
+74656
+
+
+
+
+
+
+parentid
+pos
+
+
+12
+
+
+1
+2
+1022248
+
+
+2
+3
+329129
+
+
+3
+4
+112130
+
+
+4
+12
+51126
+
+
+
+
+
+
+parentid
+sourceid
+
+
+12
+
+
+1
+2
+1022248
+
+
+2
+3
+329129
+
+
+3
+4
+112130
+
+
+4
+12
+51126
+
+
+
+
+
+
+sourceid
+id
+
+
+12
+
+
+1
+2
+1558791
+
+
+2
+35
+121717
+
+
+
+
+
+
+sourceid
+typeid
+
+
+12
+
+
+1
+2
+1631705
+
+
+2
+35
+48803
+
+
+
+
+
+
+sourceid
+pos
+
+
+12
+
+
+1
+2
+1680508
+
+
+
+
+
+
+sourceid
+parentid
+
+
+12
+
+
+1
+2
+1558791
+
+
+2
+35
+121717
+
+
+
+
+
+
+
+
+paramName
+542282
+
+
+id
+542282
+
+
+nodeName
+7603
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+542282
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+3528
+
+
+2
+3
+1325
+
+
+3
+4
+594
+
+
+4
+6
+651
+
+
+6
+11
+653
+
+
+11
+39
+571
+
+
+39
+300389
+281
+
+
+
+
+
+
+
+
+isVarargsParam
+34392
+
+
+param
+34392
+
+
+
+
+
+exceptions
+510688
+
+
+id
+510688
+
+
+typeid
+17139
+
+
+parentid
+443874
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+510688
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+510688
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+2614
+
+
+2
+3
+4066
+
+
+3
+4
+1452
+
+
+4
+5
+1742
+
+
+5
+9
+1452
+
+
+9
+11
+1161
+
+
+13
+20
+1452
+
+
+24
+29
+1161
+
+
+33
+100
+1452
+
+
+120
+1040
+580
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+2614
+
+
+2
+3
+4066
+
+
+3
+4
+1452
+
+
+4
+5
+1742
+
+
+5
+9
+1452
+
+
+9
+11
+1161
+
+
+13
+20
+1452
+
+
+24
+29
+1161
+
+
+33
+100
+1452
+
+
+120
+1040
+580
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+386938
+
+
+2
+3
+49093
+
+
+3
+6
+7843
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+386938
+
+
+2
+3
+49093
+
+
+3
+6
+7843
+
+
+
+
+
+
+
+
+isAnnotType
+2323
+
+
+interfaceid
+2323
+
+
+
+
+
+isAnnotElem
+871
+
+
+methodid
+871
+
+
+
+
+
+annotValue
+1405734
+
+
+parentid
+155253
+
+
+id2
+261
+
+
+value
+1405734
+
+
+
+
+parentid
+id2
+
+
+12
+
+
+1
+2
+37290
+
+
+2
+3
+117963
+
+
+
+
+
+
+parentid
+value
+
+
+12
+
+
+1
+2
+18391
+
+
+2
+3
+66148
+
+
+3
+4
+12412
+
+
+4
+5
+19675
+
+
+5
+6
+17
+
+
+6
+7
+15546
+
+
+7
+23
+12386
+
+
+24
+1297
+10675
+
+
+
+
+
+
+id2
+parentid
+
+
+12
+
+
+1
+2
+26
+
+
+2
+3
+17
+
+
+3
+4
+26
+
+
+4
+5
+17
+
+
+8
+9
+34
+
+
+30
+49
+17
+
+
+51
+52
+17
+
+
+79
+85
+17
+
+
+252
+253
+17
+
+
+296
+297
+17
+
+
+464
+465
+17
+
+
+1184
+2842
+17
+
+
+12426
+12427
+17
+
+
+
+
+
+
+id2
+value
+
+
+12
+
+
+1
+2
+26
+
+
+3
+4
+43
+
+
+5
+8
+17
+
+
+8
+9
+8
+
+
+12
+13
+17
+
+
+16
+39
+17
+
+
+60
+70
+17
+
+
+84
+94
+17
+
+
+219
+300
+17
+
+
+373
+474
+17
+
+
+531
+664
+17
+
+
+729
+3035
+17
+
+
+3805
+75171
+17
+
+
+75324
+75325
+8
+
+
+
+
+
+
+value
+parentid
+
+
+12
+
+
+1
+2
+1405734
+
+
+
+
+
+
+value
+id2
+
+
+12
+
+
+1
+2
+1405734
+
+
+
+
+
+
+
+
+isEnumType
+2749
+
+
+classid
+2749
+
+
+
+
+
+isEnumConst
+24624
+
+
+fieldid
+24624
+
+
+
+
+
+typeVars
+58098
+
+
+id
+58098
+
+
+nodeName
+2033
+
+
+pos
+580
+
+
+kind
+290
+
+
+parentid
+49674
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+58098
+
+
+
+
+
+
+id
+pos
+
+
+12
+
+
+1
+2
+58098
+
+
+
+
+
+
+id
+kind
+
+
+12
+
+
+1
+2
+58098
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+58098
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+290
+
+
+3
+4
+290
+
+
+8
+9
+290
+
+
+27
+28
+290
+
+
+28
+29
+290
+
+
+31
+32
+290
+
+
+102
+103
+290
+
+
+
+
+
+
+nodeName
+pos
+
+
+12
+
+
+1
+2
+1161
+
+
+2
+3
+871
+
+
+
+
+
+
+nodeName
+kind
+
+
+12
+
+
+1
+2
+2033
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+290
+
+
+3
+4
+290
+
+
+8
+9
+290
+
+
+27
+28
+290
+
+
+28
+29
+290
+
+
+31
+32
+290
+
+
+102
+103
+290
+
+
+
+
+
+
+pos
+id
+
+
+12
+
+
+29
+30
+290
+
+
+171
+172
+290
+
+
+
+
+
+
+pos
+nodeName
+
+
+12
+
+
+3
+4
+290
+
+
+7
+8
+290
+
+
+
+
+
+
+pos
+kind
+
+
+12
+
+
+1
+2
+580
+
+
+
+
+
+
+pos
+parentid
+
+
+12
+
+
+29
+30
+290
+
+
+171
+172
+290
+
+
+
+
+
+
+kind
+id
+
+
+12
+
+
+200
+201
+290
+
+
+
+
+
+
+kind
+nodeName
+
+
+12
+
+
+7
+8
+290
+
+
+
+
+
+
+kind
+pos
+
+
+12
+
+
+2
+3
+290
+
+
+
+
+
+
+kind
+parentid
+
+
+12
+
+
+171
+172
+290
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+41250
+
+
+2
+3
+8424
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+41250
+
+
+2
+3
+8424
+
+
+
+
+
+
+parentid
+pos
+
+
+12
+
+
+1
+2
+41250
+
+
+2
+3
+8424
+
+
+
+
+
+
+parentid
+kind
+
+
+12
+
+
+1
+2
+49674
+
+
+
+
+
+
+
+
+wildcards
+39393
+
+
+id
+39393
+
+
+nodeName
+10919
+
+
+kind
+17
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+39393
+
+
+
+
+
+
+id
+kind
+
+
+12
+
+
+1
+2
+39393
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+9549
+
+
+2
+5
+846
+
+
+5
+541
+523
+
+
+
+
+
+
+nodeName
+kind
+
+
+12
+
+
+1
+2
+10919
+
+
+
+
+
+
+kind
+id
+
+
+12
+
+
+1361
+1362
+8
+
+
+3152
+3153
+8
+
+
+
+
+
+
+kind
+nodeName
+
+
+12
+
+
+476
+477
+8
+
+
+775
+776
+8
+
+
+
+
+
+
+
+
+typeBounds
+45015
+
+
+id
+45015
+
+
+typeid
+33973
+
+
+pos
+34
+
+
+parentid
+44666
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+45015
+
+
+
+
+
+
+id
+pos
+
+
+12
+
+
+1
+2
+45015
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+45015
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+25733
+
+
+2
+3
+7751
+
+
+3
+67
+488
+
+
+
+
+
+
+typeid
+pos
+
+
+12
+
+
+1
+2
+33851
+
+
+2
+4
+122
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+25733
+
+
+2
+3
+7751
+
+
+3
+67
+488
+
+
+
+
+
+
+pos
+id
+
+
+12
+
+
+1
+2
+8
+
+
+11
+12
+8
+
+
+28
+29
+8
+
+
+5117
+5118
+8
+
+
+
+
+
+
+pos
+typeid
+
+
+12
+
+
+1
+2
+8
+
+
+10
+11
+8
+
+
+15
+16
+8
+
+
+3883
+3884
+8
+
+
+
+
+
+
+pos
+parentid
+
+
+12
+
+
+1
+2
+8
+
+
+11
+12
+8
+
+
+28
+29
+8
+
+
+5117
+5118
+8
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+44421
+
+
+2
+5
+244
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+44421
+
+
+2
+5
+244
+
+
+
+
+
+
+parentid
+pos
+
+
+12
+
+
+1
+2
+44421
+
+
+2
+5
+244
+
+
+
+
+
+
+
+
+typeArgs
+533376
+
+
+argumentid
+112350
+
+
+pos
+34
+
+
+parentid
+412488
+
+
+
+
+argumentid
+pos
+
+
+12
+
+
+1
+2
+90659
+
+
+2
+3
+20216
+
+
+3
+5
+1475
+
+
+
+
+
+
+argumentid
+parentid
+
+
+12
+
+
+1
+2
+33545
+
+
+2
+3
+20984
+
+
+3
+4
+17222
+
+
+4
+5
+9383
+
+
+5
+7
+8283
+
+
+7
+9
+5900
+
+
+9
+12
+9060
+
+
+12
+1286
+7969
+
+
+
+
+
+
+pos
+argumentid
+
+
+12
+
+
+83
+84
+8
+
+
+386
+387
+8
+
+
+3736
+3737
+8
+
+
+11327
+11328
+8
+
+
+
+
+
+
+pos
+parentid
+
+
+12
+
+
+115
+116
+8
+
+
+1167
+1168
+8
+
+
+12567
+12568
+8
+
+
+47255
+47256
+8
+
+
+
+
+
+
+parentid
+argumentid
+
+
+12
+
+
+1
+2
+309826
+
+
+2
+3
+93391
+
+
+3
+5
+9270
+
+
+
+
+
+
+parentid
+pos
+
+
+12
+
+
+1
+2
+302791
+
+
+2
+3
+99510
+
+
+3
+5
+10186
+
+
+
+
+
+
+
+
+isParameterized
+412488
+
+
+memberid
+412488
+
+
+
+
+
+isRaw
+13653
+
+
+memberid
+13653
+
+
+
+
+
+erasure
+421523
+
+
+memberid
+421523
+
+
+erasureid
+9470
+
+
+
+
+memberid
+erasureid
+
+
+12
+
+
+1
+2
+421523
+
+
+
+
+
+
+erasureid
+memberid
+
+
+12
+
+
+1
+2
+3317
+
+
+2
+3
+977
+
+
+3
+4
+846
+
+
+4
+5
+663
+
+
+5
+7
+689
+
+
+7
+11
+733
+
+
+11
+20
+733
+
+
+20
+65
+715
+
+
+65
+1025
+715
+
+
+1073
+5543
+78
+
+
+
+
+
+
+
+
+isAnonymClass
+33159
+
+
+classid
+33159
+
+
+parent
+33159
+
+
+
+
+classid
+parent
+
+
+12
+
+
+1
+2
+33159
+
+
+
+
+
+
+parent
+classid
+
+
+12
+
+
+1
+2
+33159
+
+
+
+
+
+
+
+
+isLocalClass
+349
+
+
+classid
+349
+
+
+parent
+349
+
+
+
+
+classid
+parent
+
+
+12
+
+
+1
+2
+349
+
+
+
+
+
+
+parent
+classid
+
+
+12
+
+
+1
+2
+349
+
+
+
+
+
+
+
+
+isDefConstr
+43941
+
+
+constructorid
+43941
+
+
+
+
+
+lambdaKind
+267
+
+
+exprId
+267
+
+
+bodyKind
+2
+
+
+
+
+exprId
+bodyKind
+
+
+12
+
+
+1
+2
+267
+
+
+
+
+
+
+bodyKind
+exprId
+
+
+12
+
+
+113
+114
+1
+
+
+154
+155
+1
+
+
+
+
+
+
+
+
+arrays
+31082
+
+
+id
+31082
+
+
+nodeName
+24401
+
+
+elementtypeid
+30501
+
+
+dimension
+580
+
+
+componenttypeid
+31082
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+id
+elementtypeid
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+id
+dimension
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+id
+componenttypeid
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+22949
+
+
+2
+21
+1452
+
+
+
+
+
+
+nodeName
+elementtypeid
+
+
+12
+
+
+1
+2
+22949
+
+
+2
+21
+1452
+
+
+
+
+
+
+nodeName
+dimension
+
+
+12
+
+
+1
+2
+24401
+
+
+
+
+
+
+nodeName
+componenttypeid
+
+
+12
+
+
+1
+2
+22949
+
+
+2
+21
+1452
+
+
+
+
+
+
+elementtypeid
+id
+
+
+12
+
+
+1
+2
+29920
+
+
+2
+3
+580
+
+
+
+
+
+
+elementtypeid
+nodeName
+
+
+12
+
+
+1
+2
+29920
+
+
+2
+3
+580
+
+
+
+
+
+
+elementtypeid
+dimension
+
+
+12
+
+
+1
+2
+29920
+
+
+2
+3
+580
+
+
+
+
+
+
+elementtypeid
+componenttypeid
+
+
+12
+
+
+1
+2
+29920
+
+
+2
+3
+580
+
+
+
+
+
+
+dimension
+id
+
+
+12
+
+
+2
+3
+290
+
+
+105
+106
+290
+
+
+
+
+
+
+dimension
+nodeName
+
+
+12
+
+
+2
+3
+290
+
+
+82
+83
+290
+
+
+
+
+
+
+dimension
+elementtypeid
+
+
+12
+
+
+2
+3
+290
+
+
+105
+106
+290
+
+
+
+
+
+
+dimension
+componenttypeid
+
+
+12
+
+
+2
+3
+290
+
+
+105
+106
+290
+
+
+
+
+
+
+componenttypeid
+id
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+componenttypeid
+nodeName
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+componenttypeid
+elementtypeid
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+componenttypeid
+dimension
+
+
+12
+
+
+1
+2
+31082
+
+
+
+
+
+
+
+
+enclInReftype
+117415
+
+
+child
+117415
+
+
+parent
+7223
+
+
+
+
+child
+parent
+
+
+12
+
+
+1
+2
+117415
+
+
+
+
+
+
+parent
+child
+
+
+12
+
+
+1
+2
+3113
+
+
+2
+3
+2353
+
+
+3
+4
+525
+
+
+4
+7
+638
+
+
+7
+49
+543
+
+
+49
+38837
+47
+
+
+
+
+
+
+
+
+extendsReftype
+499769
+
+
+id1
+495597
+
+
+id2
+199291
+
+
+
+
+id1
+id2
+
+
+12
+
+
+1
+2
+491791
+
+
+2
+23
+3805
+
+
+
+
+
+
+id2
+id1
+
+
+12
+
+
+1
+2
+178219
+
+
+2
+3
+16611
+
+
+3
+27640
+4460
+
+
+
+
+
+
+
+
+implInterface
+254182
+
+
+id1
+124621
+
+
+id2
+45607
+
+
+
+
+id1
+id2
+
+
+12
+
+
+1
+2
+49093
+
+
+2
+3
+45898
+
+
+3
+4
+5228
+
+
+4
+5
+24401
+
+
+
+
+
+
+id2
+id1
+
+
+12
+
+
+1
+2
+31082
+
+
+2
+3
+9295
+
+
+3
+15
+3485
+
+
+17
+281
+1742
+
+
+
+
+
+
+
+
+hasModifier
+5667539
+
+
+id1
+3800824
+
+
+id2
+2904
+
+
+
+
+id1
+id2
+
+
+12
+
+
+1
+2
+2090395
+
+
+2
+3
+1554143
+
+
+3
+4
+156285
+
+
+
+
+
+
+id2
+id1
+
+
+12
+
+
+10
+11
+290
+
+
+27
+28
+290
+
+
+145
+146
+290
+
+
+328
+329
+290
+
+
+334
+335
+290
+
+
+420
+421
+290
+
+
+1916
+1917
+290
+
+
+3235
+3236
+290
+
+
+3317
+3318
+290
+
+
+9778
+9779
+290
+
+
+
+
+
+
+
+
+imports
+157518
+
+
+id
+157518
+
+
+holder
+11495
+
+
+name
+1327
+
+
+kind
+10
+
+
+
+
+id
+holder
+
+
+12
+
+
+1
+2
+157518
+
+
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+157518
+
+
+
+
+
+
+id
+kind
+
+
+12
+
+
+1
+2
+157518
+
+
+
+
+
+
+holder
+id
+
+
+12
+
+
+1
+2
+3483
+
+
+2
+3
+2377
+
+
+3
+4
+1058
+
+
+4
+5
+784
+
+
+5
+7
+823
+
+
+7
+11
+903
+
+
+11
+20
+912
+
+
+20
+83
+867
+
+
+83
+2609
+284
+
+
+
+
+
+
+holder
+name
+
+
+12
+
+
+1
+2
+11286
+
+
+2
+222
+208
+
+
+
+
+
+
+holder
+kind
+
+
+12
+
+
+1
+2
+11240
+
+
+2
+4
+254
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+782
+
+
+2
+3
+217
+
+
+3
+4
+82
+
+
+4
+7
+115
+
+
+7
+36
+104
+
+
+56
+68984
+26
+
+
+
+
+
+
+name
+holder
+
+
+12
+
+
+1
+2
+1297
+
+
+2
+5272
+30
+
+
+
+
+
+
+name
+kind
+
+
+12
+
+
+1
+2
+1325
+
+
+4
+5
+2
+
+
+
+
+
+
+kind
+id
+
+
+12
+
+
+11
+12
+2
+
+
+446
+447
+2
+
+
+525
+526
+2
+
+
+3506
+3507
+2
+
+
+68001
+68002
+2
+
+
+
+
+
+
+kind
+holder
+
+
+12
+
+
+11
+12
+2
+
+
+58
+59
+2
+
+
+62
+63
+2
+
+
+111
+112
+2
+
+
+5189
+5190
+2
+
+
+
+
+
+
+kind
+name
+
+
+12
+
+
+1
+2
+8
+
+
+610
+611
+2
+
+
+
+
+
+
+
+
+stmts
+1943115
+
+
+id
+1943115
+
+
+kind
+5809
+
+
+parent
+1314195
+
+
+idx
+6100
+
+
+bodydecl
+346559
+
+
+
+
+id
+kind
+
+
+12
+
+
+1
+2
+1943115
+
+
+
+
+
+
+id
+parent
+
+
+12
+
+
+1
+2
+1943115
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+1943115
+
+
+
+
+
+
+id
+bodydecl
+
+
+12
+
+
+1
+2
+1943115
+
+
+
+
+
+
+kind
+id
+
+
+12
+
+
+2
+3
+580
+
+
+3
+4
+290
+
+
+4
+5
+580
+
+
+8
+9
+290
+
+
+29
+30
+290
+
+
+41
+42
+290
+
+
+51
+52
+580
+
+
+72
+73
+290
+
+
+87
+88
+290
+
+
+97
+98
+290
+
+
+141
+142
+290
+
+
+282
+283
+290
+
+
+530
+531
+290
+
+
+728
+729
+290
+
+
+746
+747
+290
+
+
+1461
+1462
+290
+
+
+2350
+2351
+290
+
+
+
+
+
+
+kind
+parent
+
+
+12
+
+
+2
+3
+1161
+
+
+4
+5
+580
+
+
+28
+29
+290
+
+
+38
+39
+290
+
+
+49
+50
+290
+
+
+50
+51
+290
+
+
+67
+68
+290
+
+
+86
+87
+290
+
+
+97
+98
+290
+
+
+141
+142
+290
+
+
+281
+282
+290
+
+
+354
+355
+290
+
+
+515
+516
+290
+
+
+744
+745
+290
+
+
+903
+904
+290
+
+
+2232
+2233
+290
+
+
+
+
+
+
+kind
+idx
+
+
+12
+
+
+1
+2
+871
+
+
+2
+3
+580
+
+
+3
+4
+580
+
+
+4
+5
+290
+
+
+5
+6
+290
+
+
+6
+7
+871
+
+
+8
+9
+871
+
+
+10
+11
+290
+
+
+12
+13
+290
+
+
+14
+15
+290
+
+
+15
+16
+290
+
+
+19
+20
+290
+
+
+
+
+
+
+kind
+bodydecl
+
+
+12
+
+
+2
+3
+1161
+
+
+4
+5
+580
+
+
+23
+24
+290
+
+
+35
+36
+290
+
+
+48
+49
+290
+
+
+49
+50
+290
+
+
+63
+64
+290
+
+
+83
+84
+290
+
+
+97
+98
+290
+
+
+141
+142
+290
+
+
+180
+181
+290
+
+
+272
+273
+290
+
+
+343
+344
+290
+
+
+548
+549
+290
+
+
+587
+588
+290
+
+
+1193
+1194
+290
+
+
+
+
+
+
+parent
+id
+
+
+12
+
+
+1
+2
+1029801
+
+
+2
+3
+152799
+
+
+3
+6
+101382
+
+
+6
+20
+30211
+
+
+
+
+
+
+parent
+kind
+
+
+12
+
+
+1
+2
+1104458
+
+
+2
+3
+130431
+
+
+3
+7
+79304
+
+
+
+
+
+
+parent
+idx
+
+
+12
+
+
+1
+2
+1029801
+
+
+2
+3
+152799
+
+
+3
+6
+101382
+
+
+6
+20
+30211
+
+
+
+
+
+
+parent
+bodydecl
+
+
+12
+
+
+1
+2
+1314195
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+1
+2
+290
+
+
+2
+3
+580
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+7
+8
+290
+
+
+8
+9
+290
+
+
+10
+11
+290
+
+
+16
+17
+290
+
+
+24
+25
+290
+
+
+27
+28
+290
+
+
+40
+41
+290
+
+
+66
+67
+290
+
+
+87
+88
+290
+
+
+104
+105
+290
+
+
+172
+173
+290
+
+
+266
+267
+290
+
+
+657
+658
+290
+
+
+1632
+1633
+290
+
+
+3557
+3558
+290
+
+
+
+
+
+
+idx
+kind
+
+
+12
+
+
+1
+2
+871
+
+
+2
+3
+1161
+
+
+3
+4
+290
+
+
+4
+5
+871
+
+
+6
+7
+290
+
+
+8
+9
+580
+
+
+9
+10
+290
+
+
+10
+11
+580
+
+
+11
+12
+290
+
+
+13
+14
+290
+
+
+15
+16
+290
+
+
+18
+19
+290
+
+
+
+
+
+
+idx
+parent
+
+
+12
+
+
+1
+2
+290
+
+
+2
+3
+580
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+7
+8
+290
+
+
+8
+9
+290
+
+
+10
+11
+290
+
+
+16
+17
+290
+
+
+24
+25
+290
+
+
+27
+28
+290
+
+
+40
+41
+290
+
+
+66
+67
+290
+
+
+87
+88
+290
+
+
+104
+105
+290
+
+
+172
+173
+290
+
+
+266
+267
+290
+
+
+657
+658
+290
+
+
+1632
+1633
+290
+
+
+3557
+3558
+290
+
+
+
+
+
+
+idx
+bodydecl
+
+
+12
+
+
+1
+2
+290
+
+
+2
+3
+580
+
+
+3
+4
+580
+
+
+5
+6
+290
+
+
+7
+8
+290
+
+
+8
+9
+290
+
+
+10
+11
+290
+
+
+16
+17
+290
+
+
+24
+25
+290
+
+
+26
+27
+290
+
+
+38
+39
+290
+
+
+60
+61
+290
+
+
+83
+84
+290
+
+
+96
+97
+290
+
+
+149
+150
+290
+
+
+221
+222
+290
+
+
+393
+394
+290
+
+
+572
+573
+290
+
+
+1193
+1194
+290
+
+
+
+
+
+
+bodydecl
+id
+
+
+12
+
+
+1
+2
+12781
+
+
+2
+3
+166743
+
+
+3
+4
+24982
+
+
+4
+5
+19753
+
+
+5
+7
+27887
+
+
+7
+9
+31663
+
+
+9
+13
+26144
+
+
+13
+25
+26725
+
+
+25
+76
+9876
+
+
+
+
+
+
+bodydecl
+kind
+
+
+12
+
+
+1
+2
+12781
+
+
+2
+3
+183882
+
+
+3
+4
+40669
+
+
+4
+5
+39507
+
+
+5
+6
+37764
+
+
+6
+9
+29920
+
+
+9
+11
+2033
+
+
+
+
+
+
+bodydecl
+parent
+
+
+12
+
+
+1
+2
+12781
+
+
+2
+3
+214965
+
+
+3
+4
+290
+
+
+4
+5
+38054
+
+
+5
+6
+11619
+
+
+6
+8
+29049
+
+
+8
+13
+27887
+
+
+13
+44
+11910
+
+
+
+
+
+
+bodydecl
+idx
+
+
+12
+
+
+1
+2
+179525
+
+
+2
+3
+42993
+
+
+3
+4
+52579
+
+
+4
+5
+23239
+
+
+5
+7
+29630
+
+
+7
+20
+18591
+
+
+
+
+
+
+
+
+exprs
+6159056
+
+
+id
+6159056
+
+
+kind
+69
+
+
+typeid
+47696
+
+
+parent
+3894745
+
+
+idx
+2325
+
+
+
+
+id
+kind
+
+
+12
+
+
+1
+2
+6159056
+
+
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+6159056
+
+
+
+
+
+
+id
+parent
+
+
+12
+
+
+1
+2
+6159056
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+6159056
+
+
+
+
+
+
+kind
+id
+
+
+12
+
+
+15
+117
+6
+
+
+134
+442
+6
+
+
+448
+1626
+6
+
+
+1792
+2822
+6
+
+
+3929
+6964
+6
+
+
+7591
+11712
+6
+
+
+11834
+18822
+6
+
+
+19519
+25978
+6
+
+
+29030
+52946
+6
+
+
+52986
+97216
+6
+
+
+111763
+584003
+6
+
+
+610778
+1745966
+3
+
+
+
+
+
+
+kind
+typeid
+
+
+12
+
+
+1
+2
+18
+
+
+2
+3
+6
+
+
+3
+4
+5
+
+
+4
+5
+9
+
+
+5
+7
+5
+
+
+7
+16
+6
+
+
+62
+1086
+6
+
+
+1114
+2969
+6
+
+
+5935
+20138
+6
+
+
+21098
+37152
+2
+
+
+
+
+
+
+kind
+parent
+
+
+12
+
+
+15
+96
+6
+
+
+134
+442
+6
+
+
+448
+1626
+6
+
+
+1792
+2766
+6
+
+
+3749
+6944
+6
+
+
+7591
+11539
+6
+
+
+11687
+17039
+6
+
+
+18739
+25549
+6
+
+
+26413
+47972
+6
+
+
+49648
+70156
+6
+
+
+94269
+340490
+6
+
+
+585438
+1334459
+3
+
+
+
+
+
+
+kind
+idx
+
+
+12
+
+
+1
+2
+7
+
+
+2
+3
+2
+
+
+3
+4
+9
+
+
+4
+5
+5
+
+
+5
+6
+3
+
+
+6
+7
+6
+
+
+7
+8
+5
+
+
+8
+15
+6
+
+
+15
+20
+6
+
+
+20
+28
+6
+
+
+34
+247
+6
+
+
+257
+1108
+6
+
+
+1306
+2307
+2
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+9879
+
+
+2
+3
+7196
+
+
+3
+4
+3500
+
+
+4
+6
+4344
+
+
+6
+8
+3305
+
+
+8
+12
+4097
+
+
+12
+19
+3832
+
+
+19
+34
+3741
+
+
+34
+74
+3580
+
+
+74
+531
+3580
+
+
+531
+1199212
+642
+
+
+
+
+
+
+typeid
+kind
+
+
+12
+
+
+1
+2
+15522
+
+
+2
+3
+10351
+
+
+3
+4
+5542
+
+
+4
+5
+5522
+
+
+5
+6
+4138
+
+
+6
+7
+2777
+
+
+7
+11
+3724
+
+
+11
+40
+120
+
+
+
+
+
+
+typeid
+parent
+
+
+12
+
+
+1
+2
+9896
+
+
+2
+3
+7217
+
+
+3
+4
+3750
+
+
+4
+5
+2709
+
+
+5
+7
+3924
+
+
+7
+10
+3772
+
+
+10
+15
+3689
+
+
+15
+25
+3658
+
+
+25
+49
+3641
+
+
+49
+161
+3583
+
+
+161
+797514
+1857
+
+
+
+
+
+
+typeid
+idx
+
+
+12
+
+
+1
+2
+16167
+
+
+2
+3
+11252
+
+
+3
+4
+9003
+
+
+4
+5
+8633
+
+
+5
+2311
+2641
+
+
+
+
+
+
+parent
+id
+
+
+12
+
+
+1
+2
+2288191
+
+
+2
+3
+1412581
+
+
+3
+2307
+193973
+
+
+
+
+
+
+parent
+kind
+
+
+12
+
+
+1
+2
+2752763
+
+
+2
+3
+1062190
+
+
+3
+10
+79792
+
+
+
+
+
+
+parent
+typeid
+
+
+12
+
+
+1
+2
+3158374
+
+
+2
+3
+634790
+
+
+3
+51
+101581
+
+
+
+
+
+
+parent
+idx
+
+
+12
+
+
+1
+2
+2304958
+
+
+2
+3
+1396496
+
+
+3
+2307
+193291
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+1
+6
+13
+
+
+8
+9
+258
+
+
+9
+10
+606
+
+
+10
+11
+156
+
+
+11
+12
+181
+
+
+12
+39
+175
+
+
+40
+89
+177
+
+
+89
+110
+178
+
+
+115
+161
+177
+
+
+161
+428
+176
+
+
+428
+956
+175
+
+
+960
+3458696
+53
+
+
+
+
+
+
+idx
+kind
+
+
+12
+
+
+1
+2
+1029
+
+
+2
+3
+186
+
+
+3
+4
+248
+
+
+4
+5
+322
+
+
+5
+6
+190
+
+
+6
+11
+212
+
+
+11
+68
+138
+
+
+
+
+
+
+idx
+typeid
+
+
+12
+
+
+1
+2
+1030
+
+
+2
+3
+185
+
+
+3
+4
+248
+
+
+4
+5
+317
+
+
+5
+7
+173
+
+
+7
+12
+190
+
+
+12
+531
+175
+
+
+895
+38165
+7
+
+
+
+
+
+
+idx
+parent
+
+
+12
+
+
+1
+6
+13
+
+
+8
+9
+258
+
+
+9
+10
+606
+
+
+10
+11
+156
+
+
+11
+12
+181
+
+
+12
+39
+175
+
+
+40
+89
+177
+
+
+89
+110
+178
+
+
+115
+161
+177
+
+
+161
+428
+176
+
+
+428
+956
+175
+
+
+960
+3440598
+53
+
+
+
+
+
+
+
+
+callableEnclosingExpr
+6035785
+
+
+id
+6035785
+
+
+callable_id
+198248
+
+
+
+
+id
+callable_id
+
+
+12
+
+
+1
+2
+6035785
+
+
+
+
+
+
+callable_id
+id
+
+
+12
+
+
+1
+3
+17107
+
+
+3
+4
+13539
+
+
+4
+5
+15836
+
+
+5
+6
+25856
+
+
+6
+7
+10704
+
+
+7
+8
+9775
+
+
+8
+10
+15054
+
+
+10
+13
+17742
+
+
+13
+18
+15249
+
+
+18
+26
+15934
+
+
+26
+42
+15103
+
+
+42
+89
+15103
+
+
+89
+2632
+11241
+
+
+
+
+
+
+
+
+statementEnclosingExpr
+5620332
+
+
+id
+5620332
+
+
+statement_id
+1192765
+
+
+
+
+id
+statement_id
+
+
+12
+
+
+1
+2
+5620332
+
+
+
+
+
+
+statement_id
+id
+
+
+12
+
+
+1
+2
+152574
+
+
+2
+3
+150707
+
+
+3
+4
+260769
+
+
+4
+5
+193260
+
+
+5
+6
+127526
+
+
+6
+7
+81653
+
+
+7
+8
+60015
+
+
+8
+11
+103626
+
+
+11
+3505
+62630
+
+
+
+
+
+
+
+
+callableBinding
+1437798
+
+
+callerid
+1437798
+
+
+callee
+227012
+
+
+
+
+callerid
+callee
+
+
+12
+
+
+1
+2
+1437798
+
+
+
+
+
+
+callee
+callerid
+
+
+12
+
+
+1
+2
+119131
+
+
+2
+3
+37668
+
+
+3
+4
+17900
+
+
+4
+6
+17854
+
+
+6
+13
+17467
+
+
+13
+1437
+16989
+
+
+
+
+
+
+
+
+memberRefBinding
+109
+
+
+id
+109
+
+
+callable
+85
+
+
+
+
+id
+callable
+
+
+12
+
+
+1
+2
+109
+
+
+
+
+
+
+callable
+id
+
+
+12
+
+
+1
+2
+73
+
+
+2
+3
+9
+
+
+3
+12
+3
+
+
+
+
+
+
+
+
+variableBinding
+2012792
+
+
+expr
+2012792
+
+
+variable
+473626
+
+
+
+
+expr
+variable
+
+
+12
+
+
+1
+2
+2012792
+
+
+
+
+
+
+variable
+expr
+
+
+12
+
+
+1
+2
+170241
+
+
+2
+3
+100150
+
+
+3
+4
+70384
+
+
+4
+5
+37929
+
+
+5
+7
+33090
+
+
+7
+14
+35680
+
+
+14
+464
+26149
+
+
+
+
+
+
+
+
+localvars
+269506
+
+
+id
+269506
+
+
+nodeName
+46158
+
+
+typeid
+14855
+
+
+parentid
+269506
+
+
+
+
+id
+nodeName
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+id
+typeid
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+nodeName
+id
+
+
+12
+
+
+1
+2
+27837
+
+
+2
+3
+7375
+
+
+3
+4
+3093
+
+
+4
+8
+3909
+
+
+8
+79
+3462
+
+
+79
+4879
+480
+
+
+
+
+
+
+nodeName
+typeid
+
+
+12
+
+
+1
+2
+38703
+
+
+2
+3
+3918
+
+
+3
+44
+3465
+
+
+44
+201
+71
+
+
+
+
+
+
+nodeName
+parentid
+
+
+12
+
+
+1
+2
+27837
+
+
+2
+3
+7375
+
+
+3
+4
+3093
+
+
+4
+8
+3909
+
+
+8
+79
+3462
+
+
+79
+4879
+480
+
+
+
+
+
+
+typeid
+id
+
+
+12
+
+
+1
+2
+6310
+
+
+2
+3
+2516
+
+
+3
+4
+1293
+
+
+4
+5
+819
+
+
+5
+8
+1284
+
+
+8
+16
+1151
+
+
+16
+77
+1116
+
+
+77
+18404
+363
+
+
+
+
+
+
+typeid
+nodeName
+
+
+12
+
+
+1
+2
+8820
+
+
+2
+3
+2556
+
+
+3
+4
+1067
+
+
+4
+7
+1228
+
+
+7
+105
+1116
+
+
+105
+3085
+65
+
+
+
+
+
+
+typeid
+parentid
+
+
+12
+
+
+1
+2
+6310
+
+
+2
+3
+2516
+
+
+3
+4
+1293
+
+
+4
+5
+819
+
+
+5
+8
+1284
+
+
+8
+16
+1151
+
+
+16
+77
+1116
+
+
+77
+18404
+363
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+parentid
+nodeName
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+parentid
+typeid
+
+
+12
+
+
+1
+2
+269506
+
+
+
+
+
+
+
+
+namestrings
+1876882
+
+
+name
+53884
+
+
+value
+51082
+
+
+parent
+1876882
+
+
+
+
+name
+value
+
+
+12
+
+
+1
+2
+53884
+
+
+
+
+
+
+name
+parent
+
+
+12
+
+
+1
+2
+35919
+
+
+2
+3
+7891
+
+
+3
+5
+4137
+
+
+5
+16
+4102
+
+
+16
+99498
+1833
+
+
+
+
+
+
+value
+name
+
+
+12
+
+
+1
+2
+49205
+
+
+2
+10
+1876
+
+
+
+
+
+
+value
+parent
+
+
+12
+
+
+1
+2
+34680
+
+
+2
+3
+7428
+
+
+3
+6
+4408
+
+
+6
+46
+3840
+
+
+47
+99499
+724
+
+
+
+
+
+
+parent
+name
+
+
+12
+
+
+1
+2
+1876882
+
+
+
+
+
+
+parent
+value
+
+
+12
+
+
+1
+2
+1876882
+
+
+
+
+
+
+
+
+modules
+72
+
+
+id
+72
+
+
+name
+72
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+72
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+72
+
+
+
+
+
+
+
+
+isOpen
+5
+
+
+id
+5
+
+
+
+
+
+cumodule
+30095
+
+
+fileId
+30095
+
+
+moduleId
+71
+
+
+
+
+fileId
+moduleId
+
+
+12
+
+
+1
+2
+30095
+
+
+
+
+
+
+moduleId
+fileId
+
+
+12
+
+
+2
+7
+6
+
+
+9
+14
+6
+
+
+14
+22
+6
+
+
+23
+33
+6
+
+
+34
+39
+6
+
+
+43
+59
+5
+
+
+65
+80
+6
+
+
+81
+131
+6
+
+
+149
+228
+6
+
+
+240
+471
+6
+
+
+540
+1680
+6
+
+
+1722
+5772
+6
+
+
+
+
+
+
+
+
+directives
+890
+
+
+id
+71
+
+
+directive
+890
+
+
+
+
+id
+directive
+
+
+12
+
+
+2
+3
+8
+
+
+3
+4
+9
+
+
+4
+5
+12
+
+
+5
+6
+6
+
+
+6
+7
+7
+
+
+7
+8
+3
+
+
+8
+10
+6
+
+
+10
+12
+5
+
+
+12
+22
+6
+
+
+23
+54
+6
+
+
+57
+138
+3
+
+
+
+
+
+
+directive
+id
+
+
+12
+
+
+1
+2
+890
+
+
+
+
+
+
+
+
+requires
+204
+
+
+id
+204
+
+
+target
+44
+
+
+
+
+id
+target
+
+
+12
+
+
+1
+2
+204
+
+
+
+
+
+
+target
+id
+
+
+12
+
+
+1
+2
+18
+
+
+2
+3
+9
+
+
+3
+4
+6
+
+
+4
+6
+3
+
+
+7
+9
+4
+
+
+10
+71
+4
+
+
+
+
+
+
+
+
+isTransitive
+33
+
+
+id
+33
+
+
+
+
+
+isStatic
+5
+
+
+id
+5
+
+
+
+
+
+exports
+495
+
+
+id
+495
+
+
+target
+495
+
+
+
+
+id
+target
+
+
+12
+
+
+1
+2
+495
+
+
+
+
+
+
+target
+id
+
+
+12
+
+
+1
+2
+495
+
+
+
+
+
+
+
+
+exportsTo
+365
+
+
+id
+220
+
+
+target
+57
+
+
+
+
+id
+target
+
+
+12
+
+
+1
+2
+152
+
+
+2
+3
+35
+
+
+3
+4
+21
+
+
+4
+22
+12
+
+
+
+
+
+
+target
+id
+
+
+12
+
+
+1
+2
+9
+
+
+2
+3
+15
+
+
+3
+4
+5
+
+
+4
+5
+5
+
+
+5
+6
+2
+
+
+6
+7
+4
+
+
+7
+8
+3
+
+
+8
+11
+4
+
+
+11
+15
+5
+
+
+17
+48
+5
+
+
+
+
+
+
+
+
+opens
+15
+
+
+id
+15
+
+
+target
+15
+
+
+
+
+id
+target
+
+
+12
+
+
+1
+2
+15
+
+
+
+
+
+
+target
+id
+
+
+12
+
+
+1
+2
+15
+
+
+
+
+
+
+
+
+opensTo
+15
+
+
+id
+13
+
+
+target
+7
+
+
+
+
+id
+target
+
+
+12
+
+
+1
+2
+12
+
+
+3
+4
+1
+
+
+
+
+
+
+target
+id
+
+
+12
+
+
+1
+2
+5
+
+
+2
+3
+1
+
+
+8
+9
+1
+
+
+
+
+
+
+
+
+uses
+108
+
+
+id
+108
+
+
+serviceInterface
+108
+
+
+
+
+id
+serviceInterface
+
+
+12
+
+
+1
+2
+108
+
+
+
+
+
+
+serviceInterface
+id
+
+
+12
+
+
+1
+2
+108
+
+
+
+
+
+
+
+
+provides
+68
+
+
+id
+68
+
+
+serviceInterface
+51
+
+
+
+
+id
+serviceInterface
+
+
+12
+
+
+1
+2
+68
+
+
+
+
+
+
+serviceInterface
+id
+
+
+12
+
+
+1
+2
+44
+
+
+2
+3
+4
+
+
+3
+9
+3
+
+
+
+
+
+
+
+
+providesWith
+287
+
+
+id
+68
+
+
+serviceImpl
+285
+
+
+
+
+id
+serviceImpl
+
+
+12
+
+
+1
+2
+46
+
+
+2
+3
+8
+
+
+3
+4
+3
+
+
+4
+6
+5
+
+
+6
+109
+6
+
+
+
+
+
+
+serviceImpl
+id
+
+
+12
+
+
+1
+2
+283
+
+
+2
+3
+2
+
+
+
+
+
+
+
+
+javadoc
+596965
+
+
+id
+596965
+
+
+
+
+
+isNormalComment
+318427
+
+
+commentid
+318427
+
+
+
+
+
+isEolComment
+279324
+
+
+commentid
+279324
+
+
+
+
+
+hasJavadoc
+471181
+
+
+documentableid
+420054
+
+
+javadocid
+470309
+
+
+
+
+documentableid
+javadocid
+
+
+12
+
+
+1
+2
+380837
+
+
+2
+4
+36021
+
+
+4
+6
+3195
+
+
+
+
+
+
+javadocid
+documentableid
+
+
+12
+
+
+1
+2
+469728
+
+
+2
+4
+580
+
+
+
+
+
+
+
+
+javadocTag
+1048974
+
+
+id
+1048974
+
+
+name
+2323
+
+
+parentid
+342202
+
+
+idx
+17429
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+1048974
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+1048974
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+1048974
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+12
+13
+290
+
+
+53
+54
+290
+
+
+92
+93
+290
+
+
+142
+143
+290
+
+
+383
+384
+290
+
+
+531
+532
+290
+
+
+847
+848
+290
+
+
+1551
+1552
+290
+
+
+
+
+
+
+name
+parentid
+
+
+12
+
+
+12
+13
+290
+
+
+53
+54
+290
+
+
+92
+93
+290
+
+
+111
+112
+290
+
+
+382
+383
+290
+
+
+531
+532
+290
+
+
+561
+562
+290
+
+
+836
+837
+290
+
+
+
+
+
+
+name
+idx
+
+
+12
+
+
+5
+6
+290
+
+
+16
+17
+290
+
+
+30
+31
+290
+
+
+32
+33
+290
+
+
+34
+35
+290
+
+
+35
+36
+290
+
+
+36
+37
+290
+
+
+41
+42
+290
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+83662
+
+
+2
+3
+80757
+
+
+3
+4
+58389
+
+
+4
+5
+41540
+
+
+5
+6
+33406
+
+
+6
+7
+24111
+
+
+7
+11
+20334
+
+
+
+
+
+
+parentid
+name
+
+
+12
+
+
+1
+2
+103125
+
+
+2
+3
+123169
+
+
+3
+4
+70880
+
+
+4
+5
+38345
+
+
+5
+6
+6681
+
+
+
+
+
+
+parentid
+idx
+
+
+12
+
+
+1
+2
+83662
+
+
+2
+3
+80757
+
+
+3
+4
+58389
+
+
+4
+5
+41540
+
+
+5
+6
+33406
+
+
+6
+7
+24111
+
+
+7
+11
+20334
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+1
+2
+5228
+
+
+2
+4
+1452
+
+
+4
+5
+1742
+
+
+5
+8
+1161
+
+
+8
+16
+1452
+
+
+18
+33
+1452
+
+
+36
+67
+1452
+
+
+73
+134
+1452
+
+
+184
+436
+1452
+
+
+522
+582
+580
+
+
+
+
+
+
+idx
+name
+
+
+12
+
+
+1
+2
+5519
+
+
+2
+3
+1742
+
+
+3
+4
+1742
+
+
+4
+5
+1161
+
+
+5
+6
+580
+
+
+6
+7
+2904
+
+
+7
+8
+2904
+
+
+8
+9
+871
+
+
+
+
+
+
+idx
+parentid
+
+
+12
+
+
+1
+2
+5228
+
+
+2
+4
+1452
+
+
+4
+5
+1742
+
+
+5
+8
+1161
+
+
+8
+16
+1452
+
+
+18
+33
+1452
+
+
+36
+67
+1452
+
+
+73
+134
+1452
+
+
+184
+436
+1452
+
+
+522
+582
+580
+
+
+
+
+
+
+
+
+javadocText
+3965535
+
+
+id
+3965535
+
+
+text
+1438817
+
+
+parentid
+1644487
+
+
+idx
+54903
+
+
+
+
+id
+text
+
+
+12
+
+
+1
+2
+3965535
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+3965535
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+3965535
+
+
+
+
+
+
+text
+id
+
+
+12
+
+
+1
+2
+974898
+
+
+2
+3
+244305
+
+
+3
+5
+123750
+
+
+5
+607
+95863
+
+
+
+
+
+
+text
+parentid
+
+
+12
+
+
+1
+2
+979836
+
+
+2
+3
+241110
+
+
+3
+5
+122588
+
+
+5
+486
+95282
+
+
+
+
+
+
+text
+idx
+
+
+12
+
+
+1
+2
+1352831
+
+
+2
+47
+85986
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+736983
+
+
+2
+3
+723620
+
+
+3
+12
+117940
+
+
+12
+190
+65942
+
+
+
+
+
+
+parentid
+text
+
+
+12
+
+
+1
+2
+736983
+
+
+2
+3
+723620
+
+
+3
+12
+122879
+
+
+12
+138
+61003
+
+
+
+
+
+
+parentid
+idx
+
+
+12
+
+
+1
+2
+736983
+
+
+2
+3
+723620
+
+
+3
+12
+117940
+
+
+12
+190
+65942
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+1
+2
+31663
+
+
+2
+4
+3776
+
+
+4
+5
+4357
+
+
+5
+10
+4647
+
+
+10
+41
+4357
+
+
+46
+346
+4357
+
+
+384
+5662
+1742
+
+
+
+
+
+
+idx
+text
+
+
+12
+
+
+1
+2
+31663
+
+
+2
+4
+4066
+
+
+4
+5
+4938
+
+
+5
+11
+4357
+
+
+12
+33
+4357
+
+
+33
+185
+4357
+
+
+228
+2094
+1161
+
+
+
+
+
+
+idx
+parentid
+
+
+12
+
+
+1
+2
+31663
+
+
+2
+4
+3776
+
+
+4
+5
+4357
+
+
+5
+10
+4647
+
+
+10
+41
+4357
+
+
+46
+346
+4357
+
+
+384
+5662
+1742
+
+
+
+
+
+
+
+
+xmlEncoding
+39724
+
+
+id
+39724
+
+
+encoding
+1
+
+
+
+
+id
+encoding
+
+
+12
+
+
+1
+2
+39724
+
+
+
+
+
+
+encoding
+id
+
+
+12
+
+
+39724
+39725
+1
+
+
+
+
+
+
+
+
+xmlDTDs
+1
+
+
+id
+1
+
+
+root
+1
+
+
+publicId
+1
+
+
+systemId
+1
+
+
+fileid
+1
+
+
+
+
+id
+root
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+id
+publicId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+id
+systemId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+root
+id
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+root
+publicId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+root
+systemId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+root
+fileid
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+publicId
+id
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+publicId
+root
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+publicId
+systemId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+publicId
+fileid
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+systemId
+id
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+systemId
+root
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+systemId
+publicId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+systemId
+fileid
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+fileid
+root
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+fileid
+publicId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+fileid
+systemId
+
+
+12
+
+
+1
+2
+1
+
+
+
+
+
+
+
+
+xmlElements
+1270313
+
+
+id
+1270313
+
+
+name
+4655
+
+
+parentid
+578021
+
+
+idx
+35122
+
+
+fileid
+39721
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+1270313
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+1270313
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+1270313
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+2
+1270313
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+420
+
+
+2
+5
+156
+
+
+5
+6
+3832
+
+
+6
+310317
+247
+
+
+
+
+
+
+name
+parentid
+
+
+12
+
+
+1
+2
+456
+
+
+2
+5
+150
+
+
+5
+6
+3829
+
+
+6
+161565
+220
+
+
+
+
+
+
+name
+idx
+
+
+12
+
+
+1
+2
+4358
+
+
+2
+35123
+297
+
+
+
+
+
+
+name
+fileid
+
+
+12
+
+
+1
+2
+486
+
+
+2
+5
+133
+
+
+5
+6
+3831
+
+
+6
+14503
+205
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+371969
+
+
+2
+3
+62095
+
+
+3
+4
+104113
+
+
+4
+35123
+39844
+
+
+
+
+
+
+parentid
+name
+
+
+12
+
+
+1
+2
+500482
+
+
+2
+3
+17866
+
+
+3
+4
+49117
+
+
+4
+45
+10556
+
+
+
+
+
+
+parentid
+idx
+
+
+12
+
+
+1
+2
+371969
+
+
+2
+3
+62095
+
+
+3
+4
+104113
+
+
+4
+35123
+39844
+
+
+
+
+
+
+parentid
+fileid
+
+
+12
+
+
+1
+2
+578021
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+2
+3
+606
+
+
+4
+5
+17851
+
+
+5
+6
+6533
+
+
+6
+7
+859
+
+
+7
+8
+4471
+
+
+9
+16
+2719
+
+
+16
+578022
+2083
+
+
+
+
+
+
+idx
+name
+
+
+12
+
+
+1
+2
+18457
+
+
+2
+3
+6533
+
+
+3
+4
+6178
+
+
+4
+8
+2624
+
+
+8
+4397
+1330
+
+
+
+
+
+
+idx
+parentid
+
+
+12
+
+
+2
+3
+606
+
+
+4
+5
+17851
+
+
+5
+6
+6533
+
+
+6
+7
+859
+
+
+7
+8
+4471
+
+
+9
+16
+2719
+
+
+16
+578022
+2083
+
+
+
+
+
+
+idx
+fileid
+
+
+12
+
+
+2
+3
+606
+
+
+4
+5
+17851
+
+
+5
+6
+6533
+
+
+6
+7
+859
+
+
+7
+8
+4471
+
+
+9
+16
+2719
+
+
+16
+39722
+2083
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+20457
+
+
+2
+3
+3115
+
+
+3
+7
+3026
+
+
+7
+8
+3588
+
+
+8
+9
+2220
+
+
+9
+11
+3099
+
+
+11
+19
+3087
+
+
+19
+114506
+1129
+
+
+
+
+
+
+fileid
+name
+
+
+12
+
+
+1
+2
+20459
+
+
+2
+3
+3458
+
+
+3
+5
+2569
+
+
+5
+7
+2172
+
+
+7
+8
+6158
+
+
+8
+9
+3501
+
+
+9
+46
+1404
+
+
+
+
+
+
+fileid
+parentid
+
+
+12
+
+
+1
+2
+20457
+
+
+2
+3
+3870
+
+
+3
+5
+2152
+
+
+5
+6
+2876
+
+
+6
+7
+2720
+
+
+7
+8
+4132
+
+
+8
+14
+3096
+
+
+14
+31079
+418
+
+
+
+
+
+
+fileid
+idx
+
+
+12
+
+
+1
+2
+25894
+
+
+2
+3
+5301
+
+
+3
+4
+3787
+
+
+4
+6
+3268
+
+
+6
+35123
+1471
+
+
+
+
+
+
+
+
+xmlAttrs
+1202020
+
+
+id
+1202020
+
+
+elementid
+760198
+
+
+name
+3649
+
+
+value
+121803
+
+
+idx
+2000
+
+
+fileid
+39448
+
+
+
+
+id
+elementid
+
+
+12
+
+
+1
+2
+1202020
+
+
+
+
+
+
+id
+name
+
+
+12
+
+
+1
+2
+1202020
+
+
+
+
+
+
+id
+value
+
+
+12
+
+
+1
+2
+1202020
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+1202020
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+2
+1202020
+
+
+
+
+
+
+elementid
+id
+
+
+12
+
+
+1
+2
+425697
+
+
+2
+3
+249659
+
+
+3
+4
+66474
+
+
+4
+2001
+18368
+
+
+
+
+
+
+elementid
+name
+
+
+12
+
+
+1
+2
+425778
+
+
+2
+3
+249579
+
+
+3
+4
+66475
+
+
+4
+2001
+18366
+
+
+
+
+
+
+elementid
+value
+
+
+12
+
+
+1
+2
+466237
+
+
+2
+3
+266291
+
+
+3
+46
+27670
+
+
+
+
+
+
+elementid
+idx
+
+
+12
+
+
+1
+2
+425697
+
+
+2
+3
+249659
+
+
+3
+4
+66474
+
+
+4
+2001
+18368
+
+
+
+
+
+
+elementid
+fileid
+
+
+12
+
+
+1
+2
+760198
+
+
+
+
+
+
+name
+id
+
+
+12
+
+
+1
+2
+3467
+
+
+2
+262475
+182
+
+
+
+
+
+
+name
+elementid
+
+
+12
+
+
+1
+2
+3467
+
+
+2
+262475
+182
+
+
+
+
+
+
+name
+value
+
+
+12
+
+
+1
+2
+3501
+
+
+2
+54146
+148
+
+
+
+
+
+
+name
+idx
+
+
+12
+
+
+1
+2
+3531
+
+
+2
+11
+118
+
+
+
+
+
+
+name
+fileid
+
+
+12
+
+
+1
+2
+3491
+
+
+2
+21768
+158
+
+
+
+
+
+
+value
+id
+
+
+12
+
+
+1
+2
+72032
+
+
+2
+3
+42366
+
+
+3
+199269
+7405
+
+
+
+
+
+
+value
+elementid
+
+
+12
+
+
+1
+2
+72036
+
+
+2
+3
+42374
+
+
+3
+199269
+7393
+
+
+
+
+
+
+value
+name
+
+
+12
+
+
+1
+2
+116722
+
+
+2
+2041
+5081
+
+
+
+
+
+
+value
+idx
+
+
+12
+
+
+1
+2
+117957
+
+
+2
+2001
+3846
+
+
+
+
+
+
+value
+fileid
+
+
+12
+
+
+1
+2
+86306
+
+
+2
+3
+28570
+
+
+3
+4175
+6927
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+1
+2
+1955
+
+
+2
+760199
+45
+
+
+
+
+
+
+idx
+elementid
+
+
+12
+
+
+1
+2
+1955
+
+
+2
+760199
+45
+
+
+
+
+
+
+idx
+name
+
+
+12
+
+
+1
+2
+1955
+
+
+2
+189
+45
+
+
+
+
+
+
+idx
+value
+
+
+12
+
+
+1
+2
+1955
+
+
+2
+116643
+45
+
+
+
+
+
+
+idx
+fileid
+
+
+12
+
+
+1
+2
+1955
+
+
+2
+39449
+45
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+22884
+
+
+2
+4
+2565
+
+
+4
+6
+2294
+
+
+6
+7
+3299
+
+
+7
+9
+3272
+
+
+9
+16
+3143
+
+
+16
+129952
+1991
+
+
+
+
+
+
+fileid
+elementid
+
+
+12
+
+
+1
+2
+23890
+
+
+2
+4
+2131
+
+
+4
+5
+1971
+
+
+5
+6
+4096
+
+
+6
+8
+3519
+
+
+8
+16
+3137
+
+
+16
+106600
+704
+
+
+
+
+
+
+fileid
+name
+
+
+12
+
+
+1
+2
+22946
+
+
+2
+3
+2338
+
+
+3
+4
+2726
+
+
+4
+5
+2824
+
+
+5
+6
+2994
+
+
+6
+7
+3876
+
+
+7
+2002
+1744
+
+
+
+
+
+
+fileid
+value
+
+
+12
+
+
+1
+2
+22916
+
+
+2
+4
+2772
+
+
+4
+5
+2112
+
+
+5
+6
+3510
+
+
+6
+8
+1993
+
+
+8
+11
+3365
+
+
+11
+50357
+2780
+
+
+
+
+
+
+fileid
+idx
+
+
+12
+
+
+1
+2
+26133
+
+
+2
+3
+9699
+
+
+3
+5
+3511
+
+
+5
+2001
+105
+
+
+
+
+
+
+
+
+xmlNs
+71201
+
+
+id
+4185
+
+
+prefixName
+958
+
+
+URI
+4185
+
+
+fileid
+39544
+
+
+
+
+id
+prefixName
+
+
+12
+
+
+1
+2
+2602
+
+
+2
+3
+1553
+
+
+3
+872
+30
+
+
+
+
+
+
+id
+URI
+
+
+12
+
+
+1
+2
+4185
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+6
+274
+
+
+6
+7
+3825
+
+
+7
+24905
+86
+
+
+
+
+
+
+prefixName
+id
+
+
+12
+
+
+1
+2
+915
+
+
+2
+4054
+43
+
+
+
+
+
+
+prefixName
+URI
+
+
+12
+
+
+1
+2
+915
+
+
+2
+4054
+43
+
+
+
+
+
+
+prefixName
+fileid
+
+
+12
+
+
+1
+2
+828
+
+
+2
+5
+73
+
+
+5
+24903
+57
+
+
+
+
+
+
+URI
+id
+
+
+12
+
+
+1
+2
+4185
+
+
+
+
+
+
+URI
+prefixName
+
+
+12
+
+
+1
+2
+2602
+
+
+2
+3
+1553
+
+
+3
+872
+30
+
+
+
+
+
+
+URI
+fileid
+
+
+12
+
+
+1
+6
+274
+
+
+6
+7
+3825
+
+
+7
+24905
+86
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+11655
+
+
+2
+3
+26146
+
+
+3
+8
+1743
+
+
+
+
+
+
+fileid
+prefixName
+
+
+12
+
+
+1
+2
+11653
+
+
+2
+3
+25982
+
+
+3
+31
+1909
+
+
+
+
+
+
+fileid
+URI
+
+
+12
+
+
+1
+2
+11655
+
+
+2
+3
+26146
+
+
+3
+8
+1743
+
+
+
+
+
+
+
+
+xmlHasNs
+1139730
+
+
+elementId
+1139730
+
+
+nsId
+4136
+
+
+fileid
+39537
+
+
+
+
+elementId
+nsId
+
+
+12
+
+
+1
+2
+1139730
+
+
+
+
+
+
+elementId
+fileid
+
+
+12
+
+
+1
+2
+1139730
+
+
+
+
+
+
+nsId
+elementId
+
+
+12
+
+
+1
+5
+234
+
+
+5
+6
+3824
+
+
+6
+643289
+78
+
+
+
+
+
+
+nsId
+fileid
+
+
+12
+
+
+1
+5
+257
+
+
+5
+6
+3823
+
+
+6
+24759
+56
+
+
+
+
+
+
+fileid
+elementId
+
+
+12
+
+
+1
+2
+3669
+
+
+2
+3
+20429
+
+
+3
+7
+2536
+
+
+7
+8
+3473
+
+
+8
+9
+2258
+
+
+9
+11
+3036
+
+
+11
+18
+2966
+
+
+18
+147552
+1170
+
+
+
+
+
+
+fileid
+nsId
+
+
+12
+
+
+1
+2
+18261
+
+
+2
+3
+21032
+
+
+3
+8
+244
+
+
+
+
+
+
+
+
+xmlComments
+26812
+
+
+id
+26812
+
+
+text
+22933
+
+
+parentid
+26546
+
+
+fileid
+26368
+
+
+
+
+id
+text
+
+
+12
+
+
+1
+2
+26812
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+26812
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+2
+26812
+
+
+
+
+
+
+text
+id
+
+
+12
+
+
+1
+2
+21517
+
+
+2
+62
+1416
+
+
+
+
+
+
+text
+parentid
+
+
+12
+
+
+1
+2
+21519
+
+
+2
+62
+1414
+
+
+
+
+
+
+text
+fileid
+
+
+12
+
+
+1
+2
+21522
+
+
+2
+62
+1411
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+26379
+
+
+2
+17
+167
+
+
+
+
+
+
+parentid
+text
+
+
+12
+
+
+1
+2
+26379
+
+
+2
+17
+167
+
+
+
+
+
+
+parentid
+fileid
+
+
+12
+
+
+1
+2
+26546
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+26161
+
+
+2
+17
+207
+
+
+
+
+
+
+fileid
+text
+
+
+12
+
+
+1
+2
+26165
+
+
+2
+17
+203
+
+
+
+
+
+
+fileid
+parentid
+
+
+12
+
+
+1
+2
+26223
+
+
+2
+10
+145
+
+
+
+
+
+
+
+
+xmlChars
+439958
+
+
+id
+439958
+
+
+text
+100518
+
+
+parentid
+433851
+
+
+idx
+4
+
+
+isCDATA
+1
+
+
+fileid
+26494
+
+
+
+
+id
+text
+
+
+12
+
+
+1
+2
+439958
+
+
+
+
+
+
+id
+parentid
+
+
+12
+
+
+1
+2
+439958
+
+
+
+
+
+
+id
+idx
+
+
+12
+
+
+1
+2
+439958
+
+
+
+
+
+
+id
+isCDATA
+
+
+12
+
+
+1
+2
+439958
+
+
+
+
+
+
+id
+fileid
+
+
+12
+
+
+1
+2
+439958
+
+
+
+
+
+
+text
+id
+
+
+12
+
+
+1
+2
+60389
+
+
+2
+4
+3811
+
+
+4
+5
+29257
+
+
+5
+23171
+7061
+
+
+
+
+
+
+text
+parentid
+
+
+12
+
+
+1
+2
+60389
+
+
+2
+4
+3811
+
+
+4
+5
+29257
+
+
+5
+23171
+7061
+
+
+
+
+
+
+text
+idx
+
+
+12
+
+
+1
+2
+100517
+
+
+2
+3
+1
+
+
+
+
+
+
+text
+isCDATA
+
+
+12
+
+
+1
+2
+100518
+
+
+
+
+
+
+text
+fileid
+
+
+12
+
+
+1
+2
+61284
+
+
+2
+4
+4205
+
+
+4
+5
+28328
+
+
+5
+351
+6701
+
+
+
+
+
+
+parentid
+id
+
+
+12
+
+
+1
+2
+429716
+
+
+2
+5
+4135
+
+
+
+
+
+
+parentid
+text
+
+
+12
+
+
+1
+2
+429716
+
+
+2
+5
+4135
+
+
+
+
+
+
+parentid
+idx
+
+
+12
+
+
+1
+2
+429716
+
+
+2
+5
+4135
+
+
+
+
+
+
+parentid
+isCDATA
+
+
+12
+
+
+1
+2
+433851
+
+
+
+
+
+
+parentid
+fileid
+
+
+12
+
+
+1
+2
+433851
+
+
+
+
+
+
+idx
+id
+
+
+12
+
+
+80
+81
+1
+
+
+1892
+1893
+1
+
+
+4135
+4136
+1
+
+
+433851
+433852
+1
+
+
+
+
+
+
+idx
+text
+
+
+12
+
+
+1
+2
+1
+
+
+3
+4
+1
+
+
+16
+17
+1
+
+
+100499
+100500
+1
+
+
+
+
+
+
+idx
+parentid
+
+
+12
+
+
+80
+81
+1
+
+
+1892
+1893
+1
+
+
+4135
+4136
+1
+
+
+433851
+433852
+1
+
+
+
+
+
+
+idx
+isCDATA
+
+
+12
+
+
+1
+2
+4
+
+
+
+
+
+
+idx
+fileid
+
+
+12
+
+
+4
+5
+1
+
+
+46
+47
+1
+
+
+97
+98
+1
+
+
+26494
+26495
+1
+
+
+
+
+
+
+isCDATA
+id
+
+
+12
+
+
+439958
+439959
+1
+
+
+
+
+
+
+isCDATA
+text
+
+
+12
+
+
+100518
+100519
+1
+
+
+
+
+
+
+isCDATA
+parentid
+
+
+12
+
+
+433851
+433852
+1
+
+
+
+
+
+
+isCDATA
+idx
+
+
+12
+
+
+4
+5
+1
+
+
+
+
+
+
+isCDATA
+fileid
+
+
+12
+
+
+26494
+26495
+1
+
+
+
+
+
+
+fileid
+id
+
+
+12
+
+
+1
+2
+25303
+
+
+2
+35123
+1191
+
+
+
+
+
+
+fileid
+text
+
+
+12
+
+
+1
+2
+25765
+
+
+2
+35123
+729
+
+
+
+
+
+
+fileid
+parentid
+
+
+12
+
+
+1
+2
+25312
+
+
+2
+35123
+1182
+
+
+
+
+
+
+fileid
+idx
+
+
+12
+
+
+1
+2
+26397
+
+
+2
+5
+97
+
+
+
+
+
+
+fileid
+isCDATA
+
+
+12
+
+
+1
+2
+26494
+
+
+
+
+
+
+
+
+xmllocations
+3051056
+
+
+xmlElement
+2982460
+
+
+location
+3051056
+
+
+
+
+xmlElement
+location
+
+
+12
+
+
+1
+2
+2978326
+
+
+2
+24903
+4134
+
+
+
+
+
+
+location
+xmlElement
+
+
+12
+
+
+1
+2
+3051056
+
+
+
+
+
+
+
+
+
diff --git a/java/ql/src/default.qll b/java/ql/src/default.qll
new file mode 100644
index 00000000000..69104d35b85
--- /dev/null
+++ b/java/ql/src/default.qll
@@ -0,0 +1 @@
+import java
diff --git a/java/ql/src/definitions.ql b/java/ql/src/definitions.ql
new file mode 100644
index 00000000000..ab7a8d95993
--- /dev/null
+++ b/java/ql/src/definitions.ql
@@ -0,0 +1,185 @@
+/**
+ * @name Jump-to-definition links
+ * @description Generates use-definition pairs that provide the data
+ * for jump-to-definition in the code viewer.
+ * @kind definitions
+ * @id java/jump-to-definition
+ */
+
+import java
+
+/**
+ * Restricts the location of a method access to the method identifier only,
+ * excluding its qualifier, type arguments and arguments.
+ *
+ * If there is any whitespace between the method identifier and its first argument,
+ * or between the method identifier and its qualifier (or last type argument, if any),
+ * the location may be slightly inaccurate and include such whitespace,
+ * but it should suffice for the purpose of avoiding overlapping definitions.
+ */
+class LocationOverridingMethodAccess extends MethodAccess {
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ exists(MemberRefExpr e | e.getReferencedCallable() = getMethod() |
+ exists(int elRef, int ecRef |
+ e.hasLocationInfo(path, _, _, elRef, ecRef)
+ |
+ sl = elRef and
+ sc = ecRef - getMethod().getName().length() + 1 and
+ el = elRef and
+ ec = ecRef
+ )
+ ) or
+ not exists(MemberRefExpr e | e.getReferencedCallable() = getMethod()) and
+ exists(int slSuper, int scSuper, int elSuper, int ecSuper |
+ super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper)
+ |
+ (
+ if (exists(getTypeArgument(_)))
+ then exists(Location locTypeArg | locTypeArg = getTypeArgument(count(getTypeArgument(_))-1).getLocation() |
+ sl = locTypeArg.getEndLine() and
+ sc = locTypeArg.getEndColumn()+2)
+ else (
+ if exists(getQualifier())
+ // Note: this needs to be the original (full) location of the qualifier, not the modified one.
+ then exists(Location locQual | locQual = getQualifier().getLocation() |
+ sl = locQual.getEndLine() and
+ sc = locQual.getEndColumn()+2)
+ else (
+ sl = slSuper and
+ sc = scSuper
+ )
+ )
+ )
+ and
+ (
+ if (getNumArgument()>0)
+ // Note: this needs to be the original (full) location of the first argument, not the modified one.
+ then exists(Location locArg | locArg = getArgument(0).getLocation() |
+ el = locArg.getStartLine() and
+ ec = locArg.getStartColumn()-2
+ ) else (
+ el = elSuper and
+ ec = ecSuper-2
+ )
+ )
+ )
+ }
+}
+
+/**
+ * Restricts the location of a type access to exclude
+ * the type arguments and qualifier, if any.
+ */
+class LocationOverridingTypeAccess extends TypeAccess {
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ exists(int slSuper, int scSuper, int elSuper, int ecSuper |
+ super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper)
+ |
+ (
+ if exists(getQualifier())
+ // Note: this needs to be the original (full) location of the qualifier, not the modified one.
+ then exists(Location locQual | locQual = getQualifier().getLocation() |
+ sl = locQual.getEndLine() and
+ sc = locQual.getEndColumn()+2)
+ else (
+ sl = slSuper and
+ sc = scSuper
+ )
+ )
+ and
+ (
+ if (exists(getTypeArgument(_)))
+ // Note: this needs to be the original (full) location of the first type argument, not the modified one.
+ then exists(Location locArg | locArg = getTypeArgument(0).getLocation() |
+ el = locArg.getStartLine() and
+ ec = locArg.getStartColumn()-2
+ ) else (
+ el = elSuper and
+ ec = ecSuper
+ )
+ )
+ )
+ }
+}
+
+/**
+ * Restricts the location of a field access to the name of the accessed field only,
+ * excluding its qualifier.
+ */
+class LocationOverridingFieldAccess extends FieldAccess {
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ super.hasLocationInfo(path, _, _, el, ec) and
+ sl = el and
+ sc = ec-(getField().getName().length())+1
+ }
+}
+
+/**
+ * Restricts the location of a single-type-import declaration to the name of the imported type only,
+ * excluding the `import` keyword and the package name.
+ */
+class LocationOverridingImportType extends ImportType {
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ exists(int slSuper, int scSuper, int elSuper, int ecSuper |
+ super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper)
+ |
+ el = elSuper and
+ ec = ecSuper-1 and
+ sl = el and
+ sc = ecSuper-(getImportedType().getName().length())
+ )
+ }
+}
+
+/**
+ * Restricts the location of a single-static-import declaration to the name of the imported member(s) only,
+ * excluding the `import` keyword and the package name.
+ */
+class LocationOverridingImportStaticTypeMember extends ImportStaticTypeMember {
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ exists(int slSuper, int scSuper, int elSuper, int ecSuper |
+ super.hasLocationInfo(path, slSuper, scSuper, elSuper, ecSuper)
+ |
+ el = elSuper and
+ ec = ecSuper-1 and
+ sl = el and
+ sc = ecSuper-(getName().length())
+ )
+ }
+}
+
+Element definition(Element e, string kind) {
+ e.(MethodAccess).getMethod().getSourceDeclaration() = result and kind = "M"
+ or
+ e.(TypeAccess).getType().(RefType).getSourceDeclaration() = result and kind = "T"
+ or
+ exists(Variable v | v = e.(VarAccess).getVariable() |
+ result = v.(Field).getSourceDeclaration() or
+ result = v.(Parameter).getSourceDeclaration() or
+ result = v.(LocalVariableDecl)
+ ) and kind = "V"
+ or
+ e.(ImportType).getImportedType() = result and kind = "I"
+ or
+ e.(ImportStaticTypeMember).getAMemberImport() = result and kind = "I"
+}
+
+predicate dummyVarAccess(VarAccess va) {
+ exists(AssignExpr ae, InitializerMethod im |
+ ae.getDest() = va and
+ ae.getParent() = im.getBody().getAChild()
+ )
+}
+
+predicate dummyTypeAccess(TypeAccess ta) {
+ exists(FunctionalExpr e | e.getAnonymousClass().getClassInstanceExpr().getTypeName() = ta.getParent*())
+}
+
+from Element e, Element def, string kind
+where
+ def = definition(e, kind) and
+ def.fromSource() and
+ e.fromSource() and
+ not dummyVarAccess(e) and
+ not dummyTypeAccess(e)
+select e, def, kind
diff --git a/java/ql/src/external/Clover.qll b/java/ql/src/external/Clover.qll
new file mode 100644
index 00000000000..0e182450924
--- /dev/null
+++ b/java/ql/src/external/Clover.qll
@@ -0,0 +1,137 @@
+import java
+
+/**
+ * A Clover report is characterised by the fact that one of its
+ * top-level children (usually, in fact, there is only one) is
+ * a tag with the name "coverage".
+ */
+class CloverReport extends XMLFile {
+ CloverReport() {
+ this.getAChild().getName() = "coverage"
+ }
+}
+
+/**
+ * The Clover "coverage" tag contains one or more "projects".
+ */
+class CloverCoverage extends XMLElement {
+ CloverCoverage() {
+ this.getParent() instanceof CloverReport and
+ this.getName() = "coverage"
+ }
+
+ CloverProject getAProject() {
+ result = this.getAChild()
+ }
+}
+
+/**
+ * Several elements in the Clover report contain a "metrics" element which
+ * contains various numbers, aggregated to the different levels. They are
+ * all subclasses of this class, to share code.
+ */
+abstract class CloverMetricsContainer extends XMLElement {
+ CloverMetrics getMetrics() {
+ result = this.getAChild()
+ }
+}
+
+/**
+ * A "metrics" element contains a range of numbers for the current
+ * aggregation level.
+ */
+class CloverMetrics extends XMLElement {
+ CloverMetrics() {
+ this.getParent() instanceof CloverMetricsContainer and
+ this.getName() = "metrics"
+ }
+
+ private
+ int attr(string name) { result = this.getAttribute(name).getValue().toInt() }
+
+ private
+ float ratio(string name) { result = attr("covered" + name)/(float)attr(name) }
+
+ int getNumConditionals() { result = attr("conditionals") }
+ int getNumCoveredConditionals() { result = attr("coveredconditionals") }
+ int getNumStatements() { result = attr("statements") }
+ int getNumCoveredStatements() { result = attr("coveredstatements") }
+ int getNumElements() { result = attr("elements") }
+ int getNumCoveredElements() { result = attr("coveredelements") }
+ int getNumMethods() { result = attr("methods") }
+ int getNumCoveredMethods() { result = attr("coveredmethods") }
+ int getNumLoC() { result = attr("loc") }
+ int getNumNonCommentedLoC() { result = attr("ncloc") }
+ int getNumPackages() { result = attr("packages") }
+ int getNumFiles() { result = attr("files") }
+ int getNumClasses() { result = attr("classes") }
+ int getCloverComplexity() { result = attr("complexity") }
+
+ float getConditionalCoverage() { result = ratio("conditionals") }
+ float getStatementCoverage() { result = ratio("statements") }
+ float getElementCoverage() { result = ratio("elements") }
+ float getMethodCoverage() { result = ratio("methods") }
+
+ float getNonCommentedLoCRatio() { result = attr("ncloc")/attr("loc")}
+}
+
+/**
+ * A Clover project has an aggregated "metrics" element and
+ * groups together several "package" (or "testpackage") elements.
+ */
+class CloverProject extends CloverMetricsContainer {
+ CloverProject() {
+ this.getParent() instanceof CloverCoverage
+ }
+}
+
+/**
+ * A Clover package is nested in a project and contains several files.
+ */
+class CloverPackage extends CloverMetricsContainer {
+ CloverPackage() {
+ this.getParent() instanceof CloverProject and
+ this.getName() = "package"
+ }
+
+ Package getRealPackage() {
+ result.hasName(getAttribute("name").getValue())
+ }
+}
+
+/**
+ * A Clover file is nested in a package and contains several classes.
+ */
+class CloverFile extends CloverMetricsContainer {
+ CloverFile() {
+ this.getParent() instanceof CloverPackage and
+ this.getName() = "file"
+ }
+}
+
+/**
+ * A Clover class is nested in a file and contains metric information.
+ */
+class CloverClass extends CloverMetricsContainer {
+ CloverClass() {
+ this.getParent() instanceof CloverFile and
+ this.getName() = "class"
+ }
+
+ CloverPackage getPackage() {
+ result = ((CloverFile)getParent()).getParent()
+ }
+
+ RefType getRealClass() {
+ result.hasQualifiedName(getPackage().getAttribute("name").getValue(), getAttribute("name").getValue())
+ }
+}
+
+/**
+ * Get the clover metrics associated with the given class, if any.
+ */
+CloverMetrics cloverInfo(RefType t) {
+ exists(CloverClass c | c.getRealClass() = t |
+ result = c.getMetrics()
+ )
+}
diff --git a/java/ql/src/external/CodeDuplication.qll b/java/ql/src/external/CodeDuplication.qll
new file mode 100644
index 00000000000..55571c6334a
--- /dev/null
+++ b/java/ql/src/external/CodeDuplication.qll
@@ -0,0 +1,290 @@
+import java
+
+private
+string relativePath(File file) {
+ result = file.getRelativePath().replaceAll("\\", "/")
+}
+
+private cached
+predicate tokenLocation(File file, int sl, int sc, int ec, int el, Copy copy, int index) {
+ file = copy.sourceFile() and
+ tokens(copy, index, sl, sc, ec, el)
+}
+
+class Copy extends @duplication_or_similarity
+{
+ private
+ int lastToken() {
+ result = max(int i | tokens(this, i, _, _, _, _) | i)
+ }
+
+ int tokenStartingAt(Location loc) {
+ tokenLocation(loc.getFile(), loc.getStartLine(), loc.getStartColumn(), _, _, this, result)
+ }
+
+ int tokenEndingAt(Location loc) {
+ tokenLocation(loc.getFile(), _, _, loc.getEndLine(), loc.getEndColumn(), this, result)
+ }
+
+ int sourceStartLine() {
+ tokens(this, 0, result, _, _, _)
+ }
+
+ int sourceStartColumn() {
+ tokens(this, 0, _, result, _, _)
+ }
+
+ int sourceEndLine() {
+ tokens(this, lastToken(), _, _, result, _)
+ }
+
+ int sourceEndColumn() {
+ tokens(this, lastToken(), _, _, _, result)
+ }
+
+ int sourceLines() {
+ result = this.sourceEndLine() + 1 - this.sourceStartLine()
+ }
+
+ int getEquivalenceClass() {
+ duplicateCode(this, _, result) or similarCode(this, _, result)
+ }
+
+ File sourceFile() {
+ exists(string name | duplicateCode(this, name, _) or similarCode(this, name, _) |
+ name.replaceAll("\\", "/") = relativePath(result)
+ )
+ }
+
+ predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
+ sourceFile().getAbsolutePath() = filepath and
+ startline = sourceStartLine() and
+ startcolumn = sourceStartColumn() and
+ endline = sourceEndLine() and
+ endcolumn = sourceEndColumn()
+ }
+
+ string toString() { none() }
+}
+
+class DuplicateBlock extends Copy, @duplication
+{
+ string toString() {
+ result = "Duplicate code: " + sourceLines() + " duplicated lines."
+ }
+}
+
+class SimilarBlock extends Copy, @similarity
+{
+ string toString() {
+ result = "Similar code: " + sourceLines() + " almost duplicated lines."
+ }
+}
+
+Method sourceMethod() {
+ hasLocation(result, _) and numlines(result, _, _, _)
+}
+
+int numberOfSourceMethods(Class c) {
+ result = count(Method m | m = sourceMethod() and m.getDeclaringType() = c)
+}
+
+private
+predicate blockCoversStatement(int equivClass, int first, int last, Stmt stmt) {
+ exists(DuplicateBlock b, Location loc |
+ stmt.getLocation() = loc and
+ first = b.tokenStartingAt(loc) and
+ last = b.tokenEndingAt(loc) and
+ b.getEquivalenceClass() = equivClass
+ )
+}
+
+private
+Stmt statementInMethod(Method m) {
+ result.getEnclosingCallable() = m and
+ not result instanceof Block
+}
+
+private
+predicate duplicateStatement(Method m1, Method m2, Stmt s1, Stmt s2) {
+ exists(int equivClass, int first, int last |
+ s1 = statementInMethod(m1) and
+ s2 = statementInMethod(m2) and
+ blockCoversStatement(equivClass, first, last, s1) and
+ blockCoversStatement(equivClass, first, last, s2) and
+ s1 != s2 and m1 != m2
+ )
+}
+
+predicate duplicateStatements(Method m1, Method m2, int duplicate, int total) {
+ duplicate = strictcount(Stmt s | duplicateStatement(m1, m2, s, _)) and
+ total = strictcount(statementInMethod(m1))
+}
+
+/**
+ * Pairs of methods that are identical.
+ */
+predicate duplicateMethod(Method m, Method other) {
+ exists(int total | duplicateStatements(m, other, total, total))
+}
+
+predicate similarLines(File f, int line) {
+ exists(SimilarBlock b |
+ b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
+ )
+}
+
+private
+predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f)
+{
+ lines = strictsum(SimilarBlock b, int toSum |
+ (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and (toSum = b.sourceLines()) | toSum)
+}
+
+private pragma[noopt]
+predicate similarLinesCovered(File f, int coveredLines, File otherFile) {
+ exists(int numLines | numLines = f.getTotalNumberOfLines() |
+ exists(int coveredApprox |
+ coveredApprox = strictsum(int num |
+ exists(int equivClass |
+ similarLinesPerEquivalenceClass(equivClass, num, f) and
+ similarLinesPerEquivalenceClass(equivClass, num, otherFile) and
+ f != otherFile
+ )
+ ) and
+ exists(int n, int product |
+ product = coveredApprox * 100 and n = product / numLines
+ |
+ n > 75
+ )
+ ) and
+ exists(int notCovered |
+ notCovered = count(int j | j in [1 .. numLines] and not similarLines(f, j)) and
+ coveredLines = numLines - notCovered
+ )
+ )
+}
+
+predicate duplicateLines(File f, int line) {
+ exists(DuplicateBlock b |
+ b.sourceFile() = f and line in [b.sourceStartLine() .. b.sourceEndLine()]
+ )
+}
+
+private
+predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f)
+{
+ lines = strictsum(DuplicateBlock b, int toSum |
+ (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and (toSum = b.sourceLines()) | toSum)
+}
+
+private pragma[noopt]
+predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) {
+ exists(int numLines | numLines = f.getTotalNumberOfLines() |
+ exists(int coveredApprox |
+ coveredApprox = strictsum(int num |
+ exists(int equivClass |
+ duplicateLinesPerEquivalenceClass(equivClass, num, f) and
+ duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and
+ f != otherFile
+ )
+ ) and
+ exists(int n, int product | product = coveredApprox * 100 and n = product / numLines | n > 75)
+ ) and
+ exists(int notCovered |
+ notCovered = count(int j | j in [1 .. numLines] and not duplicateLines(f, j)) and
+ coveredLines = numLines - notCovered
+ )
+ )
+}
+
+predicate similarFiles(File f, File other, int percent) {
+ exists(int covered, int total |
+ similarLinesCovered(f, covered, other) and
+ total = f.getTotalNumberOfLines() and
+ covered * 100 / total = percent and
+ percent > 80
+ ) and
+ not duplicateFiles(f, other, _)
+}
+
+predicate duplicateFiles(File f, File other, int percent) {
+ exists(int covered, int total |
+ duplicateLinesCovered(f, covered, other) and
+ total = f.getTotalNumberOfLines() and
+ covered * 100 / total = percent and
+ percent > 70
+ )
+}
+
+predicate duplicateAnonymousClass(AnonymousClass c, AnonymousClass other) {
+ exists(int numDup |
+ numDup = strictcount(Method m1 |
+ exists(Method m2 |
+ duplicateMethod(m1, m2) and
+ m1 = sourceMethod() and
+ m1.getDeclaringType() = c and
+ m2.getDeclaringType() = other and
+ c != other
+ )
+ ) and
+ numDup = numberOfSourceMethods(c) and
+ numDup = numberOfSourceMethods(other) and
+ forall(Type t | c.getASupertype() = t | t = other.getASupertype())
+ )
+}
+
+pragma[noopt]
+predicate mostlyDuplicateClassBase(Class c, Class other, int numDup, int total) {
+ numDup = strictcount(Method m1 |
+ exists(Method m2 |
+ duplicateMethod(m1, m2) and
+ m1 = sourceMethod() and
+ m1.getDeclaringType() = c and
+ m2.getDeclaringType() = other and
+ other instanceof Class and
+ c != other
+ )
+ ) and
+ total = numberOfSourceMethods(c) and
+ exists(int n, int product | product = 100 * numDup and n = product / total | n > 80)
+}
+
+predicate mostlyDuplicateClass(Class c, Class other, string message) {
+ exists(int numDup, int total |
+ mostlyDuplicateClassBase(c, other, numDup, total) and
+ not c instanceof AnonymousClass and
+ not other instanceof AnonymousClass and
+ (
+ (
+ total != numDup and
+ exists(string s1, string s2, string s3, string name |
+ s1 = " out of " and s2 = " methods in " and s3 = " are duplicated in $@." and name = c.getName()
+ |
+ message = numDup + s1 + total + s2 + name + s3
+ )
+ )
+ or
+ (
+ total = numDup and
+ exists(string s1, string s2, string name |
+ s1 = "All methods in " and s2 = " are identical in $@." and name = c.getName()
+ |
+ message = s1 + name + s2
+ )
+ )
+ )
+ )
+}
+
+predicate fileLevelDuplication(File f, File other) {
+ similarFiles(f, other, _) or duplicateFiles(f, other, _)
+}
+
+predicate classLevelDuplication(Class c, Class other) {
+ duplicateAnonymousClass(c, other) or mostlyDuplicateClass(c, other, _)
+}
+
+predicate whitelistedLineForDuplication(File f, int line) {
+ exists(Import i | i.getFile() = f and i.getLocation().getStartLine() = line)
+}
diff --git a/java/ql/src/external/DefectFilter.qll b/java/ql/src/external/DefectFilter.qll
new file mode 100644
index 00000000000..eb3588adc52
--- /dev/null
+++ b/java/ql/src/external/DefectFilter.qll
@@ -0,0 +1,51 @@
+/** Provides a class for working with defect query results stored in dashboard databases. */
+
+import java
+
+/**
+ * Holds if `id` in the opaque identifier of a result reported by query `queryPath`,
+ * such that `message` is the associated message and the location of the result spans
+ * column `startcolumn` of line `startline` to column `endcolumn` of line `endline`
+ * in file `filepath`.
+ *
+ * For more information, see [LGTM locations](https://lgtm.com/help/ql/locations).
+ */
+external predicate defectResults(int id, string queryPath,
+ string file, int startline, int startcol, int endline, int endcol,
+ string message);
+
+/**
+ * A defect query result stored in a dashboard database.
+ */
+class DefectResult extends int {
+ DefectResult() { defectResults(this, _, _, _, _, _, _, _) }
+
+ /** Gets the path of the query that reported the result. */
+ string getQueryPath() { defectResults(this, result, _, _, _, _, _, _) }
+
+ /** Gets the file in which this query result was reported. */
+ File getFile() {
+ exists(string path | defectResults(this, _, path, _, _, _, _, _) | result.getAbsolutePath() = path)
+ }
+
+ /** Gets the line on which the location of this query result starts. */
+ int getStartLine() { defectResults(this, _, _, result, _, _, _, _) }
+
+ /** Gets the column on which the location of this query result starts. */
+ int getStartColumn() { defectResults(this, _, _, _, result, _, _, _) }
+
+ /** Gets the line on which the location of this query result ends. */
+ int getEndLine() { defectResults(this, _, _, _, _, result, _, _) }
+
+ /** Gets the column on which the location of this query result ends. */
+ int getEndColumn() { defectResults(this, _, _, _, _, _, result, _) }
+
+ /** Gets the message associated with this query result. */
+ string getMessage() { defectResults(this, _, _, _, _, _, _, result) }
+
+ /** Gets the URL corresponding to the location of this query result. */
+ string getURL() {
+ result = "file://" + getFile().getAbsolutePath() + ":" +
+ getStartLine() + ":" + getStartColumn() + ":" + getEndLine() + ":" + getEndColumn()
+ }
+}
diff --git a/java/ql/src/external/DuplicateAnonymous.java b/java/ql/src/external/DuplicateAnonymous.java
new file mode 100644
index 00000000000..5181afbdf08
--- /dev/null
+++ b/java/ql/src/external/DuplicateAnonymous.java
@@ -0,0 +1,30 @@
+// BAD: Duplicate anonymous classes:
+button1.addActionListener(new ActionListener() {
+ public void actionPerfored(ActionEvent e)
+ {
+ for (ActionListener listener: listeners)
+ listeners.actionPerformed(e);
+ }
+});
+
+button2.addActionListener(new ActionListener() {
+ public void actionPerfored(ActionEvent e)
+ {
+ for (ActionListener listener: listeners)
+ listeners.actionPerformed(e);
+ }
+});
+
+// ... and so on.
+
+// GOOD: Better solution:
+class MultiplexingListener implements ActionListener {
+ public void actionPerformed(ActionEvent e) {
+ for (ActionListener listener : listeners)
+ listener.actionPerformed(e);
+ }
+}
+
+button1.addActionListener(new MultiplexingListener());
+button2.addActionListener(new MultiplexingListener());
+// ... and so on.
\ No newline at end of file
diff --git a/java/ql/src/external/DuplicateAnonymous.qhelp b/java/ql/src/external/DuplicateAnonymous.qhelp
new file mode 100644
index 00000000000..27679b3d274
--- /dev/null
+++ b/java/ql/src/external/DuplicateAnonymous.qhelp
@@ -0,0 +1,43 @@
+
+
+
+
+
+Anonymous classes are a common way of creating implementations of an interface
+or abstract class whose functionality is
+really only needed once. Duplicating the definition of an anonymous class in several
+places is usually a sign that refactoring is necessary.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+
+Introduce a concrete class
+that contains the definition just once, and replace the anonymous classes with instances
+of this concrete class.
+
+
+
+
+In the following example, the definition of the class addActionListener is duplicated for
+each button that needs to use it. A better solution is shown that defines just one class,
+MultiplexingListener, which is used by each button.
+
+
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
diff --git a/java/ql/src/external/DuplicateAnonymous.ql b/java/ql/src/external/DuplicateAnonymous.ql
new file mode 100644
index 00000000000..76a6e83f588
--- /dev/null
+++ b/java/ql/src/external/DuplicateAnonymous.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Duplicate anonymous class
+ * @description Duplicated anonymous classes indicate that refactoring is necessary.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/duplicate-anonymous-class
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+from AnonymousClass c, AnonymousClass other
+where
+ duplicateAnonymousClass(c, other) and
+ not fileLevelDuplication(c.getCompilationUnit(), other.getCompilationUnit())
+select c, "Anonymous class is identical to $@.",
+ other, "another anonymous class in " + other.getFile().getStem()
diff --git a/java/ql/src/external/DuplicateBlock.ql b/java/ql/src/external/DuplicateBlock.ql
new file mode 100644
index 00000000000..b8b36e318cc
--- /dev/null
+++ b/java/ql/src/external/DuplicateBlock.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Duplicate code
+ * @description This block of code is duplicated elsewhere. If possible, the shared code should be refactored so there is only one occurrence left. It may not always be possible to address these issues; other duplicate code checks (such as duplicate function, duplicate class) give subsets of the results with higher confidence.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision low
+ * @id java/duplicate-block
+ */
+import CodeDuplication
+
+from DuplicateBlock d, DuplicateBlock other, int lines, File otherFile, int otherLine
+where
+ lines = d.sourceLines() and
+ lines > 10 and
+ other.getEquivalenceClass() = d.getEquivalenceClass() and
+ other != d and
+ otherFile = other.sourceFile() and
+ otherLine = other.sourceStartLine()
+select
+ d,
+ "Duplicate code: " + lines + " lines are duplicated at " + otherFile.getStem() + ":" + otherLine + "."
diff --git a/java/ql/src/external/DuplicateMethod.java b/java/ql/src/external/DuplicateMethod.java
new file mode 100644
index 00000000000..bd85d8eb1db
--- /dev/null
+++ b/java/ql/src/external/DuplicateMethod.java
@@ -0,0 +1,23 @@
+class RowWidget extends Widget {
+ // ...
+ public void collectChildren(Set result) {
+ for (Widget child : this.children) {
+ if (child.isVisible()) {
+ result.add(children);
+ child.collectChildren(result);
+ }
+ }
+ }
+}
+
+class ColumnWidget extends Widget {
+ // ...
+ public void collectChildren(Set result) {
+ for (Widget child : this.children) {
+ if (child.isVisible()) {
+ result.add(children);
+ child.collectChildren(result);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/external/DuplicateMethod.qhelp b/java/ql/src/external/DuplicateMethod.qhelp
new file mode 100644
index 00000000000..cf1a173db10
--- /dev/null
+++ b/java/ql/src/external/DuplicateMethod.qhelp
@@ -0,0 +1,62 @@
+
+
+
+
+
+A method should never be duplicated exactly in several places in the code.
+The severity of this problem is higher for longer methods than for extremely short
+methods of one or two statements, but there are usually better ways of achieving the same
+effect.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+
+At its simplest, the duplication can be addressed by simply removing all but one of the duplicate
+method definitions, and changing calls to the removed methods so that they call the remaining function
+instead.
+
+This may not be possible because of visibility or accessibility. A common example is
+where two classes implement the same functionality but neither is a subtype of the other,
+so it is not possible to inherit a single method definition. In such cases, introducing a
+common superclass to share the duplicated code is a possible option. Alternatively, if the methods
+do not need access to private object state, they can be moved to a shared utility class that
+just provides the functionality itself.
+
+
+
+
+In the following example, RowWidget and ColumnWidget contain duplicate
+methods. The collectChildren method should probably be moved into the
+superclass, Widget, and shared between RowWidget and
+ColumnWidget.
+
+
+
+Alternatively, if not all kinds of Widget actually need collectChildren
+(for example, not all of them have children), it might be necessary to introduce
+a new, possibly abstract, class under Widget. For example, the new class might be called
+ContainerWidget and include a single definition of collectChildren. Both
+RowWidget and ColumnWidget could extend the class and inherit
+collectChildren.
+
+Modern IDEs may provide refactoring support for this sort of issue, usually
+with the names "Pull up" or "Extract supertype".
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
+
diff --git a/java/ql/src/external/DuplicateMethod.ql b/java/ql/src/external/DuplicateMethod.ql
new file mode 100644
index 00000000000..6084cf89c3c
--- /dev/null
+++ b/java/ql/src/external/DuplicateMethod.ql
@@ -0,0 +1,31 @@
+/**
+ * @name Duplicate method
+ * @description Duplicated methods make code more difficult to understand and introduce a risk of
+ * changes being made to only one copy.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/duplicate-method
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+predicate relevant(Method m) {
+ m.getNumberOfLinesOfCode() > 5 and not m.getName().matches("get%") or
+ m.getNumberOfLinesOfCode() > 10
+}
+
+from Method m, Method other
+where
+ duplicateMethod(m, other) and
+ relevant(m) and
+ not fileLevelDuplication(m.getCompilationUnit(), other.getCompilationUnit()) and
+ not classLevelDuplication(m.getDeclaringType(), other.getDeclaringType())
+select m, "Method " + m.getName() + " is duplicated in $@.",
+ other, other.getDeclaringType().getQualifiedName()
diff --git a/java/ql/src/external/ExternalArtifact.qll b/java/ql/src/external/ExternalArtifact.qll
new file mode 100644
index 00000000000..233dcf24f13
--- /dev/null
+++ b/java/ql/src/external/ExternalArtifact.qll
@@ -0,0 +1,44 @@
+import java
+
+class ExternalData extends @externalDataElement {
+
+ string getDataPath() { externalData(this, result, _, _) }
+
+ string getQueryPath() { result = getDataPath().regexpReplaceAll("\\.[^.]*$", ".ql") }
+
+ int getNumFields() { result = 1 + max(int i | externalData(this, _, i, _) | i) }
+
+ string getField(int index) { externalData(this, _, index, result) }
+
+ int getFieldAsInt(int index) { result = getField(index).toInt() }
+
+ float getFieldAsFloat(int index) { result = getField(index).toFloat() }
+
+ date getFieldAsDate(int index) { result = getField(index).toDate() }
+
+ string toString() {
+ result = getQueryPath() + ": " + buildTupleString(0)
+ }
+
+ private string buildTupleString(int start) {
+ (start = getNumFields() - 1 and result = getField(start))
+ or
+ (start < getNumFields() - 1 and result = getField(start) + "," + buildTupleString(start+1))
+ }
+
+}
+
+/**
+ * External data with a location, and a message, as produced by tools that used to produce QLDs.
+ */
+class DefectExternalData extends ExternalData {
+ DefectExternalData() {
+ this.getField(0).regexpMatch("\\w+://.*:[0-9]+:[0-9]+:[0-9]+:[0-9]+$") and
+ this.getNumFields() = 2
+ }
+
+ string getURL() { result = getField(0) }
+
+ string getMessage() { result = getField(1) }
+}
+
diff --git a/java/ql/src/external/MetricFilter.qll b/java/ql/src/external/MetricFilter.qll
new file mode 100644
index 00000000000..d8f4724d4f6
--- /dev/null
+++ b/java/ql/src/external/MetricFilter.qll
@@ -0,0 +1,37 @@
+import java
+
+external predicate metricResults(int id, string queryPath, string file, int startline, int startcol, int endline, int endcol, float value);
+
+class MetricResult extends int {
+
+ MetricResult() { metricResults(this, _, _, _, _, _, _, _) }
+
+ string getQueryPath() { metricResults(this, result, _, _, _, _, _, _) }
+
+ File getFile() { exists(string path | metricResults(this, _, path, _, _, _, _, _) and result.getAbsolutePath() = path) }
+
+ int getStartLine() { metricResults(this, _, _, result, _, _, _, _) }
+
+ int getStartColumn() { metricResults(this, _, _, _, result, _, _, _) }
+
+ int getEndLine() { metricResults(this, _, _, _, _, result, _, _) }
+
+ int getEndColumn() { metricResults(this, _, _, _, _, _, result, _) }
+
+ predicate hasMatchingLocation() { exists(this.getMatchingLocation()) }
+
+ Location getMatchingLocation() {
+ result.getFile() = this.getFile() and
+ result.getStartLine() = this.getStartLine() and
+ result.getEndLine() = this.getEndLine() and
+ result.getStartColumn() = this.getStartColumn() and
+ result.getEndColumn() = this.getEndColumn()
+ }
+
+ float getValue() { metricResults(this, _, _, _, _, _, _, result) }
+
+ string getURL() {
+ result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + getEndLine() + ":" + getEndColumn()
+ }
+
+}
diff --git a/java/ql/src/external/MostlyDuplicateClass.qhelp b/java/ql/src/external/MostlyDuplicateClass.qhelp
new file mode 100644
index 00000000000..a3c056b3a4f
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateClass.qhelp
@@ -0,0 +1,43 @@
+
+
+
+
+
+When most of the methods in one class are duplicated in one or more other classes,
+the classes themselves are regarded as mostly duplicate.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+Although completely duplicated classes are rare, they are usually a sign of a simple
+oversight (or deliberate copy/paste) by a developer. Usually the required solution
+is to remove all but one of them.
+
+It is more common to see duplication of many methods between two classes, leaving just
+a few that are actually different. Decide whether the differences are
+intended or the result of an inconsistent update to one of the copies:
+
+- If the two classes serve different purposes but many of their methods are duplicated, this indicates
+that there is a missing level of abstraction. Introducing a common super-class to define the
+common methods is likely to prevent many problems in the long term. Modern IDEs may provide
+refactoring support for this sort of issue, usually with the names "Pull up" or "Extract supertype".
+- If the two classes serve the same purpose and are different only as a result of inconsistent updates
+then treat the classes as completely duplicate. Determine
+the most up-to-date and correct version of the code and eliminate all near duplicates.
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
diff --git a/java/ql/src/external/MostlyDuplicateClass.ql b/java/ql/src/external/MostlyDuplicateClass.ql
new file mode 100644
index 00000000000..68734f314ee
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateClass.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Mostly duplicate class
+ * @description Classes in which most of the methods are duplicated in another class make code more
+ * difficult to understand and introduce a risk of changes being made to only one copy.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/duplicate-class
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+from Class c, string message, Class link
+where
+ mostlyDuplicateClass(c, link, message) and
+ not fileLevelDuplication(c.getCompilationUnit(), _)
+select c, message, link, link.getQualifiedName()
diff --git a/java/ql/src/external/MostlyDuplicateFile.qhelp b/java/ql/src/external/MostlyDuplicateFile.qhelp
new file mode 100644
index 00000000000..a2721c0f157
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateFile.qhelp
@@ -0,0 +1,44 @@
+
+
+
+
+
+When most of the lines in one file are duplicated in one or more other
+files, the files themselves are regarded as mostly duplicate.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+Although completely duplicated files are rare, they are usually a sign of a simple
+oversight (or deliberate copy/paste) by a developer. Usually the required solution
+is to remove all but one of them. A common exception is generated code
+that simply occurs in several places in the source tree.
+
+It is more common to see duplication of many lines between two files, leaving just
+a few that are actually different. Decide whether the differences are
+intended or the result of an inconsistent update to one of the copies:
+
+- If the two files serve different purposes but many of their lines are duplicated, this indicates
+that there is a missing level of abstraction. Look for ways to share the functionality, either
+by extracting a utility class for parts of it or by encapsulating the common parts into a new
+super class of any classes involved.
+- If the two files serve the same purpose and are different only as a result of inconsistent updates
+then treat the files as completely duplicate. Determine
+the most up-to-date and correct version of the code and eliminate all near duplicates.
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
diff --git a/java/ql/src/external/MostlyDuplicateFile.ql b/java/ql/src/external/MostlyDuplicateFile.ql
new file mode 100644
index 00000000000..2a339830839
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateFile.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Mostly duplicate file
+ * @description Files in which most of the lines are duplicated in another file make code more
+ * difficult to understand and introduce a risk of changes being made to only one copy.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/duplicate-file
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+from File f, File other, int percent
+where duplicateFiles(f, other, percent)
+select f, percent + "% of the lines in " + f.getStem() + " are copies of lines in $@.",
+ other, other.getStem()
diff --git a/java/ql/src/external/MostlyDuplicateMethod.qhelp b/java/ql/src/external/MostlyDuplicateMethod.qhelp
new file mode 100644
index 00000000000..8388520189d
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateMethod.qhelp
@@ -0,0 +1,48 @@
+
+
+
+
+When most of the lines in one method are duplicated in one or more other
+methods, the methods themselves are regarded as mostly duplicate or similar.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+Although completely duplicated methods are rare, they are usually a sign of a simple
+oversight (or deliberate copy/paste) by a developer. Usually the required solution
+is to remove all but one of them.
+
+It is more common to see duplication of many lines between two methods, leaving just
+a few that are actually different. Decide whether the differences are
+intended or the result of an inconsistent update to one of the copies.
+
+- If the two methods serve different purposes but many of their lines are duplicated, this indicates
+that there is a missing level of abstraction. Look for ways of encapsulating the commonality and sharing it while
+retaining the differences in functionality. Perhaps the method can be moved to a single place
+and given an additional parameter, allowing it to cover all use cases. Alternatively, there
+may be a common pre-processing or post-processing step that can be extracted to its own (shared)
+method, leaving only the specific parts in the existing methods. Modern IDEs may provide
+refactoring support for this sort of issue, usually with the names "Extract method", "Change method signature",
+"Pull up" or "Extract supertype".
+- If the two methods serve the same purpose and are different only as a result of inconsistent updates
+then treat the methods as completely duplicate. Determine
+the most up-to-date and correct version of the code and eliminate all near duplicates. Callers of the
+removed methods should be updated to call the remaining method instead.
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
+
diff --git a/java/ql/src/external/MostlyDuplicateMethod.ql b/java/ql/src/external/MostlyDuplicateMethod.ql
new file mode 100644
index 00000000000..53fb1271b05
--- /dev/null
+++ b/java/ql/src/external/MostlyDuplicateMethod.ql
@@ -0,0 +1,30 @@
+/**
+ * @name Mostly duplicate method
+ * @description Methods in which most of the lines are duplicated in another method make code more
+ * difficult to understand and introduce a risk of changes being made to only one copy.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/similar-method
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+from Method m, int covered, int total, Method other, int percent
+where
+ duplicateStatements(m, other, covered, total) and
+ covered != total and
+ m.getMetrics().getNumberOfLinesOfCode() > 5 and
+ covered * 100 / total = percent and
+ percent > 80 and
+ not duplicateMethod(m, other) and
+ not classLevelDuplication(m.getDeclaringType(), other.getDeclaringType()) and
+ not fileLevelDuplication(m.getCompilationUnit(), other.getCompilationUnit())
+select m, percent + "% of the statements in " + m.getName() + " are duplicated in $@.",
+ other, other.getDeclaringType().getName() + "." + other.getStringSignature()
diff --git a/java/ql/src/external/MostlySimilarFile.qhelp b/java/ql/src/external/MostlySimilarFile.qhelp
new file mode 100644
index 00000000000..fcea9164d8d
--- /dev/null
+++ b/java/ql/src/external/MostlySimilarFile.qhelp
@@ -0,0 +1,39 @@
+
+
+
+
+
+When most of the lines in one file have corresponding "similar" lines in one or more other
+files, the files themselves are regarded as mostly similar. Two lines are defined as
+similar if they are either identical or contain only very minor differences.
+
+Code duplication in general is highly undesirable for a range of reasons. The artificially
+inflated amount of code is more difficult to understand, and sequences of similar but subtly different lines
+can mask the real purpose or intention behind them. Also, there is always a risk that only one
+of several copies of the code is updated to address a defect or add a feature.
+
+
+
+
+Consider whether the differences are
+deliberate or a result of an inconsistent update to one of the clones. If the latter, then
+treating the files as completely duplicate and eliminating all but one (while preserving any
+corrections or new features that may have been introduced) is the best course. If two files serve
+genuinely different purposes but almost all of their lines are the same, that can be a sign
+that there is a missing level of abstraction. Can some of the shared code be extracted into
+methods (perhaps with additional parameters, to cover the differences in behavior)? Should it
+be moved into a utility class or file that is accessible to all current implementations, or
+should a new level of abstraction be introduced?
+
+
+
+
+E. Juergens, F. Deissenboeck, B. Hummel, S. Wagner.
+Do code clones matter? Proceedings of the 31st International Conference on
+Software Engineering,
+485-495, 2009.
+
+
+
diff --git a/java/ql/src/external/MostlySimilarFile.ql b/java/ql/src/external/MostlySimilarFile.ql
new file mode 100644
index 00000000000..70bd6e68ce4
--- /dev/null
+++ b/java/ql/src/external/MostlySimilarFile.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Mostly similar file
+ * @description Files in which most of the lines are similar to those in another file make code more
+ * difficult to understand and introduce a risk of changes being made to only one copy.
+ * @kind problem
+ * @problem.severity recommendation
+ * @precision high
+ * @id java/similar-file
+ * @tags testability
+ * maintainability
+ * useless-code
+ * duplicate-code
+ * statistical
+ * non-attributable
+ */
+import java
+import CodeDuplication
+
+from File f, File other, int percent
+where similarFiles(f, other, percent)
+select f, percent + "% of the lines in " + f.getStem() + " are similar to lines in $@.",
+ other, other.getStem()
diff --git a/java/ql/src/external/VCS.qll b/java/ql/src/external/VCS.qll
new file mode 100644
index 00000000000..b3903d8e7e7
--- /dev/null
+++ b/java/ql/src/external/VCS.qll
@@ -0,0 +1,90 @@
+import java
+
+class Commit extends @svnentry {
+
+ Commit() {
+ svnaffectedfiles(this, _, _) and
+ exists(date svnDate, date snapshotDate |
+ svnentries(this, _, _, svnDate, _) and
+ snapshotDate(snapshotDate) and
+ svnDate <= snapshotDate
+ )
+ }
+
+ string toString() { result = this.getRevisionName() }
+
+ string getRevisionName() { svnentries(this, result, _, _, _) }
+
+ string getAuthor() { svnentries(this, _, result, _, _) }
+
+ date getDate() { svnentries(this, _, _, result, _) }
+
+ int getChangeSize() { svnentries(this, _, _, _, result) }
+
+ string getMessage() { svnentrymsg(this, result) }
+
+ string getAnAffectedFilePath(string action) {
+ exists(File rawFile | svnaffectedfiles(this, rawFile, action) |
+ result = rawFile.getAbsolutePath()
+ )
+ }
+
+ string getAnAffectedFilePath() { result = getAnAffectedFilePath(_) }
+
+ File getAnAffectedFile(string action) {
+ svnaffectedfiles(this,result,action)
+ }
+
+ File getAnAffectedFile() { exists(string action | result = this.getAnAffectedFile(action)) }
+
+ predicate isRecent() { recentCommit(this) }
+
+ int daysToNow() {
+ exists(date now | snapshotDate(now) |
+ result = getDate().daysTo(now) and result >= 0
+ )
+ }
+
+ int getRecentAdditionsForFile(File f) {
+ svnchurn(this, f, result, _)
+ }
+
+ int getRecentDeletionsForFile(File f) {
+ svnchurn(this, f, _, result)
+ }
+
+ int getRecentChurnForFile(File f) {
+ exists(int added, int deleted | svnchurn(this, f, added, deleted) and result = added+deleted)
+ }
+}
+
+class Author extends string {
+ Author() { exists(Commit e | this = e.getAuthor()) }
+
+ Commit getACommit() { result.getAuthor() = this }
+
+ File getAnEditedFile() { result = this.getACommit().getAnAffectedFile() }
+}
+
+predicate recentCommit(Commit e) {
+ exists(date snapshotDate, date commitDate, int days |
+ snapshotDate(snapshotDate) and
+ e.getDate() = commitDate and
+ days = commitDate.daysTo(snapshotDate) and
+ days >= 0 and days <= 60
+ )
+}
+
+date firstChange(File f) {
+ result = min(Commit e, date toMin | f = e.getAnAffectedFile() and toMin = e.getDate() | toMin)
+}
+
+predicate firstCommit(Commit e) {
+ not exists(File f | f = e.getAnAffectedFile() |
+ firstChange(f) < e.getDate()
+ )
+}
+
+predicate artificialChange(Commit e) {
+ firstCommit(e) or e.getChangeSize() >= 50000
+}
diff --git a/java/ql/src/filters/ClassifyFiles.ql b/java/ql/src/filters/ClassifyFiles.ql
new file mode 100644
index 00000000000..080f4530a43
--- /dev/null
+++ b/java/ql/src/filters/ClassifyFiles.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Classify files
+ * @description This query produces a list of all files in a snapshot
+ * that are classified as generated code or test code.
+ * @kind file-classifier
+ * @id java/file-classifier
+ */
+
+import java
+
+predicate classify(File f, string tag) {
+ f instanceof GeneratedFile and tag = "generated" or
+ exists(GeneratedClass gc | gc.getFile() = f | tag = "generated") or
+ exists(TestClass tc | tc.getFile() = f | tag = "test") or
+ exists(TestMethod tm | tm.getFile() = f | tag = "test")
+}
+
+from File f, string tag
+where classify(f, tag)
+select f, tag
diff --git a/java/ql/src/filters/FromSource.ql b/java/ql/src/filters/FromSource.ql
new file mode 100644
index 00000000000..a2fbd760763
--- /dev/null
+++ b/java/ql/src/filters/FromSource.ql
@@ -0,0 +1,14 @@
+/**
+ * @name Filter: only keep results from source
+ * @description Shows how to filter for only certain files
+ * @kind problem
+ * @id java/source-filter
+ */
+import java
+import external.DefectFilter
+
+from DefectResult res, CompilationUnit cu
+where
+ cu = res.getFile() and
+ cu.fromSource()
+select res, res.getMessage()
diff --git a/java/ql/src/filters/ImportAdditionalLibraries.ql b/java/ql/src/filters/ImportAdditionalLibraries.ql
new file mode 100644
index 00000000000..d3a5f8bc52a
--- /dev/null
+++ b/java/ql/src/filters/ImportAdditionalLibraries.ql
@@ -0,0 +1,17 @@
+/**
+ * @name (Import additional libraries)
+ * @description This query produces no results but imports some libraries we
+ * would like to make available in the LGTM query console even
+ * if they are not used by any queries.
+ * @kind file-classifier
+ * @id java/lgtm/import-additional-libraries
+ */
+
+import java
+
+import semmle.code.java.dataflow.Guards
+import semmle.code.java.security.DataFlow
+
+from File f, string tag
+where none()
+select f, tag
diff --git a/java/ql/src/filters/NotGenerated.ql b/java/ql/src/filters/NotGenerated.ql
new file mode 100644
index 00000000000..967ec2be72d
--- /dev/null
+++ b/java/ql/src/filters/NotGenerated.ql
@@ -0,0 +1,12 @@
+/**
+ * @name Filter: non-generated files
+ * @description Only keep results that aren't in generated files
+ * @kind problem
+ * @id java/not-generated-file-filter
+ */
+import java
+import external.DefectFilter
+
+from DefectResult res
+where not res.getFile() instanceof GeneratedFile
+select res, res.getMessage()
diff --git a/java/ql/src/filters/NotGeneratedForMetric.ql b/java/ql/src/filters/NotGeneratedForMetric.ql
new file mode 100644
index 00000000000..198b9a283d5
--- /dev/null
+++ b/java/ql/src/filters/NotGeneratedForMetric.ql
@@ -0,0 +1,12 @@
+/**
+ * @name Metric Filter: non-generated files
+ * @description Only keep metric results that aren't in generated files
+ * @kind treemap
+ * @id java/not-generated-file-metric-filter
+ */
+import java
+import external.MetricFilter
+
+from MetricResult res
+where not res.getFile() instanceof GeneratedFile
+select res, res.getValue()
diff --git a/java/ql/src/filters/RecentDefects.ql b/java/ql/src/filters/RecentDefects.ql
new file mode 100644
index 00000000000..c1d22b6da73
--- /dev/null
+++ b/java/ql/src/filters/RecentDefects.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Filter: only files recently edited
+ * @description Filter a defect query to only include results in files that have been changed recently, and modify the message.
+ * @kind problem
+ * @id java/recently-changed-file-filter
+ */
+import java
+import external.DefectFilter
+import external.VCS
+
+private pragma[noopt]
+predicate recent(File file) {
+ exists(Commit e | file = e.getAnAffectedFile() |
+ e.isRecent() and not artificialChange(e)
+ ) and
+ exists(file.getLocation())
+}
+
+from DefectResult res
+where recent(res.getFile())
+select res, res.getMessage()
diff --git a/java/ql/src/filters/RecentDefectsForMetric.ql b/java/ql/src/filters/RecentDefectsForMetric.ql
new file mode 100644
index 00000000000..261977405ee
--- /dev/null
+++ b/java/ql/src/filters/RecentDefectsForMetric.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Metric filter: only files recently edited
+ * @description Filter a metric query to only include results in files that have been changed recently, and modify the message.
+ * @kind treemap
+ * @id java/recently-changed-file-metric-filter
+ */
+import java
+import external.MetricFilter
+import external.VCS
+
+private pragma[noopt]
+predicate recent(File file) {
+ exists(Commit e | file = e.getAnAffectedFile() |
+ e.isRecent() and not artificialChange(e)
+ ) and
+ exists(file.getLocation())
+}
+
+from MetricResult res
+where recent(res.getFile())
+select res, res.getValue()
diff --git a/java/ql/src/filters/SuppressionComment.ql b/java/ql/src/filters/SuppressionComment.ql
new file mode 100644
index 00000000000..fdb984fab83
--- /dev/null
+++ b/java/ql/src/filters/SuppressionComment.ql
@@ -0,0 +1,52 @@
+/**
+ * @name Filter: Suppression comments
+ * @description Recognise comments containing `NOSEMMLE` as suppression comments
+ * when they appear on a line containing an alert or the
+ * immediately preceding line. As further customisations,
+ * `NOSEMMLE(some text)` will only suppress alerts where the
+ * message contains "some text", and `NOSEMMLE/some regex/` will
+ * only suppress alerts where the message contains a match of the
+ * regex. No special way of escaping `)` or `/` in the suppression
+ * comment argument is provided.
+ * @kind problem
+ * @id java/nosemmle-suppression-comment-filter
+ */
+import java
+import external.DefectFilter
+
+class SuppressionComment extends Javadoc {
+ SuppressionComment() {
+ this.getAChild*().getText().matches("%NOSEMMLE%")
+ }
+
+ private string getASuppressionDirective() {
+ result = this.getAChild*().getText()
+ .regexpFind("\\bNOSEMMLE\\b(\\([^)]+?\\)|/[^/]+?/|)", _, 0)
+ }
+
+ private string getAnActualSubstringArg() {
+ result = this.getASuppressionDirective().regexpCapture("NOSEMMLE\\((.*)\\)", 1)
+ }
+
+ private string getAnActualRegexArg() {
+ result = ".*" + this.getASuppressionDirective().regexpCapture("NOSEMMLE/(.*)/", 1) + ".*"
+ }
+
+ private string getASuppressionRegex() {
+ result = getAnActualRegexArg() or
+ exists(string substring | substring = getAnActualSubstringArg() |
+ result = "\\Q" + substring.replaceAll("\\E", "\\E\\\\E\\Q") + "\\E"
+ ) or
+ (result = ".*" and getASuppressionDirective() = "NOSEMMLE")
+ }
+
+ predicate suppresses(DefectResult res) {
+ this.getFile() = res.getFile() and
+ res.getEndLine() - this.getLocation().getEndLine() in [0..2] and
+ res.getMessage().regexpMatch(this.getASuppressionRegex())
+ }
+}
+
+from DefectResult res
+where not exists(SuppressionComment s | s.suppresses(res))
+select res, res.getMessage()
diff --git a/java/ql/src/java.qll b/java/ql/src/java.qll
new file mode 100644
index 00000000000..c533b0af7ec
--- /dev/null
+++ b/java/ql/src/java.qll
@@ -0,0 +1,40 @@
+/** Provides all default Java QL imports. */
+
+import semmle.code.FileSystem
+import semmle.code.Location
+
+import semmle.code.java.Annotation
+import semmle.code.java.CompilationUnit
+import semmle.code.java.ControlFlowGraph
+import semmle.code.java.Dependency
+import semmle.code.java.Element
+import semmle.code.java.Exception
+import semmle.code.java.Expr
+import semmle.code.java.GeneratedFiles
+import semmle.code.java.Generics
+import semmle.code.java.Import
+import semmle.code.java.J2EE
+import semmle.code.java.Javadoc
+import semmle.code.java.JDK
+import semmle.code.java.JDKAnnotations
+import semmle.code.java.JMX
+import semmle.code.java.Member
+import semmle.code.java.Modifier
+import semmle.code.java.Modules
+import semmle.code.java.Package
+import semmle.code.java.Statement
+import semmle.code.java.Type
+import semmle.code.java.UnitTests
+import semmle.code.java.Variable
+
+import semmle.code.java.controlflow.BasicBlocks
+
+import semmle.code.java.metrics.MetricCallable
+import semmle.code.java.metrics.MetricElement
+import semmle.code.java.metrics.MetricField
+import semmle.code.java.metrics.MetricPackage
+import semmle.code.java.metrics.MetricRefType
+import semmle.code.java.metrics.MetricStmt
+
+import semmle.code.xml.Ant
+import semmle.code.xml.XML
diff --git a/java/ql/src/meta/ssa/AmbiguousToString.ql b/java/ql/src/meta/ssa/AmbiguousToString.ql
new file mode 100644
index 00000000000..d1a5d1753ab
--- /dev/null
+++ b/java/ql/src/meta/ssa/AmbiguousToString.ql
@@ -0,0 +1,31 @@
+/**
+ * @name An SSA variable without a unique 'toString()'
+ * @description An ambiguous 'toString()' indicates overlap in the defining
+ * sub-classes of 'SsaVariable'.
+ * @kind problem
+ * @problem.severity error
+ * @id java/sanity/non-unique-ssa-tostring
+ * @tags sanity
+ */
+
+import java
+import semmle.code.java.dataflow.SSA
+
+predicate noToString(SsaVariable v) {
+ not exists(v.toString())
+}
+
+predicate multipleToString(SsaVariable v) {
+ 1 < count(v.toString())
+}
+
+from SsaVariable ssa, ControlFlowNode n, Variable v, string problem
+where
+ (
+ noToString(ssa) and problem = "SSA variable without 'toString()' for " or
+ multipleToString(ssa) and problem = "SSA variable with multiple 'toString()' results for "
+ ) and
+ n = ssa.getCFGNode() and
+ v = ssa.getSourceVariable().getVariable()
+select
+ n, problem + v
diff --git a/java/ql/src/meta/ssa/TooFewPhiInputs.ql b/java/ql/src/meta/ssa/TooFewPhiInputs.ql
new file mode 100644
index 00000000000..16216b92a3d
--- /dev/null
+++ b/java/ql/src/meta/ssa/TooFewPhiInputs.ql
@@ -0,0 +1,18 @@
+/**
+ * @name A phi node without two or more inputs
+ * @description A phi node should have at least two inputs.
+ * @kind problem
+ * @problem.severity error
+ * @id java/sanity/too-few-phi-inputs
+ * @tags sanity
+ */
+
+import java
+import semmle.code.java.dataflow.SSA
+
+from SsaPhiNode phi, int inputs
+where
+ inputs = count(SsaVariable v | v = phi.getAPhiInput()) and
+ inputs < 2
+select
+ phi, "Phi node for " + phi.getSourceVariable() + " has only " + inputs + " inputs."
diff --git a/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql b/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql
new file mode 100644
index 00000000000..06f32120296
--- /dev/null
+++ b/java/ql/src/meta/ssa/UncertainDefWithoutPrior.ql
@@ -0,0 +1,25 @@
+/**
+ * @name An uncertain SSA update without a prior definition
+ * @description An uncertain SSA update may retain its previous value
+ * and should therefore have a prior definition.
+ * @kind problem
+ * @problem.severity error
+ * @id java/sanity/uncertain-ssa-update-without-prior-def
+ * @tags sanity
+ */
+
+import java
+import semmle.code.java.dataflow.SSA
+
+predicate live(SsaVariable v) {
+ exists(v.getAUse()) or
+ exists(SsaPhiNode phi | live(phi) and phi.getAPhiInput() = v) or
+ exists(SsaUncertainImplicitUpdate upd | live(upd) and upd.getPriorDef() = v)
+}
+
+from SsaUncertainImplicitUpdate upd
+where
+ live(upd) and
+ not exists(upd.getPriorDef())
+select
+ upd, "No prior definition of " + upd
diff --git a/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql b/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql
new file mode 100644
index 00000000000..8ec45904cd0
--- /dev/null
+++ b/java/ql/src/meta/ssa/UseWithoutUniqueSsaVariable.ql
@@ -0,0 +1,47 @@
+/**
+ * @name A variable use without a unique SSA variable
+ * @description Every variable use that is sufficiently trackable
+ * should have a unique associated SSA variable.
+ * @kind problem
+ * @problem.severity error
+ * @id java/sanity/use-without-unique-ssa-variable
+ * @tags sanity
+ */
+
+import java
+import semmle.code.java.dataflow.SSA
+
+class SsaConvertibleReadAccess extends RValue {
+ SsaConvertibleReadAccess() {
+ this.getEnclosingCallable().getBody().getBasicBlock().getABBSuccessor*() = this.getBasicBlock() and
+ (
+ not exists(this.getQualifier()) or
+ this.getVariable() instanceof LocalScopeVariable or
+ this.getVariable().(Field).isStatic() or
+ exists(Expr q | q = this.getQualifier() |
+ q instanceof ThisAccess or
+ q instanceof SuperAccess or
+ q instanceof SsaConvertibleReadAccess
+ )
+ )
+ }
+}
+
+predicate accessWithoutSourceVariable(SsaConvertibleReadAccess va) {
+ not exists(SsaSourceVariable v | v.getAnAccess() = va)
+}
+
+predicate readAccessWithoutSsaVariable(SsaConvertibleReadAccess va) {
+ not exists(SsaVariable v | v.getAUse() = va)
+}
+
+predicate readAccessWithAmbiguousSsaVariable(SsaConvertibleReadAccess va) {
+ 1 < strictcount(SsaVariable v | v.getAUse() = va)
+}
+
+from SsaConvertibleReadAccess va, string problem
+where
+ accessWithoutSourceVariable(va) and problem = "No source variable" or
+ readAccessWithoutSsaVariable(va) and problem = "No SSA variable" or
+ readAccessWithAmbiguousSsaVariable(va) and problem = "Multiple SSA variables"
+select va, problem
diff --git a/java/ql/src/plugin.xml b/java/ql/src/plugin.xml
new file mode 100644
index 00000000000..ff451072779
--- /dev/null
+++ b/java/ql/src/plugin.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/ql/src/queries.xml b/java/ql/src/queries.xml
new file mode 100644
index 00000000000..0d33187fe86
--- /dev/null
+++ b/java/ql/src/queries.xml
@@ -0,0 +1 @@
+
diff --git a/java/ql/src/semmle/code/FileSystem.qll b/java/ql/src/semmle/code/FileSystem.qll
new file mode 100755
index 00000000000..5a0162b9f46
--- /dev/null
+++ b/java/ql/src/semmle/code/FileSystem.qll
@@ -0,0 +1,279 @@
+/** Provides classes for working with files and folders. */
+
+import Location
+
+/** A file or folder. */
+class Container extends @container, Top {
+ /**
+ * Gets the absolute, canonical path of this container, using forward slashes
+ * as path separator.
+ *
+ * The path starts with a _root prefix_ followed by zero or more _path
+ * segments_ separated by forward slashes.
+ *
+ * The root prefix is of one of the following forms:
+ *
+ * 1. A single forward slash `/` (Unix-style)
+ * 2. An upper-case drive letter followed by a colon and a forward slash,
+ * such as `C:/` (Windows-style)
+ * 3. Two forward slashes, a computer name, and then another forward slash,
+ * such as `//FileServer/` (UNC-style)
+ *
+ * Path segments are never empty (that is, absolute paths never contain two
+ * contiguous slashes, except as part of a UNC-style root prefix). Also, path
+ * segments never contain forward slashes, and no path segment is of the
+ * form `.` (one dot) or `..` (two dots).
+ *
+ * Note that an absolute path never ends with a forward slash, except if it is
+ * a bare root prefix, that is, the path has no path segments. A container
+ * whose absolute path has no segments is always a `Folder`, not a `File`.
+ */
+ abstract string getAbsolutePath();
+
+ /**
+ * Gets a URL representing the location of this container.
+ *
+ * For more information see https://lgtm.com/help/ql/locations#providing-urls.
+ */
+ abstract string getURL();
+
+ /**
+ * Gets the relative path of this file or folder from the root folder of the
+ * analyzed source location. The relative path of the root folder itself is
+ * the empty string.
+ *
+ * This has no result if the container is outside the source root, that is,
+ * if the root folder is not a reflexive, transitive parent of this container.
+ */
+ string getRelativePath() {
+ exists(string absPath, string pref |
+ absPath = getAbsolutePath() and sourceLocationPrefix(pref)
+ |
+ absPath = pref and result = ""
+ or
+ absPath = pref.regexpReplaceAll("/$", "") + "/" + result and
+ not result.matches("/%")
+ )
+ }
+
+ /**
+ * Gets the base name of this container including extension, that is, the last
+ * segment of its absolute path, or the empty string if it has no segments.
+ *
+ * Here are some examples of absolute paths and the corresponding base names
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Base name |
+ * | "/tmp/tst.java" | "tst.java" |
+ * | "C:/Program Files (x86)" | "Program Files (x86)" |
+ * | "/" | "" |
+ * | "C:/" | "" |
+ * | "D:/" | "" |
+ * | "//FileServer/" | "" |
+ *
+ */
+ string getBaseName() {
+ result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
+ }
+
+ /**
+ * Gets the extension of this container, that is, the suffix of its base name
+ * after the last dot character, if any.
+ *
+ * In particular,
+ *
+ * - if the name does not include a dot, there is no extension, so this
+ * predicate has no result;
+ * - if the name ends in a dot, the extension is the empty string;
+ * - if the name contains multiple dots, the extension follows the last dot.
+ *
+ * Here are some examples of absolute paths and the corresponding extensions
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Extension |
+ * | "/tmp/tst.java" | "java" |
+ * | "/tmp/.classpath" | "classpath" |
+ * | "/bin/bash" | not defined |
+ * | "/tmp/tst2." | "" |
+ * | "/tmp/x.tar.gz" | "gz" |
+ *
+ */
+ string getExtension() {
+ result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3)
+ }
+
+ /**
+ * Gets the stem of this container, that is, the prefix of its base name up to
+ * (but not including) the last dot character if there is one, or the entire
+ * base name if there is not.
+ *
+ * Here are some examples of absolute paths and the corresponding stems
+ * (surrounded with quotes to avoid ambiguity):
+ *
+ *
+ * | Absolute path | Stem |
+ * | "/tmp/tst.java" | "tst" |
+ * | "/tmp/.classpath" | "" |
+ * | "/bin/bash" | "bash" |
+ * | "/tmp/tst2." | "tst2" |
+ * | "/tmp/x.tar.gz" | "x.tar" |
+ *
+ */
+ string getStem() {
+ result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1)
+ }
+
+ /** Gets the parent container of this file or folder, if any. */
+ Container getParentContainer() {
+ containerparent(result, this)
+ }
+
+ /** Gets a file or sub-folder in this container. */
+ Container getAChildContainer() {
+ this = result.getParentContainer()
+ }
+
+ /** Gets a file in this container. */
+ File getAFile() {
+ result = getAChildContainer()
+ }
+
+ /** Gets the file in this container that has the given `baseName`, if any. */
+ File getFile(string baseName) {
+ result = getAFile() and
+ result.getBaseName() = baseName
+ }
+
+ /** Gets a sub-folder in this container. */
+ Folder getAFolder() {
+ result = getAChildContainer()
+ }
+
+ /** Gets the sub-folder in this container that has the given `baseName`, if any. */
+ Folder getFolder(string baseName) {
+ result = getAFolder() and
+ result.getBaseName() = baseName
+ }
+
+ /**
+ * Gets a textual representation of the path of this container.
+ *
+ * This is the absolute path of the container.
+ */
+ override string toString() {
+ result = getAbsolutePath()
+ }
+
+ /**
+ * DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead.
+ *
+ * Gets the name of this container.
+ */
+ deprecated
+ string getName() {
+ result = getAbsolutePath()
+ }
+
+ /**
+ * DEPRECATED: use `getBaseName()` or `getStem()` instead.
+ *
+ * The short name of this container, excluding its path and (for files) extension.
+ *
+ * For folders, the short name includes the extension (if any), so the short name
+ * of the folder with absolute path `/home/user/.m2` is `.m2`.
+ */
+ deprecated
+ string getShortName() {
+ folders(this,_,result) or
+ files(this,_,result,_,_)
+ }
+
+ /**
+ * DEPRECATED: use `getAbsolutePath()` instead.
+ *
+ * Gets the full name of this container, including its path and extension (if any).
+ */
+ deprecated
+ string getFullName() {
+ result = getAbsolutePath()
+ }
+}
+
+/** A folder. */
+class Folder extends Container, @folder {
+ override string getAbsolutePath() {
+ folders(this, result, _)
+ }
+
+ /** Gets the URL of this folder. */
+ override string getURL() {
+ result = "folder://" + getAbsolutePath()
+ }
+}
+
+/**
+ * A file.
+ *
+ * Note that `File` extends `Container` as it may be a `jar` file.
+ */
+class File extends Container, @file {
+ override string getAbsolutePath() {
+ files(this, result, _, _, _)
+ }
+
+ /** Gets the URL of this file. */
+ override string getURL() {
+ result = "file://" + this.getAbsolutePath() + ":0:0:0:0"
+ }
+
+ /**
+ * DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead.
+ *
+ * Holds if this file has the specified `name`.
+ */
+ deprecated
+ predicate hasName(string name) { name = this.getAbsolutePath() }
+}
+
+/**
+ * A Java archive file with a ".jar" extension.
+ */
+class JarFile extends File {
+ JarFile() {
+ getExtension() = "jar"
+ }
+
+ /**
+ * Gets the main attribute with the specified `key`
+ * from this JAR file's manifest.
+ */
+ string getManifestMainAttribute(string key) {
+ jarManifestMain(this, key, result)
+ }
+
+ /**
+ * Gets the "Specification-Version" main attribute
+ * from this JAR file's manifest.
+ */
+ string getSpecificationVersion() {
+ result = getManifestMainAttribute("Specification-Version")
+ }
+
+ /**
+ * Gets the "Implementation-Version" main attribute
+ * from this JAR file's manifest.
+ */
+ string getImplementationVersion() {
+ result = getManifestMainAttribute("Implementation-Version")
+ }
+
+ /**
+ * Gets the per-entry attribute for the specified `entry` and `key`
+ * from this JAR file's manifest.
+ */
+ string getManifestEntryAttribute(string entry, string key) {
+ jarManifestEntries(this, entry, key, result)
+ }
+}
diff --git a/java/ql/src/semmle/code/Location.qll b/java/ql/src/semmle/code/Location.qll
new file mode 100755
index 00000000000..18e99fddb60
--- /dev/null
+++ b/java/ql/src/semmle/code/Location.qll
@@ -0,0 +1,158 @@
+/**
+ * Provides classes and predicates for working with locations.
+ *
+ * Locations represent parts of files and are used to map elements to their source location.
+ */
+
+import FileSystem
+import semmle.code.java.Element
+
+/** Holds if element `e` has name `name`. */
+predicate hasName(Element e, string name) {
+ classes(e,name,_,_) or
+ interfaces(e,name,_,_) or
+ primitives(e,name) or
+ constrs(e,name,_,_,_,_) or
+ methods(e,name,_,_,_,_) or
+ fields(e,name,_,_,_) or
+ packages(e,name) or
+ files(e,_,name,_,_) or
+ paramName(e,name) or
+ exists(int pos |
+ params(e,_,pos,_,_) and
+ not paramName(e,_) and
+ name = "p"+pos
+ ) or
+ localvars(e,name,_,_) or
+ typeVars(e,name,_,_,_) or
+ wildcards(e,name,_) or
+ arrays(e,name,_,_,_) or
+ modifiers(e,name)
+}
+
+/**
+ * Top is the root of the QL type hierarchy; it defines some default
+ * methods for obtaining locations and a standard `toString()` method.
+ */
+class Top extends @top {
+ /** Gets the source location for this element. */
+ Location getLocation() { fixedHasLocation(this, result, _) }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [LGTM locations](https://lgtm.com/help/ql/locations).
+ */
+ predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
+ exists(File f, Location l | fixedHasLocation(this, l, f) |
+ locations_default(l,f,startline,startcolumn,endline,endcolumn) and
+ filepath = f.getAbsolutePath()
+ )
+ }
+
+ /** Gets the file associated with this element. */
+ File getFile() {
+ fixedHasLocation(this, _, result)
+ }
+
+ /**
+ * Gets the total number of lines that this element ranges over,
+ * including lines of code, comment and whitespace-only lines.
+ */
+ int getTotalNumberOfLines() {
+ numlines(this, result, _, _)
+ }
+
+ /** Gets the number of lines of code that this element ranges over. */
+ int getNumberOfLinesOfCode() {
+ numlines(this, _, result, _)
+ }
+
+ /** Gets the number of comment lines that this element ranges over. */
+ int getNumberOfCommentLines() {
+ numlines(this, _, _, result)
+ }
+
+ /** Gets a textual representation of this element. */
+ string toString() { hasName(this, result) }
+}
+
+/** A location maps language elements to positions in source files. */
+class Location extends @location {
+ /** Gets the line number where this location starts. */
+ int getStartLine() { locations_default(this,_,result,_,_,_) }
+
+ /** Gets the column number where this location starts. */
+ int getStartColumn() { locations_default(this,_,_,result,_,_) }
+
+ /** Gets the line number where this location ends. */
+ int getEndLine() { locations_default(this,_,_,_,result,_) }
+
+ /** Gets the column number where this location ends. */
+ int getEndColumn() { locations_default(this,_,_,_,_,result) }
+
+ /**
+ * Gets the total number of lines that this location ranges over,
+ * including lines of code, comment and whitespace-only lines.
+ */
+ int getNumberOfLines() {
+ exists(@sourceline s | hasLocation(s, this) |
+ numlines(s,result,_,_) or
+ (not numlines(s,_,_,_) and result = 0)
+ )
+ }
+
+ /** Gets the number of lines of code that this location ranges over. */
+ int getNumberOfLinesOfCode() {
+ exists(@sourceline s | hasLocation(s, this) |
+ numlines(s,_,result,_) or
+ (not numlines(s,_,_,_) and result = 0)
+ )
+ }
+
+ /** Gets the number of comment lines that this location ranges over. */
+ int getNumberOfCommentLines() {
+ exists(@sourceline s | hasLocation(s, this) |
+ numlines(s,_,_,result) or
+ (not numlines(s,_,_,_) and result = 0)
+ )
+ }
+
+ /** Gets the file containing this location. */
+ File getFile() { locations_default(this,result,_,_,_,_) }
+
+ /** Gets a string representation containing the file and range for this location. */
+ string toString() {
+ exists(File f, int startLine, int endLine | locations_default(this,f,startLine,_,endLine,_) |
+ if endLine = startLine then
+ result = f.toString() + ":" + startLine.toString()
+ else
+ result = f.toString() + ":" + startLine.toString() + "-" + endLine.toString()
+ )
+ }
+
+ /**
+ * Holds if this element is at the specified location.
+ * The location spans column `startcolumn` of line `startline` to
+ * column `endcolumn` of line `endline` in file `filepath`.
+ * For more information, see
+ * [LGTM locations](https://lgtm.com/help/ql/locations).
+ */
+ predicate hasLocationInfo(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
+ exists(File f | locations_default(this,f,startline,startcolumn,endline,endcolumn) |
+ filepath = f.getAbsolutePath()
+ )
+ }
+}
+
+private predicate hasSourceLocation(Top l, Location loc, File f) {
+ hasLocation(l, loc) and f = loc.getFile() and f.getExtension() = "java"
+}
+
+cached
+private predicate fixedHasLocation(Top l, Location loc, File f) {
+ hasSourceLocation(l, loc, f) or
+ (hasLocation(l, loc) and not hasSourceLocation(l, _, _) and locations_default(loc, f, _, _, _, _))
+}
diff --git a/java/ql/src/semmle/code/java/Annotation.qll b/java/ql/src/semmle/code/java/Annotation.qll
new file mode 100755
index 00000000000..377e57bcc92
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Annotation.qll
@@ -0,0 +1,158 @@
+/**
+ * Provides classes and predicates for working with Java annotations.
+ *
+ * Annotations are used to add meta-information to language elements in a
+ * uniform fashion. They can be seen as typed modifiers that can take
+ * parameters.
+ *
+ * Each annotation type has zero or more annotation elements that contain a
+ * name and possibly a value.
+ */
+
+import Element
+import Expr
+import Type
+import Member
+import JDKAnnotations
+
+/** Any annotation used to annotate language elements with meta-information. */
+class Annotation extends @annotation, Expr {
+ /** Holds if this annotation applies to a declaration. */
+ predicate isDeclAnnotation() { this instanceof DeclAnnotation }
+
+ /** Holds if this annotation applies to a type. */
+ predicate isTypeAnnotation() { this instanceof TypeAnnotation }
+
+ /** Gets the element being annotated. */
+ Element getAnnotatedElement() { this.getParent() = result }
+
+ /** Gets the annotation type declaration for this annotation. */
+ override AnnotationType getType() { result = Expr.super.getType() }
+
+ /** Gets the annotation element with the specified `name`. */
+ AnnotationElement getAnnotationElement(string name) {
+ result = this.getType().getAnnotationElement(name)
+ }
+
+ /** Gets a value of an annotation element. */
+ Expr getAValue() { filteredAnnotValue(this, _, result) }
+
+ /** Gets the value of the annotation element with the specified `name`. */
+ Expr getValue(string name) {
+ filteredAnnotValue(this, this.getAnnotationElement(name), result)
+ }
+
+ /** Gets the element being annotated. */
+ Element getTarget() {
+ exprs(this, _, _, result, _)
+ }
+
+ override string toString() { result = this.getType().getName() }
+
+ /** This expression's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "Annotation" }
+
+ /**
+ * Gets a value of the annotation element with the specified `name`, which must be declared as an array
+ * type.
+ *
+ * If the annotation element is defined with an array initializer, then the returned value will
+ * be one of the elements of that array. Otherwise, the returned value will be the single
+ * expression defined for the value.
+ */
+ Expr getAValue(string name) {
+ getType().getAnnotationElement(name).getType() instanceof Array and
+ exists(Expr value | value = getValue(name) |
+ if value instanceof ArrayInit then
+ result = value.(ArrayInit).getAnInit()
+ else
+ result = value
+ )
+ }
+}
+
+/** An `Annotation` that applies to a declaration. */
+class DeclAnnotation extends @declannotation, Annotation {
+}
+
+/** An `Annotation` that applies to a type. */
+class TypeAnnotation extends @typeannotation, Annotation {
+}
+
+/**
+ * There may be duplicate entries in annotValue(...) - one entry for
+ * information populated from bytecode, and one for information populated
+ * from source. This removes the duplication.
+ */
+private
+predicate filteredAnnotValue(Annotation a, Method m, Expr val) {
+ annotValue(a, m, val) and
+ (sourceAnnotValue(a, m, val) or not sourceAnnotValue(a, m, _))
+}
+
+private
+predicate sourceAnnotValue(Annotation a, Method m, Expr val) {
+ annotValue(a, m, val) and
+ val.getFile().getExtension() = "java"
+}
+
+/** An abstract representation of language elements that can be annotated. */
+class Annotatable extends Element {
+ /** Holds if this element has an annotation. */
+ predicate hasAnnotation() { exists(Annotation a | a.getAnnotatedElement() = this) }
+
+ /** Holds if this element has the specified annotation. */
+ predicate hasAnnotation(string package, string name) {
+ exists(AnnotationType at | at = getAnAnnotation().getType() | at.nestedName() = name and at.getPackage().getName() = package)
+ }
+
+ /** Gets an annotation that applies to this element. */
+ Annotation getAnAnnotation() { result.getAnnotatedElement() = this }
+
+ /**
+ * Holds if this or any enclosing `Annotatable` has a `@SuppressWarnings("")`
+ * annotation attached to it for the specified `category`.
+ */
+ predicate suppressesWarningsAbout(string category) {
+ exists(string withQuotes
+ | withQuotes = ((SuppressWarningsAnnotation) getAnAnnotation()).getASuppressedWarning()
+ | category = withQuotes.substring(1, withQuotes.length() - 1)
+ ) or
+ this.(Member).getDeclaringType().suppressesWarningsAbout(category) or
+ this.(Expr).getEnclosingCallable().suppressesWarningsAbout(category) or
+ this.(Stmt).getEnclosingCallable().suppressesWarningsAbout(category) or
+ this.(NestedClass).getEnclosingType().suppressesWarningsAbout(category) or
+ this.(LocalVariableDecl).getCallable().suppressesWarningsAbout(category)
+ }
+}
+
+/** An annotation type is a special kind of interface type declaration. */
+class AnnotationType extends Interface {
+ AnnotationType() { isAnnotType(this) }
+
+ /** Gets the annotation element with the specified `name`. */
+ AnnotationElement getAnnotationElement(string name) {
+ methods(result,_,_,_,this,_) and result.hasName(name)
+ }
+
+ /** Gets an annotation element that is a member of this annotation type. */
+ AnnotationElement getAnAnnotationElement() {
+ methods(result,_,_,_,this,_)
+ }
+
+ /** Holds if this annotation type is annotated with the meta-annotation `@Inherited`. */
+ predicate isInherited() {
+ exists(Annotation ann |
+ ann.getAnnotatedElement() = this and
+ ann.getType().hasQualifiedName("java.lang.annotation", "Inherited")
+ )
+ }
+}
+
+/** An annotation element is a member declared in an annotation type. */
+class AnnotationElement extends Member {
+ AnnotationElement() { isAnnotElem(this) }
+
+ /** Gets the type of this annotation element. */
+ Type getType() { methods(this,_,_,result,_,_) }
+}
diff --git a/java/ql/src/semmle/code/java/Collections.qll b/java/ql/src/semmle/code/java/Collections.qll
new file mode 100644
index 00000000000..7e64456bc4d
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Collections.qll
@@ -0,0 +1,130 @@
+import java
+
+/**
+ * The type `t` is a parameterization of `g`, where the `i`-th type parameter of
+ * `g` is instantiated to `a`?
+ *
+ * For example, `List` parameterizes `List`, instantiating its `0`-th
+ * type parameter to `Integer`, while the raw type `List` also parameterizes
+ * `List`, instantiating the type parameter to `Object`.
+ */
+predicate instantiates(RefType t, GenericType g, int i, RefType arg) {
+ t = g.getAParameterizedType() and exists(g.getTypeParameter(i)) and
+ (
+ arg = t.(ParameterizedType).getTypeArgument(i) or
+ t instanceof RawType and arg instanceof TypeObject
+ )
+}
+
+/**
+ * Generalisation of `instantiates` that takes subtyping into account:
+ *
+ * - `HashSet` indirectly instantiates `Collection` (but also `HashSet` and `Set`),
+ * with the `0`-th type parameter being `Integer`;
+ * - a class `MyList extends ArrayList` also instantiates `Collection`
+ * (as well as `AbstractList`, `AbstractCollection` and `List`), with the `0`-th type
+ * parameter being `Runnable`;
+ * - the same is true of `class MyOtherList extends ArrayList` (note that
+ * it does _not_ instantiate the type parameter to `T`);
+ * - a class `MyIntMap extends HashMap` instantiates `Map` (among others)
+ * with the `0`-th type parameter being `Integer` and the `1`-th type parameter being `V`.
+ */
+predicate indirectlyInstantiates(RefType t, GenericType g, int i, RefType arg) {
+ exists(RefType tsrc | tsrc = t.getSourceDeclaration() |
+ // base case: `t` directly instantiates `g`
+ tsrc = g and instantiates(t, g, i, arg)
+ or
+ // inductive step
+ exists(RefType sup, RefType suparg |
+ // follow `extends`/`implements`
+ (extendsReftype(tsrc, sup) or implInterface(tsrc, sup)) and
+ // check whether the subtype instantiates `g`
+ indirectlyInstantiates(sup, g, i, suparg)
+ |
+ // if `t` is itself an instantiation of `tsrc` and `sup` instantiates
+ // `g` to one of the type parameters of `tsrc`, we return the corresponding
+ // instantiation in `t`
+ exists(int j | suparg = tsrc.(GenericType).getTypeParameter(j) |
+ instantiates(t, tsrc, j, arg)
+ )
+ or
+ // otherwise, we directly return `suparg`
+ not (
+ t = tsrc.(GenericType).getAParameterizedType() and
+ suparg = tsrc.(GenericType).getATypeParameter()
+ ) and
+ arg = suparg
+ )
+ )
+}
+
+/** A reference type that extends a parameterization of `java.util.Collection`. */
+class CollectionType extends RefType {
+ CollectionType() {
+ exists(ParameterizedInterface coll |
+ coll.getSourceDeclaration().hasQualifiedName("java.util", "Collection")
+ |
+ this.hasSupertype*(coll)
+ )
+ }
+
+ /** Gets the type of elements stored in this collection. */
+ RefType getElementType() {
+ exists(GenericInterface coll | coll.hasQualifiedName("java.util", "Collection") |
+ indirectlyInstantiates(this, coll, 0, result)
+ )
+ }
+}
+
+/** A method declared in a collection type. */
+class CollectionMethod extends Method {
+ CollectionMethod() {
+ this.getDeclaringType() instanceof CollectionType
+ }
+
+ /** Gets the type of elements of the collection to which this method belongs. */
+ RefType getReceiverElementType() {
+ result = this.getDeclaringType().(CollectionType).getElementType()
+ }
+}
+
+/** The `size` method on `java.util.Collection`. */
+class CollectionSizeMethod extends CollectionMethod {
+ CollectionSizeMethod() {
+ this.hasName("size") and this.hasNoParameters()
+ }
+}
+
+/** A method that mutates the collection it belongs to. */
+class CollectionMutator extends CollectionMethod {
+ CollectionMutator() {
+ this.getName().regexpMatch("add.*|remove.*|push|pop|clear")
+ }
+}
+
+/** A method call that mutates a collection. */
+class CollectionMutation extends MethodAccess {
+ CollectionMutation() {
+ this.getMethod() instanceof CollectionMutator
+ }
+
+ predicate resultIsChecked() {
+ not this.getParent() instanceof ExprStmt
+ }
+}
+
+/** A method that queries the contents of a collection without mutating it. */
+class CollectionQueryMethod extends CollectionMethod {
+ CollectionQueryMethod() {
+ this.getName().regexpMatch("contains|containsAll|get|size|peek")
+ }
+}
+
+/** A `new` expression that allocates a fresh, empty collection. */
+class FreshCollection extends ClassInstanceExpr {
+ FreshCollection() {
+ this.getConstructedType() instanceof CollectionType and
+ this.getNumArgument() = 0 and
+ not exists(this.getAnonymousClass())
+ }
+}
diff --git a/java/ql/src/semmle/code/java/CompilationUnit.qll b/java/ql/src/semmle/code/java/CompilationUnit.qll
new file mode 100755
index 00000000000..c2661a498db
--- /dev/null
+++ b/java/ql/src/semmle/code/java/CompilationUnit.qll
@@ -0,0 +1,41 @@
+/**
+ * Provides classes and predicates for working with Java compilation units.
+ */
+
+import Element
+import Package
+import semmle.code.FileSystem
+
+/**
+ * A compilation unit is a `.java` or `.class` file.
+ */
+class CompilationUnit extends Element, File {
+ CompilationUnit() {
+ cupackage(this,_)
+ }
+
+ /** Gets the name of the compilation unit (not including its extension). */
+ override string getName() {
+ result = Element.super.getName()
+ }
+
+ /**
+ * Holds if this compilation unit has the specified `name`,
+ * which must not include the file extension.
+ */
+ override predicate hasName(string name) {
+ Element.super.hasName(name)
+ }
+
+ override string toString() {
+ result = Element.super.toString()
+ }
+
+ /** Gets the declared package of this compilation unit. */
+ Package getPackage() { cupackage(this,result) }
+
+ /**
+ * Gets the module associated with this compilation unit, if any.
+ */
+ Module getModule() { cumodule(this, result) }
+}
diff --git a/java/ql/src/semmle/code/java/Completion.qll b/java/ql/src/semmle/code/java/Completion.qll
new file mode 100644
index 00000000000..bd32ca4af5b
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Completion.qll
@@ -0,0 +1,87 @@
+/**
+ * Provides classes and predicates for representing completions.
+ */
+
+/*
+ * A completion represents how a statement or expression terminates.
+ *
+ * There are five kinds of completions: normal completion,
+ * `return` completion, `break` completion,
+ * `continue` completion, and `throw` completion.
+ *
+ * Normal completions are further subdivided into boolean completions and all
+ * other normal completions. A boolean completion adds the information that the
+ * cfg node terminated with the given boolean value due to a subexpression
+ * terminating with the other given boolean value. This is only
+ * relevant for conditional contexts in which the value controls the
+ * control-flow successor.
+ */
+
+import java
+
+/**
+ * A label of a `LabeledStmt`.
+ */
+newtype Label = MkLabel(string l) { exists(LabeledStmt lbl | l = lbl.getLabel()) }
+
+
+/**
+ * Either a `Label` or nothing.
+ */
+newtype MaybeLabel = JustLabel(Label l) or NoLabel()
+
+
+/**
+ * A completion of a statement or an expression.
+ */
+newtype Completion =
+ /**
+ * The statement or expression completes normally and continues to the next statement.
+ */
+ NormalCompletion() or
+ /**
+ * The statement or expression completes by returning from the function.
+ */
+ ReturnCompletion() or
+ /**
+ * The expression completes with value `outerValue` overall and with the last control
+ * flow node having value `innerValue`.
+ */
+ BooleanCompletion(boolean outerValue, boolean innerValue) {
+ (outerValue = true or outerValue = false) and (innerValue = true or innerValue = false)
+ } or
+ /**
+ * The expression or statement completes via a `break` statement.
+ */
+ BreakCompletion(MaybeLabel l) or
+ /**
+ * The expression or statement completes via a `continue` statement.
+ */
+ ContinueCompletion(MaybeLabel l) or
+ /**
+ * The expression or statement completes by throwing a `ThrowableType`.
+ */
+ ThrowCompletion(ThrowableType tt)
+
+
+ContinueCompletion anonymousContinueCompletion() {
+ result = ContinueCompletion(NoLabel())
+}
+
+ContinueCompletion labelledContinueCompletion(Label l) {
+ result = ContinueCompletion(JustLabel(l))
+}
+
+BreakCompletion anonymousBreakCompletion() {
+ result = BreakCompletion(NoLabel())
+}
+
+BreakCompletion labelledBreakCompletion(Label l) {
+ result = BreakCompletion(JustLabel(l))
+}
+
+/** Gets the completion `booleanCompletion(value, value)`. */
+Completion basicBooleanCompletion(boolean value) {
+ result = BooleanCompletion(value, value)
+}
+
diff --git a/java/ql/src/semmle/code/java/Concurrency.qll b/java/ql/src/semmle/code/java/Concurrency.qll
new file mode 100644
index 00000000000..d6f4660a39f
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Concurrency.qll
@@ -0,0 +1,48 @@
+
+import java
+
+/**
+ * Holds if `e` is synchronized by a local synchronized statement `sync` on the variable `v`.
+ */
+predicate locallySynchronizedOn(Expr e, SynchronizedStmt sync, Variable v) {
+ e.getEnclosingStmt().getParent+() = sync and
+ sync.getExpr().(VarAccess).getVariable() = v
+}
+
+/**
+ * Holds if `e` is synchronized by a local synchronized statement on a `this` of type `thisType`, or by a synchronized
+ * modifier on the enclosing (non-static) method.
+ */
+predicate locallySynchronizedOnThis(Expr e, RefType thisType) {
+ exists(SynchronizedStmt sync | e.getEnclosingStmt().getParent+() = sync |
+ sync.getExpr().getProperExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = thisType
+ )
+ or
+ exists(SynchronizedCallable c | c = e.getEnclosingCallable() |
+ not c.isStatic() and thisType = c.getDeclaringType()
+ )
+}
+
+/**
+ * Holds if `e` is synchronized by a `synchronized` modifier on the enclosing (static) method.
+ */
+predicate locallySynchronizedOnClass(Expr e, RefType classType) {
+ exists(SynchronizedCallable c | c = e.getEnclosingCallable() |
+ c.isStatic() and classType = c.getDeclaringType()
+ )
+}
+
+/**
+ * A callable that is synchronized on its enclosing instance, either by a `synchronized` modifier, or
+ * by having a body which is precisely `synchronized(this) { ... }`.
+ */
+class SynchronizedCallable extends Callable {
+ SynchronizedCallable() {
+ this.isSynchronized()
+ or
+ // The body is just `synchronized(this) { ... }`.
+ exists(SynchronizedStmt s | this.getBody().(SingletonBlock).getStmt() = s |
+ s.getExpr().(ThisAccess).getType() = this.getDeclaringType()
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/ControlFlowGraph.qll b/java/ql/src/semmle/code/java/ControlFlowGraph.qll
new file mode 100644
index 00000000000..94a4f633e5a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/ControlFlowGraph.qll
@@ -0,0 +1,1009 @@
+/**
+ * Provides classes and predicates for computing expression-level intra-procedural control flow graphs.
+ *
+ * The only API exported by this library are the toplevel classes `ControlFlowNode`
+ * and its subclass `ConditionNode`, which wrap the successor relation and the
+ * concept of true- and false-successors of conditions. A cfg node may either be a
+ * statement, an expression, or the enclosing callable, indicating that
+ * execution of the callable terminates.
+ */
+
+/*
+ * The implementation is centered around the concept of a _completion_, which
+ * models how the execution of a statement or expression terminates.
+ * Completions are represented as an algebraic data type `Completion` defined in
+ * `Completion.qll`.
+ *
+ * The CFG is built by structural recursion over the AST. To achieve this the
+ * CFG edges related to a given AST node, `n`, is divided into three categories:
+ * 1. The in-going edge that points to the first CFG node to execute when the
+ * `n` is going to be executed.
+ * 2. The out-going edges for control-flow leaving `n` that are going to some
+ * other node in the surrounding context of `n`.
+ * 3. The edges that have both of their end-points entirely within the AST
+ * node and its children.
+ * The edges in (1) and (2) are inherently non-local and are therefore
+ * initially calculated as half-edges, that is, the single node, `k`, of the
+ * edge contained within `n`, by the predicates `k = first(n)` and
+ * `last(n, k, _)`, respectively. The edges in (3) can then be enumerated
+ * directly by the predicate `succ` by calling `first` and `last` recursively
+ * on the children of `n` and connecting the end-points. This yields the entire
+ * CFG, since all edges are in (3) for _some_ AST node.
+ *
+ * The third parameter of `last` is the completion, which is necessary to
+ * distinguish the out-going edges from `n`. Note that the completion changes
+ * as the calculation of `last` proceeds outward through the AST; for example,
+ * a `breakCompletion` is caught up by its surrounding loop and turned into a
+ * `normalCompletion`, or a `normalCompletion` proceeds outward through the end
+ * of a `finally` block and is turned into whatever completion was caught by
+ * the `finally`, or a `booleanCompletion(false, _)` occurs in a loop condition
+ * and is turned into a `normalCompletion` of the entire loop. When the edge is
+ * eventually connected we use the completion at that level of the AST as the
+ * label of the edge, thus creating an edge-labelled CFG.
+ *
+ * An important goal of the CFG is to get the order of side-effects correct.
+ * Most expressions can have side-effects and must therefore be modeled in the
+ * CFG in AST post-order. For example, a `MethodAccess` evaluates its arguments
+ * before the call. Most statements don't have side-effects, but merely affect
+ * the control-flow and some could therefore be excluded from the CFG. However,
+ * as a design choice, all statements are included in the CFG and generally
+ * serve as their own entry-points, thus executing in some version of AST
+ * pre-order. A few notable exceptions are `ReturnStmt`, `ThrowStmt`,
+ * `SynchronizedStmt`, `ThisConstructorInvocationStmt`, and
+ * `SuperConstructorInvocationStmt`, which all have side-effects and therefore
+ * are modeled in side-effect order. Loop statement nodes are only passed on
+ * entry, after which control goes back and forth between body and loop
+ * condition.
+ *
+ * Some out-going edges from boolean expressions have a known value and in some
+ * contexts this affects the possible successors. For example, in `if(A || B)`
+ * a short-circuit edge that skips `B` must be true and can therefore only lead
+ * to the then-branch. If the `||` is modeled in post-order then this
+ * information is lost, and consequently it is better to model `||` and `&&` in
+ * pre-order. The conditional expression `? :` is also modeled in pre-order to
+ * achieve consistent CFGs for the equivalent `A && B` and `A ? B : false`.
+ * Finally, the logical negation is also modeled in pre-order to achieve
+ * consistent CFGs for the equivalent `!(A || B)` and `!A && !B`. The boolean
+ * value `b` is tracked with the completion `booleanCompletion(b, _)`.
+ *
+ * Note that the second parameter in a `booleanCompletion` isn't needed to
+ * calculate the CFG. It is, however, needed to track the value of the
+ * sub-expression. For example, this ensures that the false-successor of the
+ * `ConditionNode` `A` in `if(!(A && B))` can be correctly identified as the
+ * then-branch (even though this completion turns into a
+ * `booleanCompletion(true, _)` from the perspective of the `if`-node).
+ *
+ * As a final note, expressions that aren't actually executed in the usual
+ * sense are excluded from the CFG. This covers, for example, parentheses,
+ * l-values that aren't r-values as well, and expressions in `ConstCase`s.
+ * For example, the `x` in `x=3` is not in the CFG, but the `x` in `x+=3` is.
+ */
+
+import java
+
+private import Completion
+
+/** A node in the expression-level control-flow graph. */
+class ControlFlowNode extends Top, @exprparent {
+ /** Gets the statement containing this node, if any. */
+ Stmt getEnclosingStmt() {
+ result = this or
+ result = this.(Expr).getEnclosingStmt()
+ }
+
+ /** Gets the immediately enclosing callable whose body contains this node. */
+ Callable getEnclosingCallable() {
+ result = this or
+ result = this.(Stmt).getEnclosingCallable() or
+ result = this.(Expr).getEnclosingCallable()
+ }
+
+ /** Gets an immediate successor of this node. */
+ ControlFlowNode getASuccessor() {
+ result = succ(this)
+ }
+
+ /** Gets an immediate predecessor of this node. */
+ ControlFlowNode getAPredecessor() {
+ this = succ(result)
+ }
+
+ /** Gets an exception successor of this node. */
+ ControlFlowNode getAnExceptionSuccessor() {
+ result = succ(this, ThrowCompletion(_))
+ }
+
+ /** Gets a successor of this node that is neither an exception successor nor a jump (break, continue, return). */
+ ControlFlowNode getANormalSuccessor() {
+ result = succ(this, BooleanCompletion(_, _)) or
+ result = succ(this, NormalCompletion())
+ }
+
+ BasicBlock getBasicBlock() {
+ result.getANode() = this
+ }
+}
+
+/** Gets the intra-procedural successor of `n`. */
+private ControlFlowNode succ(ControlFlowNode n) {
+ result = succ(n, _)
+}
+
+private cached module ControlFlowGraphImpl {
+
+ /**
+ * Gets a label that applies to this statement.
+ */
+ private Label getLabel(Stmt s) {
+ exists(LabeledStmt l | s = l.getStmt() |
+ result = MkLabel(l.getLabel()) or
+ result = getLabel(l)
+ )
+ }
+
+ /**
+ * A throwable that's a (reflexive, transitive) supertype of an unchecked
+ * exception. Besides the unchecked exceptions themselves, this includes
+ * `java.lang.Throwable` and `java.lang.Exception`.
+ */
+ private class UncheckedThrowableSuperType extends RefType {
+ UncheckedThrowableSuperType() {
+ this instanceof TypeThrowable or
+ this instanceof TypeException or
+ this instanceof UncheckedThrowableType
+ }
+
+ /** An unchecked throwable that is a subtype of this `UncheckedThrowableSuperType` and
+ * sits as high as possible in the type hierarchy. This is mostly unique except for
+ * `TypeThrowable` which results in both `TypeError` and `TypeRuntimeException`.
+ */
+ UncheckedThrowableType getAnUncheckedSubtype() {
+ result = (UncheckedThrowableType)this or
+ result instanceof TypeError and this instanceof TypeThrowable or
+ result instanceof TypeRuntimeException and (this instanceof TypeThrowable or this instanceof TypeException)
+ }
+ }
+
+ /**
+ * Bind `t` to an exception type that may be thrown during execution of `n`,
+ * either because `n` is a `throw` statement, or because it is a call
+ * that may throw an exception, or because it is a cast and a
+ * `ClassCastException` is expected.
+ */
+ private predicate mayThrow(ControlFlowNode n, ThrowableType t) {
+ t = n.(ThrowStmt).getThrownExceptionType() or
+ exists(Call c | c = n |
+ t = c.getCallee().getAThrownExceptionType() or
+ uncheckedExceptionFromCatch(n, t) or
+ uncheckedExceptionFromFinally(n, t)
+ ) or
+ exists(CastExpr c | c = n |
+ t instanceof TypeClassCastException and
+ uncheckedExceptionFromCatch(n, t)
+ )
+ }
+
+ /**
+ * Bind `t` to an unchecked exception that may transfer control to a finally
+ * block inside which `n` is nested.
+ */
+ private predicate uncheckedExceptionFromFinally(ControlFlowNode n, ThrowableType t) {
+ exists(TryStmt try |
+ n.getEnclosingStmt().getParent+() = try.getBlock() or
+ n.(Expr).getParent*() = try.getAResource()
+ |
+ exists(try.getFinally()) and
+ (t instanceof TypeError or t instanceof TypeRuntimeException)
+ )
+ }
+
+ /**
+ * Bind `t` to all unchecked exceptions that may be caught by some
+ * `try-catch` inside which `n` is nested.
+ */
+ private predicate uncheckedExceptionFromCatch(ControlFlowNode n, ThrowableType t) {
+ exists(TryStmt try, UncheckedThrowableSuperType caught |
+ n.getEnclosingStmt().getParent+() = try.getBlock() or
+ n.(Expr).getParent*() = try.getAResource()
+ |
+ t = caught.getAnUncheckedSubtype() and
+ try.getACatchClause().getACaughtType() = caught
+ )
+ }
+
+ /**
+ * Gets an exception type that may be thrown during execution of the
+ * body or the resources (if any) of `try`.
+ */
+ private ThrowableType thrownInBody(TryStmt try) {
+ exists(ControlFlowNode n | mayThrow(n, result) |
+ n.getEnclosingStmt().getParent+() = try.getBlock() or
+ n.(Expr).getParent*() = try.getAResource()
+ )
+ }
+
+ /**
+ * Bind `thrown` to an exception type that may be thrown during execution
+ * of the body or the resource declarations of the `try` block to which
+ * `c` belongs, such that `c` definitely catches that exception (if no
+ * prior catch clause handles it).
+ */
+ private predicate mustCatch(CatchClause c, ThrowableType thrown) {
+ thrown = thrownInBody(c.getTry()) and
+ hasSubtype*(c.getACaughtType(), thrown)
+ }
+
+ /**
+ * Bind `thrown` to an exception type that may be thrown during execution
+ * of the body or the resource declarations of the `try` block to which
+ * `c` belongs, such that `c` may _not_ catch that exception.
+ *
+ * This predicate computes the complement of `mustCatch` over those
+ * exception types that are thrown in the body/resource declarations of
+ * the corresponding `try`.
+ */
+ private predicate mayNotCatch(CatchClause c, ThrowableType thrown) {
+ thrown = thrownInBody(c.getTry()) and
+ not hasSubtype*(c.getACaughtType(), thrown)
+ }
+
+ /**
+ * Bind `thrown` to an exception type that may be thrown during execution
+ * of the body or the resource declarations of the `try` block to which
+ * `c` belongs, such that `c` possibly catches that exception.
+ */
+ private predicate mayCatch(CatchClause c, ThrowableType thrown) {
+ mustCatch(c, thrown) or
+ mayNotCatch(c, thrown) and exists(c.getACaughtType().commonSubtype(thrown))
+ }
+
+ /**
+ * Given an exception type `thrown`, determine which catch clauses of
+ * `try` may possibly catch that exception.
+ */
+ private CatchClause handlingCatchClause(TryStmt try, ThrowableType thrown) {
+ exists(int i | result = try.getCatchClause(i) |
+ mayCatch(result, thrown) and
+ not exists(int j | j < i | mustCatch(try.getCatchClause(j), thrown))
+ )
+ }
+
+ /**
+ * Boolean expressions that occur in a context in which their value affect control flow.
+ * That is, contexts where the control-flow edges depend on `value` given that `b` ends
+ * with a `booleanCompletion(value, _)`.
+ */
+ private predicate inBooleanContext(Expr b) {
+ exists(LogicExpr logexpr |
+ logexpr.(BinaryExpr).getLeftOperand() = b or
+ // Cannot use LogicExpr.getAnOperand or BinaryExpr.getAnOperand as they remove parentheses.
+ logexpr.(BinaryExpr).getRightOperand() = b and inBooleanContext(logexpr) or
+ logexpr.(UnaryExpr).getExpr() = b and inBooleanContext(logexpr)
+ )
+ or
+ exists(ParExpr parexpr |
+ parexpr.getExpr() = b and inBooleanContext(parexpr)
+ )
+ or
+ exists(ConditionalExpr condexpr |
+ condexpr.getCondition() = b or
+ (condexpr.getTrueExpr() = b or condexpr.getFalseExpr() = b) and inBooleanContext(condexpr)
+ )
+ or
+ exists(ConditionalStmt condstmt |
+ condstmt.getCondition() = b
+ )
+ }
+
+ /**
+ * A virtual method with a unique implementation. That is, the method does not
+ * participate in overriding and there are no call targets that could dispatch
+ * to both this and another method.
+ */
+ private class EffectivelyNonVirtualMethod extends SrcMethod {
+ EffectivelyNonVirtualMethod() {
+ exists(this.getBody()) and
+ this.isVirtual() and
+ not this = any(Method m).getASourceOverriddenMethod() and
+ not this.overrides(_) and
+ // guard against implicit overrides of default methods
+ not this.getAPossibleImplementationOfSrcMethod() != this and
+ // guard against interface implementations in inheriting subclasses
+ not exists(SrcMethod m |
+ 1 < strictcount(m.getAPossibleImplementationOfSrcMethod()) and
+ this = m.getAPossibleImplementationOfSrcMethod()
+ ) and
+ // UnsupportedOperationException could indicate that this is meant to be overridden
+ not exists(ClassInstanceExpr ex |
+ this.getBody().getLastStmt().(ThrowStmt).getExpr() = ex and
+ ex.getConstructedType().hasQualifiedName("java.lang", "UnsupportedOperationException")
+ ) and
+ // an unused parameter could indicate that this is meant to be overridden
+ forall(Parameter p | p = this.getAParameter() | exists(p.getAnAccess()))
+ }
+
+ /** Gets a `MethodAccess` that calls this method. */
+ MethodAccess getAnAccess() {
+ result.getMethod().getAPossibleImplementation() = this
+ }
+ }
+
+ /** Holds if a call to `m` indicates that `m` is expected to return. */
+ private predicate expectedReturn(EffectivelyNonVirtualMethod m) {
+ exists(Stmt s, Block b |
+ m.getAnAccess().getEnclosingStmt() = s and
+ b.getAStmt() = s and
+ not b.getLastStmt() = s
+ )
+ }
+
+ /**
+ * Gets a non-overridable method that always throws an exception or calls `exit`.
+ */
+ private Method nonReturningMethod() {
+ result instanceof MethodExit or
+ not result.isOverridable() and
+ exists(Block body |
+ body = result.getBody() and
+ not exists(ReturnStmt ret | ret.getEnclosingCallable() = result)
+ |
+ not result.getReturnType() instanceof VoidType or
+ body.getLastStmt() = nonReturningStmt()
+ )
+ }
+
+ /**
+ * Gets a virtual method that always throws an exception or calls `exit`.
+ */
+ private EffectivelyNonVirtualMethod likelyNonReturningMethod() {
+ result.getReturnType() instanceof VoidType and
+ not exists(ReturnStmt ret | ret.getEnclosingCallable() = result) and
+ not expectedReturn(result) and
+ forall(Parameter p | p = result.getAParameter() | exists(p.getAnAccess())) and
+ result.getBody().getLastStmt() = nonReturningStmt()
+ }
+
+ /**
+ * Gets a `MethodAccess` that always throws an exception or calls `exit`.
+ */
+ private MethodAccess nonReturningMethodAccess() {
+ result.getMethod().getSourceDeclaration() = nonReturningMethod() or
+ result = likelyNonReturningMethod().getAnAccess()
+ }
+
+ /**
+ * Gets a statement that always throws an exception or calls `exit`.
+ */
+ private Stmt nonReturningStmt() {
+ result instanceof ThrowStmt or
+ result.(ExprStmt).getExpr() = nonReturningMethodAccess() or
+ result.(Block).getLastStmt() = nonReturningStmt() or
+ exists(IfStmt ifstmt | ifstmt = result |
+ ifstmt.getThen() = nonReturningStmt() and
+ ifstmt.getElse() = nonReturningStmt()
+ ) or
+ exists(TryStmt try | try = result |
+ try.getBlock() = nonReturningStmt() and
+ forall(CatchClause cc | cc = try.getACatchClause() | cc.getBlock() = nonReturningStmt())
+ )
+ }
+
+ /**
+ * Expressions and statements with CFG edges in post-order AST traversal.
+ *
+ * This includes most expressions, except those that initiate or propagate branching control
+ * flow (`LogicExpr`, `ConditionalExpr`), and parentheses, which aren't in the CFG.
+ * Only a few statements are included; those with specific side-effects
+ * occurring after the evaluation of their children, that is, `Call`, `ReturnStmt`,
+ * and `ThrowStmt`. CFG nodes without child nodes in the CFG that may complete
+ * normally are also included.
+ */
+ private
+ class PostOrderNode extends ControlFlowNode {
+ PostOrderNode() {
+ // For VarAccess and ArrayAccess only read accesses (r-values) are included,
+ // as write accesses aren't included in the CFG.
+ this instanceof ArrayAccess and not exists(AssignExpr a | this = a.getDest()) or
+ this instanceof ArrayCreationExpr or
+ this instanceof ArrayInit or
+ this instanceof Assignment or
+ this instanceof BinaryExpr and not this instanceof LogicExpr or
+ this instanceof UnaryExpr and not this instanceof LogNotExpr or
+ this instanceof CastExpr or
+ this instanceof InstanceOfExpr or
+ this instanceof LocalVariableDeclExpr or
+ this instanceof RValue or
+ this instanceof Call or // includes both expressions and statements
+ this instanceof ReturnStmt or
+ this instanceof ThrowStmt or
+ this instanceof Literal or
+ this instanceof TypeLiteral or
+ this instanceof ThisAccess or
+ this instanceof SuperAccess or
+ this.(Block).getNumStmt() = 0 or
+ this instanceof SwitchCase or
+ this instanceof EmptyStmt or
+ this instanceof LocalClassDeclStmt or
+ this instanceof AssertStmt
+ }
+
+ /** Gets child nodes in their order of execution. Indexing starts at either -1 or 0. */
+ ControlFlowNode getChildNode(int index) {
+ exists(ArrayAccess e | e = this |
+ index = 0 and result = e.getArray() or
+ index = 1 and result = e.getIndexExpr()
+ ) or
+ exists(ArrayCreationExpr e | e = this |
+ result = e.getDimension(index) or
+ index = count(e.getADimension()) and result = e.getInit()
+ ) or
+ result = this.(ArrayInit).getInit(index) and index >= 0 or
+ exists(AssignExpr e, ArrayAccess lhs | e = this and lhs = e.getDest() |
+ index = 0 and result = lhs.getArray() or
+ index = 1 and result = lhs.getIndexExpr() or
+ index = 2 and result = e.getSource()
+ ) or
+ exists(AssignExpr e, VarAccess lhs | e = this and lhs = e.getDest() |
+ index = -1 and result = lhs.getQualifier() and not result instanceof TypeAccess or
+ index = 0 and result = e.getSource()
+ ) or
+ exists(AssignOp e | e = this |
+ index = 0 and result = e.getDest() or
+ index = 1 and result = e.getRhs()
+ ) or
+ exists(BinaryExpr e | e = this |
+ index = 0 and result = e.getLeftOperand() or
+ index = 1 and result = e.getRightOperand()
+ ) or
+ index = 0 and result = this.(UnaryExpr).getExpr() or
+ index = 0 and result = this.(CastExpr).getExpr() or
+ index = 0 and result = this.(InstanceOfExpr).getExpr() or
+ index = 0 and result = this.(LocalVariableDeclExpr).getInit() or
+ index = 0 and result = this.(RValue).getQualifier() and not result instanceof TypeAccess or
+ exists(Call e | e = this |
+ index = -1 and result = e.getQualifier() and not result instanceof TypeAccess or
+ result = e.getArgument(index)
+ ) or
+ index = 0 and result = this.(ReturnStmt).getResult() or
+ index = 0 and result = this.(ThrowStmt).getExpr() or
+ index = 0 and result = this.(AssertStmt).getExpr()
+ }
+
+ /** Gets the first child node, if any. */
+ ControlFlowNode firstChild() {
+ result = getChildNode(-1) or
+ result = getChildNode(0) and not exists(getChildNode(-1))
+ }
+
+ /** Holds if this CFG node has any child nodes. */
+ predicate isLeafNode() {
+ not exists(getChildNode(_))
+ }
+
+ /** Holds if this node can finish with a `normalCompletion`. */
+ predicate mayCompleteNormally() {
+ not this instanceof BooleanLiteral and
+ not this instanceof ReturnStmt and
+ not this instanceof ThrowStmt and
+ not this = nonReturningMethodAccess()
+ }
+ }
+
+ /**
+ * If the body of `loop` finishes with `completion`, the loop will
+ * continue executing (provided the loop condition still holds).
+ */
+ private predicate continues(Completion completion, LoopStmt loop) {
+ completion = NormalCompletion() or
+ // only consider continue completions if there actually is a `continue`
+ // somewhere inside this loop; we don't particularly care whether that
+ // `continue` could actually target this loop, we just want to restrict
+ // the size of the predicate
+ exists(ContinueStmt cnt | cnt.getParent+() = loop |
+ completion = anonymousContinueCompletion() or
+ completion = labelledContinueCompletion(getLabel(loop))
+ )
+ }
+
+ /**
+ * Determine the part of the AST node `n` that will be executed first.
+ */
+ private ControlFlowNode first(ControlFlowNode n) {
+ result = n and n instanceof LogicExpr or
+ result = n and n instanceof ConditionalExpr or
+ result = n and n.(PostOrderNode).isLeafNode() or
+ result = first(n.(PostOrderNode).firstChild()) or
+ result = first(n.(ParExpr).getExpr()) or
+ result = first(n.(SynchronizedStmt).getExpr()) or
+ result = n and n instanceof Stmt and
+ not n instanceof PostOrderNode and
+ not n instanceof SynchronizedStmt
+ }
+
+ /**
+ * Bind `last` to a node inside the body of `try` that may finish with `completion`
+ * such that control will be transferred to a `catch` block or the `finally` block of `try`.
+ *
+ * In other words, `last` is either a resource declaration that throws, or a
+ * node in the `try` block that may not complete normally, or a node in
+ * the `try` block that has no control flow successors inside the block.
+ */
+ private predicate catchOrFinallyCompletion(TryStmt try, ControlFlowNode last, Completion completion) {
+ last(try.getBlock(), last, completion) or
+ last(try.getAResource(), last, completion) and completion = ThrowCompletion(_)
+ }
+
+ /**
+ * Bind `last` to a node inside the body of `try` that may finish with `completion`
+ * such that control may be transferred to the `finally` block (if it exists).
+ *
+ * In other words, if `last` throws an exception it is possibly not caught by any
+ * of the catch clauses.
+ */
+ private predicate uncaught(TryStmt try, ControlFlowNode last, Completion completion) {
+ catchOrFinallyCompletion(try, last, completion) and
+ (
+ exists(ThrowableType thrown |
+ thrown = thrownInBody(try) and completion = ThrowCompletion(thrown) and
+ not mustCatch(try.getACatchClause(), thrown)
+ ) or
+ completion = NormalCompletion() or completion = ReturnCompletion() or
+ completion = anonymousBreakCompletion() or completion = labelledBreakCompletion(_) or
+ completion = anonymousContinueCompletion() or completion = labelledContinueCompletion(_)
+ )
+ }
+
+ /**
+ * Bind `last` to a node inside `try` that may finish with `completion` such
+ * that control may be transferred to the `finally` block (if it exists).
+ *
+ * This is similar to `uncaught`, but also includes final statements of `catch`
+ * clauses.
+ */
+ private predicate finallyPred(TryStmt try, ControlFlowNode last, Completion completion) {
+ uncaught(try, last, completion) or
+ last(try.getACatchClause(), last, completion)
+ }
+
+ private predicate lastInFinally(TryStmt try, ControlFlowNode last) {
+ last(try.getFinally(), last, NormalCompletion())
+ }
+
+ /**
+ * Bind `last` to a cfg node nested inside `n` (or, indeed, `n` itself) such
+ * that `last` may be the last node during an execution of `n` and finish
+ * with the given completion.
+ *
+ * A `booleanCompletion` implies that `n` is an `Expr`. Any abnormal
+ * completion besides `throwCompletion` implies that `n` is a `Stmt`.
+ */
+ private predicate last(ControlFlowNode n, ControlFlowNode last, Completion completion) {
+ // Exceptions are propagated from any sub-expression.
+ exists(Expr e | e.getParent() = n | last(e, last, completion) and completion = ThrowCompletion(_)) or
+
+ // If an expression doesn't finish with a throw completion, then it executes normally with
+ // either a `normalCompletion` or a `booleanCompletion`.
+
+ // A boolean completion in a non-boolean context just indicates a normal completion
+ // and a normal completion in a boolean context indicates an arbitrary boolean completion.
+ last(n, last, NormalCompletion()) and inBooleanContext(n) and completion = basicBooleanCompletion(_) or
+ last(n, last, BooleanCompletion(_, _)) and not inBooleanContext(n) and completion = NormalCompletion() or
+
+ // Logic expressions and conditional expressions are executed in AST pre-order to facilitate
+ // proper short-circuit representation. All other expressions are executed in post-order.
+
+ // The last node of a logic expression is either in the right operand with an arbitrary
+ // completion, or in the left operand with the corresponding boolean completion.
+ exists(AndLogicalExpr andexpr | andexpr = n |
+ last(andexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(false, _) or
+ last(andexpr.getRightOperand(), last, completion)
+ ) or
+ exists(OrLogicalExpr orexpr | orexpr = n |
+ last(orexpr.getLeftOperand(), last, completion) and completion = BooleanCompletion(true, _) or
+ last(orexpr.getRightOperand(), last, completion)
+ ) or
+
+ // The last node of a `LogNotExpr` is in its sub-expression with an inverted boolean completion
+ // (or a `normalCompletion`).
+ exists(Completion subcompletion | last(n.(LogNotExpr).getExpr(), last, subcompletion) |
+ subcompletion = NormalCompletion() and completion = NormalCompletion() and not inBooleanContext(n) or
+ exists(boolean outervalue, boolean innervalue |
+ subcompletion = BooleanCompletion(outervalue, innervalue) and
+ completion = BooleanCompletion(outervalue.booleanNot(), innervalue)
+ )
+ ) or
+
+ // The last node of a `ConditionalExpr` is in either of its branches.
+ exists(ConditionalExpr condexpr | condexpr = n |
+ last(condexpr.getFalseExpr(), last, completion) or
+ last(condexpr.getTrueExpr(), last, completion)
+ ) or
+
+ // Parentheses are skipped in the CFG.
+ last(n.(ParExpr).getExpr(), last, completion) or
+
+ // The last node of a node executed in post-order is the node itself.
+ n.(PostOrderNode).mayCompleteNormally() and last = n and completion = NormalCompletion() or
+
+ last = n and completion = basicBooleanCompletion(n.(BooleanLiteral).getBooleanValue()) or
+
+ // The last statement in a block is any statement that does not complete normally,
+ // or the last statement.
+ exists(Block blk | blk = n |
+ last(blk.getAStmt(), last, completion) and completion != NormalCompletion() or
+ last(blk.getStmt(blk.getNumStmt()-1), last, completion)
+ ) or
+
+ // The last node in an `if` statement is the last node in either of its branches or
+ // the last node of the condition with a false-completion in the absence of an else-branch.
+ exists(IfStmt ifstmt | ifstmt = n |
+ last(ifstmt.getCondition(), last, BooleanCompletion(false, _)) and completion = NormalCompletion() and not exists(ifstmt.getElse()) or
+ last(ifstmt.getThen(), last, completion) or
+ last(ifstmt.getElse(), last, completion)
+ ) or
+
+ // A loop may terminate normally if its condition is false...
+ exists(LoopStmt loop | loop = n |
+ last(loop.getCondition(), last, BooleanCompletion(false, _)) and completion = NormalCompletion() or
+ // ...or if it's an enhanced for loop running out of items to iterate over...
+ // ...which may happen either immediately after the loop expression...
+ last(loop.(EnhancedForStmt).getExpr(), last, completion) and completion = NormalCompletion() or
+ exists(Completion bodyCompletion | last(loop.getBody(), last, bodyCompletion) |
+ // ...or after the last node in the loop's body in an iteration that would otherwise continue.
+ loop instanceof EnhancedForStmt and continues(bodyCompletion, loop) and completion = NormalCompletion() or
+ // Otherwise the last node is the last node in the loop's body...
+ // ...if it is an unlabelled `break` (causing the entire loop to complete normally)
+ (if bodyCompletion = anonymousBreakCompletion() then
+ completion = NormalCompletion()
+ // ...or if it is some other completion that does not continue the loop.
+ else
+ (not continues(bodyCompletion, loop) and completion = bodyCompletion))
+ )
+ ) or
+
+ // `try` statements are a bit more complicated:
+ exists(TryStmt try | try = n |
+ // the last node in a `try` is the last node in its `finally` block
+
+ // if the `finally` block completes normally, it resumes any completion that
+ // was current before the `finally` block was entered
+ lastInFinally(try, last) and
+ finallyPred(try, _, completion)
+ or
+ // otherwise, just take the completion of the `finally` block itself
+ last(try.getFinally(), last, completion) and
+ completion != NormalCompletion()
+ or
+
+ // if there is no `finally` block, take the last node of the body or
+ // any of the `catch` clauses
+ not exists(try.getFinally()) and finallyPred(try, last, completion)
+ ) or
+
+ // handle `switch` statements
+ exists(SwitchStmt switch | switch = n |
+ // unlabelled `break` causes the whole `switch` to complete normally
+ last(switch.getAStmt(), last, anonymousBreakCompletion()) and
+ completion = NormalCompletion()
+ or
+ // any other abnormal completion is propagated
+ last(switch.getAStmt(), last, completion) and
+ completion != anonymousBreakCompletion() and
+ completion != NormalCompletion()
+ or
+ // if the last case completes normally, then so does the switch
+ last(switch.getStmt(strictcount(switch.getAStmt())-1), last, NormalCompletion()) and
+ completion = NormalCompletion()
+ or
+ // if no default case exists, then normal completion of the expression may terminate the switch
+ not exists(switch.getDefaultCase()) and
+ last(switch.getExpr(), last, completion) and
+ completion = NormalCompletion()
+ ) or
+
+ // the last statement of a synchronized statement is the last statement of its body
+ last(n.(SynchronizedStmt).getBlock(), last, completion) or
+
+ // `return` statements give rise to a `Return` completion
+ last = (ReturnStmt)n and completion = ReturnCompletion() or
+
+ // `throw` statements or throwing calls give rise to ` Throw` completion
+ exists(ThrowableType tt | mayThrow(n, tt) | last = n and completion = ThrowCompletion(tt)) or
+
+ // `break` statements give rise to a `Break` completion
+ exists(BreakStmt break | break = n and last = n |
+ completion = labelledBreakCompletion(MkLabel(break.getLabel())) or
+ not exists(break.getLabel()) and completion = anonymousBreakCompletion()
+ ) or
+
+ // `continue` statements give rise to a `Continue` completion
+ exists(ContinueStmt cont | cont = n and last = n |
+ completion = labelledContinueCompletion(MkLabel(cont.getLabel())) or
+ not exists(cont.getLabel()) and completion = anonymousContinueCompletion()
+ ) or
+
+ // the last node in an `ExprStmt` is the last node in the expression
+ last(n.(ExprStmt).getExpr(), last, completion) and completion = NormalCompletion() or
+
+ // the last statement of a labeled statement is the last statement of its body...
+ exists(LabeledStmt lbl, Completion bodyCompletion | lbl = n and last(lbl.getStmt(), last, bodyCompletion) |
+ // ...except if it's a `break` that refers to this labelled statement
+ if bodyCompletion = labelledBreakCompletion(MkLabel(lbl.getLabel())) then
+ completion = NormalCompletion()
+ else
+ completion = bodyCompletion
+ ) or
+
+ // the last statement of a `catch` clause is the last statement of its block
+ last(n.(CatchClause).getBlock(), last, completion) or
+
+ // the last node in a variable declaration statement is in the last of its individual declarations
+ exists(LocalVariableDeclStmt s | s = n |
+ last(s.getVariable(count(s.getAVariable())), last, completion) and completion = NormalCompletion()
+ )
+ }
+
+ /**
+ * Compute the intra-procedural successors of cfg node `n`, assuming its
+ * execution finishes with the given completion.
+ */
+ cached
+ ControlFlowNode succ(ControlFlowNode n, Completion completion) {
+ // Callables serve as their own exit nodes.
+ exists(Callable c | last(c.getBody(), n, completion) | result = c) or
+
+ // Logic expressions and conditional expressions execute in AST pre-order.
+ completion = NormalCompletion() and
+ (result = first(n.(AndLogicalExpr).getLeftOperand()) or
+ result = first(n.(OrLogicalExpr).getLeftOperand()) or
+ result = first(n.(LogNotExpr).getExpr()) or
+ result = first(n.(ConditionalExpr).getCondition())) or
+
+ // If a logic expression doesn't short-circuit then control flows from its left operand to its right.
+ exists(AndLogicalExpr e |
+ last(e.getLeftOperand(), n, completion) and completion = BooleanCompletion(true, _) and
+ result = first(e.getRightOperand())
+ ) or
+ exists(OrLogicalExpr e |
+ last(e.getLeftOperand(), n, completion) and completion = BooleanCompletion(false, _) and
+ result = first(e.getRightOperand())
+ ) or
+
+ // Control flows to the corresponding branch depending on the boolean completion of the condition.
+ exists(ConditionalExpr e |
+ last(e.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(e.getTrueExpr()) or
+ last(e.getCondition(), n, completion) and completion = BooleanCompletion(false, _) and result = first(e.getFalseExpr())
+ ) or
+
+ // In other expressions control flows from left to right and ends in the node itself.
+ exists(PostOrderNode p, int i | last(p.getChildNode(i), n, completion) and completion = NormalCompletion() |
+ result = first(p.getChildNode(i+1)) or
+ not exists(p.getChildNode(i+1)) and result = p
+ ) or
+
+ // Statements within a block execute sequentially.
+ result = first(n.(Block).getStmt(0)) and completion = NormalCompletion() or
+ exists(Block blk, int i |
+ last(blk.getStmt(i), n, completion) and completion = NormalCompletion() and result = first(blk.getStmt(i+1))
+ ) or
+
+ // Control flows to the corresponding branch depending on the boolean completion of the condition.
+ exists(IfStmt s |
+ n = s and result = first(s.getCondition()) and completion = NormalCompletion() or
+ last(s.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(s.getThen()) or
+ last(s.getCondition(), n, completion) and completion = BooleanCompletion(false, _) and result = first(s.getElse())
+ ) or
+
+ // For statements:
+ exists(ForStmt for, ControlFlowNode condentry |
+ // Any part of the control flow that aims for the condition needs to hit either the condition...
+ condentry = first(for.getCondition()) or
+ // ...or the body if the for doesn't include a condition.
+ not exists(for.getCondition()) and condentry = first(for.getStmt())
+ |
+ // From the entry point, which is the for statement itself, control goes to either the first init expression...
+ n = for and result = first(for.getInit(0)) and completion = NormalCompletion() or
+ // ...or the condition if the for doesn't include init expressions.
+ n = for and not exists(for.getAnInit()) and result = condentry and completion = NormalCompletion() or
+ // Init expressions execute sequentially, after which control is transferred to the condition.
+ exists(int i | last(for.getInit(i), n, completion) and completion = NormalCompletion() |
+ result = first(for.getInit(i+1)) or
+ not exists(for.getInit(i+1)) and result = condentry
+ ) or
+ // The true-successor of the condition is the body of the for loop.
+ last(for.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(for.getStmt()) or
+ // The updates execute sequentially, after which control is transferred to the condition.
+ exists(int i | last(for.getUpdate(i), n, completion) and completion = NormalCompletion() |
+ result = first(for.getUpdate(i+1)) or
+ not exists(for.getUpdate(i+1)) and result = condentry
+ ) or
+ // The back edge of the loop: control goes to either the first update or the condition if no updates exist.
+ last(for.getStmt(), n, completion) and continues(completion, for) and
+ (result = first(for.getUpdate(0)) or
+ result = condentry and not exists(for.getAnUpdate()))
+ ) or
+
+ // Enhanced for statements:
+ exists(EnhancedForStmt for |
+ // First the expression gets evaluated...
+ n = for and result = first(for.getExpr()) and completion = NormalCompletion() or
+ // ...then the variable gets assigned...
+ last(for.getExpr(), n, completion) and completion = NormalCompletion() and result = for.getVariable() or
+ // ...and then control goes to the body of the loop.
+ n = for.getVariable() and result = first(for.getStmt()) and completion = NormalCompletion() or
+ // Finally, the back edge of the loop goes to reassign the variable.
+ last(for.getStmt(), n, completion) and continues(completion, for) and result = for.getVariable()
+ ) or
+
+ // While loops start at the condition...
+ result = first(n.(WhileStmt).getCondition()) and completion = NormalCompletion() or
+ // ...and do-while loops start at the body.
+ result = first(n.(DoStmt).getStmt()) and completion = NormalCompletion() or
+ exists(LoopStmt loop | loop instanceof WhileStmt or loop instanceof DoStmt |
+ // Control goes from the condition via a true-completion to the body...
+ last(loop.getCondition(), n, completion) and completion = BooleanCompletion(true, _) and result = first(loop.getBody()) or
+ // ...and through the back edge from the body back to the condition.
+ last(loop.getBody(), n, completion) and continues(completion, loop) and result = first(loop.getCondition())
+ ) or
+
+ // Resource declarations in a try-with-resources execute sequentially.
+ exists(TryStmt try, int i | last(try.getResource(i), n, completion) and completion = NormalCompletion() |
+ result = first(try.getResource(i+1)) or
+ not exists(try.getResource(i+1)) and result = first(try.getBlock())
+ ) or
+
+ // After the last resource declaration, control transfers to the body.
+ exists(TryStmt try | n = try and completion = NormalCompletion() |
+ result = first(try.getResource(0)) or
+ not exists(try.getAResource()) and result = first(try.getBlock())
+ ) or
+
+ // exceptional control flow
+ exists(TryStmt try | catchOrFinallyCompletion(try, n, completion) |
+ // if the body of the `try` throws...
+ exists(ThrowableType tt | completion = ThrowCompletion(tt) |
+ // ...control transfers to a catch clause...
+ result = first(handlingCatchClause(try, tt)) or
+ // ...or to the finally block
+ not mustCatch(try.getACatchClause(), tt) and result = first(try.getFinally())
+ ) or
+
+ // if the body completes normally, control transfers to the finally block
+ not completion = ThrowCompletion(_) and result = first(try.getFinally())
+ ) or
+
+ // after each catch clause, control transfers to the finally block
+ exists(TryStmt try | last(try.getACatchClause(), n, completion) |
+ result = first(try.getFinally())
+ ) or
+
+ // Catch clauses first assign their variable and then execute their block
+ exists(CatchClause cc | completion = NormalCompletion() |
+ n = cc and result = first(cc.getVariable()) or
+ last(cc.getVariable(), n, completion) and result = first(cc.getBlock())
+ ) or
+
+ // Switch statements
+ exists(SwitchStmt switch | completion = NormalCompletion() |
+ // From the entry point control is transferred first to the expression...
+ n = switch and result = first(switch.getExpr()) or
+ // ...and then to one of the cases.
+ last(switch.getExpr(), n, completion) and result = first(switch.getACase()) or
+ // Statements within a switch body execute sequentially.
+ exists(int i | last(switch.getStmt(i), n, completion) and result = first(switch.getStmt(i+1)))
+ ) or
+
+ // No edges in a SwitchCase - the constant expression in a ConstCase isn't included in the CFG.
+
+ // Synchronized statements execute their expression _before_ synchronization, so the CFG reflects that.
+ exists(SynchronizedStmt synch | completion = NormalCompletion() |
+ last(synch.getExpr(), n, completion) and result = synch or
+ n = synch and result = first(synch.getBlock())
+ ) or
+
+ result = first(n.(ExprStmt).getExpr()) and completion = NormalCompletion() or
+
+ result = first(n.(LabeledStmt).getStmt()) and completion = NormalCompletion() or
+
+ // Variable declarations in a variable declaration statement are executed sequentially.
+ exists(LocalVariableDeclStmt s | completion = NormalCompletion() |
+ n = s and result = first(s.getVariable(1)) or
+ exists(int i | last(s.getVariable(i), n, completion) and result = first(s.getVariable(i+1)))
+ )
+ }
+
+ /*
+ * Conditions give rise to nodes with two normal successors, a true successor
+ * and a false successor. At least one of them is completely contained in the
+ * AST node of the branching construct and is therefore tagged with the
+ * corresponding `booleanCompletion` in the `succ` relation (for example, the
+ * then-branch of an if-statement, or the right operand of a binary logic
+ * expression). The other successor may be tagged with either the corresponding
+ * `booleanCompletion` (for example in an if-statement with an else-branch or
+ * in a `ConditionalExpr`) or a `normalCompletion` (for example in an
+ * if-statement without an else-part).
+ *
+ * If the other successor ends a finally block it may also be tagged with an
+ * abnormal completion instead of a `normalCompletion`. This completion is
+ * calculated by the `resumption` predicate. In this case the successor might
+ * no longer be unique, as there can be multiple completions to be resumed
+ * after the finally block.
+ */
+
+ /**
+ * Gets the _resumption_ for cfg node `n`, that is, the completion according
+ * to which control flow is determined if `n` completes normally.
+ *
+ * In most cases, the resumption is simply the normal completion, except if
+ * `n` is the last node of a `finally` block, in which case it is the
+ * completion of any predecessors of the `finally` block as determined by
+ * predicate `finallyPred`, since their completion is resumed after normal
+ * completion of the `finally`.
+ */
+ private Completion resumption(ControlFlowNode n) {
+ exists(TryStmt try | lastInFinally(try, n) and finallyPred(try, _, result)) or
+ not lastInFinally(_, n) and result = NormalCompletion()
+ }
+
+ /**
+ * A true- or false-successor that is tagged with the corresponding `booleanCompletion`.
+ *
+ * That is, the `booleanCompletion` is the label of the edge in the CFG.
+ */
+ private ControlFlowNode mainBranchSucc(ControlFlowNode n, boolean b) {
+ result = succ(n, BooleanCompletion(_, b))
+ }
+
+ /**
+ * A true- or false-successor that is not tagged with a `booleanCompletion`.
+ *
+ * That is, the label of the edge in the CFG is a `normalCompletion` or
+ * some other completion if `n` occurs as the last node in a finally block.
+ *
+ * In the latter case, when `n` occurs as the last node in a finally block, there might be
+ * multiple different such successors.
+ */
+ private ControlFlowNode otherBranchSucc(ControlFlowNode n, boolean b) {
+ exists(ControlFlowNode main | main = mainBranchSucc(n, b.booleanNot()) |
+ result = succ(n, resumption(n)) and
+ not result = main and
+ (b = true or b = false)
+ )
+ }
+
+ /** Gets a true- or false-successor of `n`. */
+ cached
+ ControlFlowNode branchSuccessor(ControlFlowNode n, boolean branch) {
+ result = mainBranchSucc(n, branch) or
+ result = otherBranchSucc(n, branch)
+ }
+
+}
+private import ControlFlowGraphImpl
+
+/** A control-flow node that branches based on a condition. */
+class ConditionNode extends ControlFlowNode {
+ ConditionNode() {
+ exists(branchSuccessor(this, _))
+ }
+
+ /** Gets a true- or false-successor of the `ConditionNode`. */
+ ControlFlowNode getABranchSuccessor(boolean branch) {
+ result = branchSuccessor(this, branch)
+ }
+
+ /** Gets a true-successor of the `ConditionNode`. */
+ ControlFlowNode getATrueSuccessor() {
+ result = getABranchSuccessor(true)
+ }
+
+ /** Gets a false-successor of the `ConditionNode`. */
+ ControlFlowNode getAFalseSuccessor() {
+ result = getABranchSuccessor(false)
+ }
+
+ /** Gets the condition of this `ConditionNode`. This is equal to the node itself. */
+ Expr getCondition() {
+ result = this
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Conversions.qll b/java/ql/src/semmle/code/java/Conversions.qll
new file mode 100644
index 00000000000..2916b8b5404
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Conversions.qll
@@ -0,0 +1,172 @@
+/**
+ * Provides support for conversion contexts, in which an expression is converted
+ * (implicitly or explicitly) to a different type.
+ *
+ * See the Java Language Specification, Section 5, for details.
+ */
+import java
+import semmle.code.java.arithmetic.Overflow
+
+/**
+ * A expression where an implicit conversion can occur.
+ *
+ * See the Java Language Specification, Section 5.
+ */
+abstract class ConversionSite extends Expr {
+ /**
+ * Gets the type that is converted to.
+ */
+ abstract Type getConversionTarget();
+
+ /**
+ * Gets the type that is converted from.
+ */
+ Type getConversionSource() {
+ result = this.getType()
+ }
+
+ /**
+ * Whether this conversion site actually induces a conversion.
+ */
+ predicate isTrivial() {
+ getConversionTarget() = getConversionSource()
+ }
+
+ /**
+ * Whether this conversion is implicit.
+ */
+ predicate isImplicit() {
+ any()
+ }
+
+ abstract string kind();
+}
+
+/**
+ * An assignment conversion. For example, `x += b` converts `b`
+ * to be of the type of `x`.
+ *
+ * See the Java Language Specification, Section 5.2.
+ */
+class AssignmentConversionContext extends ConversionSite {
+ Variable v;
+ AssignmentConversionContext() {
+ this = v.getAnAssignedValue() or
+ exists(Assignment a | a.getDest().getProperExpr() = v.getAnAccess() and this = a.getSource())
+ }
+
+ override Type getConversionTarget() {
+ result = v.getType()
+ }
+
+ override string kind() {
+ result = "assignment context"
+ }
+}
+
+/**
+ * An return conversion. For example, `return b` converts `b`
+ * to be of the return type of the enclosing callable.
+ *
+ * Note that the Java Language Specification handles these as
+ * assignment conversions (section 5.2), but for clarity we split them out here.
+ */
+class ReturnConversionSite extends ConversionSite {
+ ReturnStmt r;
+ ReturnConversionSite() {
+ this = r.getResult()
+ }
+
+ override Type getConversionTarget() {
+ result = r.getEnclosingCallable().getReturnType()
+ }
+ override string kind() {
+ result = "return context"
+ }
+}
+
+/**
+ * An invocation conversion. For example `f(b)` converts `b` to
+ * have the type of the corresponding parameter of `f`.
+ *
+ * See the Java Language Specification, Section 5.3.
+ */
+class InvocationConversionContext extends ConversionSite {
+ Call c;
+ int index;
+ InvocationConversionContext() {
+ this = c.getArgument(index)
+ }
+
+ override Type getConversionTarget() {
+ result = c.getCallee().getParameter(index).getType()
+ }
+
+ override string kind() {
+ result = "invocation context"
+ }
+}
+
+/**
+ * A string conversion. For example `a + b`, where `a` is a
+ * `String`, converts `b` to have type `String`.
+ *
+ * See the Java Language Specification, Section 5.4.
+ */
+class StringConversionContext extends ConversionSite {
+ AddExpr a;
+ StringConversionContext() {
+ a.getAnOperand() = this and
+ not this.getType() instanceof TypeString and
+ a.getAnOperand().getType() instanceof TypeString
+ }
+
+ override Type getConversionTarget() {
+ result instanceof TypeString
+ }
+
+ override string kind() {
+ result = "string context"
+ }
+}
+
+class CastConversionContext extends ConversionSite {
+ CastExpr c;
+ CastConversionContext() {
+ this = c.getExpr()
+ }
+
+ override Type getConversionTarget() {
+ result = c.getType()
+ }
+
+ override predicate isImplicit() {
+ none()
+ }
+
+ override string kind() {
+ result = "cast context"
+ }
+}
+
+/**
+ * A numeric conversion. For example, `a * b` converts `a` and
+ * `b` to have an appropriate numeric type.
+ *
+ * See the Java Language Specification, Section 5.4.
+ */
+class NumericConversionContext extends ConversionSite {
+ ArithExpr e;
+ NumericConversionContext() {
+ this = e.getAnOperand()
+ }
+
+ override Type getConversionTarget() {
+ result = e.getType()
+ }
+
+ override string kind() {
+ result = "numeric context"
+ }
+
+}
diff --git a/java/ql/src/semmle/code/java/Dependency.qll b/java/ql/src/semmle/code/java/Dependency.qll
new file mode 100755
index 00000000000..a682b115149
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Dependency.qll
@@ -0,0 +1,117 @@
+/**
+ * Provides utility predicates for representing dependencies between types.
+ */
+
+import Type
+import Generics
+import Expr
+
+/**
+ * Holds if type `t` depends on type `dep`.
+ *
+ * Dependencies are restricted to generic and non-generic reference types.
+ *
+ * Dependencies on parameterized or raw types are decomposed into
+ * a dependency on the corresponding generic type and separate
+ * dependencies on (source declarations of) any type arguments.
+ *
+ * For example, a dependency on type `List>` is represented by
+ * dependencies on the generic types `List` and `Set` as well as a dependency
+ * on the type `String` but not on the parameterized types `List>`
+ * or `Set`.
+ */
+predicate depends(RefType t, RefType dep) {
+ // Type `t` is neither a parameterized nor a raw type and is distinct from `dep`.
+ not isParameterized(t) and
+ not isRaw(t) and
+ not t = dep and
+ // Type `t` depends on:
+ (
+ // its supertypes,
+ usesType(t.getASupertype(), dep)
+ or
+ // its enclosing type,
+ usesType(t.(NestedType).getEnclosingType(), dep)
+ or
+ // the type of any field declared in `t`,
+ exists(Field f | f.getDeclaringType() = t |
+ usesType(f.getType(), dep)
+ ) or
+ // the return type of any method declared in `t`,
+ exists(Method m | m.getDeclaringType() = t |
+ usesType(m.getReturnType(), dep)
+ ) or
+ // the type of any parameter of a callable in `t`,
+ exists(Callable c | c.getDeclaringType() = t |
+ usesType(c.getAParamType(), dep)
+ ) or
+ // the type of any exception in the `throws` clause of a callable declared in `t`,
+ exists(Exception e | e.getCallable().getDeclaringType() = t |
+ usesType(e.getType(), dep)
+ ) or
+ // the declaring type of a callable accessed in `t`,
+ exists(Callable c |
+ c.getAReference().getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(c.getSourceDeclaration().getDeclaringType(), dep)
+ ) or
+ // the declaring type of a field accessed in `t`,
+ exists(Field f |
+ f.getAnAccess().getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(f.getSourceDeclaration().getDeclaringType(), dep)
+ ) or
+ // the type of a local variable declared in `t`,
+ exists(LocalVariableDeclExpr decl |
+ decl.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(decl.getType(), dep)
+ ) or
+ // the type of a type literal accessed in `t`,
+ exists(TypeLiteral l |
+ l.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(l.getTypeName().getType(), dep)
+ ) or
+ // the type of an annotation (or one of its element values) that annotates `t` or one of its members,
+ exists(Annotation a |
+ a.getAnnotatedElement() = t or
+ a.getAnnotatedElement().(Member).getDeclaringType() = t
+ |
+ usesType(a.getType(), dep) or
+ usesType(a.getAValue().getType(), dep)
+ ) or
+ // the type accessed in an `instanceof` expression in `t`.
+ exists(InstanceOfExpr ioe |
+ t = ioe.getEnclosingCallable().getDeclaringType()
+ |
+ usesType(ioe.getTypeName().getType(), dep)
+ )
+ )
+}
+
+/**
+ * Bind the reference type `dep` to the source declaration of any types used to construct `t`,
+ * including (possibly nested) type parameters of parameterized types, element types of array types,
+ * and bounds of type variables or wildcards.
+ */
+cached
+predicate usesType(Type t, RefType dep) {
+ dep = inside*(t).getSourceDeclaration() and
+ not dep instanceof TypeVariable and
+ not dep instanceof Wildcard and
+ not dep instanceof Array
+}
+
+/**
+ * Gets a type argument of a parameterized type,
+ * the element type of an array type, or
+ * a bound of a type variable or wildcard.
+ */
+private
+RefType inside(Type t) {
+ result = t.(TypeVariable).getATypeBound().getType() or
+ result = t.(Wildcard).getATypeBound().getType() or
+ result = t.(ParameterizedType).getATypeArgument() or
+ result = t.(Array).getElementType()
+}
diff --git a/java/ql/src/semmle/code/java/DependencyCounts.qll b/java/ql/src/semmle/code/java/DependencyCounts.qll
new file mode 100644
index 00000000000..fefa16ab216
--- /dev/null
+++ b/java/ql/src/semmle/code/java/DependencyCounts.qll
@@ -0,0 +1,150 @@
+/**
+ * This library provides utility predicates for representing the number of dependencies between types.
+ */
+
+import Type
+import Generics
+import Expr
+
+/**
+ * The number of dependencies from type `t` on type `dep`.
+ *
+ * Dependencies are restricted to generic and non-generic reference types.
+ *
+ * Dependencies on parameterized or raw types are decomposed into
+ * a dependency on the corresponding generic type and separate
+ * dependencies on (source declarations of) any type arguments.
+ *
+ * For example, a dependency on type `List>` is represented by
+ * dependencies on the generic types `List` and `Set` as well as a dependency
+ * on the type `String` but not on the parameterized types `List>`
+ * or `Set`.
+ */
+pragma[nomagic]
+predicate numDepends(RefType t, RefType dep, int value) {
+ // Type `t` is neither a parameterized nor a raw type and is distinct from `dep`.
+ not isParameterized(t) and
+ not isRaw(t) and
+ not t = dep and
+ // Type `t` depends on:
+ value = strictcount(Element elem |
+ // its supertypes,
+ exists(RefType s | elem = s and t.hasSupertype(s) |
+ usesType(s, dep)
+ ) or
+ // its enclosing types,
+ exists(RefType s | elem = s and t.getEnclosingType() = s |
+ usesType(s, dep)
+ ) or
+ // the type of any field declared in `t`,
+ exists(Field f | elem = f and f.getDeclaringType() = t |
+ usesType(f.getType(), dep)
+ ) or
+ // the return type of any method declared in `t`,
+ exists(Method m | elem = m and m.getDeclaringType() = t |
+ usesType(m.getReturnType(), dep)
+ ) or
+ // the type of any parameter of a callable in `t`,
+ exists(Parameter p | elem = p and p.getCallable().getDeclaringType() = t |
+ usesType(p.getType(), dep)
+ ) or
+ // the type of any exception in the `throws` clause of a callable declared in `t`,
+ exists(Exception e | elem = e and e.getCallable().getDeclaringType() = t |
+ usesType(e.getType(), dep)
+ ) or
+ // the declaring type of a callable accessed in `t`,
+ exists(Call c | elem = c and
+ c.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(c.getCallee().getSourceDeclaration().getDeclaringType(), dep)
+ ) or
+ // the declaring type of a field accessed in `t`,
+ exists(FieldAccess fa | elem = fa and
+ fa.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(fa.getField().getSourceDeclaration().getDeclaringType(), dep)
+ ) or
+ // the type of a local variable declared in `t`,
+ exists(LocalVariableDeclExpr decl | elem = decl and
+ decl.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(decl.getType(), dep)
+ ) or
+ // the type of a type literal accessed in `t`,
+ exists(TypeLiteral l | elem = l and
+ l.getEnclosingCallable().getDeclaringType() = t
+ |
+ usesType(l.getTypeName().getType(), dep)
+ ) or
+ // the type of an annotation (or one of its element values) that annotates `t` or one of its members,
+ exists(Annotation a |
+ a.getAnnotatedElement() = t or
+ a.getAnnotatedElement().(Member).getDeclaringType() = t
+ |
+ elem = a and usesType(a.getType(), dep) or
+ elem = a.getAValue() and elem.getFile().getExtension() = "java" and usesType(elem.(Expr).getType(), dep)
+ ) or
+ // the type accessed in an `instanceof` expression in `t`.
+ exists(InstanceOfExpr ioe | elem = ioe and
+ t = ioe.getEnclosingCallable().getDeclaringType()
+ |
+ usesType(ioe.getTypeName().getType(), dep)
+ )
+ )
+}
+
+predicate filePackageDependencyCount(File sourceFile, int total, string entity) {
+ exists(Package targetPackage |
+ total = strictsum(RefType sourceType, RefType targetType, int num |
+ sourceType.getFile() = sourceFile and
+ sourceType.fromSource() and
+ sourceType.getPackage() != targetPackage and
+ targetType.getPackage() = targetPackage and
+ numDepends(sourceType, targetType, num)
+ |
+ num)
+ and
+ entity = "/" + sourceFile.getRelativePath() + "<|>" + targetPackage + "<|>N/A"
+ )
+}
+
+private string nameVersionRegex() {
+ result = "([_.A-Za-z0-9-]*)-([0-9][A-Za-z0-9.+_-]*)"
+}
+
+/**
+ * Given a JAR filename, try to split it into a name and version.
+ * This is a heuristic approach assuming that the a dash is used to
+ * separate the library name from a largely numeric version such as
+ * `commons-io-2.4`.
+ */
+bindingset[target]
+predicate hasDashedVersion(string target, string name, string version) {
+ exists(string regex | regex = nameVersionRegex() |
+ name = target.regexpCapture(regex, 1) and
+ version = target.regexpCapture(regex, 2))
+}
+
+predicate fileJarDependencyCount(File sourceFile, int total, string entity) {
+ exists(Container targetJar, string jarStem |
+ jarStem = targetJar.getStem() and
+ targetJar.(File).getExtension() = "jar" and
+ jarStem != "rt"
+ |
+ total = strictsum(RefType r, RefType dep, int num |
+ r.getFile() = sourceFile and
+ r.fromSource() and
+ dep.getFile().getParentContainer*() = targetJar and
+ numDepends(r, dep, num)
+ |
+ num) and
+ exists(string name, string version |
+ if hasDashedVersion(jarStem, _, _) then
+ hasDashedVersion(jarStem, name, version)
+ else
+ (name = jarStem and version = "unknown")
+ |
+ entity = "/" + sourceFile.getRelativePath() + "<|>" + name + "<|>" + version
+ )
+ )
+}
diff --git a/java/ql/src/semmle/code/java/Element.qll b/java/ql/src/semmle/code/java/Element.qll
new file mode 100755
index 00000000000..f4496a0bc93
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Element.qll
@@ -0,0 +1,61 @@
+/**
+ * Provides a class that represents named elements in Java programs.
+ */
+
+import CompilationUnit
+import semmle.code.Location
+import Javadoc
+
+/** A program element that has a name. */
+class Element extends @element, Top {
+ /** Holds if this element has the specified `name`. */
+ predicate hasName(string name) { hasName(this,name) }
+
+ /** Gets the name of this element. */
+ string getName() { this.hasName(result) }
+
+ /**
+ * Holds if this element transitively contains the specified element `e`.
+ */
+ predicate contains(Element e) { this.hasChildElement+(e) }
+
+ /**
+ * Holds if this element is the immediate parent of the specified element `e`.
+ *
+ * It is usually preferable to use more specific predicates such as
+ * `getEnclosingCallable()`, `getDeclaringType()` and/or `getEnclosingType()`
+ * instead of this general predicate.
+ */
+ predicate hasChildElement(Element e) { hasChildElement(this, e) }
+
+ /**
+ * Holds if this element pertains to a source file.
+ *
+ * Elements pertaining to source files may include generated elements
+ * not visible in source code, such as implicit default constructors.
+ */
+ predicate fromSource() {
+ getCompilationUnit().getExtension() = "java"
+ }
+
+ /** Gets the compilation unit that this element belongs to. */
+ CompilationUnit getCompilationUnit() { result = getFile() }
+
+ /** Cast this element to a `Documentable`. */
+ Documentable getDoc() { result = this }
+}
+
+/**
+ * Holds if element `parent` is immediately above element `e` in the syntax tree.
+ */
+private predicate hasChildElement(Element parent, Element e) {
+ cupackage(e,parent) or
+ enclInReftype(e,parent) or
+ (not(enclInReftype(e,_)) and e.(Class).getCompilationUnit() = parent) or
+ (not(enclInReftype(e,_)) and e.(Interface).getCompilationUnit() = parent) or
+ methods(e,_,_,_,parent,_) or
+ constrs(e,_,_,_,parent,_) or
+ params(e,_,_,parent,_) or
+ fields(e,_,_,parent,_) or
+ typeVars(e,_,_,_,parent)
+}
diff --git a/java/ql/src/semmle/code/java/Exception.qll b/java/ql/src/semmle/code/java/Exception.qll
new file mode 100755
index 00000000000..1b4e0ba85a2
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Exception.qll
@@ -0,0 +1,30 @@
+/**
+ * Provides classes and predicates for working with Java exceptions.
+ */
+
+import Element
+import Type
+
+/**
+ * An Exception represents an element listed in the `throws` clause
+ * of a method of constructor.
+ *
+ * For example, `E` is an exception thrown by method `m` in
+ * `void m() throws E;`, whereas `T` is an exception _type_ in
+ * `class T extends Exception { }`.
+ */
+class Exception extends Element, @exception {
+ /** Gets the type of this exception. */
+ RefType getType() { exceptions(this,result,_) }
+
+ /** Gets the callable whose `throws` clause contains this exception. */
+ Callable getCallable() { exceptions(this,_,result) }
+
+ /** Gets the name of this exception, that is, the name of its type. */
+ override string getName() { result = this.getType().getName() }
+
+ /** Holds if this exception has the specified `name`. */
+ override predicate hasName(string name) { this.getType().hasName(name) }
+
+ override string toString() { result = this.getType().toString() }
+}
diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll
new file mode 100755
index 00000000000..64eac31dff3
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Expr.qll
@@ -0,0 +1,1663 @@
+/**
+ * Provides classes for working with Java expressions.
+ */
+
+import Member
+import Type
+import Variable
+import Statement
+
+/** A common super-class that represents all kinds of expressions. */
+class Expr extends ExprParent, @expr {
+ /*abstract*/ override string toString() { result = "expr" }
+
+ /**
+ * Gets the callable in which this expression occurs, if any.
+ */
+ Callable getEnclosingCallable() {
+ callableEnclosingExpr(this,result)
+ }
+
+ /** Gets the index of this expression as a child of its parent. */
+ int getIndex() { exprs(this,_,_,_,result) }
+
+ /** Gets the parent of this expression. */
+ ExprParent getParent() { exprs(this,_,_,result,_) }
+
+ /** Holds if this expression is the child of the specified parent at the specified (zero-based) position. */
+ predicate isNthChildOf(ExprParent parent, int index) {
+ exprs(this,_,_,parent,index)
+ }
+
+ /** Gets the type of this expression. */
+ Type getType() { exprs(this,_,result,_,_) }
+
+ /** Gets the compilation unit in which this expression occurs. */
+ CompilationUnit getCompilationUnit() { result = this.getFile() }
+
+ /**
+ * Gets the kind of this expression.
+ *
+ * Each kind of expression has a unique (integer) identifier.
+ * This is an implementation detail that should not normally
+ * be referred to by library users, since the kind of an expression
+ * is also represented by its QL type.
+ *
+ * In a few rare situations, referring to the kind of an expression
+ * via its unique identifier may be appropriate; for example, when
+ * comparing whether two expressions have the same kind (as opposed
+ * to checking whether an expression has a particular kind).
+ */
+ int getKind() { exprs(this,result,_,_,_) }
+
+ /** Gets this expression with any surrounding parentheses removed. */
+ Expr getProperExpr() {
+ result = this.(ParExpr).getExpr().getProperExpr() or
+ result = this and not this instanceof ParExpr
+ }
+
+ /** Gets the statement containing this expression, if any. */
+ Stmt getEnclosingStmt() {
+ statementEnclosingExpr(this,result)
+ }
+
+ /** Gets a child of this expression. */
+ Expr getAChildExpr() { exprs(result,_,_,this,_) }
+
+ /** Gets the basic block in which this expression occurs, if any. */
+ BasicBlock getBasicBlock() { result.getANode() = this }
+
+ /** Gets the `ControlFlowNode` corresponding to this expression. */
+ ControlFlowNode getControlFlowNode() { result = this }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ string getHalsteadID() { result = this.toString() }
+
+ /**
+ * Holds if this expression is a compile-time constant.
+ *
+ * See JLS v8, section 15.28 (Constant Expressions).
+ */
+ predicate isCompileTimeConstant() { this instanceof CompileTimeConstantExpr }
+
+ /** Holds if this expression occurs in a static context. */
+ predicate isInStaticContext() {
+ /*
+ * JLS 8.1.3 (Inner Classes and Enclosing Instances)
+ * A statement or expression occurs in a static context if and only if the
+ * innermost method, constructor, instance initializer, static initializer,
+ * field initializer, or explicit constructor invocation statement
+ * enclosing the statement or expression is a static method, a static
+ * initializer, the variable initializer of a static variable, or an
+ * explicit constructor invocation statement.
+ */
+ getEnclosingCallable().isStatic() or
+ getParent+() instanceof ThisConstructorInvocationStmt or
+ getParent+() instanceof SuperConstructorInvocationStmt or
+ exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() and lam.isInStaticContext())
+ }
+}
+
+/**
+ * Holds if the specified type is either a primitive type or type `String`.
+ *
+ * Auxiliary predicate used by `CompileTimeConstantExpr`.
+ */
+private
+predicate primitiveOrString(Type t) {
+ t instanceof PrimitiveType or
+ t instanceof TypeString
+}
+
+/**
+ * A compile-time constant expression.
+ *
+ * See JLS v8, section 15.28 (Constant Expressions).
+ */
+class CompileTimeConstantExpr extends Expr {
+ CompileTimeConstantExpr() {
+ primitiveOrString(getType()) and (
+ // Literals of primitive type and literals of type `String`.
+ this instanceof Literal or
+ // Casts to primitive types and casts to type `String`.
+ this.(CastExpr).getExpr().isCompileTimeConstant() or
+ // The unary operators `+`, `-`, `~`, and `!` (but not `++` or `--`).
+ this.(PlusExpr).getExpr().isCompileTimeConstant() or
+ this.(MinusExpr).getExpr().isCompileTimeConstant() or
+ this.(BitNotExpr).getExpr().isCompileTimeConstant() or
+ this.(LogNotExpr).getExpr().isCompileTimeConstant() or
+ // The multiplicative operators `*`, `/`, and `%`,
+ // the additive operators `+` and `-`,
+ // the shift operators `<<`, `>>`, and `>>>`,
+ // the relational operators `<`, `<=`, `>`, and `>=` (but not `instanceof`),
+ // the equality operators `==` and `!=`,
+ // the bitwise and logical operators `&`, `^`, and `|`,
+ // the conditional-and operator `&&` and the conditional-or operator `||`.
+ // These are precisely the operators represented by `BinaryExpr`.
+ (
+ this.(BinaryExpr).getLeftOperand().isCompileTimeConstant() and
+ this.(BinaryExpr).getRightOperand().isCompileTimeConstant()
+ ) or
+ // The ternary conditional operator ` ? : `.
+ exists(ConditionalExpr e | this = e |
+ e.getCondition().isCompileTimeConstant() and
+ e.getTrueExpr().isCompileTimeConstant() and
+ e.getFalseExpr().isCompileTimeConstant()
+ ) or
+ // Parenthesized expressions whose contained expression is a constant expression.
+ this.(ParExpr).getExpr().isCompileTimeConstant() or
+ // Access to a final variable initialized by a compile-time constant.
+ exists(Variable v | this = v.getAnAccess() |
+ v.isFinal() and
+ v.getInitializer().isCompileTimeConstant()
+ )
+ )
+ }
+
+ /**
+ * Gets the string value of this expression, where possible.
+ */
+ string getStringValue() {
+ result = this.(StringLiteral).getRepresentedString()
+ or
+ result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getStringValue()
+ or
+ result = this.(AddExpr).getLeftOperand().(CompileTimeConstantExpr).getStringValue()
+ + this.(AddExpr).getRightOperand().(CompileTimeConstantExpr).getStringValue()
+ or
+ // Ternary conditional, with compile-time constant condition.
+ exists(ConditionalExpr ce, boolean condition |
+ ce = this and
+ condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue()
+ |
+ if condition = true then
+ result = ce.getTrueExpr().(CompileTimeConstantExpr).getStringValue()
+ else
+ result = ce.getFalseExpr().(CompileTimeConstantExpr).getStringValue()
+ ) or
+ exists(Variable v | this = v.getAnAccess() |
+ result = v.getInitializer().(CompileTimeConstantExpr).getStringValue()
+ )
+ }
+
+ /**
+ * Gets the boolean value of this expression, where possible.
+ */
+ boolean getBooleanValue() {
+ // Literal value.
+ result = this.(BooleanLiteral).getBooleanValue() or
+ // No casts relevant to booleans.
+ // `!` is the only unary operator that evaluates to a boolean.
+ result = this.(LogNotExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue().booleanNot() or
+ // Handle binary expressions that have integer operands and a boolean result.
+ exists(BinaryExpr b, int left, int right |
+ b = this and
+ left = b.getLeftOperand().(CompileTimeConstantExpr).getIntValue() and
+ right = b.getRightOperand().(CompileTimeConstantExpr).getIntValue()
+ |
+ (b instanceof LTExpr and if left < right then result = true else result = false) or
+ (b instanceof LEExpr and if left <= right then result = true else result = false) or
+ (b instanceof GTExpr and if left > right then result = true else result = false) or
+ (b instanceof GEExpr and if left >= right then result = true else result = false) or
+ (b instanceof EQExpr and if left = right then result = true else result = false) or
+ (b instanceof NEExpr and if left != right then result = true else result = false)
+ ) or
+ // Handle binary expressions that have boolean operands and a boolean result.
+ exists(BinaryExpr b, boolean left, boolean right |
+ b = this and
+ left = b.getLeftOperand().(CompileTimeConstantExpr).getBooleanValue() and
+ right = b.getRightOperand().(CompileTimeConstantExpr).getBooleanValue()
+ |
+ (b instanceof EQExpr and if left = right then result = true else result = false) or
+ (b instanceof NEExpr and if left != right then result = true else result = false) or
+ ((b instanceof AndBitwiseExpr or b instanceof AndLogicalExpr) and result = left.booleanAnd(right)) or
+ ((b instanceof OrBitwiseExpr or b instanceof OrLogicalExpr) and result = left.booleanOr(right)) or
+ (b instanceof XorBitwiseExpr and result = left.booleanXor(right))
+ ) or
+ // Handle binary expressions that have `String` operands and a boolean result.
+ exists(BinaryExpr b, string left, string right |
+ b = this and
+ left = b.getLeftOperand().(CompileTimeConstantExpr).getStringValue() and
+ right = b.getRightOperand().(CompileTimeConstantExpr).getStringValue()
+ |
+ /*
+ * JLS 15.28 specifies that compile-time `String` constants are interned. Therefore `==`
+ * equality can be interpreted as equality over the constant values, not the references.
+ */
+ (b instanceof EQExpr and if left = right then result = true else result = false) or
+ (b instanceof NEExpr and if left != right then result = true else result = false)
+ ) or
+ // Note: no `getFloatValue()`, so we cannot support binary expressions with float or double operands.
+ // Ternary expressions, where the `true` and `false` expressions are boolean compile-time constants.
+ exists(ConditionalExpr ce, boolean condition |
+ ce = this and
+ condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue()
+ |
+ if condition = true then
+ result = ce.getTrueExpr().(CompileTimeConstantExpr).getBooleanValue()
+ else
+ result = ce.getFalseExpr().(CompileTimeConstantExpr).getBooleanValue()
+ ) or
+ // Parenthesized expressions containing a boolean value.
+ result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue() or
+ // Simple or qualified names where the variable is final and the initializer is a constant.
+ exists(Variable v | this = v.getAnAccess() |
+ result = v.getInitializer().(CompileTimeConstantExpr).getBooleanValue()
+ )
+ }
+
+ /**
+ * Gets the integer value of this expression, where possible.
+ *
+ * All computations are performed on QL 32-bit `int`s, so no
+ * truncation is performed in the case of overflow within `byte` or `short`:
+ * `((byte)127)+((byte)1)` evaluates to 128 rather than to -128.
+ *
+ * Note that this does not handle the following cases:
+ *
+ * - values of type `long`,
+ * - `char` literals.
+ */
+ cached
+ int getIntValue() {
+ exists(IntegralType t | this.getType() = t |
+ t.getName().toLowerCase() != "long"
+ )
+ and
+ (
+ exists(string lit | lit = this.(Literal).getValue() |
+ // `char` literals may get parsed incorrectly, so disallow.
+ not this instanceof CharacterLiteral and
+ result = lit.toInt()
+ )
+ or
+ exists(CastExpr cast, int val | cast = this and val = cast.getExpr().(CompileTimeConstantExpr).getIntValue() |
+ if cast.getType().hasName("byte") then result = (val + 128).bitAnd(255) - 128
+ else if cast.getType().hasName("short") then result = (val + 32768).bitAnd(65535) - 32768
+ else result = val
+ ) or
+ result = this.(PlusExpr).getExpr().(CompileTimeConstantExpr).getIntValue()
+ or
+ result = -(this.(MinusExpr).getExpr().(CompileTimeConstantExpr).getIntValue())
+ or
+ result = this.(BitNotExpr).getExpr().(CompileTimeConstantExpr).getIntValue().bitNot()
+ // No `int` value for `LogNotExpr`.
+ or
+ exists(BinaryExpr b, int v1, int v2 | b = this and
+ v1 = b.getLeftOperand().(CompileTimeConstantExpr).getIntValue() and
+ v2 = b.getRightOperand().(CompileTimeConstantExpr).getIntValue()
+ |
+ b instanceof MulExpr and result = v1 * v2
+ or
+ b instanceof DivExpr and result = v1 / v2
+ or
+ b instanceof RemExpr and result = v1 % v2
+ or
+ b instanceof AddExpr and result = v1 + v2
+ or
+ b instanceof SubExpr and result = v1 - v2
+ or
+ b instanceof LShiftExpr and result = v1.bitShiftLeft(v2)
+ or
+ b instanceof RShiftExpr and result = v1.bitShiftRightSigned(v2)
+ or
+ b instanceof URShiftExpr and result = v1.bitShiftRight(v2)
+ or
+ b instanceof AndBitwiseExpr and result = v1.bitAnd(v2)
+ or
+ b instanceof OrBitwiseExpr and result = v1.bitOr(v2)
+ or
+ b instanceof XorBitwiseExpr and result = v1.bitXor(v2)
+ // No `int` value for `AndLogicalExpr` or `OrLogicalExpr`.
+ // No `int` value for `LTExpr`, `GTExpr`, `LEExpr`, `GEExpr`, `EQExpr` or `NEExpr`.
+ )
+ or
+ // Ternary conditional, with compile-time constant condition.
+ exists(ConditionalExpr ce, boolean condition |
+ ce = this and
+ condition = ce.getCondition().(CompileTimeConstantExpr).getBooleanValue()
+ |
+ if condition = true then
+ result = ce.getTrueExpr().(CompileTimeConstantExpr).getIntValue()
+ else
+ result = ce.getFalseExpr().(CompileTimeConstantExpr).getIntValue()
+ )
+ or
+ result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getIntValue()
+ or
+ // If a `Variable` is a `CompileTimeConstantExpr`, its value is its initializer.
+ exists(Variable v | this = v.getAnAccess() |
+ result = v.getInitializer().(CompileTimeConstantExpr).getIntValue()
+ )
+ )
+ }
+}
+
+/** An expression parent is an element that may have an expression as its child. */
+class ExprParent extends @exprparent, Top {
+}
+
+/**
+ * An array access.
+ *
+ * For example, `a[i++]` is an array access, where
+ * `a` is the accessed array and `i++` is
+ * the index expression of the array access.
+ */
+class ArrayAccess extends Expr,@arrayaccess {
+ /** Gets the array that is accessed in this array access. */
+ Expr getArray() { result.isNthChildOf(this, 0) }
+
+ /** Gets the index expression of this array access. */
+ Expr getIndexExpr() { result.isNthChildOf(this, 1) }
+
+ override string toString() { result = "...[...]" }
+}
+
+/**
+ * An array creation expression.
+ *
+ * For example, an expression such as `new String[3][2]` or
+ * `new String[][] { { "a", "b", "c" } , { "d", "e", "f" } }`.
+ *
+ * In both examples, `String` is the type name. In the first
+ * example, `2` and `3` are the 0th and 1st dimensions,
+ * respectively. In the second example,
+ * `{ { "a", "b", "c" } , { "d", "e", "f" } }` is the initializer.
+ */
+class ArrayCreationExpr extends Expr,@arraycreationexpr {
+ /** Gets a dimension of this array creation expression. */
+ Expr getADimension() { result.getParent() = this and result.getIndex() >= 0 }
+
+ /** Gets the dimension of this array creation expression at the specified (zero-based) position. */
+ Expr getDimension(int index) {
+ result = this.getADimension() and
+ result.getIndex() = index
+ }
+
+ /** Gets the initializer of this array creation expression. */
+ ArrayInit getInit() { result.isNthChildOf(this, -2) }
+
+ /**
+ * Gets the size of the first dimension, if it can be statically determined.
+ */
+ int getFirstDimensionSize() {
+ if exists(getInit()) then
+ result = count(getInit().getAnInit())
+ else
+ result = getDimension(0).(CompileTimeConstantExpr).getIntValue()
+ }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "new " + this.getType().toString() }
+}
+
+/** An array initializer occurs in an array creation expression. */
+class ArrayInit extends Expr,@arrayinit {
+ /**
+ * An expression occurring in this initializer.
+ * This may either be an initializer itself or an
+ * expression representing an element of the array,
+ * depending on the level of nesting.
+ */
+ Expr getAnInit() { result.getParent() = this }
+
+ /** Gets the initializer occurring at the specified (zero-based) position. */
+ Expr getInit(int index) { result = this.getAnInit() and result.getIndex() = index }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "{...}" }
+}
+
+/** A common super-class that represents all varieties of assignments. */
+class Assignment extends Expr,@assignment {
+ /** Gets the destination (left-hand side) of the assignment. */
+ Expr getDest() { result.isNthChildOf(this, 0) }
+
+ /**
+ * Gets the source (right-hand side) of the assignment.
+ *
+ * For assignments with an implicit operator such as `x += 23`,
+ * the left-hand side is also a source.
+ */
+ Expr getSource() { result.isNthChildOf(this, 1) }
+
+ /** Gets the right-hand side of the assignment. */
+ Expr getRhs() { result.isNthChildOf(this, 1) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...=..." }
+}
+
+/**
+ * A simple assignment expression using the `=` operator.
+ *
+ * For example, `x = 23`.
+ */
+class AssignExpr extends Assignment,@assignexpr { }
+
+/**
+ * A common super-class to represent compound assignments, which include an implicit operator.
+ *
+ * For example, the compound assignment `x += 23`
+ * uses `+` as the implicit operator.
+ */
+class AssignOp extends Assignment,@assignop {
+ /**
+ * Gets a source of the compound assignment, which includes both the right-hand side
+ * and the left-hand side of the assignment.
+ */
+ override Expr getSource() { result.getParent() = this }
+
+ /** Gets a string representation of the assignment operator of this compound assignment. */
+ /*abstract*/ string getOp() { result = "??=" }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "..." + this.getOp() + "..." }
+}
+
+/** A compound assignment expression using the `+=` operator. */
+class AssignAddExpr extends AssignOp,@assignaddexpr { override string getOp() { result = "+=" } }
+/** A compound assignment expression using the `-=` operator. */
+class AssignSubExpr extends AssignOp,@assignsubexpr { override string getOp() { result = "-=" } }
+/** A compound assignment expression using the `*=` operator. */
+class AssignMulExpr extends AssignOp,@assignmulexpr { override string getOp() { result = "*=" } }
+/** A compound assignment expression using the `/=` operator. */
+class AssignDivExpr extends AssignOp,@assigndivexpr { override string getOp() { result = "/=" } }
+/** A compound assignment expression using the `%=` operator. */
+class AssignRemExpr extends AssignOp,@assignremexpr { override string getOp() { result = "%=" } }
+/** A compound assignment expression using the `&=` operator. */
+class AssignAndExpr extends AssignOp,@assignandexpr { override string getOp() { result = "&=" } }
+/** A compound assignment expression using the `|=` operator. */
+class AssignOrExpr extends AssignOp,@assignorexpr { override string getOp() { result = "|=" } }
+/** A compound assignment expression using the `^=` operator. */
+class AssignXorExpr extends AssignOp,@assignxorexpr { override string getOp() { result = "^=" } }
+/** A compound assignment expression using the `<<=` operator. */
+class AssignLShiftExpr extends AssignOp,@assignlshiftexpr { override string getOp() { result = "<<=" } }
+/** A compound assignment expression using the `>>=` operator. */
+class AssignRShiftExpr extends AssignOp,@assignrshiftexpr { override string getOp() { result = ">>=" } }
+/** A compound assignment expression using the `>>>=` operator. */
+class AssignURShiftExpr extends AssignOp,@assignurshiftexpr { override string getOp() { result = ">>>=" } }
+
+/** A common super-class to represent constant literals. */
+class Literal extends Expr,@literal {
+ /** Gets a string representation of this literal. */
+ string getLiteral() { namestrings(result,_,this) }
+
+ /** Gets a string representation of the value of this literal. */
+ string getValue() { namestrings(_,result,this) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = this.getLiteral() }
+
+ /** Holds if this literal is a compile-time constant expression (as per JLS v8, section 15.28). */
+ override predicate isCompileTimeConstant() {
+ this.getType() instanceof PrimitiveType or
+ this.getType() instanceof TypeString
+ }
+}
+
+/** A boolean literal. Either `true` or `false`. */
+class BooleanLiteral extends Literal,@booleanliteral {
+
+ /** Gets the boolean representation of this literal. */
+ boolean getBooleanValue() {
+ result = true and getLiteral() = "true" or
+ result = false and getLiteral() = "false"
+ }
+}
+
+/** An integer literal. For example, `23`. */
+class IntegerLiteral extends Literal,@integerliteral {
+
+ /** Gets the int representation of this literal. */
+ int getIntValue() {
+ result = getValue().toInt()
+ }
+}
+
+/** A long literal. For example, `23l`. */
+class LongLiteral extends Literal,@longliteral {}
+
+/** A floating point literal. For example, `4.2f`. */
+class FloatingPointLiteral extends Literal,@floatingpointliteral {}
+
+/** A double literal. For example, `4.2`. */
+class DoubleLiteral extends Literal,@doubleliteral {}
+
+/** A character literal. For example, `'\n'`. */
+class CharacterLiteral extends Literal,@characterliteral {}
+
+/** A string literal. For example, `"hello world"`. */
+class StringLiteral extends Literal,@stringliteral {
+
+ /**
+ * Gets the literal string without the quotes.
+ */
+ string getRepresentedString() {
+ result = getValue()
+ }
+}
+
+/** The null literal, written `null`. */
+class NullLiteral extends Literal,@nullliteral {
+ override string getLiteral() { result = "null" }
+ override string getValue() { result = "null" }
+}
+
+
+/** A common super-class to represent binary operator expressions. */
+class BinaryExpr extends Expr,@binaryexpr {
+ /** Gets the operand on the left-hand side of this binary expression. */
+ Expr getLeftOperand() { result.isNthChildOf(this, 0) }
+
+ /** Gets the operand on the right-hand side of this binary expression. */
+ Expr getRightOperand() { result.isNthChildOf(this, 1) }
+
+ /** Gets an operand (left or right), with any parentheses removed. */
+ Expr getAnOperand() {
+ exists(Expr r | r = this.getLeftOperand() or r = this.getRightOperand() |
+ result = r.getProperExpr()
+ )
+ }
+
+ /** The operands of this binary expression are `e` and `f`, in either order. */
+ predicate hasOperands(Expr e, Expr f) {
+ exists(int i | i in [0..1] |
+ e.isNthChildOf(this, i) and
+ f.isNthChildOf(this, 1-i)
+ )
+ }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "..." + this.getOp() + "..." }
+
+ /** Gets a string representation of the operator of this binary expression. */
+ /*abstract*/ string getOp() { result = " ?? " }
+}
+
+/** A binary expression using the `*` operator. */
+class MulExpr extends BinaryExpr,@mulexpr { override string getOp() { result = " * " } }
+/** A binary expression using the `/` operator. */
+class DivExpr extends BinaryExpr,@divexpr { override string getOp() { result = " / " } }
+/** A binary expression using the `%` operator. */
+class RemExpr extends BinaryExpr,@remexpr { override string getOp() { result = " % " } }
+/** A binary expression using the `+` operator. */
+class AddExpr extends BinaryExpr,@addexpr { override string getOp() { result = " + " } }
+/** A binary expression using the `-` operator. */
+class SubExpr extends BinaryExpr,@subexpr { override string getOp() { result = " - " } }
+/** A binary expression using the `<<` operator. */
+class LShiftExpr extends BinaryExpr,@lshiftexpr { override string getOp() { result = " << " } }
+/** A binary expression using the `>>` operator. */
+class RShiftExpr extends BinaryExpr,@rshiftexpr { override string getOp() { result = " >> " } }
+/** A binary expression using the `>>>` operator. */
+class URShiftExpr extends BinaryExpr,@urshiftexpr { override string getOp() { result = " >>> " } }
+/** A binary expression using the `&` operator. */
+class AndBitwiseExpr extends BinaryExpr,@andbitexpr { override string getOp() { result = " & " } }
+/** A binary expression using the `|` operator. */
+class OrBitwiseExpr extends BinaryExpr,@orbitexpr { override string getOp() { result = " | " } }
+/** A binary expression using the `^` operator. */
+class XorBitwiseExpr extends BinaryExpr,@xorbitexpr { override string getOp() { result = " ^ " } }
+/** A binary expression using the `&&` operator. */
+class AndLogicalExpr extends BinaryExpr,@andlogicalexpr { override string getOp() { result = " && " } }
+/** A binary expression using the `||` operator. */
+class OrLogicalExpr extends BinaryExpr,@orlogicalexpr { override string getOp() { result = " || " } }
+/** A binary expression using the `<` operator. */
+class LTExpr extends BinaryExpr,@ltexpr { override string getOp() { result = " < " } }
+/** A binary expression using the `>` operator. */
+class GTExpr extends BinaryExpr,@gtexpr { override string getOp() { result = " > " } }
+/** A binary expression using the `<=` operator. */
+class LEExpr extends BinaryExpr,@leexpr { override string getOp() { result = " <= " } }
+/** A binary expression using the `>=` operator. */
+class GEExpr extends BinaryExpr,@geexpr { override string getOp() { result = " >= " } }
+/** A binary expression using the `==` operator. */
+class EQExpr extends BinaryExpr,@eqexpr { override string getOp() { result = " == " } }
+/** A binary expression using the `!=` operator. */
+class NEExpr extends BinaryExpr,@neexpr { override string getOp() { result = " != " } }
+
+/**
+ * A bitwise expression.
+ *
+ * This includes expressions involving the operators
+ * `&`, `|`, `^`, or `~`,
+ * possibly parenthesized.
+ */
+class BitwiseExpr extends Expr {
+ BitwiseExpr() {
+ exists(Expr proper | proper = this.getProperExpr() |
+ proper instanceof AndBitwiseExpr or
+ proper instanceof OrBitwiseExpr or
+ proper instanceof XorBitwiseExpr or
+ proper instanceof BitNotExpr
+ )
+ }
+}
+
+/** A logical expression.
+ *
+ * This includes expressions involving the operators
+ * `&&`, `||`, or `!`.
+ */
+class LogicExpr extends Expr {
+ LogicExpr() {
+ this instanceof AndLogicalExpr or
+ this instanceof OrLogicalExpr or
+ this instanceof LogNotExpr
+ }
+
+ /** Gets an operand of this logical expression. */
+ Expr getAnOperand() {
+ this.(BinaryExpr).getAnOperand() = result or
+ this.(UnaryExpr).getExpr() = result
+ }
+}
+
+/**
+ * A comparison expression.
+ *
+ * This includes expressions using the operators
+ * `<=`, `>=`, `<` or `>`.
+ */
+abstract class ComparisonExpr extends BinaryExpr {
+ /**
+ * DEPRECATED: use `getLesserOperand()` instead.
+ */
+ deprecated
+ Expr getLesser() {
+ result = getLesserOperand()
+ }
+
+ /**
+ * DEPRECATED: use `getGreaterOperand()` instead.
+ */
+ deprecated
+ Expr getGreater() {
+ result = getGreaterOperand()
+ }
+
+ /**
+ * Gets the lesser operand of this comparison expression.
+ *
+ * For example, `x` is the lesser operand
+ * in `x < 0`, and `0` is the
+ * lesser operand in `x > 0`.
+ */
+ abstract Expr getLesserOperand();
+
+ /**
+ * Gets the greater operand of this comparison expression.
+ *
+ * For example, `x` is the greater operand
+ * in `x > 0`, and `0` is the
+ * greater operand in `x < 0`.
+ */
+ abstract Expr getGreaterOperand();
+
+ /** Holds if this comparison is strict, i.e. `<` or `>`. */
+ predicate isStrict() {
+ this instanceof LTExpr or this instanceof GTExpr
+ }
+}
+
+/** A comparison expression using the operator `<` or `<=`. */
+class LessThanComparison extends ComparisonExpr {
+ LessThanComparison() {
+ this instanceof LTExpr or this instanceof LEExpr
+ }
+
+ /** Gets the lesser operand of this comparison expression. */
+ override Expr getLesserOperand() {
+ result = this.getLeftOperand()
+ }
+
+ /** Gets the greater operand of this comparison expression. */
+ override Expr getGreaterOperand() {
+ result = this.getRightOperand()
+ }
+}
+
+/** A comparison expression using the operator `>` or `>=`. */
+class GreaterThanComparison extends ComparisonExpr {
+ GreaterThanComparison() {
+ this instanceof GTExpr or this instanceof GEExpr
+ }
+
+ /** Gets the lesser operand of this comparison expression. */
+ override Expr getLesserOperand() {
+ result = this.getRightOperand()
+ }
+
+ /** Gets the greater operand of this comparison expression. */
+ override Expr getGreaterOperand() {
+ result = this.getLeftOperand()
+ }
+}
+
+/**
+ * An equality test is a binary expression using
+ * the `==` or `!=` operator.
+ */
+class EqualityTest extends BinaryExpr {
+ EqualityTest() {
+ this instanceof EQExpr or
+ this instanceof NEExpr
+ }
+
+ boolean polarity() {
+ result = true and this instanceof EQExpr
+ or
+ result = false and this instanceof NEExpr
+ }
+}
+
+/** A common super-class that represents unary operator expressions. */
+class UnaryExpr extends Expr,@unaryexpr {
+ /** Gets the operand expression. */
+ Expr getExpr() { result.getParent() = this }
+}
+
+/**
+ * A unary assignment expression is a unary expression using the
+ * prefix or postfix `++` or `--` operator.
+ */
+class UnaryAssignExpr extends UnaryExpr,@unaryassignment {
+}
+
+/** A post-increment expression. For example, `i++`. */
+class PostIncExpr extends UnaryAssignExpr,@postincexpr { override string toString() { result = "...++" } }
+/** A post-decrement expression. For example, `i--`. */
+class PostDecExpr extends UnaryAssignExpr,@postdecexpr { override string toString() { result = "...--" } }
+/** A pre-increment expression. For example, `++i`. */
+class PreIncExpr extends UnaryAssignExpr,@preincexpr { override string toString() { result = "++..." } }
+/** A pre-decrement expression. For example, `--i`. */
+class PreDecExpr extends UnaryAssignExpr,@predecexpr { override string toString() { result = "--..." } }
+/** A unary minus expression. For example, `-i`. */
+class MinusExpr extends UnaryExpr,@minusexpr { override string toString() { result = "-..." } }
+/** A unary plus expression. For example, `+i`. */
+class PlusExpr extends UnaryExpr,@plusexpr { override string toString() { result = "+..." } }
+/** A bit negation expression. For example, `~x`. */
+class BitNotExpr extends UnaryExpr,@bitnotexpr { override string toString() { result = "~..." } }
+/** A logical negation expression. For example, `!b`. */
+class LogNotExpr extends UnaryExpr,@lognotexpr { override string toString() { result = "!..." } }
+
+/** A cast expression. */
+class CastExpr extends Expr,@castexpr {
+ /** Gets the target type of this cast expression. */
+ Expr getTypeExpr() { result.isNthChildOf(this, 0) }
+
+ /** Gets the expression to which the cast operator is applied. */
+ Expr getExpr() { result.isNthChildOf(this, 1) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "(...)..." }
+}
+
+/** A class instance creation expression. */
+class ClassInstanceExpr extends Expr, ConstructorCall, @classinstancexpr {
+ /** Gets the number of arguments provided to the constructor of the class instance creation expression. */
+ override int getNumArgument() { count(this.getAnArgument()) = result }
+
+ /** Gets an argument provided to the constructor of this class instance creation expression. */
+ override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
+
+ /**
+ * Gets the argument provided to the constructor of this class instance creation expression
+ * at the specified (zero-based) position.
+ */
+ override Expr getArgument(int index) {
+ result.getIndex() = index and
+ result = this.getAnArgument()
+ }
+
+ /**
+ * Gets a type argument provided to the constructor of this class instance creation expression.
+ *
+ * This is used for instantiations of parameterized classes.
+ */
+ Expr getATypeArgument() { result = this.getTypeName().(TypeAccess).getATypeArgument() }
+
+ /**
+ * Gets the type argument provided to the constructor of this class instance creation expression
+ * at the specified (zero-based) position.
+ */
+ Expr getTypeArgument(int index) { result = this.getTypeName().(TypeAccess).getTypeArgument(index) }
+
+ /** Gets the qualifier of this class instance creation expression, if any. */
+ override Expr getQualifier() { result.isNthChildOf(this, -2) }
+
+ /**
+ * Gets the access to the type that is instantiated or subclassed by this
+ * class instance creation expression.
+ */
+ Expr getTypeName() { result.isNthChildOf(this, -3) }
+
+ /** Gets the constructor invoked by this class instance creation expression. */
+ override Constructor getConstructor() { callableBinding(this,result) }
+
+ /** Gets the anonymous class created by this class instance creation expression, if any. */
+ AnonymousClass getAnonymousClass() { isAnonymClass(result, this) }
+
+ /**
+ * Holds if this class instance creation expression has an
+ * empty type argument list of the form `<>`.
+ */
+ predicate isDiamond() {
+ this.getType() instanceof ParameterizedClass and
+ not exists(this.getATypeArgument())
+ }
+
+ /** Gets the immediately enclosing callable of this class instance creation expression. */
+ override Callable getEnclosingCallable() { result = Expr.super.getEnclosingCallable() }
+
+ /** Gets the immediately enclosing statement of this class instance creation expression. */
+ override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "new " + this.getConstructor().getName() + "(...)" }
+}
+
+/** A functional expression is either a lambda expression or a member reference expression. */
+abstract class FunctionalExpr extends ClassInstanceExpr {
+ /** Gets the implicit method corresponding to this functional expression. */
+ abstract Method asMethod();
+}
+
+/**
+ * Lambda expressions are represented by their implicit class instance creation expressions,
+ * which instantiate an anonymous class that overrides the unique method designated by
+ * their functional interface type. The parameters of the lambda expression correspond
+ * to the parameters of the overriding method, and the lambda body corresponds to the
+ * body of the overriding method (enclosed by a return statement and a block in the case
+ * of lambda expressions whose body is an expression).
+ *
+ * For details, see JLS v8 section 15.27.4: Run-Time Evaluation of Lambda Expressions.
+ */
+class LambdaExpr extends FunctionalExpr, @lambdaexpr {
+ /**
+ * Gets the implicit method corresponding to this lambda expression.
+ * The parameters of the lambda expression are the parameters of this method.
+ */
+ override Method asMethod() { result = getAnonymousClass().getAMethod() }
+
+ /** Holds if the body of this lambda is an expression. */
+ predicate hasExprBody() { lambdaKind(this,0) }
+ /** Holds if the body of this lambda is a statement. */
+ predicate hasStmtBody() { lambdaKind(this,1) }
+
+ /** Gets the body of this lambda expression, if it is an expression. */
+ Expr getExprBody() { hasExprBody() and result = asMethod().getBody().getAChild().(ReturnStmt).getResult() }
+ /** Gets the body of this lambda expression, if it is a statement. */
+ Stmt getStmtBody() { hasStmtBody() and result = asMethod().getBody() }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...->..." }
+}
+
+/**
+ * Member references are represented by their implicit class instance expressions,
+ * which instantiate an anonymous class that overrides the unique method designated by
+ * their functional interface type. The correspondence of the parameters of the overriding
+ * method in the anonymous class with the parameters of the callable that is referenced
+ * differs depending on the particular kind of member reference expression.
+ *
+ * For details, see JLS v8 section 15.13.3: Run-Time Evaluation of Method References.
+ */
+class MemberRefExpr extends FunctionalExpr, @memberref {
+ /**
+ * Gets the implicit method corresponding to this member reference expression.
+ * The body of this method is a return statement (enclosed in a block) whose expression
+ * is either a method access (if the reference is to a method), a class instance creation expression
+ * (if the reference is to a constructor) or an array creation expression (if the reference
+ * is to an array constructor).
+ */
+ override Method asMethod() { result = getAnonymousClass().getAMethod() }
+ /**
+ * Gets the method or constructor referenced by this member reference expression.
+ */
+ Callable getReferencedCallable() { memberRefBinding(this,result) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...::..." }
+}
+
+/**
+ * A conditional expression of the form `a ? b : c`, where `a` is the condition,
+ * `b` is the expression that is evaluated if the condition evaluates to `true`,
+ * and `c` is the expression that is evaluated if the condition evaluates to `false`.
+ */
+class ConditionalExpr extends Expr,@conditionalexpr {
+ /** Gets the condition of this conditional expression. */
+ Expr getCondition() { result.isNthChildOf(this, 0) }
+
+ /**
+ * Gets the expression that is evaluated if the condition of this
+ * conditional expression evaluates to `true`.
+ */
+ Expr getTrueExpr() { result.isNthChildOf(this, 1) }
+
+ /**
+ * Gets the expression that is evaluated if the condition of this
+ * conditional expression evaluates to `false`.
+ */
+ Expr getFalseExpr() { result.isNthChildOf(this, 2) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...?...:..." }
+}
+
+/** A parenthesised expression. */
+class ParExpr extends Expr,@parexpr {
+ /** Gets the expression inside the parentheses. */
+ Expr getExpr() { result.getParent() = this }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "(...)" }
+}
+
+/** An `instanceof` expression. */
+class InstanceOfExpr extends Expr,@instanceofexpr {
+ /** Gets the expression on the left-hand side of the `instanceof` operator. */
+ Expr getExpr() { result.isNthChildOf(this, 0) }
+
+ /** Gets the access to the type on the right-hand side of the `instanceof` operator. */
+ Expr getTypeName() { result.isNthChildOf(this, 1) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...instanceof..." }
+}
+
+/**
+ * A local variable declaration expression.
+ *
+ * Contexts in which such expressions may occur include
+ * local variable declaration statements and `for` loops.
+ */
+class LocalVariableDeclExpr extends Expr,@localvariabledeclexpr {
+ /** Gets an access to the variable declared by this local variable declaration expression. */
+ VarAccess getAnAccess() { variableBinding(result,this.getVariable()) }
+
+ /** Gets the local variable declared by this local variable declaration expression. */
+ LocalVariableDecl getVariable() { localvars(result,_,_,this) }
+
+ /** Gets the type access of this local variable declaration expression. */
+ Expr getTypeAccess() {
+ exists(LocalVariableDeclStmt lvds | lvds.getAVariable() = this | result.isNthChildOf(lvds, 0)) or
+ exists(CatchClause cc | cc.getVariable() = this | result.isNthChildOf(cc, -1)) or
+ exists(ForStmt fs | fs.getAnInit() = this | result.isNthChildOf(fs, 0)) or
+ exists(EnhancedForStmt efs | efs.getVariable() = this | result.isNthChildOf(efs, -1))
+ }
+
+ /** Gets the name of the variable declared by this local variable declaration expression. */
+ string getName() { result = this.getVariable().getName() }
+
+ /** Gets the initializer expression of this local variable declaration expression, if any. */
+ Expr getInit() { result.isNthChildOf(this, 0) }
+
+ /** Holds if this variable declaration implicitly initializes the variable. */
+ predicate hasImplicitInit() {
+ exists(CatchClause cc | cc.getVariable() = this) or
+ exists(EnhancedForStmt efs | efs.getVariable() = this)
+ }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = this.getName() }
+}
+
+/** An update of a variable or an initialization of the variable. */
+class VariableUpdate extends Expr {
+ VariableUpdate() {
+ this.(Assignment).getDest() instanceof VarAccess or
+ this instanceof LocalVariableDeclExpr or
+ this.(UnaryAssignExpr).getExpr() instanceof VarAccess
+ }
+
+ /** Gets the destination of this variable update. */
+ Variable getDestVar() {
+ result.getAnAccess() = this.(Assignment).getDest() or
+ result = this.(LocalVariableDeclExpr).getVariable() or
+ result.getAnAccess() = this.(UnaryAssignExpr).getExpr()
+ }
+}
+
+/**
+ * An assignment to a variable or an initialization of the variable.
+ */
+class VariableAssign extends VariableUpdate {
+ VariableAssign() {
+ this instanceof AssignExpr or
+ this instanceof LocalVariableDeclExpr
+ }
+
+ /**
+ * Gets the source of this assignment, if any.
+ *
+ * An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and
+ * does not have a source.
+ */
+ Expr getSource() {
+ result = this.(AssignExpr).getSource() or
+ result = this.(LocalVariableDeclExpr).getInit()
+ }
+}
+
+/** A type literal. For example, `String.class`. */
+class TypeLiteral extends Expr,@typeliteral {
+ /** Gets the access to the type whose class is accessed. */
+ Expr getTypeName() { result.getParent() = this }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = this.getTypeName().toString() + ".class" }
+}
+
+/**
+ * A use of one of the keywords `this` or `super`, which may be qualified.
+ */
+abstract class InstanceAccess extends Expr {
+ /**
+ * Gets the qualifying expression, if any.
+ *
+ * For example, the qualifying expression of `A.this` is `A`.
+ */
+ Expr getQualifier() { result.getParent() = this }
+
+ /**
+ * Holds if this instance access gets the value of `this`. That is, it is not
+ * an enclosing instance.
+ * This never holds for accesses in lambda expressions as they cannot access
+ * their own instance directly.
+ */
+ predicate isOwnInstanceAccess() {
+ not isEnclosingInstanceAccess(_)
+ }
+
+ /** Holds if this instance access is to an enclosing instance of type `t`. */
+ predicate isEnclosingInstanceAccess(RefType t) {
+ t = getQualifier().getType().(RefType).getSourceDeclaration() and
+ t != getEnclosingCallable().getDeclaringType()
+ or
+ not exists(getQualifier()) and
+ exists(LambdaExpr lam | lam.asMethod() = getEnclosingCallable() |
+ t = lam.getAnonymousClass().getEnclosingType()
+ )
+ }
+}
+
+/**
+ * A use of the keyword `this`, which may be qualified.
+ *
+ * Such an expression allows access to an enclosing instance.
+ * For example, `A.this` refers to the enclosing instance
+ * of type `A`.
+ */
+class ThisAccess extends InstanceAccess,@thisaccess {
+ /** Gets a printable representation of this expression. */
+ override string toString() {
+ if exists(this.getQualifier()) then (
+ result = this.getQualifier() + ".this"
+ ) else (
+ result = "this"
+ )
+ }
+}
+
+/**
+ * A use of the keyword `super`, which may be qualified.
+ *
+ * Such an expression allows access to super-class members of an enclosing instance.
+ * For example, `A.super.x`.
+ */
+class SuperAccess extends InstanceAccess,@superaccess {
+ /** Gets a printable representation of this expression. */
+ override string toString() {
+ if exists(this.getQualifier()) then (
+ result = this.getQualifier() + ".super"
+ ) else (
+ result = "super"
+ )
+ }
+}
+
+/**
+ * A variable access is a (possibly qualified) reference to
+ * a field, parameter or local variable.
+ */
+class VarAccess extends Expr,@varaccess {
+ /** Gets the qualifier of this variable access, if any. */
+ Expr getQualifier() { result.getParent() = this }
+
+ /** Holds if this variable access has a qualifier. */
+ predicate hasQualifier() { exists(getQualifier()) }
+
+ /** Gets the variable accessed by this variable access. */
+ Variable getVariable() { variableBinding(this,result) }
+
+ /**
+ * Holds if this variable access is an l-value.
+ *
+ * An l-value is a write access to a variable, which occurs as the destination of an assignment.
+ */
+ predicate isLValue() {
+ exists(Assignment a | a.getDest() = this) or
+ exists(PreIncExpr e | e.getExpr() = this) or
+ exists(PreDecExpr e | e.getExpr() = this) or
+ exists(PostIncExpr e | e.getExpr() = this) or
+ exists(PostDecExpr e | e.getExpr() = this)
+ }
+
+ /**
+ * Holds if this variable access is an r-value.
+ *
+ * An r-value is a read access to a variable.
+ * In other words, it is a variable access that does _not_ occur as the destination of
+ * a simple assignment, but it may occur as the destination of a compound assignment
+ * or a unary assignment.
+ */
+ predicate isRValue() {
+ not exists(AssignExpr a | a.getDest() = this)
+ }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() {
+ result = this.getQualifier().toString() + "." + this.getVariable().getName() or
+ (not this.hasQualifier() and result = this.getVariable().getName())
+ }
+
+ /**
+ * Holds if this access refers to a local variable or a field of
+ * the receiver of the enclosing method or constructor.
+ */
+ predicate isLocal() {
+ // The access has no qualifier, or...
+ not hasQualifier() or
+ // the qualifier is either `this` or `A.this`, where `A` is the enclosing type, or
+ // the qualifier is either `super` or `A.super`, where `A` is the enclosing type.
+ getQualifier().(InstanceAccess).isOwnInstanceAccess()
+ }
+}
+
+/**
+ * An l-value is a write access to a variable, which occurs as the destination of an assignment.
+ */
+class LValue extends VarAccess {
+ LValue() { this.isLValue() }
+
+ /**
+ * Gets a source expression used in an assignment to this l-value.
+ *
+ * For assignments using the `=` operator, the source expression
+ * is simply the RHS of the assignment.
+ *
+ * Note that for l-values occurring on the LHS of compound assignment operators
+ * (such as (`+=`), both the RHS and the LHS of the compound assignment
+ * are source expressions of the assignment.
+ */
+ Expr getRHS() {
+ exists(Assignment e | e.getDest() = this and e.getSource() = result)
+ }
+}
+
+/**
+ * An r-value is a read access to a variable.
+ *
+ * In other words, it is a variable access that does _not_ occur as the destination of
+ * a simple assignment, but it may occur as the destination of a compound assignment
+ * or a unary assignment.
+ */
+class RValue extends VarAccess {
+ RValue() { this.isRValue() }
+}
+
+/** A method access is an invocation of a method with a list of arguments. */
+class MethodAccess extends Expr, Call, @methodaccess {
+ /** Gets the qualifying expression of this method access, if any. */
+ override Expr getQualifier() { result.isNthChildOf(this, -1) }
+
+ /** Holds if this method access has a qualifier. */
+ predicate hasQualifier() { exists(getQualifier()) }
+
+ /** Gets an argument supplied to the method that is invoked using this method access. */
+ override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
+
+ /** Gets the argument at the specified (zero-based) position in this method access. */
+ override Expr getArgument(int index) { exprs(result, _, _, this, index) and index >= 0 }
+
+ /** Gets a type argument supplied as part of this method access, if any. */
+ Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this }
+
+ /** Gets the type argument at the specified (zero-based) position in this method access, if any. */
+ Expr getTypeArgument(int index) {
+ result = this.getATypeArgument() and
+ (-2 - result.getIndex()) = index
+ }
+
+ /** Gets the method accessed by this method access. */
+ Method getMethod() { callableBinding(this,result) }
+
+ /** Gets the immediately enclosing callable that contains this method access. */
+ override Callable getEnclosingCallable() { result = Expr.super.getEnclosingCallable() }
+
+ /** Gets the immediately enclosing statement that contains this method access. */
+ override Stmt getEnclosingStmt() { result = Expr.super.getEnclosingStmt() }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = this.printAccess() }
+
+ /** Gets a printable representation of this expression. */
+ string printAccess() {
+ result = this.getMethod().getName() + "(...)"
+ }
+
+ /**
+ * Gets the type of the qualifier on which this method is invoked, or
+ * the enclosing type if there is no qualifier.
+ */
+ RefType getReceiverType() {
+ result = getQualifier().getType() or
+ not hasQualifier() and result = getEnclosingCallable().getDeclaringType()
+ }
+
+ /**
+ * Holds if this is a method access to an instance method of `this`. That is,
+ * the qualifier is either an explicit or implicit unqualified `this` or `super`.
+ */
+ predicate isOwnMethodAccess() {
+ Qualifier::ownMemberAccess(this)
+ }
+
+ /**
+ * Holds if this is a method access to an instance method of the enclosing
+ * class `t`. That is, the qualifier is either an explicit or implicit
+ * `t`-qualified `this` or `super`.
+ */
+ predicate isEnclosingMethodAccess(RefType t) {
+ Qualifier::enclosingMemberAccess(this, t)
+ }
+}
+
+/** A type access is a (possibly qualified) reference to a type. */
+class TypeAccess extends Expr, Annotatable, @typeaccess {
+ /** Gets the qualifier of this type access, if any. */
+ Expr getQualifier() { result.isNthChildOf(this, -1) }
+
+ /** Holds if this type access has a qualifier. */
+ predicate hasQualifier() { exists(Expr e | e = this.getQualifier()) }
+
+ /** Gets a type argument supplied to this type access. */
+ Expr getATypeArgument() { result.getIndex() >= 0 and result.getParent() = this }
+
+ /** Gets the type argument at the specified (zero-based) position in this type access. */
+ Expr getTypeArgument(int index) {
+ result = this.getATypeArgument() and
+ result.getIndex() = index
+ }
+
+ /** Holds if this type access has a type argument. */
+ predicate hasTypeArgument() { exists(Expr e | e = this.getATypeArgument()) }
+
+ /** Gets the compilation unit in which this type access occurs. */
+ override CompilationUnit getCompilationUnit() { result = Expr.super.getCompilationUnit() }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() {
+ result = this.getQualifier().toString() + "." + this.getType().toString() or
+ (not this.hasQualifier() and result = this.getType().toString())
+ }
+}
+
+/** An array type access is a type access of the form `String[]`. */
+class ArrayTypeAccess extends Expr,@arraytypeaccess {
+ /**
+ * Gets the expression representing the component type of this array type access.
+ *
+ * For example, in the array type access `String[][]`,
+ * the component type is `String[]` and the
+ * element type is `String`.
+ */
+ Expr getComponentName() { result.getParent() = this }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...[]" }
+}
+
+/**
+ * A union type access is a type access of the form `T1 | T2`.
+ *
+ * Such a type access can only occur in a multi-catch clause.
+ */
+class UnionTypeAccess extends Expr, @uniontypeaccess {
+ /** One of the alternatives in the union type access. */
+ Expr getAnAlternative() { result.getParent() = this }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...|..." }
+}
+
+/**
+ * An intersection type access expression is a type access
+ * of the form `T0 & T1 & ... & Tn`,
+ * where `T0` is a class or interface type and
+ * `T1, ..., Tn` are interface types.
+ *
+ * An intersection type access _expression_ can only
+ * occur in a cast expression.
+ *
+ * Note that intersection types can also occur as the bound
+ * of a bounded type, such as a type variable. Each component
+ * of such a bound is represented by the QL class `TypeBound`.
+ */
+class IntersectionTypeAccess extends Expr, @intersectiontypeaccess {
+ /**
+ * One of the bounds of this intersection type access expression.
+ *
+ * For example, in the intersection type access expression
+ * `Runnable & Cloneable`, both `Runnable`
+ * and `Cloneable` are bounds.
+ */
+ Expr getABound() { result.getParent() = this }
+ /**
+ * Gets the bound at a specified (zero-based) position in this intersection type access expression.
+ *
+ * For example, in the intersection type access expression
+ * `Runnable & Cloneable`, the bound at position 0 is
+ * `Runnable` and the bound at position 1 is `Cloneable`.
+ */
+ Expr getBound(int index) { result.isNthChildOf(this, index) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "...&..." }
+}
+
+/** A package access. */
+class PackageAccess extends Expr,@packageaccess {
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "package" }
+}
+
+/** A wildcard type access, which may have either a lower or an upper bound. */
+class WildcardTypeAccess extends Expr,@wildcardtypeaccess {
+ /** Gets the upper bound of this wildcard type access, if any. */
+ Expr getUpperBound() { result.isNthChildOf(this, 0) }
+
+ /** Gets the lower bound of this wildcard type access, if any. */
+ Expr getLowerBound() { result.isNthChildOf(this, 1) }
+
+ /** Holds if this wildcard is not bounded by any type bounds. */
+ predicate hasNoBound() { not exists(TypeAccess t | t.getParent() = this) }
+
+ /** Gets a printable representation of this expression. */
+ override string toString() { result = "? ..." }
+}
+
+/**
+ * Any call to a callable.
+ *
+ * This includes method calls, constructor and super constructor invocations,
+ * and constructors invoked through class instantiation.
+ */
+class Call extends Top, @caller {
+ /** Gets an argument supplied in this call. */
+ /*abstract*/ Expr getAnArgument() { none() }
+ /** Gets the argument specified at the (zero-based) position in this call. */
+ /*abstract*/ Expr getArgument(int n) { none() }
+ /** Gets the immediately enclosing callable that contains this call. */
+ /*abstract*/ Callable getEnclosingCallable() { none() }
+ /** Gets the qualifying expression of this call, if any. */
+ /*abstract*/ Expr getQualifier() { none() }
+ /** Gets the enclosing statement of this call. */
+ /*abstract*/ Stmt getEnclosingStmt() { none() }
+
+ /** Gets the number of arguments provided in this call. */
+ int getNumArgument() { count(this.getAnArgument()) = result }
+
+ /** Gets the target callable of this call. */
+ Callable getCallee() {
+ callableBinding(this,result)
+ }
+
+ /** Gets the callable invoking this call. */
+ Callable getCaller() {
+ result = getEnclosingCallable()
+ }
+}
+
+/** A polymorphic call to an instance method. */
+class VirtualMethodAccess extends MethodAccess {
+ VirtualMethodAccess() {
+ this.getMethod().isVirtual() and
+ not this.getQualifier() instanceof SuperAccess
+ }
+}
+
+/** A static method call. */
+class StaticMethodAccess extends MethodAccess {
+ StaticMethodAccess() {
+ this.getMethod().isStatic()
+ }
+}
+
+/** A call to a method in the superclass. */
+class SuperMethodAccess extends MethodAccess {
+ SuperMethodAccess() {
+ this.getQualifier() instanceof SuperAccess
+ }
+}
+
+/**
+ * A constructor call, which occurs either as a constructor invocation inside a
+ * constructor, or as part of a class instance expression.
+ */
+abstract class ConstructorCall extends Call {
+
+ /** Gets the target constructor of the class being instantiated. */
+ abstract Constructor getConstructor();
+
+ /** Holds if this constructor call is an explicit call to `this(...)`. */
+ predicate callsThis() {
+ this instanceof ThisConstructorInvocationStmt
+ }
+
+ /** Holds if this constructor call is an explicit call to `super(...)`. */
+ predicate callsSuper() {
+ this instanceof SuperConstructorInvocationStmt
+ }
+
+ /** Gets the type of the object instantiated by this constructor call. */
+ RefType getConstructedType() { result = this.getConstructor().getDeclaringType() }
+}
+
+/** An expression that accesses a field. */
+class FieldAccess extends VarAccess {
+ FieldAccess() {
+ this.getVariable() instanceof Field
+ }
+
+ /** Gets the field accessed by this field access expression. */
+ Field getField() {
+ this.getVariable() = result
+ }
+
+ /** Gets the immediately enclosing callable that contains this field access expression. */
+ Callable getSite() {
+ this.getEnclosingCallable() = result
+ }
+
+ /**
+ * Holds if this is a field access to an instance field of `this`. That is,
+ * the qualifier is either an explicit or implicit unqualified `this` or `super`.
+ */
+ predicate isOwnFieldAccess() {
+ Qualifier::ownMemberAccess(this)
+ }
+
+ /**
+ * Holds if this is a field access to an instance field of the enclosing
+ * class `t`. That is, the qualifier is either an explicit or implicit
+ * `t`-qualified `this` or `super`.
+ */
+ predicate isEnclosingFieldAccess(RefType t) {
+ Qualifier::enclosingMemberAccess(this, t)
+ }
+}
+
+private module Qualifier {
+ /** A type qualifier for an `InstanceAccess`. */
+ private newtype TThisQualifier = TThis() or TEnclosing(RefType t)
+
+ /** An expression that accesses a member. That is, either a `FieldAccess` or a `MethodAccess`. */
+ class MemberAccess extends Expr {
+ MemberAccess() {
+ this instanceof FieldAccess or
+ this instanceof MethodAccess
+ }
+ /** Gets the member accessed by this member access. */
+ Member getMember() {
+ result = this.(FieldAccess).getField() or
+ result = this.(MethodAccess).getMethod()
+ }
+ /** Gets the qualifier of this member access, if any. */
+ Expr getQualifier() {
+ result = this.(FieldAccess).getQualifier() or
+ result = this.(MethodAccess).getQualifier()
+ }
+ }
+
+ /**
+ * Gets the implicit type qualifier of the implicit `ThisAccess` qualifier of
+ * an access to `m` from within `ic`, which does not itself inherit `m`.
+ */
+ private RefType getImplicitEnclosingQualifier(InnerClass ic, Member m) {
+ exists(RefType enclosing | enclosing = ic.getEnclosingType() |
+ if enclosing.inherits(m) then
+ result = enclosing
+ else
+ result = getImplicitEnclosingQualifier(enclosing, m)
+ )
+ }
+
+ /**
+ * Gets the implicit type qualifier of the implicit `ThisAccess` qualifier of `ma`.
+ */
+ private TThisQualifier getImplicitQualifier(MemberAccess ma) {
+ exists(Member m | m = ma.getMember() |
+ not m.isStatic() and
+ not exists(ma.getQualifier()) and
+ exists(RefType t | t = ma.getEnclosingCallable().getDeclaringType() |
+ not t instanceof InnerClass and result = TThis() or
+ exists(InnerClass ic | ic = t |
+ if ic.inherits(m) then
+ result = TThis()
+ else
+ result = TEnclosing(getImplicitEnclosingQualifier(ic, m))
+ )
+ )
+ )
+ }
+
+ /**
+ * Gets the type qualifier of the `InstanceAccess` qualifier of `ma`.
+ */
+ private TThisQualifier getThisQualifier(MemberAccess ma) {
+ result = getImplicitQualifier(ma) or
+ exists(Expr q |
+ not ma.getMember().isStatic() and
+ q = ma.getQualifier()
+ |
+ exists(InstanceAccess ia | ia = q and ia.isOwnInstanceAccess() and result = TThis()) or
+ exists(InstanceAccess ia, RefType qt | ia = q and ia.isEnclosingInstanceAccess(qt) and result = TEnclosing(qt))
+ )
+ }
+
+ /**
+ * Holds if `ma` is a member access to an instance field or method of `this`. That is,
+ * the qualifier is either an explicit or implicit unqualified `this` or `super`.
+ */
+ predicate ownMemberAccess(MemberAccess ma) {
+ TThis() = getThisQualifier(ma)
+ }
+
+ /**
+ * Holds if `ma` is a member access to an instance field or method of the enclosing
+ * class `t`. That is, the qualifier is either an explicit or implicit
+ * `t`-qualified `this` or `super`.
+ */
+ predicate enclosingMemberAccess(MemberAccess ma, RefType t) {
+ TEnclosing(t) = getThisQualifier(ma)
+ }
+}
+
+/** An expression that assigns a value to a field. */
+class FieldWrite extends FieldAccess {
+ FieldWrite() {
+ exists(Field f | f = getVariable() and isLValue())
+ }
+}
+
+/** An expression that reads a field. */
+class FieldRead extends FieldAccess {
+ FieldRead() {
+ exists(Field f | f = getVariable() and isRValue())
+ }
+}
+
+private predicate hasInstantiation(RefType t) {
+ t instanceof TypeVariable or
+ t instanceof Wildcard or
+ hasInstantiation(t.(Array).getComponentType()) or
+ hasInstantiation(t.(ParameterizedType).getATypeArgument())
+}
+
+/** An argument to a call. */
+class Argument extends Expr {
+ Call call;
+ int pos;
+
+ Argument() {
+ call.getArgument(pos) = this
+ }
+
+ /** Gets the call that has this argument. */
+ Call getCall() { result = call }
+
+ /** Gets the position of this argument. */
+ int getPosition() {
+ result = pos
+ }
+
+ /**
+ * Holds if this argument is an array of the appropriate type passed to a
+ * varargs parameter.
+ */
+ predicate isExplicitVarargsArray() {
+ exists(Array typ, Parameter p, Type ptyp |
+ typ = this.getType() and
+ pos = call.getNumArgument() - 1 and
+ call.getCallee().getParameter(pos) = p and
+ p.isVarargs() and
+ ptyp = p.getType() and
+ (
+ hasSubtype*(ptyp, typ) or
+ // If the types don't match then we'll guess based on whether there are type variables involved.
+ hasInstantiation(ptyp.(Array).getComponentType())
+ )
+ )
+ }
+
+ /** Holds if this argument is part of an implicit varargs array. */
+ predicate isVararg() {
+ isNthVararg(_)
+ }
+
+ /**
+ * Holds if this argument is part of an implicit varargs array at the
+ * given array index.
+ */
+ predicate isNthVararg(int arrayindex) {
+ not isExplicitVarargsArray() and
+ exists(Callable tgt |
+ call.getCallee() = tgt and
+ tgt.isVarargs() and
+ arrayindex = pos - tgt.getNumberOfParameters() + 1 and
+ arrayindex >= 0
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/GeneratedFiles.qll b/java/ql/src/semmle/code/java/GeneratedFiles.qll
new file mode 100644
index 00000000000..bd49af52c8b
--- /dev/null
+++ b/java/ql/src/semmle/code/java/GeneratedFiles.qll
@@ -0,0 +1,70 @@
+/**
+ * Provides classes and predicates for working with the most common types of generated files.
+ */
+
+import Type
+private import semmle.code.java.frameworks.JavaxAnnotations
+
+/** A Java class that is detected as having been generated. */
+abstract class GeneratedClass extends Class {
+
+}
+
+/**
+ * A Java class annotated with a `@Generated` annotation.
+ */
+class AnnotatedGeneratedClass extends GeneratedClass {
+ AnnotatedGeneratedClass() {
+ this.getAnAnnotation() instanceof GeneratedAnnotation
+ }
+}
+
+/** A Java class generated by an ANTLR scanner or parser class. */
+class AntlrGenerated extends GeneratedClass {
+ AntlrGenerated() {
+ exists(RefType t | this.getASupertype+() = t |
+ // ANTLR v3
+ t.hasQualifiedName("org.antlr.runtime", "Lexer") or
+ t.hasQualifiedName("org.antlr.runtime", "Parser") or
+ t.hasQualifiedName("org.antlr.runtime.tree", "TreeParser") or
+ // ANTLR v2
+ t.hasQualifiedName("antlr","TreeParser") or
+ t.hasQualifiedName("antlr","CharScanner") or
+ t.hasQualifiedName("antlr","LLkParser")
+ )
+ }
+}
+
+/** A generated callable is a callable declared in a generated class. */
+class GeneratedCallable extends Callable {
+ GeneratedCallable() {
+ this.getDeclaringType() instanceof GeneratedClass
+ }
+}
+
+/**
+ * A file that is detected as having been generated.
+ */
+abstract class GeneratedFile extends File {
+}
+
+/**
+ * A file detected as generated based on commonly-used marker comments.
+ */
+library
+class MarkerCommentGeneratedFile extends GeneratedFile {
+ MarkerCommentGeneratedFile() {
+ exists(JavadocElement t | t.getFile() = this |
+ exists(string msg | msg = t.getText() |
+ msg.regexpMatch("(?i).*\\bGenerated By\\b.*\\bDo not edit\\b.*") or
+ msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is|(has been)) (?:auto[ -]?)?gener(e?)ated.*") or
+ msg.regexpMatch("(?i).*\\bAny modifications to this file will be lost\\b.*") or
+ msg.regexpMatch("(?i).*\\bThis (file|class|interface|art[ei]fact) (was|is) (?:mechanically|automatically) generated\\b.*") or
+ msg.regexpMatch("(?i).*\\bThe following code was (?:auto[ -]?)?generated (?:by|from)\\b.*") or
+ msg.regexpMatch("(?i).*\\bAutogenerated by Thrift.*") or
+ msg.regexpMatch("(?i).*\\bGenerated By.*JavaCC.*") or
+ msg.regexpMatch("(?i).*\\bGenerated from .* by ANTLR.*")
+ )
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Generics.qll b/java/ql/src/semmle/code/java/Generics.qll
new file mode 100755
index 00000000000..73b08d9de62
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Generics.qll
@@ -0,0 +1,565 @@
+/**
+ * Provides classes and predicates for working with generic types.
+ *
+ * A generic type as declared in the program, for example
+ *
+ * ```
+ * class X { }
+ * ```
+ * is represented by a `GenericType`.
+ *
+ * A parameterized instance of such a type, for example
+ *
+ * ```
+ * X
+ * ```
+ * is represented by a `ParameterizedType`.
+ *
+ * For dealing with legacy code that is unaware of generics, every generic type has a
+ * "raw" version, represented by a `RawType`. In the example, `X` is the raw version of
+ * `X`.
+ *
+ * The erasure of a parameterized or raw type is its generic counterpart.
+ *
+ * Type parameters may have bounds as in
+ *
+ * ```
+ * class X { }
+ * ```
+ * which are represented by a `TypeBound`.
+ *
+ * The terminology for generic methods is analogous.
+ */
+
+import Type
+
+/**
+ * A generic type is a type that has a type parameter.
+ *
+ * For example, `X` in `class X { }`.
+ */
+class GenericType extends RefType {
+ GenericType() { typeVars(_,_,_,_,this) }
+
+ /**
+ * Gets a parameterization of this generic type, where each use of
+ * a formal type parameter has been replaced by its argument.
+ *
+ * For example, `List` is a parameterization of
+ * the generic type `List`, where `E` is a type parameter.
+ */
+ ParameterizedType getAParameterizedType() { result.getErasure() = this }
+
+ /**
+ * Gets the raw type corresponding to this generic type.
+ *
+ * The raw version of this generic type is the type that is formed by
+ * using the name of this generic type without specifying its type arguments.
+ *
+ * For example, `List` is the raw version of the generic type
+ * `List`, where `E` is a type parameter.
+ */
+ RawType getRawType() { result.getErasure() = this }
+
+ /**
+ * Gets the `i`-th type parameter of this generic type.
+ */
+ TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this) }
+
+ /**
+ * Gets a type parameter of this generic type.
+ */
+ TypeVariable getATypeParameter() { result = getTypeParameter(_) }
+
+ /**
+ * Gets the number of type parameters of this generic type.
+ */
+ int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
+}
+
+/** A generic type that is a class. */
+class GenericClass extends GenericType, Class {
+}
+
+/** A generic type that is an interface. */
+class GenericInterface extends GenericType, Interface {
+}
+
+/**
+ * A common super-class for Java types that may have a type bound.
+ * This includes type parameters and wildcards.
+ */
+abstract class BoundedType extends RefType, @boundedtype {
+ /** Holds if this type is bounded. */
+ predicate hasTypeBound() { exists(TypeBound tb | tb = this.getATypeBound()) }
+
+ /** Gets a type bound for this type, if any. */
+ TypeBound getATypeBound() { result.getBoundedType() = this }
+
+ /** Gets the first type bound for this type, if any. */
+ TypeBound getFirstTypeBound() { result = getATypeBound() and result.getPosition() = 0 }
+
+ /**
+ * Gets an upper type bound of this type, or `Object`
+ * if no explicit type bound is present.
+ */
+ abstract RefType getUpperBoundType();
+
+ /**
+ * Gets the first upper type bound of this type, or `Object`
+ * if no explicit type bound is present.
+ */
+ abstract RefType getFirstUpperBoundType();
+
+ /** Gets a transitive upper bound for this type that is not itself a bounded type. */
+ RefType getAnUltimateUpperBoundType() {
+ result = getUpperBoundType() and not result instanceof BoundedType or
+ result = getUpperBoundType().(BoundedType).getAnUltimateUpperBoundType()
+ }
+}
+
+/**
+ * A type parameter used in the declaration of a generic type or method.
+ *
+ * For example, `T` is a type parameter in
+ * `class X { }` and in ` void m() { }`.
+ */
+class TypeVariable extends BoundedType, @typevariable {
+ /** Gets the generic type that is parameterized by this type parameter, if any. */
+ RefType getGenericType() { typeVars(this,_,_,_,result) }
+
+ /** Gets the generic callable that is parameterized by this type parameter, if any. */
+ GenericCallable getGenericCallable() { typeVars(this,_,_,_,result) }
+
+ /**
+ * Gets an upper bound of this type parameter, or `Object`
+ * if no explicit type bound is present.
+ */
+ pragma[nomagic]
+ override RefType getUpperBoundType() {
+ if this.hasTypeBound() then
+ result = this.getATypeBound().getType()
+ else
+ result instanceof TypeObject
+ }
+
+ /**
+ * Gets the first upper bound of this type parameter, or `Object`
+ * if no explicit type bound is present.
+ */
+ pragma[nomagic]
+ override RefType getFirstUpperBoundType() {
+ if this.hasTypeBound() then
+ result = this.getFirstTypeBound().getType()
+ else
+ result instanceof TypeObject
+ }
+
+ /** Gets the lexically enclosing package of this type parameter, if any. */
+ override Package getPackage() {
+ result = getGenericType().getPackage() or
+ result = getGenericCallable().getDeclaringType().getPackage()
+ }
+
+ /** Finds a type that was supplied for this parameter. */
+ RefType getASuppliedType() {
+ exists(RefType typearg |
+ exists(GenericType gen, int pos |
+ this = gen.getTypeParameter(pos) and
+ typearg = gen.getAParameterizedType().getTypeArgument(pos)
+ ) or
+ typearg = any(GenericCall call).getATypeArgument(this)
+ |
+ if typearg.(Wildcard).isUnconstrained() and this.hasTypeBound() then
+ result.(Wildcard).getUpperBound().getType() = this.getUpperBoundType()
+ else
+ result = typearg
+ )
+ }
+
+ /** Finds a non-typevariable type that was transitively supplied for this parameter. */
+ RefType getAnUltimatelySuppliedType() {
+ result = getASuppliedType() and not result instanceof TypeVariable or
+ result = getASuppliedType().(TypeVariable).getAnUltimatelySuppliedType()
+ }
+}
+
+/**
+ * A wildcard used as a type argument.
+ *
+ * For example, in
+ *
+ * ```
+ * Map extends Number, ? super Float>
+ * ```
+ * the first wildcard has an upper bound of `Number`
+ * and the second wildcard has a lower bound of `Float`.
+ */
+class Wildcard extends BoundedType, @wildcard {
+ /** Holds if this wildcard has an upper bound. */
+ predicate hasUpperBound() {
+ wildcards(this, _, 1)
+ }
+
+ /** Holds if this wildcard has a lower bound. */
+ predicate hasLowerBound() {
+ wildcards(this, _, 2)
+ }
+
+ /** Gets the upper bound for this wildcard, if any. */
+ TypeBound getUpperBound() {
+ this.hasUpperBound() and result = this.getATypeBound()
+ }
+
+ /**
+ * Gets an upper bound type of this wildcard, or `Object`
+ * if no explicit type bound is present.
+ */
+ override RefType getUpperBoundType() {
+ if this.hasUpperBound() then
+ result = this.getUpperBound().getType()
+ else
+ result instanceof TypeObject
+ }
+
+ /**
+ * Gets the first upper bound type of this wildcard, or `Object`
+ * if no explicit type bound is present.
+ */
+ override RefType getFirstUpperBoundType() {
+ if this.hasUpperBound() then
+ result = this.getFirstTypeBound().getType()
+ else
+ result instanceof TypeObject
+ }
+
+ /** Gets the lower bound of this wildcard, if any. */
+ TypeBound getLowerBound() {
+ this.hasLowerBound() and result = this.getATypeBound()
+ }
+
+ /**
+ * Gets the lower bound type for this wildcard,
+ * if an explicit lower bound is present.
+ */
+ Type getLowerBoundType() {
+ result = this.getLowerBound().getType()
+ }
+
+ /**
+ * Holds if this is the unconstrained wildcard `?`.
+ */
+ predicate isUnconstrained() {
+ not hasLowerBound() and
+ wildcards(this, "?", _)
+ }
+}
+
+/**
+ * A type bound on a type variable.
+ *
+ * For example, `Number` is a type bound on the type variable
+ * `T` in `class X { }`.
+ *
+ * Type variables can have multiple type bounds, specified by
+ * an intersection type `T0 & T1 & ... & Tn`.
+ * A bound with position 0 is an interface type or class type (possibly `Object`) and
+ * a bound with a non-zero position is an interface type.
+ */
+class TypeBound extends @typebound {
+ /**
+ * Gets the type variable that is bounded by this type bound.
+ *
+ * For example, `T` is the type variable bounded by the
+ * type `Number` in `T extends Number`.
+ */
+ BoundedType getBoundedType() { typeBounds(this,_,_,result) }
+
+ /**
+ * Gets the type of this bound.
+ *
+ * For example, `Number` is the type of the bound (of
+ * the type variable `T`) in `T extends Number`.
+ */
+ RefType getType() { typeBounds(this,result,_,_) }
+
+ /**
+ * Gets the (zero-indexed) position of this bound.
+ *
+ * For example, in
+ *
+ * ```
+ * class X { }
+ * ```
+ * the position of the bound `Runnable` is 0 and
+ * the position of the bound `Cloneable` is 1.
+ */
+ int getPosition() { typeBounds(this,_,result,_) }
+
+ /** Gets a textual representation of this type bound. */
+ string toString() { result = this.getType().getName() }
+}
+
+// -------- Parameterizations of generic types --------
+
+/**
+ * A parameterized type is an instantiation of a generic type, where
+ * each formal type variable has been replaced with a type argument.
+ *
+ * For example, `List` is a parameterization of
+ * the generic type `List`, where `E` is a type parameter.
+ */
+class ParameterizedType extends RefType {
+ ParameterizedType() {
+ typeArgs(_,_,this) or
+ typeVars(_,_,_,_,this)
+ }
+
+ /**
+ * The erasure of a parameterized type is its generic counterpart.
+ *
+ * For example, the erasure of both `X` and `X` is `X`.
+ */
+ override RefType getErasure() { erasure(this,result) or this.(GenericType) = result }
+
+ /**
+ * Gets the generic type corresponding to this parameterized type.
+ *
+ * For example, the generic type for both `X` and `X` is `X`.
+ */
+ GenericType getGenericType() { result.getAParameterizedType() = this }
+
+ /**
+ * Gets a type argument for this parameterized type.
+ *
+ * For example, `Number` in `List`.
+ */
+ RefType getATypeArgument() {
+ typeArgs(result,_,this) or
+ typeVars(result,_,_,_,this)
+ }
+
+ /** Gets the type argument of this parameterized type at the specified position. */
+ RefType getTypeArgument(int pos) {
+ typeArgs(result,pos,this) or
+ typeVars(result,_,pos,_,this)
+ }
+
+ /** Gets the number of type arguments of this parameterized type. */
+ int getNumberOfTypeArguments() {
+ result = count(int pos |
+ typeArgs(_,pos,this) or
+ typeVars(_,_,pos,_,this)
+ )
+ }
+
+ /** Holds if this type originates from source code. */
+ override predicate fromSource() { typeVars(_,_,_,_,this) and RefType.super.fromSource() }
+}
+
+/** A parameterized type that is a class. */
+class ParameterizedClass extends Class, ParameterizedType {
+}
+
+/** A parameterized type that is an interface. */
+class ParameterizedInterface extends Interface, ParameterizedType {
+}
+
+/**
+ * The raw version of a generic type is the type that is formed by
+ * using the name of a generic type without specifying its type arguments.
+ *
+ * For example, `List` is the raw version of the generic type
+ * `List`, where `E` is a type parameter.
+ *
+ * Raw types typically occur in legacy code that was written
+ * prior to the introduction of generic types in Java 5.
+ */
+class RawType extends RefType {
+ RawType() { isRaw(this) }
+
+ /**
+ * The erasure of a raw type is its generic counterpart.
+ *
+ * For example, the erasure of `List` is `List`.
+ */
+ override RefType getErasure() { erasure(this,result) }
+
+ /** Holds if this type originates from source code. */
+ override predicate fromSource() { not any() }
+}
+
+/** A raw type that is a class. */
+class RawClass extends Class, RawType {
+}
+
+/** A raw type that is an interface. */
+class RawInterface extends Interface, RawType {
+}
+
+// -------- Generic callables --------
+
+/**
+ * A generic callable is a callable with a type parameter.
+ */
+class GenericCallable extends Callable {
+ GenericCallable() {
+ exists(Callable srcDecl |
+ methods(this,_,_,_,_,srcDecl) or constrs(this,_,_,_,_,srcDecl)
+ |
+ typeVars(_,_,_,_,srcDecl)
+ )
+ }
+
+ /**
+ * Gets the `i`-th type parameter of this generic callable.
+ */
+ TypeVariable getTypeParameter(int i) { typeVars(result, _, i, _, this.getSourceDeclaration()) }
+
+ /**
+ * Gets a type parameter of this generic callable.
+ */
+ TypeVariable getATypeParameter() { result = getTypeParameter(_) }
+
+ /**
+ * Gets the number of type parameters of this generic callable.
+ */
+ int getNumberOfTypeParameters() { result = strictcount(getATypeParameter()) }
+}
+
+/**
+ * A call where the callee is a generic callable.
+ */
+class GenericCall extends Call {
+ GenericCall() {
+ this.getCallee() instanceof GenericCallable
+ }
+
+ private RefType getAnInferredTypeArgument(TypeVariable v) {
+ typevarArg(this, v, result)
+ or
+ not typevarArg(this, v, _) and
+ v = this.getCallee().(GenericCallable).getATypeParameter() and
+ result.(Wildcard).getUpperBound().getType() = v.getUpperBoundType()
+ }
+
+ private RefType getAnExplicitTypeArgument(TypeVariable v) {
+ exists(GenericCallable gen, MethodAccess call, int i |
+ this = call and
+ gen = call.getCallee() and
+ v = gen.getTypeParameter(i) and
+ result = call.getTypeArgument(i).getType()
+ )
+ }
+
+ /** Gets a type argument of the call for the given `TypeVariable`. */
+ RefType getATypeArgument(TypeVariable v) {
+ result = getAnExplicitTypeArgument(v) or
+ not exists(getAnExplicitTypeArgument(v)) and
+ result = getAnInferredTypeArgument(v)
+ }
+}
+
+/** Infers a type argument of `call` for `v` using a simple unification. */
+private predicate typevarArg(Call call, TypeVariable v, RefType typearg) {
+ exists(GenericCallable gen |
+ gen = call.getCallee() and
+ v = gen.getATypeParameter()
+ |
+ hasSubstitution(gen.getReturnType(), call.(Expr).getType(), v, typearg)
+ or
+ exists(int n | hasSubtypedSubstitution(gen.getParameterType(n), call.getArgument(n).getType(), v, typearg))
+ )
+}
+
+/**
+ * The reflexive transitive closure of `RefType.extendsOrImplements` including reflexivity on `Type`s.
+ */
+private Type getShallowSupertype(Type t) {
+ result = t or t.(RefType).extendsOrImplements+(result)
+}
+
+/**
+ * Manual magic sets optimization for the "inputs" of `hasSubstitution` and
+ * `hasParameterSubstitution`.
+ */
+private predicate unificationTargets(RefType t1, Type t2) {
+ exists(GenericCallable gen, Call call |
+ gen = call.getCallee()
+ |
+ t1 = gen.getReturnType() and t2 = call.(Expr).getType() or
+ exists(int n | t1 = gen.getParameterType(n) and t2 = getShallowSupertype(call.getArgument(n).getType()))
+ ) or
+ exists(Array a1, Array a2 |
+ unificationTargets(a1, a2) and
+ t1 = a1.getComponentType() and
+ t2 = a2.getComponentType()
+ ) or
+ exists(ParameterizedType pt1, ParameterizedType pt2, int pos |
+ unificationTargets(pt1, pt2) and
+ t1 = pt1.getTypeArgument(pos) and
+ t2 = pt2.getTypeArgument(pos)
+ )
+}
+
+/**
+ * Unifies `t1` and `t2` with respect to `v` allowing subtyping.
+ *
+ * `t1` contains `v` and equals a supertype of `t2` if `subst` is substituted for `v`.
+ * Only shallow supertypes of `t2` are considered in order to get the most precise result for `subst`.
+ * For example:
+ * If `t1` is `List` and `t2` is `ArrayList` we only want `subst` to be `T` and not, say, `?`.
+ */
+pragma[nomagic]
+private predicate hasSubtypedSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) {
+ hasSubstitution(t1, t2, v, subst) or
+ exists(GenericType g | hasParameterSubstitution(g, t1, g, getShallowSupertype(t2), v, subst))
+}
+
+/**
+ * Unifies `t1` and `t2` with respect to `v`.
+ *
+ * `t1` contains `v` and equals `t2` if `subst` is substituted for `v`.
+ * As a special case `t2` can be a primitive type and the equality hold when
+ * `t2` is auto-boxed.
+ */
+private predicate hasSubstitution(RefType t1, Type t2, TypeVariable v, RefType subst) {
+ unificationTargets(t1, t2) and
+ (
+ t1 = v and (t2 = subst or t2.(PrimitiveType).getBoxedType() = subst) or
+ hasSubstitution(t1.(Array).getComponentType(), t2.(Array).getComponentType(), v, subst) or
+ exists(GenericType g | hasParameterSubstitution(g, t1, g, t2, v, subst))
+ )
+}
+
+private predicate hasParameterSubstitution(GenericType g1, ParameterizedType pt1, GenericType g2, ParameterizedType pt2, TypeVariable v, RefType subst) {
+ unificationTargets(pt1, pt2) and
+ exists(int pos |
+ hasSubstitution(pt1.getTypeArgument(pos), pt2.getTypeArgument(pos), v, subst)
+ ) and
+ g1 = pt1.getGenericType() and
+ g2 = pt2.getGenericType()
+}
+
+/**
+ * A generic constructor is a constructor with a type parameter.
+ *
+ * For example, ` C(T t) { }` is a generic constructor for type `C`.
+ */
+class GenericConstructor extends Constructor, GenericCallable {
+ override GenericConstructor getSourceDeclaration() { result = Constructor.super.getSourceDeclaration() }
+ override ConstructorCall getAReference() { result = Constructor.super.getAReference() }
+}
+
+/**
+ * A generic method is a method with a type parameter.
+ *
+ * For example, ` void m(T t) { }` is a generic method.
+ */
+class GenericMethod extends Method, GenericCallable {
+ override GenericSrcMethod getSourceDeclaration() { result = Method.super.getSourceDeclaration() }
+}
+
+/** A generic method that is the same as its source declaration. */
+class GenericSrcMethod extends SrcMethod, GenericMethod {
+}
diff --git a/java/ql/src/semmle/code/java/Import.qll b/java/ql/src/semmle/code/java/Import.qll
new file mode 100755
index 00000000000..90a85595a8a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Import.qll
@@ -0,0 +1,140 @@
+/**
+ * Provides classes and predicates for working with Java imports.
+ */
+
+import semmle.code.Location
+import CompilationUnit
+
+/** A common super-class for all kinds of Java import declarations. */
+class Import extends Element, @import {
+ /** Gets the compilation unit in which this import declaration occurs. */
+ override CompilationUnit getCompilationUnit() { result = this.getFile() }
+
+ /** Holds if this import declaration occurs in source code. */
+ override predicate fromSource() { any() }
+
+ /*abstract*/ override string toString() { result = "import" }
+}
+
+/**
+ * A single-type-import declaration.
+ *
+ * For example, `import java.util.Set;`.
+ */
+class ImportType extends Import {
+ ImportType() { imports(this,_,_,1) }
+
+ /** Gets the imported type. */
+ RefType getImportedType() { imports(this,result,_,_) }
+
+ override string toString() { result = "import " + this.getImportedType().toString() }
+}
+
+/**
+ * A type-import-on-demand declaration that allows all accessible
+ * nested types of a named type to be imported as needed.
+ *
+ * For example, `import java.util.Map.*;` imports
+ * the nested type `java.util.Map.Entry` from the type
+ * `java.util.Map`.
+ */
+class ImportOnDemandFromType extends Import {
+ ImportOnDemandFromType() { imports(this,_,_,2) }
+
+ /** Gets the type from which accessible nested types are imported. */
+ RefType getTypeHoldingImport() { imports(this,result,_,_) }
+
+ /** Gets an imported type. */
+ NestedType getAnImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
+
+ override string toString() {
+ result = "import " + this.getTypeHoldingImport().toString() + ".*"
+ }
+}
+
+/**
+ * A type-import-on-demand declaration that allows all accessible
+ * types of a named package to be imported as needed.
+ *
+ * For example, `import java.util.*;`.
+ */
+class ImportOnDemandFromPackage extends Import {
+ ImportOnDemandFromPackage() { imports(this,_,_,3) }
+
+ /** Gets the package from which accessible types are imported. */
+ Package getPackageHoldingImport() { imports(this,result,_,_) }
+
+ /** Gets an imported type. */
+ RefType getAnImport() { result.getPackage() = this.getPackageHoldingImport() }
+
+ /** Gets a printable representation of this import declaration. */
+ override string toString() {
+ result = "import " + this.getPackageHoldingImport().toString() + ".*"
+ }
+}
+
+/**
+ * A static-import-on-demand declaration, which allows all accessible
+ * static members of a named type to be imported as needed.
+ *
+ * For example, `import static java.lang.System.*;`.
+ */
+class ImportStaticOnDemand extends Import {
+ ImportStaticOnDemand() { imports(this,_,_,4) }
+
+ /** Gets the type from which accessible static members are imported. */
+ RefType getTypeHoldingImport() { imports(this,result,_,_) }
+
+ /** Gets an imported type. */
+ NestedType getATypeImport() { result.getEnclosingType() = this.getTypeHoldingImport() }
+
+ /** Gets an imported method. */
+ Method getAMethodImport() { result.getDeclaringType() = this.getTypeHoldingImport() }
+
+ /** Gets an imported field. */
+ Field getAFieldImport() { result.getDeclaringType() = this.getTypeHoldingImport() }
+
+ /** Gets a printable representation of this import declaration. */
+ override string toString() {
+ result = "import static " + this.getTypeHoldingImport().toString() + ".*"
+ }
+}
+
+/**
+ * A single-static-import declaration, which imports all accessible
+ * static members with a given simple name from a type.
+ *
+ * For example, `import static java.util.Collections.sort;`
+ * imports all the static methods named `sort` from the
+ * class `java.util.Collections`.
+ */
+class ImportStaticTypeMember extends Import {
+ ImportStaticTypeMember() { imports(this,_,_,5) }
+
+ /** Gets the type from which static members with a given name are imported. */
+ RefType getTypeHoldingImport() { imports(this,result,_,_) }
+
+ /** Gets the name of the imported member(s). */
+ override string getName() { imports(this,_,result,_) }
+
+ /** Gets an imported member. */
+ Member getAMemberImport() {
+ this.getTypeHoldingImport().getAMember() = result and
+ result.getName() = this.getName() and result.isStatic()
+ }
+
+ /** Gets an imported type. */
+ NestedType getATypeImport() { result = this.getAMemberImport() }
+
+ /** Gets an imported method. */
+ Method getAMethodImport() { result = this.getAMemberImport() }
+
+ /** Gets an imported field. */
+ Field getAFieldImport() { result = this.getAMemberImport() }
+
+ /** Gets a printable representation of this import declaration. */
+ override string toString() {
+ result = "import static " + this.getTypeHoldingImport().toString()
+ + "." + this.getName()
+ }
+}
diff --git a/java/ql/src/semmle/code/java/J2EE.qll b/java/ql/src/semmle/code/java/J2EE.qll
new file mode 100755
index 00000000000..783d8e7c66c
--- /dev/null
+++ b/java/ql/src/semmle/code/java/J2EE.qll
@@ -0,0 +1,77 @@
+/**
+ * Provides classes and predicates for working with J2EE bean types.
+ */
+
+import Type
+
+/** An entity bean. */
+class EntityBean extends Class {
+ EntityBean() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EntityBean") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** An enterprise bean. */
+class EnterpriseBean extends RefType {
+ EnterpriseBean() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EnterpriseBean") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A local EJB home interface. */
+class LocalEJBHomeInterface extends Interface {
+ LocalEJBHomeInterface() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EJBLocalHome") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A remote EJB home interface. */
+class RemoteEJBHomeInterface extends Interface {
+ RemoteEJBHomeInterface() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EJBHome") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A local EJB interface. */
+class LocalEJBInterface extends Interface {
+ LocalEJBInterface() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EJBLocalObject") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A remote EJB interface. */
+class RemoteEJBInterface extends Interface {
+ RemoteEJBInterface() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","EJBObject") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A message bean. */
+class MessageBean extends Class {
+ MessageBean() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","MessageDrivenBean") |
+ this.hasSupertype+(i)
+ )
+ }
+}
+
+/** A session bean. */
+class SessionBean extends Class {
+ SessionBean() {
+ exists(Interface i | i.hasQualifiedName("javax.ejb","SessionBean") |
+ this.hasSupertype+(i)
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/JDK.qll b/java/ql/src/semmle/code/java/JDK.qll
new file mode 100644
index 00000000000..73ec22b700e
--- /dev/null
+++ b/java/ql/src/semmle/code/java/JDK.qll
@@ -0,0 +1,428 @@
+/**
+ * Provides classes and predicates for working with standard classes and methods from the JDK.
+ */
+
+import Member
+
+// --- Standard types ---
+
+/** The class `java.lang.Object`. */
+class TypeObject extends Class {
+ pragma[noinline]
+ TypeObject() {
+ this.hasQualifiedName("java.lang", "Object")
+ }
+}
+
+/** The interface `java.lang.Cloneable`. */
+class TypeCloneable extends Interface {
+ TypeCloneable() {
+ this.hasQualifiedName("java.lang", "Cloneable")
+ }
+}
+
+/** The class `java.lang.ProcessBuilder`. */
+class TypeProcessBuilder extends Class {
+ TypeProcessBuilder() {
+ hasQualifiedName("java.lang", "ProcessBuilder")
+ }
+}
+
+/** The class `java.lang.Runtime`. */
+class TypeRuntime extends Class {
+ TypeRuntime() {
+ hasQualifiedName("java.lang", "Runtime")
+ }
+}
+
+/** The class `java.lang.String`. */
+class TypeString extends Class {
+ TypeString() {
+ this.hasQualifiedName("java.lang", "String")
+ }
+}
+
+/** The `length()` method of the class `java.lang.String`. */
+class StringLengthMethod extends Method {
+ StringLengthMethod() {
+ this.hasName("length") and this.getDeclaringType() instanceof TypeString
+ }
+}
+
+/** The class `java.lang.StringBuffer`. */
+class TypeStringBuffer extends Class {
+ TypeStringBuffer() {
+ this.hasQualifiedName("java.lang", "StringBuffer")
+ }
+}
+
+/** The class `java.lang.StringBuilder`. */
+class TypeStringBuilder extends Class {
+ TypeStringBuilder() {
+ this.hasQualifiedName("java.lang", "StringBuilder")
+ }
+}
+
+/** The class `java.lang.System`. */
+class TypeSystem extends Class {
+ TypeSystem() {
+ this.hasQualifiedName("java.lang", "System")
+ }
+}
+
+/** The class `java.lang.Throwable`. */
+class TypeThrowable extends Class {
+ TypeThrowable() {
+ this.hasQualifiedName("java.lang", "Throwable")
+ }
+}
+
+/** The class `java.lang.Exception`. */
+class TypeException extends Class {
+ TypeException() {
+ this.hasQualifiedName("java.lang", "Exception")
+ }
+}
+
+/** The class `java.lang.Error`. */
+class TypeError extends Class {
+ TypeError() {
+ this.hasQualifiedName("java.lang", "Error")
+ }
+}
+
+/** The class `java.lang.RuntimeException`. */
+class TypeRuntimeException extends Class {
+ TypeRuntimeException() {
+ this.hasQualifiedName("java.lang", "RuntimeException")
+ }
+}
+
+/** The class `java.lang.ClassCastException`. */
+class TypeClassCastException extends Class {
+ TypeClassCastException() {
+ this.hasQualifiedName("java.lang", "ClassCastException")
+ }
+}
+
+/**
+ * The class `java.lang.Class`.
+ *
+ * This includes the generic source declaration, any parameterized instances and the raw type.
+ */
+class TypeClass extends Class {
+ TypeClass() {
+ this.getSourceDeclaration().hasQualifiedName("java.lang", "Class")
+ }
+}
+
+/**
+ * The class `java.lang.Constructor`.
+ *
+ * This includes the generic source declaration, any parameterized instances and the raw type.
+ */
+class TypeConstructor extends Class {
+ TypeConstructor() {
+ this.getSourceDeclaration().hasQualifiedName("java.lang.reflect", "Constructor")
+ }
+}
+
+/** The class `java.lang.Math`. */
+class TypeMath extends Class {
+ TypeMath() {
+ this.hasQualifiedName("java.lang", "Math")
+ }
+}
+
+/** A numeric type, including both primitive and boxed types. */
+class NumericType extends Type {
+ NumericType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or
+ name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name.regexpMatch("byte|short|int|long|double|float")
+ )
+ }
+}
+
+/** An immutable type. */
+class ImmutableType extends Type {
+ ImmutableType() {
+ this instanceof PrimitiveType or
+ this instanceof NullType or
+ this instanceof VoidType or
+ this instanceof BoxedType or
+ this instanceof TypeString
+ }
+}
+
+// --- Java IO ---
+
+/** The interface `java.io.Serializable`. */
+class TypeSerializable extends Interface {
+ TypeSerializable() {
+ hasQualifiedName("java.io", "Serializable")
+ }
+}
+
+/** The interface `java.io.ObjectOutput`. */
+class TypeObjectOutput extends Interface {
+ TypeObjectOutput() {
+ hasQualifiedName("java.io", "ObjectOutput")
+ }
+}
+
+/** The type `java.io.ObjectOutputStream`. */
+class TypeObjectOutputStream extends RefType {
+ TypeObjectOutputStream() {
+ hasQualifiedName("java.io", "ObjectOutputStream")
+ }
+}
+
+/** The class `java.nio.file.Paths`. */
+class TypePaths extends Class {
+ TypePaths() {
+ this.hasQualifiedName("java.nio.file", "Paths")
+ }
+}
+
+/** The class `java.nio.file.Path`. */
+class TypePath extends Class {
+ TypePath() {
+ this.hasQualifiedName("java.nio.file", "Path")
+ }
+}
+
+/** The class `java.nio.file.FileSystem`. */
+class TypeFileSystem extends Class {
+ TypeFileSystem() {
+ this.hasQualifiedName("java.nio.file", "FileSystem")
+ }
+}
+
+/** The class `java.io.File`. */
+class TypeFile extends Class {
+ TypeFile() {
+ this.hasQualifiedName("java.io", "File")
+ }
+}
+
+// --- Standard methods ---
+
+/**
+ * Any of the methods named `command` on class `java.lang.ProcessBuilder`.
+ */
+class MethodProcessBuilderCommand extends Method {
+ MethodProcessBuilderCommand() {
+ hasName("command") and
+ getDeclaringType() instanceof TypeProcessBuilder
+ }
+}
+
+/**
+ * Any method named `exec` on class `java.lang.Runtime`.
+ */
+class MethodRuntimeExec extends Method {
+ MethodRuntimeExec() {
+ hasName("exec") and
+ getDeclaringType() instanceof TypeRuntime
+ }
+}
+
+/**
+ * Any method named `getenv` on class `java.lang.System`.
+ */
+class MethodSystemGetenv extends Method {
+ MethodSystemGetenv() {
+ hasName("getenv") and
+ getDeclaringType() instanceof TypeSystem
+ }
+}
+
+/**
+ * Any method named `getProperty` on class `java.lang.System`.
+ */
+class MethodSystemGetProperty extends Method {
+ MethodSystemGetProperty() {
+ hasName("getProperty") and
+ getDeclaringType() instanceof TypeSystem
+ }
+}
+
+/**
+ * Any method named `exit` on class `java.lang.Runtime` or `java.lang.System`.
+ */
+class MethodExit extends Method {
+ MethodExit() {
+ hasName("exit") and
+ (getDeclaringType() instanceof TypeRuntime or getDeclaringType() instanceof TypeSystem)
+ }
+}
+
+/**
+ * A method named `writeObject` on type `java.io.ObjectOutput`
+ * or `java.io.ObjectOutputStream`.
+ */
+class WriteObjectMethod extends Method {
+ WriteObjectMethod() {
+ hasName("writeObject") and
+ (
+ getDeclaringType() instanceof TypeObjectOutputStream or
+ getDeclaringType() instanceof TypeObjectOutput
+ )
+ }
+}
+
+/**
+ * A method that reads an object on type `java.io.ObjectInputStream`,
+ * including `readObject`, `readObjectOverride`, `readUnshared` and `resolveObject`.
+ */
+class ReadObjectMethod extends Method {
+ ReadObjectMethod() {
+ this.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
+ (
+ this.hasName("readObject") or
+ this.hasName("readObjectOverride") or
+ this.hasName("readUnshared") or
+ this.hasName("resolveObject")
+ )
+ }
+}
+
+/** The method `Class.getName()`. */
+class ClassNameMethod extends Method {
+ ClassNameMethod() {
+ hasName("getName") and
+ getDeclaringType() instanceof TypeClass
+ }
+}
+
+/** The method `Class.getSimpleName()`. */
+class ClassSimpleNameMethod extends Method {
+ ClassSimpleNameMethod() {
+ hasName("getSimpleName") and
+ getDeclaringType() instanceof TypeClass
+ }
+}
+
+/** The method `Math.abs`. */
+class MethodAbs extends Method {
+ MethodAbs() {
+ this.getDeclaringType() instanceof TypeMath and
+ this.getName() = "abs"
+ }
+}
+
+// --- Standard fields ---
+
+/** The field `System.in`. */
+class SystemIn extends Field {
+ SystemIn() {
+ hasName("in") and
+ getDeclaringType() instanceof TypeSystem
+ }
+}
+
+/** The field `System.out`. */
+class SystemOut extends Field {
+ SystemOut() {
+ hasName("out") and
+ getDeclaringType() instanceof TypeSystem
+ }
+}
+
+/** The field `System.err`. */
+class SystemErr extends Field {
+ SystemErr() {
+ hasName("err") and
+ getDeclaringType() instanceof TypeSystem
+ }
+}
+
+// --- User-defined methods with a particular meaning ---
+
+/** A method with the same signature as `java.lang.Object.equals`. */
+class EqualsMethod extends Method {
+ EqualsMethod() {
+ this.hasName("equals") and
+ this.getNumberOfParameters() = 1 and
+ this.getParameter(0).getType().(RefType).hasQualifiedName("java.lang", "Object")
+ }
+
+ /** Gets the single parameter of this method. */
+ Parameter getParameter() {
+ result = this.getAParameter()
+ }
+}
+
+/** A method with the same signature as `java.lang.Object.hashCode`. */
+class HashCodeMethod extends Method {
+ HashCodeMethod() {
+ this.hasName("hashCode") and
+ this.getNumberOfParameters() = 0
+ }
+}
+
+/** A method with the same signature as `java.lang.Object.clone`. */
+class CloneMethod extends Method {
+ CloneMethod() {
+ this.hasName("clone") and
+ this.hasNoParameters()
+ }
+}
+
+/**
+ * The public static `main` method, with a single formal parameter
+ * of type `String[]` and return type `void`.
+ */
+class MainMethod extends Method {
+ MainMethod() {
+ this.isPublic() and
+ this.isStatic() and
+ this.getReturnType().hasName("void") and
+ this.hasName("main") and
+ this.getNumberOfParameters() = 1 and
+ exists(Array a |
+ a = this.getAParameter().getType() and
+ a.getDimension() = 1 and
+ a.getElementType() instanceof TypeString
+ )
+ }
+}
+
+/** A premain method is an agent entry-point. */
+class PreMainMethod extends Method {
+ PreMainMethod() {
+ this.isPublic() and
+ this.isStatic() and
+ this.getReturnType().hasName("void") and
+ this.getNumberOfParameters() < 3 and
+ this.getParameter(0).getType() instanceof TypeString and
+ (exists(this.getParameter(1)) implies
+ this.getParameter(1).getType().hasName("Instrumentation"))
+ }
+}
+
+/** The `length` field of the array type. */
+class ArrayLengthField extends Field {
+ ArrayLengthField() {
+ this.getDeclaringType() instanceof Array and
+ this.hasName("length")
+ }
+}
+
+/** A (reflexive, transitive) subtype of `java.lang.Throwable`. */
+class ThrowableType extends RefType {
+ ThrowableType() {
+ exists(TypeThrowable throwable | hasSubtype*(throwable, this))
+ }
+}
+
+/** An unchecked exception. That is, a (reflexive, transitive) subtype of `java.lang.Error` or `java.lang.RuntimeException`. */
+class UncheckedThrowableType extends RefType {
+ UncheckedThrowableType() {
+ exists(TypeError e | hasSubtype*(e, this)) or
+ exists(TypeRuntimeException e | hasSubtype*(e, this))
+ }
+}
diff --git a/java/ql/src/semmle/code/java/JDKAnnotations.qll b/java/ql/src/semmle/code/java/JDKAnnotations.qll
new file mode 100644
index 00000000000..12936f9f9ff
--- /dev/null
+++ b/java/ql/src/semmle/code/java/JDKAnnotations.qll
@@ -0,0 +1,136 @@
+/**
+ * Provides classes that represent standard annotations from the JDK.
+ */
+
+import java
+
+/** A `@Deprecated` annotation. */
+class DeprecatedAnnotation extends Annotation {
+ DeprecatedAnnotation() {
+ this.getType().hasQualifiedName("java.lang", "Deprecated")
+ }
+}
+
+/** An `@Override` annotation. */
+class OverrideAnnotation extends Annotation {
+ OverrideAnnotation() {
+ this.getType().hasQualifiedName("java.lang", "Override")
+ }
+}
+
+/** A `@SuppressWarnings` annotation. */
+class SuppressWarningsAnnotation extends Annotation {
+ SuppressWarningsAnnotation() {
+ this.getType().hasQualifiedName("java.lang", "SuppressWarnings")
+ }
+
+ /** Gets the name of a warning suppressed by this annotation. */
+ string getASuppressedWarning() {
+ result = this.getAValue().(StringLiteral).getLiteral() or
+ result = this.getAValue().(ArrayInit).getAnInit().(StringLiteral).getLiteral()
+ }
+}
+
+/** A `@Target` annotation. */
+class TargetAnnotation extends Annotation {
+ TargetAnnotation() {
+ this.getType().hasQualifiedName("java.lang.annotation", "Target")
+ }
+
+ /**
+ * Gets a target expression within this annotation.
+ *
+ * For example, the field access `ElementType.FIELD` is a target expression in
+ * `@Target({ElementType.FIELD, ElementType.METHOD})`.
+ */
+ Expr getATargetExpression() {
+ not result instanceof ArrayInit and
+ (
+ result = this.getAValue() or
+ result = this.getAValue().(ArrayInit).getAnInit()
+ )
+ }
+
+ /**
+ * Gets the name of a target element type.
+ *
+ * For example, `METHOD` is the name of a target element type in
+ * `@Target({ElementType.FIELD, ElementType.METHOD})`.
+ */
+ string getATargetElementType() {
+ exists(EnumConstant ec |
+ ec = this.getATargetExpression().(VarAccess).getVariable() and
+ ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "ElementType")
+ |
+ result = ec.getName())
+ }
+}
+
+/** A `@Retention` annotation. */
+class RetentionAnnotation extends Annotation {
+ RetentionAnnotation() {
+ this.getType().hasQualifiedName("java.lang.annotation", "Retention")
+ }
+
+ /**
+ * Gets the retention policy expression within this annotation.
+ *
+ * For example, the field access `RetentionPolicy.RUNTIME` is the
+ * retention policy expression in `@Retention(RetentionPolicy.RUNTIME)`.
+ */
+ Expr getRetentionPolicyExpression() {
+ result = this.getValue("value")
+ }
+
+ /**
+ * Gets the name of the retention policy of this annotation.
+ *
+ * For example, `RUNTIME` is the name of the retention policy
+ * in `@Retention(RetentionPolicy.RUNTIME)`.
+ */
+ string getRetentionPolicy() {
+ exists(EnumConstant ec |
+ ec = this.getRetentionPolicyExpression().(VarAccess).getVariable() and
+ ec.getDeclaringType().hasQualifiedName("java.lang.annotation", "RetentionPolicy")
+ |
+ result = ec.getName())
+ }
+}
+
+/**
+ * An annotation suggesting that the annotated element may be accessed reflectively.
+ *
+ * This is implemented by negation of a white-list of standard annotations that are
+ * known not to be reflection-related; all other annotations are assumed to potentially
+ * be reflection-related.
+ *
+ * A typical use-case is the exclusion of results relating to various frameworks,
+ * where an exhaustive list of all annotations for all frameworks that may exist
+ * can be difficult to obtain and maintain.
+ */
+class ReflectiveAccessAnnotation extends Annotation {
+ ReflectiveAccessAnnotation() {
+ // We conservatively white-list a few standard annotations that have nothing to do
+ // with reflection, and assume that any other annotation may be reflection-related.
+ not this instanceof NonReflectiveAnnotation
+ }
+}
+
+/**
+ * An annotation that does not indicate that a field may be accessed reflectively.
+ *
+ * Any annotation that is not a subclass of `NonReflectiveAnnotation` is assumed to
+ * allow for reflective access.
+ */
+abstract class NonReflectiveAnnotation extends Annotation {}
+
+library class StandardNonReflectiveAnnotation extends NonReflectiveAnnotation {
+ StandardNonReflectiveAnnotation() {
+ exists(AnnotationType anntp | anntp = this.getType() |
+ anntp.hasQualifiedName("java.lang", "Override") or
+ anntp.hasQualifiedName("java.lang", "Deprecated") or
+ anntp.hasQualifiedName("java.lang", "SuppressWarnings") or
+ anntp.hasQualifiedName("java.lang", "SafeVarargs")
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/JMX.qll b/java/ql/src/semmle/code/java/JMX.qll
new file mode 100644
index 00000000000..d8693eab35b
--- /dev/null
+++ b/java/ql/src/semmle/code/java/JMX.qll
@@ -0,0 +1,103 @@
+/**
+ * Provides classes and predicates for working with JMX bean types.
+ */
+
+import Type
+
+/** A managed bean. */
+abstract class ManagedBean extends Interface {
+}
+
+/** An `MBean`. */
+class MBean extends ManagedBean {
+ MBean() {
+ this.getQualifiedName().matches("%MBean%")
+ }
+}
+
+/** An `MXBean`. */
+class MXBean extends ManagedBean {
+ MXBean() {
+ this.getQualifiedName().matches("%MXBean%") or
+ this.getAnAnnotation().getType().hasQualifiedName("javax.management", "MXBean")
+ }
+}
+
+/**
+ * An managed bean implementation which is seen to be registered with the `MBeanServer`, directly or
+ * indirectly.
+ */
+class RegisteredManagedBeanImpl extends Class {
+ RegisteredManagedBeanImpl() {
+ getAnAncestor() instanceof ManagedBean and
+ exists(JMXRegistrationCall registerCall |
+ registerCall.getObjectArgument().getType() = this
+ )
+ }
+
+ /**
+ * Gets a managed bean that this registered bean class implements.
+ */
+ ManagedBean getAnImplementedManagedBean() {
+ result = getAnAncestor()
+ }
+}
+
+/**
+ * A call that registers an object with the `MBeanServer`, directly or indirectly.
+ */
+class JMXRegistrationCall extends MethodAccess {
+ JMXRegistrationCall() {
+ getCallee() instanceof JMXRegistrationMethod
+ }
+
+ /**
+ * Gets the argument that represents the object in the registration call.
+ */
+ Expr getObjectArgument() {
+ result = getArgument(getCallee().(JMXRegistrationMethod).getObjectPosition())
+ }
+}
+
+/**
+ * A method used to register `MBean` and `MXBean` instances with the `MBeanServer`.
+ *
+ * This is either the `registerMBean` method on `MBeanServer`, or it is a wrapper around that
+ * registration method.
+ */
+class JMXRegistrationMethod extends Method {
+ JMXRegistrationMethod() {
+ (
+ // A direct registration with the `MBeanServer`.
+ getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
+ getName() = "registerMBean"
+ ) or
+ /*
+ * The `MBeanServer` is often wrapped by an application specific management class, so identify
+ * methods that wrap a call to another `JMXRegistrationMethod`.
+ */
+ exists(JMXRegistrationCall c |
+ /*
+ * This must be a call to another JMX registration method, where the object argument is an access
+ * of one of the parameters of this method.
+ */
+ c.getObjectArgument().(VarAccess).getVariable() = getAParameter()
+ )
+ }
+
+ /**
+ * Gets the position of the parameter through which the "object" to be registered is passed.
+ */
+ int getObjectPosition() {
+ (
+ // Passed as the first argument to `registerMBean`.
+ getDeclaringType().hasQualifiedName("javax.management", "MBeanServer") and
+ getName() = "registerMBean" and
+ result = 0
+ ) or
+ // Identify the position in this method where the object parameter should be passed.
+ exists(JMXRegistrationCall c |
+ c.getObjectArgument().(VarAccess).getVariable() = getParameter(result)
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Javadoc.qll b/java/ql/src/semmle/code/java/Javadoc.qll
new file mode 100755
index 00000000000..8cab8a615d0
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Javadoc.qll
@@ -0,0 +1,170 @@
+/**
+ * Provides classes and predicates for working with Javadoc documentation.
+ */
+
+import semmle.code.Location
+import Element
+
+/** A Javadoc parent is an element whose child can be some Javadoc documentation. */
+class JavadocParent extends @javadocParent, Top {
+ /** Gets a documentation element attached to this parent. */
+ JavadocElement getAChild() { result.getParent() = this }
+
+ /** Gets the child documentation element at the specified (zero-based) position. */
+ JavadocElement getChild(int index) {
+ result = this.getAChild() and result.getIndex() = index
+ }
+
+ /** Gets the number of documentation elements attached to this parent. */
+ int getNumChild() {
+ result = count(getAChild())
+ }
+
+ /** Gets a documentation element with the specified Javadoc tag name. */
+ JavadocTag getATag(string name) {
+ result = this.getAChild() and result.getTagName() = name
+ }
+
+ /*abstract*/ override string toString() { result = "Javadoc" }
+}
+
+/** A Javadoc comment. */
+class Javadoc extends JavadocParent, @javadoc {
+
+ /** Gets the number of lines in this Javadoc comment. */
+ int getNumberOfLines() {
+ result = this.getLocation().getNumberOfCommentLines()
+ }
+
+ /** Gets the value of the `@version` tag, if any. */
+ string getVersion() {
+ result = this.getATag("@version").getChild(0).toString()
+ }
+
+ /** Gets the value of the `@author` tag, if any. */
+ string getAuthor() {
+ result = this.getATag("@author").getChild(0).toString()
+ }
+
+ override string toString() {
+ result = toStringPrefix() + getChild(0) + toStringPostfix()
+ }
+
+ private string toStringPrefix() {
+ if isEolComment(this) then
+ result = "//"
+ else (
+ if isNormalComment(this) then
+ result = "/* "
+ else
+ result = "/** "
+ )
+ }
+
+ private string toStringPostfix() {
+ if isEolComment(this) then
+ result = ""
+ else (
+ if strictcount(getAChild()) = 1 then
+ result = " */"
+ else
+ result = " ... */"
+ )
+ }
+
+ /** Gets the Java code element that is commented by this piece of Javadoc. */
+ Documentable getCommentedElement() { result.getJavadoc() = this }
+}
+
+/** A documentable element that can have an attached Javadoc comment. */
+class Documentable extends Element, @member {
+ /** Gets the Javadoc comment attached to this element. */
+ Javadoc getJavadoc() { hasJavadoc(this,result) and not isNormalComment(result) }
+
+ /** Gets the name of the author(s) of this element, if any. */
+ string getAuthor() {
+ result = this.getJavadoc().getAuthor()
+ }
+}
+
+/** A common super-class for Javadoc elements, which may be either tags or text. */
+abstract class JavadocElement extends @javadocElement, Top {
+ /** Gets the parent of this Javadoc element. */
+ JavadocParent getParent() {
+ javadocTag(this,_,result,_) or javadocText(this,_,result,_)
+ }
+
+ /** Gets the index of this child element relative to its parent. */
+ int getIndex() {
+ javadocTag(this,_,_,result) or javadocText(this,_,_,result)
+ }
+
+ /** Gets a printable representation of this Javadoc element. */
+ /*abstract*/ override string toString() { result = "Javadoc element" }
+
+ /** Gets the line of text associated with this Javadoc element. */
+ abstract string getText();
+}
+
+/** A Javadoc tag. */
+class JavadocTag extends JavadocElement, JavadocParent, @javadocTag {
+ /** Gets the name of this Javadoc tag. */
+ string getTagName() { javadocTag(this,result,_,_) }
+
+ /** Gets a printable representation of this Javadoc tag. */
+ override string toString() { result = this.getTagName() }
+
+ /** Gets the text associated with this Javadoc tag. */
+ override string getText() { result = this.getChild(0).toString() }
+}
+
+/** A Javadoc `@param` tag. */
+class ParamTag extends JavadocTag {
+ ParamTag() { this.getTagName() = "@param" }
+
+ /** Gets the name of the parameter. */
+ string getParamName() { result = this.getChild(0).toString() }
+
+ /** Gets the documentation for the parameter. */
+ override string getText() { result = this.getChild(1).toString() }
+}
+
+/** A Javadoc `@throws` or `@exception` tag. */
+class ThrowsTag extends JavadocTag {
+ ThrowsTag() { this.getTagName() = "@throws" or this.getTagName() = "@exception" }
+
+ /** Gets the name of the exception. */
+ string getExceptionName() { result = this.getChild(0).toString() }
+
+ /** Gets the documentation for the exception. */
+ override string getText() { result = this.getChild(1).toString() }
+}
+
+/** A Javadoc `@see` tag. */
+class SeeTag extends JavadocTag {
+ SeeTag() { getTagName() = "@see" }
+
+ /** Gets the name of the entity referred to. */
+ string getReference() { result = getChild(0).toString() }
+}
+
+/** A Javadoc `@author` tag. */
+class AuthorTag extends JavadocTag {
+ AuthorTag() { this.getTagName() = "@author" }
+
+ /** Gets the name of the author. */
+ string getAuthorName() { result = this.getChild(0).toString() }
+}
+
+/** A piece of Javadoc text. */
+class JavadocText extends JavadocElement, @javadocText {
+
+ /** Gets the Javadoc comment that contains this piece of text. */
+ Javadoc getJavadoc() { result.getAChild+() = this }
+
+ /** Gets the text itself. */
+ override string getText() { javadocText(this,result,_,_) }
+
+ /** Gets a printable representation of this Javadoc element. */
+ override string toString() { result = this.getText() }
+}
diff --git a/java/ql/src/semmle/code/java/Maps.qll b/java/ql/src/semmle/code/java/Maps.qll
new file mode 100644
index 00000000000..b259637de22
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Maps.qll
@@ -0,0 +1,102 @@
+import java
+import Collections
+
+/** A reference type that extends a parameterization of `java.util.Map`. */
+class MapType extends RefType {
+ MapType() {
+ exists(ParameterizedInterface coll |
+ coll.getSourceDeclaration().hasQualifiedName("java.util", "Map")
+ |
+ this.hasSupertype*(coll)
+ )
+ }
+
+ /** Gets the type of keys stored in this map. */
+ RefType getKeyType() {
+ exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") |
+ indirectlyInstantiates(this, map, 0, result)
+ )
+ }
+
+ /** Gets the type of values stored in this map. */
+ RefType getValueType() {
+ exists(GenericInterface map | map.hasQualifiedName("java.util", "Map") |
+ indirectlyInstantiates(this, map, 1, result)
+ )
+ }
+}
+
+/** A method declared in a map type. */
+class MapMethod extends Method {
+ MapMethod() {
+ this.getDeclaringType() instanceof MapType
+ }
+
+ /** Gets the type of keys of the map to which this method belongs. */
+ RefType getReceiverKeyType() {
+ result = this.getDeclaringType().(MapType).getKeyType()
+ }
+
+ /** Gets the type of values of the map to which this method belongs. */
+ RefType getReceiverValueType() {
+ result = this.getDeclaringType().(MapType).getValueType()
+ }
+}
+
+/** A method that mutates the map it belongs to. */
+class MapMutator extends MapMethod {
+ MapMutator() {
+ this.getName().regexpMatch("(put.*|remove|clear)")
+ }
+}
+
+/** The `size` method of `java.util.Map`. */
+class MapSizeMethod extends MapMethod {
+ MapSizeMethod() {
+ this.hasName("size") and this.hasNoParameters()
+ }
+}
+
+/** A method call that mutates a map. */
+class MapMutation extends MethodAccess {
+ MapMutation() {
+ this.getMethod() instanceof MapMutator
+ }
+
+ predicate resultIsChecked() {
+ not this.getParent() instanceof ExprStmt
+ }
+}
+
+/** A method that queries the contents of the map it belongs to without mutating it. */
+class MapQueryMethod extends MapMethod {
+ MapQueryMethod() {
+ this.getName().regexpMatch("get|containsKey|containsValue|entrySet|keySet|values|isEmpty|size")
+ }
+}
+
+/** A `new` expression that allocates a fresh, empty map. */
+class FreshMap extends ClassInstanceExpr {
+ FreshMap() {
+ this.getConstructedType() instanceof MapType and
+ this.getNumArgument() = 0 and
+ not exists(this.getAnonymousClass())
+ }
+}
+
+/**
+ * A call to `Map.put(key, value)`.
+ */
+class MapPutCall extends MethodAccess {
+ MapPutCall() {
+ getCallee().(MapMethod).hasName("put")
+ }
+
+ Expr getKey() {
+ result = getArgument(0)
+ }
+
+ Expr getValue() {
+ result = getArgument(1)
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Member.qll b/java/ql/src/semmle/code/java/Member.qll
new file mode 100755
index 00000000000..829ce4386ef
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Member.qll
@@ -0,0 +1,645 @@
+/**
+ * Provides classes and predicates for working with members of Java classes and interfaces,
+ * that is, methods, constructors, fields and nested types.
+ */
+
+import Element
+import Type
+import Annotation
+import Exception
+import metrics.MetricField
+
+/**
+ * A common abstraction for type member declarations,
+ * including methods, constructors, fields, and nested types.
+ */
+class Member extends Element, Annotatable, Modifiable, @member {
+ Member() {
+ declaresMember(_,this)
+ }
+
+ /** Gets the type in which this member is declared. */
+ RefType getDeclaringType() { declaresMember(result, this) }
+
+ /** Gets the qualified name of this member. */
+ string getQualifiedName() {
+ result = getDeclaringType().getName() + "." + getName()
+ }
+
+ /** Holds if this member is package protected, that is, neither public nor private nor protected. */
+ predicate isPackageProtected() {
+ not isPrivate() and
+ not isProtected() and
+ not isPublic()
+ }
+
+ /**
+ * Gets the immediately enclosing callable, if this member is declared in
+ * an anonymous or local class.
+ */
+ Callable getEnclosingCallable() {
+ exists(NestedClass nc | this.getDeclaringType() = nc |
+ nc.(AnonymousClass).getClassInstanceExpr().getEnclosingCallable() = result or
+ nc.(LocalClass).getLocalClassDeclStmt().getEnclosingCallable() = result
+ )
+ }
+}
+
+/** A callable is a method or constructor. */
+class Callable extends StmtParent, Member, @callable {
+ /**
+ * Gets the declared return type of this callable (`void` for
+ * constructors).
+ */
+ Type getReturnType() {
+ constrs(this, _, _, result, _, _) or
+ methods(this, _, _, result, _, _)
+ }
+
+ /**
+ * Gets a callee that may be called from this callable.
+ */
+ Callable getACallee() { this.calls(result) }
+
+ /** Gets the call site of a call from this callable to a callee. */
+ Call getACallSite(Callable callee) {
+ result.getCaller() = this and
+ result.getCallee() = callee
+ }
+
+ /**
+ * Gets the bytecode method descriptor, encoding parameter and return types,
+ * but not the name of the callable.
+ */
+ string getMethodDescriptor() {
+ exists(string return | return = this.getReturnType().getTypeDescriptor() |
+ result = "(" + descriptorUpTo(this.getNumberOfParameters()) + ")" + return
+ )
+ }
+
+ private string descriptorUpTo(int n) {
+ (n = 0 and result = "") or
+ exists(Parameter p | p = this.getParameter(n-1) |
+ result = descriptorUpTo(n-1) + p.getType().getTypeDescriptor()
+ )
+ }
+
+ /** Holds if this callable calls `target`. */
+ predicate calls(Callable target) {
+ exists(getACallSite(target))
+ }
+
+ /**
+ * Holds if this callable calls `target`
+ * using a `super(...)` constructor call.
+ */
+ predicate callsSuperConstructor(Constructor target) {
+ getACallSite(target) instanceof SuperConstructorInvocationStmt
+ }
+
+ /**
+ * Holds if this callable calls `target`
+ * using a `this(...)` constructor call.
+ */
+ predicate callsThis(Constructor target) {
+ getACallSite(target) instanceof ThisConstructorInvocationStmt
+ }
+
+ /**
+ * Holds if this callable calls `target`
+ * using a `super` method call.
+ */
+ predicate callsSuper(Method target) {
+ getACallSite(target) instanceof SuperMethodAccess
+ }
+
+ /**
+ * Holds if this callable calls `c` using
+ * either a `super(...)` constructor call
+ * or a `this(...)` constructor call.
+ */
+ predicate callsConstructor(Constructor c) {
+ this.callsSuperConstructor(c) or this.callsThis(c)
+ }
+
+ /**
+ * Holds if this callable may call the specified callable,
+ * taking overriding into account.
+ */
+ predicate polyCalls(Callable m) {
+ this.calls(m) or
+ exists(Method mSuper, VirtualMethodAccess c |
+ c.getCaller() = this and c.getMethod() = mSuper
+ |
+ m.(Method).overrides(mSuper)
+ )
+ }
+
+ /**
+ * Holds if field `f` may be assigned a value
+ * within the body of this callable.
+ */
+ predicate writes(Field f) {
+ f.getAnAccess().(LValue).getEnclosingCallable() = this
+ }
+
+ /**
+ * Holds if field `f` may be read
+ * within the body of this callable.
+ */
+ predicate reads(Field f) {
+ f.getAnAccess().(RValue).getEnclosingCallable() = this
+ }
+
+ /**
+ * Holds if field `f` may be either read or written
+ * within the body of this callable.
+ */
+ predicate accesses(Field f) { this.writes(f) or this.reads(f) }
+
+ /**
+ * Gets a field accessed in this callable.
+ */
+ Field getAnAccessedField() { this.accesses(result) }
+
+ /** Gets the type of a formal parameter of this callable. */
+ Type getAParamType() { result = getParameterType(_) }
+
+ /** Holds if this callable does not have any formal parameters. */
+ predicate hasNoParameters() { not exists(getAParameter()) }
+
+ /** Gets the number of formal parameters of this callable. */
+ int getNumberOfParameters() {
+ result = count(getAParameter())
+ }
+
+ /** Gets a formal parameter of this callable. */
+ Parameter getAParameter() { result.getCallable() = this }
+
+ /** Gets the formal parameter at the specified (zero-based) position. */
+ Parameter getParameter(int n) { params(result, _, n, this, _) }
+
+ /** Gets the type of the formal parameter at the specified (zero-based) position. */
+ Type getParameterType(int n) { params(_, result, n, this, _) }
+
+ /**
+ * Gets the signature of this callable, including its name and the types of all its parameters,
+ * identified by their simple (unqualified) names.
+ *
+ * Use `getSignature` to obtain a signature including fully qualified type names.
+ */
+ string getStringSignature() {
+ result = this.getName() + this.paramsString()
+ }
+
+ /** Gets a parenthesized string containing all parameter types of this callable, separated by a comma. */
+ pragma[nomagic]
+ string paramsString() {
+ exists(int n | n = getNumberOfParameters() |
+ n = 0 and result = "()"
+ or
+ n > 0 and result = "(" + this.paramUpTo(n-1) + ")"
+ )
+ }
+
+ /**
+ * Gets a string containing the parameter types of this callable
+ * from left to right, up to (and including) the `n`-th parameter.
+ */
+ private string paramUpTo(int n) {
+ n = 0 and result = getParameterType(0).toString()
+ or
+ n > 0 and result = paramUpTo(n-1) + ", " + getParameterType(n)
+ }
+
+ /** Holds if this callable has the specified string signature. */
+ predicate hasStringSignature(string sig) {
+ sig = this.getStringSignature()
+ }
+
+ /** Gets an exception that occurs in the `throws` clause of this callable. */
+ Exception getAnException() { exceptions(result,_,this) }
+
+ /** Gets an exception type that occurs in the `throws` clause of this callable. */
+ RefType getAThrownExceptionType() { result = getAnException().getType() }
+
+ /** Gets a call site that references this callable. */
+ Call getAReference() {
+ result.getCallee() = this
+ }
+
+ /** Gets the body of this callable, if any. */
+ Block getBody() { result.getParent() = this }
+
+ /**
+ * Gets the source declaration of this callable.
+ *
+ * For parameterized instances of generic methods, the
+ * source declaration is the corresponding generic method.
+ *
+ * For non-parameterized callables declared inside a parameterized
+ * instance of a generic type, the source declaration is the
+ * corresponding callable in the generic type.
+ *
+ * For all other callables, the source declaration is the callable itself.
+ */
+ Callable getSourceDeclaration() { result = this }
+
+ /** Holds if this callable is the same as its source declaration. */
+ predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
+
+ /** Cast this callable to a class that provides access to metrics information. */
+ MetricCallable getMetrics() { result = this }
+
+ /** Holds if the last parameter of this callable is a varargs (variable arity) parameter. */
+ predicate isVarargs() { this.getAParameter().isVarargs() }
+
+ /**
+ * Gets the signature of this callable, where all types in the signature have a fully-qualified name.
+ *
+ * For example, method `void m(String s)` has the signature `m(java.lang.String)`.
+ */
+ string getSignature() {
+ constrs(this, _, result, _, _, _) or
+ methods(this, _, result, _, _, _)
+ }
+}
+
+/** Holds if method `m1` overrides method `m2`. */
+private
+predicate overrides(Method m1, Method m2) {
+ exists(RefType t1, RefType t2 | overridesIgnoringAccess(m1, t1, m2, t2) |
+ m2.isPublic() or
+ m2.isProtected() or
+ m2.isPackageProtected() and t1.getPackage() = t2.getPackage()
+ )
+}
+
+/**
+ * Auxiliary predicate: whether method `m1` overrides method `m2`,
+ * ignoring any access modifiers. Additionally, this predicate binds
+ * `t1` to the type declaring `m1` and `t2` to the type declaring `m2`.
+ */
+pragma[noopt]
+predicate overridesIgnoringAccess(Method m1, RefType t1, Method m2, RefType t2) {
+ exists(string sig |
+ virtualMethodWithSignature(sig, t1, m1) and
+ t1.extendsOrImplements+(t2) and
+ virtualMethodWithSignature(sig, t2, m2)
+ )
+}
+
+private predicate virtualMethodWithSignature(string sig, RefType t, Method m) {
+ methods(m,_,_,_,t,_) and
+ sig = m.getSignature() and
+ m.isVirtual()
+}
+
+private predicate potentialInterfaceImplementationWithSignature(string sig, RefType t, Method impl) {
+ t.hasMethod(impl, _) and
+ sig = impl.getSignature() and
+ impl.isVirtual() and
+ impl.isPublic() and
+ not t instanceof Interface and
+ not t.isAbstract()
+}
+
+pragma[nomagic]
+private predicate implementsInterfaceMethod(SrcMethod impl, SrcMethod m) {
+ exists(RefType t, Interface i, Method minst, Method implinst |
+ m = minst.getSourceDeclaration() and
+ i = minst.getDeclaringType() and
+ t.extendsOrImplements+(i) and
+ t.isSourceDeclaration() and
+ potentialInterfaceImplementationWithSignature(minst.getSignature(), t, implinst) and
+ impl = implinst.getSourceDeclaration()
+ )
+}
+
+/** A method is a particular kind of callable. */
+class Method extends Callable, @method {
+ /** Holds if this method (directly) overrides the specified callable. */
+ predicate overrides(Method m) { overrides(this, m) }
+
+ /**
+ * Holds if this method either overrides `m`, or `m` is the
+ * source declaration of this method (and not equal to it).
+ */
+ predicate overridesOrInstantiates(Method m) {
+ this.overrides(m) or
+ this.getSourceDeclaration() = m and this != m
+ }
+
+ /** Gets a method (directly or transitively) overridden by this method. */
+ Method getAnOverride() {
+ this.overrides+(result)
+ }
+
+ /** Gets the source declaration of a method overridden by this method. */
+ SrcMethod getASourceOverriddenMethod() {
+ exists(Method m | this.overrides(m) and result = m.getSourceDeclaration())
+ }
+
+ override string getSignature() { methods(this,_,result,_,_,_) }
+
+ /**
+ * Holds if this method and method `m` are declared in the same type
+ * and have the same parameter types.
+ */
+ predicate sameParamTypes(Method m) {
+ // `this` and `m` are different methods,
+ this != m and
+ // `this` and `m` are declared in the same type,
+ this.getDeclaringType() = m.getDeclaringType() and
+ // `this` and `m` are of the same arity, and
+ this.getNumberOfParameters() = m.getNumberOfParameters() and
+ // there does not exist a pair of parameters whose types differ.
+ not exists(int n | this.getParameterType(n) != m.getParameterType(n))
+ }
+
+ override SrcMethod getSourceDeclaration() { methods(this,_,_,_,_,result) }
+
+ /**
+ * All the methods that could possibly be called when this method
+ * is called. For class methods this includes the method itself and all its
+ * overriding methods (if any), and for interface methods this includes
+ * matching methods defined on or inherited by implementing classes.
+ *
+ * Only includes method implementations, not abstract or non-default interface methods.
+ * Native methods are included, since they have an implementation (just not in Java).
+ */
+ SrcMethod getAPossibleImplementation() {
+ this.getSourceDeclaration().getAPossibleImplementationOfSrcMethod() = result
+ }
+
+ override MethodAccess getAReference() {
+ result = Callable.super.getAReference()
+ }
+
+ override predicate isPublic() {
+ Callable.super.isPublic() or
+ // JLS 9.4: Every method declaration in the body of an interface is implicitly public.
+ getDeclaringType() instanceof Interface or
+ exists(FunctionalExpr func | func.asMethod() = this)
+ }
+
+ override predicate isAbstract() {
+ Callable.super.isAbstract()
+ or
+ // JLS 9.4: An interface method lacking a `default` modifier or a `static` modifier
+ // is implicitly abstract.
+ this.getDeclaringType() instanceof Interface and
+ not this.isDefault() and
+ not this.isStatic()
+ }
+
+ override predicate isStrictfp() {
+ Callable.super.isStrictfp() or
+ // JLS 8.1.1.3, JLS 9.1.1.2
+ getDeclaringType().isStrictfp()
+ }
+
+ /**
+ * Holds if this method is neither private nor a static interface method
+ * nor an initializer method, and hence could be inherited.
+ */
+ predicate isInheritable() {
+ not isPrivate() and
+ not (isStatic() and getDeclaringType() instanceof Interface) and
+ not this instanceof InitializerMethod
+ }
+
+ /**
+ * Holds if this method is neither private nor static, and hence
+ * uses dynamic dispatch.
+ */
+ predicate isVirtual() {
+ not isPrivate() and not isStatic()
+ }
+
+ /** Holds if this method can be overridden. */
+ predicate isOverridable() {
+ isVirtual() and
+ not isFinal() and
+ not getDeclaringType().isFinal()
+ }
+}
+
+/** A method that is the same as its source declaration. */
+class SrcMethod extends Method {
+ SrcMethod() { methods(_,_,_,_,_,this) }
+
+ /**
+ * All the methods that could possibly be called when this method
+ * is called. For class methods this includes the method itself and all its
+ * overriding methods (if any), and for interface methods this includes
+ * matching methods defined on or inherited by implementing classes.
+ *
+ * Only includes method implementations, not abstract or non-default interface methods.
+ * Native methods are included, since they have an implementation (just not in Java).
+ */
+ SrcMethod getAPossibleImplementationOfSrcMethod() {
+ (
+ if this.getDeclaringType() instanceof Interface and this.isVirtual() then
+ implementsInterfaceMethod(result, this)
+ else
+ result.getASourceOverriddenMethod*() = this
+ ) and
+ (exists(result.getBody()) or result.hasModifier("native"))
+ }
+}
+
+/**
+ * A _setter_ method is a method with the following properties:
+ *
+ * - it has exactly one parameter,
+ * - its body contains exactly one statement
+ * that assigns the value of the method parameter to a field
+ * declared in the same type as the method.
+ */
+class SetterMethod extends Method {
+ SetterMethod() {
+ this.getNumberOfParameters() = 1 and
+ exists(ExprStmt s, Assignment a |
+ s = this.getBody().(SingletonBlock).getStmt() and a = s.getExpr()
+ |
+ exists(Field f | f.getDeclaringType() = this.getDeclaringType() |
+ a.getDest() = f.getAnAccess() and
+ a.getSource() = this.getAParameter().getAnAccess()
+ )
+ )
+ }
+
+ /** Gets the field assigned by this setter method. */
+ Field getField() {
+ exists(Assignment a | a.getEnclosingCallable() = this |
+ a.getDest() = result.getAnAccess()
+ )
+ }
+}
+
+/**
+ * A _getter_ method is a method with the following properties:
+ *
+ * - it has no parameters,
+ * - its body contains exactly one statement
+ * that returns the value of a field.
+ */
+class GetterMethod extends Method {
+ GetterMethod() {
+ this.hasNoParameters() and
+ exists(ReturnStmt s, Field f | s = this.getBody().(SingletonBlock).getStmt() |
+ s.getResult() = f.getAnAccess()
+ )
+ }
+
+ /** Gets the field whose value is returned by this getter method. */
+ Field getField() {
+ exists(ReturnStmt r | r.getEnclosingCallable() = this |
+ r.getResult() = result.getAnAccess()
+ )
+ }
+}
+
+/**
+ * A finalizer method, with name `finalize`,
+ * return type `void` and modifier `protected`.
+ */
+class FinalizeMethod extends Method {
+ FinalizeMethod() {
+ this.hasName("finalize") and
+ this.getReturnType().hasName("void") and
+ this.isProtected()
+ }
+}
+
+/** A constructor is a particular kind of callable. */
+class Constructor extends Callable, @constructor {
+ /** Holds if this is a default constructor, not explicitly declared in source code. */
+ predicate isDefaultConstructor() { isDefConstr(this) }
+
+ override Constructor getSourceDeclaration() { constrs(this,_,_,_,_,result) }
+
+ override string getSignature() { constrs(this,_,result,_,_,_) }
+}
+
+/**
+ * A compiler-generated initializer method (could be static or
+ * non-static), which is used to hold (static or non-static) field
+ * initializers, as well as explicit initializer blocks.
+ */
+abstract class InitializerMethod extends Method {}
+
+/**
+ * A static initializer is a method that contains all static
+ * field initializations and static initializer blocks.
+ */
+class StaticInitializer extends InitializerMethod {
+ StaticInitializer() {
+ hasName("")
+ }
+}
+
+/**
+ * An instance initializer is a method that contains field initializations
+ * and explicit instance initializer blocks.
+ */
+class InstanceInitializer extends InitializerMethod {
+ InstanceInitializer() {
+ this.hasName("")
+ }
+}
+
+/** A field declaration that declares one or more class or instance fields. */
+class FieldDeclaration extends ExprParent, @fielddecl, Annotatable {
+ /** Gets the access to the type of the field(s) in this declaration. */
+ Expr getTypeAccess() { result.getParent() = this }
+
+ /** Gets a field declared in this declaration. */
+ Field getAField() { fieldDeclaredIn(result, this, _) }
+
+ /** Gets the field declared at the specified (zero-based) position in this declaration */
+ Field getField(int idx) { fieldDeclaredIn(result, this, idx) }
+
+ /** Gets the number of fields declared in this declaration. */
+ int getNumField() { result = max(int idx | fieldDeclaredIn(_, this, idx) | idx) + 1 }
+
+ override string toString() {
+ if this.getNumField() = 0 then
+ result = this.getTypeAccess() + " " + this.getField(0) + ";"
+ else
+ result = this.getTypeAccess() + " " + this.getField(0) + ", ...;"
+ }
+}
+
+/** A class or instance field. */
+class Field extends Member, ExprParent, @field, Variable {
+ /** Gets the declared type of this field. */
+ override Type getType() { fields(this, _, result, _, _) }
+
+ /** Gets the type in which this field is declared. */
+ override RefType getDeclaringType() { fields(this, _, _, result, _) }
+
+ /**
+ * Gets the field declaration in which this field is declared.
+ *
+ * Note that this declaration is only available if the field occurs in source code.
+ */
+ FieldDeclaration getDeclaration() { result.getAField() = this }
+
+ /** Gets the initializer expression of this field, if any. */
+ override Expr getInitializer() {
+ exists(AssignExpr e, InitializerMethod im |
+ e.getDest() = this.getAnAccess() and
+ e.getSource() = result and
+ result.getEnclosingCallable() = im and
+ // This rules out updates in explicit initializer blocks as they are nested inside the compiler generated initializer blocks.
+ e.getEnclosingStmt().getParent() = im.getBody()
+ )
+ }
+
+ /**
+ * Gets the source declaration of this field.
+ *
+ * For fields that are members of a parameterized
+ * instance of a generic type, the source declaration is the
+ * corresponding field in the generic type.
+ *
+ * For all other fields, the source declaration is the field itself.
+ */
+ Field getSourceDeclaration() { fields(this,_,_,_,result) }
+
+ /** Holds if this field is the same as its source declaration. */
+ predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
+
+ override predicate isPublic() {
+ Member.super.isPublic() or
+ // JLS 9.3: Every field declaration in the body of an interface is
+ // implicitly public, static, and final
+ getDeclaringType() instanceof Interface
+ }
+
+ override predicate isStatic() {
+ Member.super.isStatic() or
+ // JLS 9.3: Every field declaration in the body of an interface is
+ // implicitly public, static, and final
+ this.getDeclaringType() instanceof Interface
+ }
+
+ override predicate isFinal() {
+ Member.super.isFinal() or
+ // JLS 9.3: Every field declaration in the body of an interface is
+ // implicitly public, static, and final
+ this.getDeclaringType() instanceof Interface
+ }
+
+ /** Cast this field to a class that provides access to metrics information. */
+ MetricField getMetrics() { result = this }
+}
+
+/** An instance field. */
+class InstanceField extends Field {
+ InstanceField() {
+ not this.isStatic()
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Modifier.qll b/java/ql/src/semmle/code/java/Modifier.qll
new file mode 100755
index 00000000000..73463369e6c
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Modifier.qll
@@ -0,0 +1,69 @@
+/**
+ * Provides classes and predicates for working with Java modifiers.
+ */
+
+import Element
+
+/** A modifier such as `private`, `static` or `abstract`. */
+class Modifier extends Element, @modifier {
+ /** Gets the element to which this modifier applies. */
+ Element getElement() { hasModifier(result,this) }
+}
+
+/** An element of the Java syntax tree that may have a modifier. */
+abstract class Modifiable extends Element {
+ /**
+ * Holds if this element has modifier `m`.
+ *
+ * For most purposes, the more specialized predicates `isAbstract`, `isPublic`, etc.
+ * should be used, which also take implicit modifiers into account.
+ * For instance, non-default instance methods in interfaces are implicitly
+ * abstract, so `isAbstract()` will hold for them even if `hasModifier("abstract")`
+ * does not.
+ */
+ predicate hasModifier(string m) {
+ modifiers(getAModifier(), m)
+ }
+
+ /** Holds if this element has no modifier. */
+ predicate hasNoModifier() { not hasModifier(this,_) }
+
+ /** Gets a modifier of this element. */
+ Modifier getAModifier() { this = result.getElement() }
+
+ /** Holds if this element has an `abstract` modifier or is implicitly abstract. */
+ predicate isAbstract() { hasModifier("abstract") }
+
+ /** Holds if this element has a `static` modifier or is implicitly static. */
+ predicate isStatic() { hasModifier("static") }
+
+ /** Holds if this element has a `final` modifier or is implicitly final. */
+ predicate isFinal() { hasModifier("final") }
+
+ /** Holds if this element has a `public` modifier or is implicitly public. */
+ predicate isPublic() { hasModifier("public") }
+
+ /** Holds if this element has a `protected` modifier. */
+ predicate isProtected() { hasModifier("protected") }
+
+ /** Holds if this element has a `private` modifier or is implicitly private. */
+ predicate isPrivate() { hasModifier("private") }
+
+ /** Holds if this element has a `volatile` modifier. */
+ predicate isVolatile() { hasModifier("volatile") }
+
+ /** Holds if this element has a `synchronized` modifier. */
+ predicate isSynchronized() { hasModifier("synchronized") }
+
+ /** Holds if this element has a `native` modifier. */
+ predicate isNative() { hasModifier("native") }
+
+ /** Holds if this element has a `default` modifier. */
+ predicate isDefault() { this.hasModifier("default") }
+
+ /** Holds if this element has a `transient` modifier. */
+ predicate isTransient() { this.hasModifier("transient") }
+
+ /** Holds if this element has a `strictfp` modifier. */
+ predicate isStrictfp() { this.hasModifier("strictfp") }
+}
diff --git a/java/ql/src/semmle/code/java/Modules.qll b/java/ql/src/semmle/code/java/Modules.qll
new file mode 100755
index 00000000000..61973c3a22c
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Modules.qll
@@ -0,0 +1,211 @@
+/**
+ * Provides classes for working with Java modules.
+ */
+
+import CompilationUnit
+
+/**
+ * A module.
+ */
+class Module extends @module {
+ Module() {
+ modules(this, _)
+ }
+
+ /**
+ * Gets the name of this module.
+ */
+ string getName() { modules(this, result) }
+
+ /**
+ * Holds if this module is an `open` module, that is,
+ * it grants access _at run time_ to types in all its packages,
+ * as if all packages had been exported.
+ */
+ predicate isOpen() { isOpen(this) }
+
+ /**
+ * Gets a directive of this module.
+ */
+ Directive getADirective() { directives(this, result) }
+
+ /**
+ * Gets a compilation unit associated with this module.
+ */
+ CompilationUnit getACompilationUnit() { cumodule(result, this) }
+
+ /** Gets a textual representation of this module. */
+ string toString() { modules(this, result) }
+}
+
+/**
+ * A directive in a module declaration.
+ */
+abstract class Directive extends @directive {
+ /** Gets a textual representation of this directive. */
+ abstract string toString();
+}
+
+/**
+ * A `requires` directive in a module declaration.
+ */
+class RequiresDirective extends Directive, @requires {
+ RequiresDirective() {
+ requires(this,_)
+ }
+
+ /**
+ * Holds if this `requires` directive is `transitive`,
+ * that is, any module that depends on this module
+ * has an implicitly declared dependency on the
+ * module specified in this `requires` directive.
+ */
+ predicate isTransitive() { isTransitive(this) }
+
+ /**
+ * Holds if this `requires` directive is `static`,
+ * that is, the dependence specified by this `requires`
+ * directive is only mandatory at compile time but
+ * optional at run time.
+ */
+ predicate isStatic() { isStatic(this) }
+
+ /**
+ * Gets the module on which this module depends.
+ */
+ Module getTargetModule() { requires(this, result) }
+
+ override
+ string toString() {
+ exists(string transitive, string static |
+ (if isTransitive() then transitive = "transitive " else transitive = "") and
+ (if isStatic() then static = "static " else static = "")
+ |
+ result = "requires " + transitive + static + getTargetModule() + ";"
+ )
+ }
+}
+
+/**
+ * An `exports` directive in a module declaration.
+ */
+class ExportsDirective extends Directive, @exports {
+ ExportsDirective() {
+ exports(this,_)
+ }
+
+ /**
+ * Gets the package exported by this `exports` directive.
+ */
+ Package getExportedPackage() { exports(this, result) }
+
+ /**
+ * Holds if this `exports` directive is qualified, that is,
+ * it contains a `to` clause.
+ *
+ * For qualified `exports` directives, exported types and members
+ * are accessible only to code in the specified modules.
+ * For unqualified `exports` directives, they are accessible
+ * to code in any module.
+ */
+ predicate isQualified() { exportsTo(this, _) }
+
+ /**
+ * Gets a module specified in the `to` clause of this
+ * `exports` directive, if any.
+ */
+ Module getATargetModule() { exportsTo(this, result) }
+
+ override
+ string toString() {
+ exists(string toClause |
+ if isQualified() then toClause = (" to " + concat(getATargetModule().getName(), ", ")) else toClause = ""
+ |
+ result = "exports " + getExportedPackage() + toClause + ";"
+ )
+ }
+}
+
+/**
+ * An `opens` directive in a module declaration.
+ */
+class OpensDirective extends Directive, @opens {
+ OpensDirective() {
+ opens(this,_)
+ }
+
+ /**
+ * Gets the package opened by this `opens` directive.
+ */
+ Package getOpenedPackage() { opens(this, result) }
+
+ /**
+ * Holds if this `opens` directive is qualified, that is,
+ * it contains a `to` clause.
+ *
+ * For qualified `opens` directives, opened types and members
+ * are accessible only to code in the specified modules.
+ * For unqualified `opens` directives, they are accessible
+ * to code in any module.
+ */
+ predicate isQualified() { opensTo(this, _) }
+
+ /**
+ * Gets a module specified in the `to` clause of this
+ * `exports` directive, if any.
+ */
+ Module getATargetModule() { opensTo(this, result) }
+
+ override
+ string toString() {
+ exists(string toClause |
+ if isQualified() then toClause = (" to " + concat(getATargetModule().getName(), ", ")) else toClause = ""
+ |
+ result = "opens " + getOpenedPackage() + toClause + ";"
+ )
+ }
+}
+
+/**
+ * A `uses` directive in a module declaration.
+ */
+class UsesDirective extends Directive, @uses {
+ UsesDirective() {
+ uses(this,_)
+ }
+
+ /**
+ * Gets the qualified name of the service interface specified in this `uses` directive.
+ */
+ string getServiceInterfaceName() { uses(this, result) }
+
+ override
+ string toString() {
+ result = "uses " + getServiceInterfaceName() + ";"
+ }
+}
+
+/**
+ * A `provides` directive in a module declaration.
+ */
+class ProvidesDirective extends Directive, @provides {
+ ProvidesDirective() {
+ provides(this,_)
+ }
+
+ /**
+ * Gets the qualified name of the service interface specified in this `provides` directive.
+ */
+ string getServiceInterfaceName() { provides(this, result) }
+
+ /**
+ * Gets the qualified name of a service implementation specified in this `provides` directive.
+ */
+ string getServiceImplementationName() { providesWith(this, result) }
+
+ override
+ string toString() {
+ result = "provides " + getServiceInterfaceName() + " with " + concat(getServiceImplementationName(), ", ") + ";"
+ }
+}
+
diff --git a/java/ql/src/semmle/code/java/Package.qll b/java/ql/src/semmle/code/java/Package.qll
new file mode 100755
index 00000000000..ae21a688d3b
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Package.qll
@@ -0,0 +1,34 @@
+/**
+ * Provides classes and predicates for working with Java packages.
+ */
+
+import Element
+import Type
+import metrics.MetricPackage
+
+/**
+ * A package may be used to abstract over all of its members,
+ * regardless of which compilation unit they are defined in.
+ */
+class Package extends Element, Annotatable, @package {
+ /** Gets a top level type in this package. */
+ TopLevelType getATopLevelType() { result.getPackage() = this }
+
+ /** Holds if at least one reference type in this package originates from source code. */
+ override predicate fromSource() {
+ exists(RefType t | t.fromSource() and t.getPackage() = this)
+ }
+
+ /** Cast this package to a class that provides access to metrics information. */
+ MetricPackage getMetrics() { result = this }
+
+ /**
+ * A dummy URL for packages.
+ *
+ * This declaration is required to allow selection of packages in QL queries.
+ * Without it, an implicit call to `Package.getLocation()` would be generated
+ * when selecting a package, which would result in a compile-time error
+ * since packages do not have locations.
+ */
+ string getURL() { result = "file://:0:0:0:0" }
+}
diff --git a/java/ql/src/semmle/code/java/Reflection.qll b/java/ql/src/semmle/code/java/Reflection.qll
new file mode 100644
index 00000000000..700ca69dd1e
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Reflection.qll
@@ -0,0 +1,400 @@
+/**
+ * Provides classes and predicates for working with Java Reflection.
+ */
+
+import java
+import JDKAnnotations
+import Serializability
+import semmle.code.java.dataflow.DefUse
+
+predicate reflectivelyRead(Field f){
+ f instanceof SerializableField or
+ f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
+ referencedInXmlFile(f)
+}
+
+predicate reflectivelyWritten(Field f){
+ f instanceof DeserializableField or
+ f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
+ referencedInXmlFile(f)
+}
+
+/**
+ * Holds if a field's name and declaring type are referenced in an XML file.
+ * Usually, this implies that the field may be accessed reflectively.
+ */
+predicate referencedInXmlFile(Field f) {
+ elementReferencingField(f).getParent*() = elementReferencingType(f.getDeclaringType())
+}
+
+/**
+ * Gets an XML element with an attribute whose value is the name of `f`,
+ * suggesting that it might reference `f`.
+ */
+private XMLElement elementReferencingField(Field f) {
+ exists(elementReferencingType(f.getDeclaringType())) and
+ result.getAnAttribute().getValue() = f.getName()
+}
+
+/**
+ * Gets an XML element with an attribute whose value is the fully qualified
+ * name of `rt`, suggesting that it might reference `rt`.
+ */
+private XMLElement elementReferencingType(RefType rt) {
+ result.getAnAttribute().getValue() = rt.getSourceDeclaration().getQualifiedName()
+}
+
+private abstract class ReflectiveClassIdentifier extends Expr {
+ abstract RefType getReflectivelyIdentifiedClass();
+}
+
+private class ReflectiveClassIdentifierLiteral extends ReflectiveClassIdentifier, TypeLiteral {
+ override RefType getReflectivelyIdentifiedClass() {
+ result = getTypeName().getType().(RefType).getSourceDeclaration()
+ }
+}
+
+/**
+ * A call to a Java standard library method which constructs or returns a `Class` from a `String`.
+ */
+library class ReflectiveClassIdentifierMethodAccess extends ReflectiveClassIdentifier, MethodAccess {
+ ReflectiveClassIdentifierMethodAccess() {
+ // A call to `Class.forName(...)`, from which we can infer `T` in the returned type `Class`.
+ getCallee().getDeclaringType() instanceof TypeClass and getCallee().hasName("forName") or
+ // A call to `ClassLoader.loadClass(...)`, from which we can infer `T` in the returned type `Class`.
+ getCallee().getDeclaringType().hasQualifiedName("java.lang", "ClassLoader") and getCallee().hasName("loadClass")
+ }
+
+ /**
+ * If the argument to this call is a `StringLiteral`, then return that string.
+ */
+ string getTypeName() {
+ result = getArgument(0).(StringLiteral).getRepresentedString()
+ }
+
+ override RefType getReflectivelyIdentifiedClass() {
+ // We only handle cases where the class is specified as a string literal to this call.
+ result.getQualifiedName() = getTypeName()
+ }
+}
+
+/**
+ * Gets a `ReflectiveClassIdentifier` that we believe may represent the value of `expr`.
+ */
+private ReflectiveClassIdentifier pointsToReflectiveClassIdentifier(Expr expr) {
+ // If this is an expression creating a `Class`, return the inferred `T` from the creation expression.
+ result = expr or
+ // Or if this is an access of a variable which was defined as an expression creating a `Class`,
+ // return the inferred `T` from the definition expression.
+ exists(RValue use, VariableAssign assign |
+ use = expr and
+ defUsePair(assign, use) and
+ // The source of the assignment must be a `ReflectiveClassIdentifier`.
+ result = assign.getSource()
+ )
+}
+
+/**
+ * Holds if `type` is considered to be "overly" generic.
+ */
+private predicate overlyGenericType(Type type) {
+ type instanceof TypeObject or
+ type instanceof TypeSerializable
+}
+
+/**
+ * Identify "catch-all" bounded types, where the upper bound is an overly generic type, such as
+ * `? extends Object` and `? extends Serializable`.
+ */
+private predicate catchallType(BoundedType type) {
+ exists(Type upperBound |
+ upperBound = type.getUpperBoundType()
+ |
+ overlyGenericType(upperBound)
+ )
+}
+
+/**
+ * Given `Class` or `Constructor`, return all types `T`, such that
+ * `Class` or `Constructor` is, or is a sub-type of, `type`.
+ *
+ * In the case that `X` is a bounded type with an upper bound, and that upper bound is `Object` or
+ * `Serializable`, we return no sub-types.
+ */
+pragma[nomagic]
+private Type parameterForSubTypes(ParameterizedType type) {
+ (
+ type instanceof TypeClass or type instanceof TypeConstructor
+ ) and
+ // Only report "real" types.
+ not result instanceof TypeVariable and
+ // Identify which types the type argument `arg` could represent.
+ exists(Type arg |
+ arg = type.getTypeArgument(0) and
+ // Must not be a catch-all.
+ not catchallType(arg)
+ |
+ (
+ // Simple case - this type is not a bounded type, so must represent exactly the `arg` class.
+ not arg instanceof BoundedType and result = arg
+ ) or
+ exists(RefType upperBound |
+ // Upper bound case
+ upperBound = arg.(BoundedType).getUpperBoundType()
+ |
+ /*
+ * `T extends Foo` implies that `Foo`, or any sub-type of `Foo`, may be represented.
+ */
+ result.(RefType).getAnAncestor() = upperBound
+ ) or
+ exists(RefType lowerBound |
+ // Lower bound case
+ lowerBound = arg.(Wildcard).getLowerBoundType()
+ |
+ /*
+ * `T super Foo` implies that `Foo`, or any super-type of `Foo`, may be represented.
+ */
+ lowerBound.(RefType).getAnAncestor() = result
+ )
+ )
+}
+
+/**
+ * Given an expression whose type is `Class`, infer a possible set of types for `T`.
+ */
+Type inferClassParameterType(Expr expr) {
+ // Must be of type `Class` or `Class`.
+ expr.getType() instanceof TypeClass and
+ (
+ /*
+ * If this `expr` is a `VarAccess` of a final or effectively final parameter, then look at the
+ * arguments to calls to this method, to see if we can infer anything from that case.
+ */
+ exists(Parameter p |
+ p = expr.(VarAccess).getVariable() and
+ p.isEffectivelyFinal()
+ |
+ result = inferClassParameterType(p.getAnArgument())
+ )
+ or
+ if exists(pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass()) then
+ /*
+ * We've been able to identify where this `Class` instance was created, and identified the
+ * particular class that was loaded.
+ */
+ result = pointsToReflectiveClassIdentifier(expr).getReflectivelyIdentifiedClass()
+ else
+ (
+ /*
+ * If we haven't been able to find where the value for this expression was defined, then we
+ * resort to the type `T` in `Class`.
+ *
+ * If `T` refers to a bounded type with an upper bound, then we return all sub-types of the upper
+ * bound as possibilities for the instantiation, so long as this is not a catch-all type.
+ *
+ * A "catch-all" type is something like `? extends Object` or `? extends Serialization`, which
+ * would return too many sub-types.
+ */
+ result = parameterForSubTypes(expr.getType())
+ )
+ )
+}
+
+/**
+ * Given an expression whose type is `Constructor`, infer a possible set of types for `T`.
+ */
+private Type inferConstructorParameterType(Expr expr) {
+ expr.getType() instanceof TypeConstructor and
+ // Return all the possible sub-types that could be instantiated.
+ // Not a catch-all `Constructor`, for example, `? extends Object` or `? extends Serializable`.
+ result = parameterForSubTypes(expr.getType())
+}
+
+/**
+ * Holds if a `Constructor.newInstance(...)` call for this type would expect an enclosing instance
+ * argument in the first position.
+ */
+private predicate expectsEnclosingInstance(RefType r) {
+ r instanceof NestedType and
+ not r.(NestedType).isStatic()
+}
+
+/**
+ * A call to `Class.newInstance()` or `Constructor.newInstance()`.
+ */
+class NewInstance extends MethodAccess {
+ NewInstance() {
+ (getCallee().getDeclaringType() instanceof TypeClass or getCallee().getDeclaringType() instanceof TypeConstructor) and
+ getCallee().hasName("newInstance")
+ }
+
+ /**
+ * Gets the `Constructor` that we believe will be invoked when this `newInstance()` method is
+ * called.
+ */
+ Constructor getInferredConstructor() {
+ result = getInferredConstructedType().getAConstructor() and
+ if getCallee().getDeclaringType() instanceof TypeClass then
+ result.getNumberOfParameters() = 0
+ else if getNumArgument() = 1 and getArgument(0).getType() instanceof Array then
+ /*
+ * This is a var-args array argument. If array argument is initialized inline, then identify
+ * the number of arguments specified in the array.
+ */
+ if exists(getArgument(0).(ArrayCreationExpr).getInit()) then
+ // Count the number of elements in the initializer, and find the matching constructors.
+ matchConstructorArguments(result, count(getArgument(0).(ArrayCreationExpr).getInit().getAnInit()))
+ else
+ // Could be any of the constructors on this class.
+ any()
+ else
+ /*
+ * No var-args in play, just use the number of arguments to the `newInstance(..)` to determine
+ * which constructors may be called.
+ */
+ matchConstructorArguments(result, getNumArgument())
+ }
+
+ /**
+ * Use the number of arguments to a `newInstance(..)` call to determine which constructor might be
+ * called.
+ *
+ * If the `Constructor` is for a non-static nested type, an extra argument is expected to be
+ * provided for the enclosing instance.
+ */
+ private predicate matchConstructorArguments(Constructor c, int numArguments) {
+ if expectsEnclosingInstance(c.getDeclaringType()) then
+ c.getNumberOfParameters() = numArguments - 1
+ else
+ c.getNumberOfParameters() = numArguments
+ }
+
+ /**
+ * Gets an inferred type for the constructed class.
+ *
+ * To infer the constructed type we infer a type `T` for `Class` or `Constructor`, by inspecting
+ * points to results.
+ */
+ RefType getInferredConstructedType() {
+ // Inferred type cannot be abstract.
+ not result.isAbstract() and
+ // `TypeVariable`s cannot be constructed themselves.
+ not result instanceof TypeVariable and
+ (
+ // If this is called on a `Class` instance, return the inferred type `T`.
+ result = inferClassParameterType(getQualifier()) or
+ // If this is called on a `Constructor` instance, return the inferred type `T`.
+ result = inferConstructorParameterType(getQualifier()) or
+ // If the result of this is cast to a particular type, then use that type.
+ result = getCastInferredConstructedTypes()
+ )
+ }
+
+ /**
+ * If the result of this `newInstance` call is casted, infer the types that we could have
+ * constructed based on the cast. If the cast is to `Object` or `Serializable`, then we ignore the
+ * cast.
+ */
+ private Type getCastInferredConstructedTypes() {
+ exists(CastExpr cast |
+ cast.getExpr() = this or cast.getExpr().(ParExpr).getExpr() = this
+ |
+ result = cast.getType() or
+ (
+ /*
+ * If we cast the result of this method, then this is either the type specified, or a
+ * sub-type of that type. Make sure we exclude overly generic types such as `Object`.
+ */
+ not overlyGenericType(cast.getType()) and
+ hasSubtype*(cast.getType(), result)
+ )
+ )
+ }
+}
+
+/**
+ * A `MethodAccess` on a `Class` element.
+ */
+class ClassMethodAccess extends MethodAccess {
+ ClassMethodAccess() {
+ this.getCallee().getDeclaringType() instanceof TypeClass
+ }
+
+ /**
+ * Gets an inferred type for the `Class` represented by this expression.
+ */
+ RefType getInferredClassType() {
+ // `TypeVariable`s do not have methods themselves.
+ not result instanceof TypeVariable and
+ // If this is called on a `Class` instance, return the inferred type `T`.
+ result = inferClassParameterType(getQualifier())
+ }
+}
+
+/**
+ * A call to `Class.getMethod(..)` or `Class.getDeclaredMethod(..)`.
+ */
+class ReflectiveMethodAccess extends ClassMethodAccess {
+ ReflectiveMethodAccess() {
+ this.getCallee().hasName("getMethod") or
+ this.getCallee().hasName("getDeclaredMethod")
+ }
+
+ /**
+ * Gets a `Method` that is inferred to be accessed by this reflective use of `getMethod(..)`.
+ */
+ Method inferAccessedMethod() {
+ (
+ if this.getCallee().hasName("getDeclaredMethod") then
+ // The method must be declared on the type itself.
+ result.getDeclaringType() = getInferredClassType()
+ else
+ // The method may be declared on an inferred type or a super-type.
+ getInferredClassType().inherits(result)
+ )
+ and
+ // Only consider instances where the method name is provided as a `StringLiteral`.
+ result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
+ }
+}
+
+/**
+ * A call to `Class.getAnnotation(..)`.
+ */
+class ReflectiveAnnotationAccess extends ClassMethodAccess {
+ ReflectiveAnnotationAccess() {
+ this.getCallee().hasName("getAnnotation")
+ }
+
+ /**
+ * Gets a possible annotation type for this reflective annotation access.
+ */
+ AnnotationType getAPossibleAnnotationType() {
+ result = inferClassParameterType(getArgument(0))
+ }
+}
+
+/**
+ * A call to `Class.getField(..)` that accesses a field.
+ */
+class ReflectiveFieldAccess extends ClassMethodAccess {
+ ReflectiveFieldAccess() {
+ this.getCallee().hasName("getField") or
+ this.getCallee().hasName("getDeclaredField")
+ }
+
+ Field inferAccessedField() {
+ (
+ if this.getCallee().hasName("getDeclaredField") then
+ // Declared fields must be on the type itself.
+ result.getDeclaringType() = getInferredClassType()
+ else
+ (
+ // This field must be public, and be inherited by one of the inferred class types.
+ result.isPublic() and
+ getInferredClassType().inherits(result)
+ )
+ ) and
+ result.hasName(getArgument(0).(StringLiteral).getRepresentedString())
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Serializability.qll b/java/ql/src/semmle/code/java/Serializability.qll
new file mode 100644
index 00000000000..508ef67bad8
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Serializability.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes and predicates for working with Java Serialization.
+ */
+
+import java
+private import frameworks.jackson.JacksonSerializability
+private import frameworks.google.GoogleHttpClientApi
+
+/**
+ * A serializable field may be read without code referencing it,
+ * due to the use of serialization.
+ */
+abstract class SerializableField extends Field {
+
+}
+/**
+ * A deserializable field may be written without code referencing it,
+ * due to the use of serialization.
+ */
+abstract class DeserializableField extends Field {
+
+}
+
+/**
+ * A non-`transient` field in a type that (directly or indirectly) implements the `Serializable` interface
+ * and may be read or written via serialization.
+ */
+library class StandardSerializableField extends SerializableField, DeserializableField {
+ StandardSerializableField() {
+ this.getDeclaringType().getASupertype*() instanceof TypeSerializable and
+ not this.isTransient()
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Statement.qll b/java/ql/src/semmle/code/java/Statement.qll
new file mode 100755
index 00000000000..cc17f35e4eb
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Statement.qll
@@ -0,0 +1,825 @@
+/**
+ * Provides classes and predicates for working with Java statements.
+ */
+
+import Expr
+import metrics.MetricStmt
+
+/** A common super-class of all statements. */
+class Stmt extends StmtParent, ExprParent, @stmt {
+ /*abstract*/ override string toString() { result = "stmt" }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ string pp() { result = "stmt" }
+
+ /**
+ * Gets the immediately enclosing callable (method or constructor)
+ * whose body contains this statement.
+ */
+ Callable getEnclosingCallable() { stmts(this,_,_,_,result) }
+
+ /** Gets the index of this statement as a child of its parent. */
+ int getIndex() { stmts(this,_,_,result,_) }
+
+ /** Gets the parent of this statement. */
+ StmtParent getParent() { stmts(this,_,result,_,_) }
+
+ /** Holds if this statement is the child of the specified parent at the specified (zero-based) position. */
+ predicate isNthChildOf(StmtParent parent, int index) {
+ this.getParent() = parent and this.getIndex() = index
+ }
+
+ /** Gets the compilation unit in which this statement occurs. */
+ CompilationUnit getCompilationUnit() { result = this.getFile() }
+
+ /** Gets a child of this statement, if any. */
+ Stmt getAChild() { result.getParent() = this }
+
+ /** Gets the basic block in which this statement occurs. */
+ BasicBlock getBasicBlock() { result.getANode() = this }
+
+ /** Gets the `ControlFlowNode` corresponding to this statement. */
+ ControlFlowNode getControlFlowNode() { result = this }
+
+ /** Cast this statement to a class that provides access to metrics information. */
+ MetricStmt getMetrics() { result = this }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ string getHalsteadID() { result = "Stmt" }
+}
+
+/** A statement parent is any element that can have a statement as its child. */
+class StmtParent extends @stmtparent, Top {
+}
+
+/** A block of statements. */
+class Block extends Stmt,@block {
+ /** Gets a statement that is an immediate child of this block. */
+ Stmt getAStmt() { result.getParent() = this }
+
+ /** Gets the immediate child statement of this block that occurs at the specified (zero-based) position. */
+ Stmt getStmt(int index) { result.getIndex() = index and result.getParent() = this }
+
+ /** Gets the number of immediate child statements in this block. */
+ int getNumStmt() { result = count(this.getAStmt()) }
+
+ /** Gets the last statement in this block. */
+ Stmt getLastStmt() { result = getStmt(getNumStmt()-1) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() { result = "{ ... }" }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "Block" }
+}
+
+/** A block with only a single statement. */
+class SingletonBlock extends Block {
+ SingletonBlock() { this.getNumStmt() = 1 }
+
+ /** Gets the single statement in this block. */
+ Stmt getStmt() { result = getStmt(0) }
+}
+
+/**
+ * A conditional statement, including `if`, `for`,
+ * `while` and `dowhile` statements.
+ */
+abstract class ConditionalStmt extends Stmt {
+ /** Gets the boolean condition of this conditional statement. */
+ abstract Expr getCondition();
+
+ /**
+ * Gets the statement that is executed whenever the condition
+ * of this branch statement evaluates to `true`.
+ *
+ * DEPRECATED: use `ConditionNode.getATrueSuccessor()` instead.
+ */
+ deprecated abstract Stmt getTrueSuccessor();
+}
+
+/** An `if` statement. */
+class IfStmt extends ConditionalStmt,@ifstmt {
+ /** Gets the boolean condition of this `if` statement. */
+ override Expr getCondition() { result.isNthChildOf(this, 0) }
+
+ /** Gets the `then` branch of this `if` statement. */
+ Stmt getThen() { result.isNthChildOf(this, 1) }
+
+ /**
+ * Gets the statement that is executed whenever the condition
+ * of this branch statement evaluates to `true`.
+ */
+ override Stmt getTrueSuccessor() { result = getThen() }
+
+ /** Gets the `else` branch of this `if` statement. */
+ Stmt getElse() { result.isNthChildOf(this, 2) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "if (...) " + this.getThen().pp() + " else " + this.getElse().pp()
+ or
+ (not exists(this.getElse()) and result = "if (...) " + this.getThen().pp())
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "IfStmt" }
+}
+
+/** A `for` loop. */
+class ForStmt extends ConditionalStmt,@forstmt {
+ /**
+ * Gets an initializer expression of the loop.
+ *
+ * This may be an assignment expression or a
+ * local variable declaration expression.
+ */
+ Expr getAnInit() {
+ exists(int index | result.isNthChildOf(this, index) | index <= -1)
+ }
+
+ /** Gets the initializer expression of the loop at the specified (zero-based) position. */
+ Expr getInit(int index) {
+ result = getAnInit() and
+ index = -1 - result.getIndex()
+ }
+
+ /** Gets the boolean condition of this `for` loop. */
+ override Expr getCondition() { result.isNthChildOf(this, 1) }
+
+ /** Gets an update expression of this `for` loop. */
+ Expr getAnUpdate() {
+ exists(int index | result.isNthChildOf(this, index) | index >= 3)
+ }
+
+ /** Gets the update expression of this loop at the specified (zero-based) position. */
+ Expr getUpdate(int index) {
+ result = getAnUpdate() and
+ index = result.getIndex() - 3
+ }
+
+ /** Gets the body of this `for` loop. */
+ Stmt getStmt() { result.getParent() = this and result.getIndex() = 2 }
+
+ /**
+ * Gets the statement that is executed whenever the condition
+ * of this branch statement evaluates to true.
+ */
+ override Stmt getTrueSuccessor() { result = getStmt() }
+
+ /**
+ * Gets a variable that is used as an iteration variable: it is defined,
+ * updated or tested in the head of the `for` statement.
+ *
+ * This only returns variables that are quite certainly loop variables;
+ * for complex iterations, it may not return anything.
+ *
+ * More precisely, it returns variables that are both accessed in the
+ * condition of this `for` statement and updated in the update expression
+ * of this for statement but may be initialized elsewhere.
+ */
+ Variable getAnIterationVariable() {
+ // Check that the variable is assigned to, incremented or decremented in the update expression, and...
+ exists(Expr update | update = getAnUpdate().getAChildExpr*() |
+ update.(UnaryAssignExpr).getExpr() = result.getAnAccess() or
+ update = result.getAnAssignedValue()
+ ) and
+ // ...that it is checked or used in the condition.
+ getCondition().getAChildExpr*() = result.getAnAccess()
+ }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "for (...;...;...) " + this.getStmt().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ForStmt" }
+}
+
+/** An enhanced `for` loop. (Introduced in Java 5.) */
+class EnhancedForStmt extends Stmt,@enhancedforstmt {
+ /** Gets the local variable declaration expression of this enhanced `for` loop. */
+ LocalVariableDeclExpr getVariable() { result.getParent() = this }
+
+ /** Gets the expression over which this enhanced `for` loop iterates. */
+ Expr getExpr() { result.isNthChildOf(this, 1) }
+
+ /** Gets the body of this enhanced `for` loop. */
+ Stmt getStmt() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "for (...) " + this.getStmt().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "EnhancedForStmt" }
+}
+
+/** A `while` loop. */
+class WhileStmt extends ConditionalStmt,@whilestmt {
+ /** Gets the boolean condition of this `while` loop. */
+ override Expr getCondition() { result.getParent() = this }
+
+ /** Gets the body of this `while` loop. */
+ Stmt getStmt() { result.getParent() = this }
+
+ /**
+ * Gets the statement that is executed whenever the condition
+ * of this branch statement evaluates to true.
+ */
+ override Stmt getTrueSuccessor() { result = getStmt() }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "while (...) " + this.getStmt().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "WhileStmt" }
+}
+
+/** A `do` loop. */
+class DoStmt extends ConditionalStmt,@dostmt {
+ /** Gets the condition of this `do` loop. */
+ override Expr getCondition() { result.getParent() = this }
+
+ /** Gets the body of this `do` loop. */
+ Stmt getStmt() { result.getParent() = this }
+
+ /**
+ * Gets the statement that is executed whenever the condition
+ * of this branch statement evaluates to `true`.
+ */
+ override Stmt getTrueSuccessor() { result = getStmt() }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "do " + this.getStmt().pp() + " while (...)"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "DoStmt" }
+}
+
+/**
+ * A loop statement, including `for`, enhanced `for`,
+ * `while` and `do` statements.
+ */
+class LoopStmt extends Stmt {
+ LoopStmt() {
+ this instanceof ForStmt or
+ this instanceof EnhancedForStmt or
+ this instanceof WhileStmt or
+ this instanceof DoStmt
+ }
+
+ /** Gets the body of this loop statement. */
+ Stmt getBody() {
+ result = this.(ForStmt).getStmt() or
+ result = this.(EnhancedForStmt).getStmt() or
+ result = this.(WhileStmt).getStmt() or
+ result = this.(DoStmt).getStmt()
+ }
+
+ /** Gets the boolean condition of this loop statement. */
+ Expr getCondition() {
+ result = this.(ForStmt).getCondition() or
+ result = this.(WhileStmt).getCondition() or
+ result = this.(DoStmt).getCondition()
+ }
+}
+
+/** A `try` statement. */
+class TryStmt extends Stmt,@trystmt {
+ /** Gets the block of the `try` statement. */
+ Stmt getBlock() { result.isNthChildOf(this, -1) }
+
+ /** Gets a `catch` clause of this `try` statement. */
+ CatchClause getACatchClause() { result.getParent() = this }
+
+ /**
+ * Gets the `catch` clause at the specified (zero-based) position
+ * in this `try` statement.
+ */
+ CatchClause getCatchClause(int index) {
+ result = this.getACatchClause() and
+ result.getIndex() = index
+ }
+
+ /** Gets the `finally` block, if any. */
+ Block getFinally() { result.isNthChildOf(this, -2) }
+
+ /** Gets a resource variable declaration, if any. */
+ LocalVariableDeclStmt getAResourceDecl() {
+ result.getParent() = this and result.getIndex() <= -3
+ }
+
+ /** Gets the resource variable declaration at the specified position in this `try` statement. */
+ LocalVariableDeclStmt getResourceDecl(int index) {
+ result = this.getAResourceDecl() and
+ index = -3 - result.getIndex()
+ }
+
+ /** Gets a resource expression, if any. */
+ VarAccess getAResourceExpr() {
+ result.getParent() = this and result.getIndex() <= -3
+ }
+
+ /** Gets the resource expression at the specified position in this `try` statement. */
+ VarAccess getResourceExpr(int index) {
+ result = this.getAResourceExpr() and
+ index = -3 - result.getIndex()
+ }
+
+ /** Gets a resource in this `try` statement, if any. */
+ ExprParent getAResource() {
+ result = getAResourceDecl() or result = getAResourceExpr()
+ }
+
+ /** Gets the resource at the specified position in this `try` statement. */
+ ExprParent getResource(int index) {
+ result = getResourceDecl(index) or result = getResourceExpr(index)
+ }
+
+ /** Gets a resource variable, if any, either from a resource variable declaration or resource expression. */
+ Variable getAResourceVariable() {
+ result = getAResourceDecl().getAVariable().getVariable() or
+ result = getAResourceExpr().getVariable()
+ }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "try " + this.getBlock().pp() + " catch (...)"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "TryStmt" }
+}
+
+/** A `catch` clause in a `try` statement. */
+class CatchClause extends Stmt,@catchclause {
+ /** Gets the block of this `catch` clause. */
+ Block getBlock() { result.getParent() = this }
+
+ /** Gets the `try` statement in which this `catch` clause occurs. */
+ TryStmt getTry() { this = result.getACatchClause() }
+
+ /** Gets the parameter of this `catch` clause. */
+ LocalVariableDeclExpr getVariable() { result.getParent() = this }
+
+ /** Holds if this `catch` clause is a _multi_-`catch` clause. */
+ predicate isMultiCatch() {
+ this.getVariable().getTypeAccess() instanceof UnionTypeAccess
+ }
+
+ /** Gets a type caught by this `catch` clause. */
+ RefType getACaughtType() {
+ exists(Expr ta | ta = getVariable().getTypeAccess() |
+ result = ta.(TypeAccess).getType() or
+ result = ta.(UnionTypeAccess).getAnAlternative().getType()
+ )
+ }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "catch (...) " + this.getBlock().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "CatchClause" }
+}
+
+/** A `switch` statement. */
+class SwitchStmt extends Stmt,@switchstmt {
+ /** Gets an immediate child statement of this `switch` statement. */
+ Stmt getAStmt() { result.getParent() = this }
+
+ /**
+ * Gets the immediate child statement of this `switch` statement
+ * that occurs at the specified (zero-based) position.
+ */
+ Stmt getStmt(int index) { result = this.getAStmt() and result.getIndex() = index }
+
+ /**
+ * Gets a case of this `switch` statement,
+ * which may be either a normal `case` or a `default`.
+ */
+ SwitchCase getACase() { result = getAConstCase() or result = getDefaultCase() }
+
+ /** Gets a (non-default) `case` of this `switch` statement. */
+ ConstCase getAConstCase() { result.getParent() = this }
+
+ /** Gets the `default` case of this switch statement, if any. */
+ DefaultCase getDefaultCase() { result.getParent() = this }
+
+ /** Gets the expression of this `switch` statement. */
+ Expr getExpr() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "switch (...)"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "SwitchStmt" }
+}
+
+/**
+ * A case of a `switch` statement.
+ *
+ * This includes both normal `case`s and the `default` case.
+ */
+class SwitchCase extends Stmt, @case {
+ SwitchStmt getSwitch() { result.getACase() = this }
+}
+
+/** A constant `case` of a switch statement. */
+class ConstCase extends SwitchCase {
+ ConstCase() { exists(Expr e | e.getParent() = this) }
+
+ /** Gets the expression of this `case`. */
+ Expr getValue() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "case ...:"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ConstCase" }
+}
+
+/** A `default` case of a `switch` statement */
+class DefaultCase extends SwitchCase {
+ DefaultCase() { not exists(Expr e | e.getParent() = this) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "default"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "DefaultCase" }
+}
+
+/** A `synchronized` statement. */
+class SynchronizedStmt extends Stmt,@synchronizedstmt {
+ /** Gets the expression on which this `synchronized` statement synchronizes. */
+ Expr getExpr() { result.getParent() = this }
+
+ /** Gets the block of this `synchronized` statement. */
+ Stmt getBlock() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "synchronized (...) " + this.getBlock().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "SynchronizedStmt" }
+}
+
+/** A `return` statement. */
+class ReturnStmt extends Stmt,@returnstmt {
+ /** Gets the expression returned by this `return` statement, if any. */
+ Expr getResult() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "return ..."
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ReturnStmt" }
+}
+
+/** A `throw` statement. */
+class ThrowStmt extends Stmt,@throwstmt {
+ /** Gets the expression thrown by this `throw` statement. */
+ Expr getExpr() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "throw ..."
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ThrowStmt" }
+
+ /** Gets the type of the expression thrown by this `throw` statement. */
+ RefType getThrownExceptionType() { result = getExpr().getType() }
+
+ /**
+ * Gets the `catch` clause that catches the exception
+ * thrown by this `throws` statement and occurs
+ * in the same method as this `throws` statement,
+ * provided such a `catch` exists.
+ */
+ CatchClause getLexicalCatchIfAny() {
+ exists(TryStmt try | try = findEnclosing() and result = catchClauseForThis(try))
+ }
+
+ private Stmt findEnclosing() {
+ result = getParent() or
+ exists(Stmt mid |
+ mid = findEnclosing() and
+ not exists(this.catchClauseForThis((TryStmt)mid)) and
+ result = mid.getParent()
+ )
+ }
+
+ private CatchClause catchClauseForThis(TryStmt try) {
+ result = try.getACatchClause() and
+ result.getEnclosingCallable() = this.getEnclosingCallable() and
+ ((RefType)getExpr().getType()).hasSupertype*((RefType)result.getVariable().getType()) and
+ not this.getParent+() = result
+ }
+}
+
+/** A `break` or `continue` statement. */
+class JumpStmt extends Stmt {
+ JumpStmt() {
+ this instanceof BreakStmt or
+ this instanceof ContinueStmt
+ }
+
+ /**
+ * Gets the labeled statement that this `break` or
+ * `continue` statement refers to, if any.
+ */
+ LabeledStmt getTargetLabel() {
+ this.getParent+() = result and
+ namestrings(result.getLabel(), _, this)
+ }
+
+ private Stmt getLabelTarget() {
+ result = getTargetLabel().getStmt()
+ }
+
+ private Stmt getAPotentialTarget() {
+ this.getParent+() = result and
+ (
+ result instanceof LoopStmt or
+ this instanceof BreakStmt and result instanceof SwitchStmt
+ )
+ }
+
+ private Stmt getEnclosingTarget() {
+ result = getAPotentialTarget() and
+ not exists(Stmt other | other = getAPotentialTarget() | other.getParent+() = result)
+ }
+
+ /**
+ * Gets the statement that this `break` or `continue` jumps to.
+ */
+ Stmt getTarget() {
+ result = getLabelTarget() or
+ (not exists(getLabelTarget()) and result = getEnclosingTarget())
+ }
+}
+
+/** A `break` statement. */
+class BreakStmt extends Stmt,@breakstmt {
+ /** Gets the label targeted by this `break` statement, if any. */
+ string getLabel() { namestrings(result,_,this) }
+
+ /** Holds if this `break` statement has an explicit label. */
+ predicate hasLabel() { exists(string s | s = this.getLabel()) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ if this.hasLabel() then
+ result = "break " + this.getLabel()
+ else
+ result = "break"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "BreakStmt" }
+}
+
+/** A `continue` statement. */
+class ContinueStmt extends Stmt,@continuestmt {
+ /** Gets the label targeted by this `continue` statement, if any. */
+ string getLabel() { namestrings(result,_,this) }
+
+ /** Holds if this `continue` statement has an explicit label. */
+ predicate hasLabel() { exists(string s | s = this.getLabel()) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ if this.hasLabel() then
+ result = "continue " + this.getLabel()
+ else
+ result = "continue"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ContinueStmt" }
+}
+
+/** The empty statement. */
+class EmptyStmt extends Stmt,@emptystmt {
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = ";"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "EmptyStmt" }
+}
+
+/**
+ * An expression statement.
+ *
+ * Certain kinds of expressions may be used as statements by appending a semicolon.
+ */
+class ExprStmt extends Stmt,@exprstmt {
+ /** Gets the expression of this expression statement. */
+ Expr getExpr() { result.getParent() = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "...;"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ExprStmt" }
+
+ /** Holds if this statement represents a field declaration with an initializer. */
+ predicate isFieldDecl() {
+ getEnclosingCallable() instanceof InitializerMethod and
+ exists(FieldDeclaration fd, Location fdl, Location sl |
+ fdl = fd.getLocation() and sl = getLocation()
+ |
+ fdl.getFile() = sl.getFile() and
+ fdl.getStartLine() = sl.getStartLine() and
+ fdl.getStartColumn() = sl.getStartColumn()
+ )
+ }
+}
+
+/** A labeled statement. */
+class LabeledStmt extends Stmt,@labeledstmt {
+ /** Gets the statement of this labeled statement. */
+ Stmt getStmt() { result.getParent() = this }
+
+ /** Gets the label of this labeled statement. */
+ string getLabel() { namestrings(result,_,this) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = this.getLabel() + ": " + this.getStmt().pp()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = this.getLabel() + ":" }
+}
+
+/** An `assert` statement. */
+class AssertStmt extends Stmt,@assertstmt {
+ /** Gets the boolean expression of this `assert` statement. */
+ Expr getExpr() { exprs(result,_,_,this,_) and result.getIndex() = 0 }
+
+ /** Gets the assertion message expression, if any. */
+ Expr getMessage() { exprs(result,_,_,this,_) and result.getIndex() = 1 }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ if exists(this.getMessage()) then
+ result = "assert ... : ..."
+ else
+ result = "assert ..."
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "AssertStmt" }
+}
+
+/** A statement that declares one or more local variables. */
+class LocalVariableDeclStmt extends Stmt,@localvariabledeclstmt {
+ /** Gets a declared variable. */
+ LocalVariableDeclExpr getAVariable() { result.getParent() = this }
+
+ /** Gets the variable declared at the specified (one-based) position in this local variable declaration statement. */
+ LocalVariableDeclExpr getVariable(int index) {
+ result = this.getAVariable() and
+ result.getIndex() = index
+ }
+
+ /** Gets an index of a variable declared in this local variable declaration statement. */
+ int getAVariableIndex() {
+ exists(getVariable(result))
+ }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "local variable declaration"
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "LocalVariableDeclStmt" }
+}
+
+/** A statement that declares a local class. */
+class LocalClassDeclStmt extends Stmt,@localclassdeclstmt {
+ /** Gets the local class declared by this statement. */
+ LocalClass getLocalClass() { isLocalClass(result,this) }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "local class declaration: " + this.getLocalClass().toString()
+ }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "LocalClassDeclStmt" }
+}
+
+/** An explicit `this(...)` constructor invocation. */
+class ThisConstructorInvocationStmt extends Stmt, ConstructorCall, @constructorinvocationstmt {
+ /** Gets an argument of this constructor invocation. */
+ override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
+
+ /** Gets the argument at the specified (zero-based) position in this constructor invocation. */
+ override Expr getArgument(int index) {
+ result = this.getAnArgument() and
+ result.getIndex() = index
+ }
+
+ /** Gets a type argument of this constructor invocation. */
+ Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this }
+
+ /** Gets the type argument at the specified (zero-based) position in this constructor invocation. */
+ Expr getTypeArgument(int index) {
+ result = this.getATypeArgument() and
+ (-2 - result.getIndex()) = index
+ }
+
+ /** Gets the constructor invoked by this constructor invocation. */
+ override Constructor getConstructor() { callableBinding(this,result) }
+
+ override Expr getQualifier() { none() }
+
+ /** Gets the immediately enclosing callable of this constructor invocation. */
+ override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() }
+
+ /** Gets the immediately enclosing statement of this constructor invocation. */
+ override Stmt getEnclosingStmt() { result = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "this(...)"
+ }
+
+ /** Gets a printable representation of this statement. */
+ override string toString() { result = pp() }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "ConstructorInvocationStmt" }
+}
+
+/** An explicit `super(...)` constructor invocation. */
+class SuperConstructorInvocationStmt extends Stmt, ConstructorCall, @superconstructorinvocationstmt {
+ /** Gets an argument of this constructor invocation. */
+ override Expr getAnArgument() { result.getIndex() >= 0 and result.getParent() = this }
+
+ /** Gets the argument at the specified (zero-based) position in this constructor invocation. */
+ override Expr getArgument(int index) {
+ result = this.getAnArgument() and
+ result.getIndex() = index
+ }
+
+ /** Gets a type argument of this constructor invocation. */
+ Expr getATypeArgument() { result.getIndex() <= -2 and result.getParent() = this }
+
+ /** Gets the type argument at the specified (zero-based) position in this constructor invocation. */
+ Expr getTypeArgument(int index) {
+ result = this.getATypeArgument() and
+ (-2 - result.getIndex()) = index
+ }
+
+ /** Gets the constructor invoked by this constructor invocation. */
+ override Constructor getConstructor() { callableBinding(this,result) }
+
+ /** Gets the qualifier expression of this `super(...)` constructor invocation, if any. */
+ override Expr getQualifier() { result.isNthChildOf(this, -1) }
+
+ /** Gets the immediately enclosing callable of this constructor invocation. */
+ override Callable getEnclosingCallable() { result = Stmt.super.getEnclosingCallable() }
+
+ /** Gets the immediately enclosing statement of this constructor invocation. */
+ override Stmt getEnclosingStmt() { result = this }
+
+ /** Gets a printable representation of this statement. May include more detail than `toString()`. */
+ override string pp() {
+ result = "super(...)"
+ }
+
+ /** Gets a printable representation of this statement. */
+ override string toString() { result = pp() }
+
+ /** This statement's Halstead ID (used to compute Halstead metrics). */
+ override string getHalsteadID() { result = "SuperConstructorInvocationStmt" }
+}
diff --git a/java/ql/src/semmle/code/java/StringFormat.qll b/java/ql/src/semmle/code/java/StringFormat.qll
new file mode 100644
index 00000000000..18ff3307b2a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/StringFormat.qll
@@ -0,0 +1,422 @@
+import java
+import dataflow.DefUse
+
+/**
+ * A library method that formats a number of its arguments according to a
+ * format string.
+ */
+private abstract class FormatMethod extends Method {
+ /** Gets the index of the format string argument. */
+ abstract int getFormatStringIndex();
+}
+
+/**
+ * A library method that acts like `String.format` by formatting a number of
+ * its arguments according to a format string.
+ */
+class StringFormatMethod extends FormatMethod {
+ StringFormatMethod() {
+ (
+ this.hasName("format") or
+ this.hasName("printf") or
+ this.hasName("readLine") or
+ this.hasName("readPassword")
+ ) and (
+ this.getDeclaringType().hasQualifiedName("java.lang", "String") or
+ this.getDeclaringType().hasQualifiedName("java.io", "PrintStream") or
+ this.getDeclaringType().hasQualifiedName("java.io", "PrintWriter") or
+ this.getDeclaringType().hasQualifiedName("java.io", "Console") or
+ this.getDeclaringType().hasQualifiedName("java.util", "Formatter")
+ )
+ }
+
+ override int getFormatStringIndex() {
+ result = 0 and this.getSignature() = "format(java.lang.String,java.lang.Object[])" or
+ result = 0 and this.getSignature() = "printf(java.lang.String,java.lang.Object[])" or
+ result = 1 and this.getSignature() = "format(java.util.Locale,java.lang.String,java.lang.Object[])" or
+ result = 1 and this.getSignature() = "printf(java.util.Locale,java.lang.String,java.lang.Object[])" or
+ result = 0 and this.getSignature() = "readLine(java.lang.String,java.lang.Object[])" or
+ result = 0 and this.getSignature() = "readPassword(java.lang.String,java.lang.Object[])"
+ }
+}
+
+/**
+ * A format method using the `org.slf4j.Logger` format string syntax. That is,
+ * the placeholder string is `"{}"`.
+ */
+class LoggerFormatMethod extends FormatMethod {
+ LoggerFormatMethod() {
+ (
+ this.hasName("debug") or
+ this.hasName("error") or
+ this.hasName("info") or
+ this.hasName("trace") or
+ this.hasName("warn")
+ ) and
+ this.getDeclaringType().getASourceSupertype*().hasQualifiedName("org.slf4j", "Logger")
+ }
+
+ override int getFormatStringIndex() {
+ (result = 0 or result = 1) and
+ this.getParameterType(result) instanceof TypeString
+ }
+}
+
+private newtype TFmtSyntax = TFmtPrintf() or TFmtLogger()
+
+/** A syntax for format strings. */
+class FmtSyntax extends TFmtSyntax {
+ string toString() {
+ result = "printf (%) syntax" and this = TFmtPrintf() or
+ result = "logger ({}) syntax" and this = TFmtLogger()
+ }
+}
+
+/**
+ * Holds if `c` wraps a call to a `StringFormatMethod`, such that `fmtix` is
+ * the index of the format string argument to `c` and the following and final
+ * argument is the `Object[]` that holds the arguments to be formatted.
+ */
+private predicate formatWrapper(Callable c, int fmtix, FmtSyntax syntax) {
+ exists(Parameter fmt, Parameter args, Call fmtcall, int i |
+ fmt = c.getParameter(fmtix) and
+ fmt.getType() instanceof TypeString and
+ args = c.getParameter(fmtix+1) and
+ args.getType().(Array).getElementType() instanceof TypeObject and
+ c.getNumberOfParameters() = fmtix+2 and
+ fmtcall.getEnclosingCallable() = c and
+ (
+ formatWrapper(fmtcall.getCallee(), i, syntax) or
+ fmtcall.getCallee().(StringFormatMethod).getFormatStringIndex() = i and syntax = TFmtPrintf() or
+ fmtcall.getCallee().(LoggerFormatMethod).getFormatStringIndex() = i and syntax = TFmtLogger()
+ ) and
+ fmtcall.getArgument(i) = fmt.getAnAccess() and
+ fmtcall.getArgument(i+1) = args.getAnAccess()
+ )
+}
+
+/**
+ * A call to a `StringFormatMethod` or a callable wrapping a `StringFormatMethod`.
+ */
+class FormattingCall extends Call {
+ FormattingCall() {
+ this.getCallee() instanceof FormatMethod or
+ formatWrapper(this.getCallee(), _, _)
+ }
+
+ /** Gets the index of the format string argument. */
+ private int getFormatStringIndex() {
+ this.getCallee().(FormatMethod).getFormatStringIndex() = result or
+ formatWrapper(this.getCallee(), result, _)
+ }
+
+ FmtSyntax getSyntax() {
+ this.getCallee() instanceof StringFormatMethod and result = TFmtPrintf() or
+ this.getCallee() instanceof LoggerFormatMethod and result = TFmtLogger() or
+ formatWrapper(this.getCallee(), _, result)
+ }
+
+ private Expr getLastArg() {
+ exists(Expr last |
+ last = this.getArgument(this.getNumArgument() - 1)
+ |
+ if this.hasExplicitVarargsArray() then
+ result = last.(ArrayCreationExpr).getInit().getInit(getVarargsCount() - 1)
+ else
+ result = last
+ )
+ }
+
+ predicate hasTrailingThrowableArgument() {
+ getSyntax() = TFmtLogger() and
+ getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable
+ }
+
+ /** Gets the argument to this call in the position of the format string */
+ Expr getFormatArgument() {
+ result = this.getArgument(this.getFormatStringIndex())
+ }
+
+ /** Gets an argument to be formatted. */
+ Expr getAnArgumentToBeFormatted() {
+ exists(int i |
+ result = this.getArgument(i) and
+ i > this.getFormatStringIndex() and
+ not hasExplicitVarargsArray()
+ )
+ }
+
+ /** Holds if the varargs argument is given as an explicit array. */
+ private predicate hasExplicitVarargsArray() {
+ this.getNumArgument() = this.getFormatStringIndex() + 2 and
+ this.getArgument(1 + this.getFormatStringIndex()).getType() instanceof Array
+ }
+
+ /** Gets the length of the varargs array if it can determined. */
+ int getVarargsCount() {
+ if this.hasExplicitVarargsArray() then
+ exists(Expr arg | arg = this.getArgument(1 + this.getFormatStringIndex()) |
+ result = arg.(ArrayCreationExpr).getFirstDimensionSize() or
+ result = arg.(VarAccess).getVariable().getAnAssignedValue().(ArrayCreationExpr).getFirstDimensionSize()
+ )
+ else
+ result = this.getNumArgument() - this.getFormatStringIndex() - 1
+ }
+
+ /** Gets a `FormatString` that is used by this call. */
+ FormatString getAFormatString() {
+ result.getAFormattingUse() = this
+ }
+}
+
+/** Holds if `m` calls `toString()` on its `i`th argument. */
+private predicate printMethod(Method m, int i) {
+ exists(RefType t |
+ t = m.getDeclaringType() and
+ m.getParameterType(i) instanceof TypeObject
+ |
+ (t.hasQualifiedName("java.io", "PrintWriter") or t.hasQualifiedName("java.io", "PrintStream")) and
+ (m.hasName("print") or m.hasName("println"))
+ or
+ (t.hasQualifiedName("java.lang", "StringBuilder") or t.hasQualifiedName("java.lang", "StringBuffer")) and
+ (m.hasName("append") or m.hasName("insert"))
+ or
+ t instanceof TypeString and m.hasName("valueOf")
+ )
+}
+
+/**
+ * Holds if `e` occurs in a position where it may be converted to a string by
+ * an implicit call to `toString()`.
+ */
+predicate implicitToStringCall(Expr e) {
+ not e.getType() instanceof TypeString and
+ (
+ exists(FormattingCall fmtcall | fmtcall.getAnArgumentToBeFormatted() = e) or
+ exists(AddExpr add | add.getType() instanceof TypeString and add.getAnOperand() = e) or
+ exists(MethodAccess ma, Method m, int i |
+ ma.getMethod() = m and
+ ma.getArgument(i) = e and
+ printMethod(m, i)
+ )
+ )
+}
+
+/**
+ * A call to a `format` or `printf` method.
+ */
+class StringFormat extends MethodAccess, FormattingCall {
+ StringFormat() {
+ this.getCallee() instanceof StringFormatMethod
+ }
+}
+
+/**
+ * Holds if `fmt` is used as part of a format string.
+ */
+private predicate formatStringFragment(Expr fmt) {
+ any(FormattingCall call).getFormatArgument() = fmt or
+ exists(Expr e | formatStringFragment(e) |
+ e.(VarAccess).getVariable().getAnAssignedValue() = fmt or
+ e.(AddExpr).getLeftOperand() = fmt or
+ e.(AddExpr).getRightOperand() = fmt or
+ e.(ConditionalExpr).getTrueExpr() = fmt or
+ e.(ConditionalExpr).getFalseExpr() = fmt or
+ e.(ParExpr).getExpr() = fmt
+ )
+}
+
+/**
+ * Holds if `e` is a part of a format string with the approximate value
+ * `fmtvalue`. The value is approximated by ignoring details that are
+ * irrelevant for determining the number of format specifiers in the resulting
+ * string.
+ */
+private predicate formatStringValue(Expr e, string fmtvalue) {
+ formatStringFragment(e) and
+ (
+ e.(StringLiteral).getRepresentedString() = fmtvalue or
+ e.getType() instanceof IntegralType and fmtvalue = "1" or // dummy value
+ e.getType() instanceof BooleanType and fmtvalue = "x" or // dummy value
+ e.getType() instanceof EnumType and fmtvalue = "x" or // dummy value
+ formatStringValue(e.(ParExpr).getExpr(), fmtvalue) or
+ exists(Variable v |
+ e = v.getAnAccess() and
+ v.isFinal() and
+ v.getType() instanceof TypeString and
+ formatStringValue(v.getInitializer(), fmtvalue)
+ ) or
+ exists(LocalVariableDecl v |
+ e = v.getAnAccess() and
+ not exists(AssignAddExpr aa | aa.getDest() = v.getAnAccess()) and
+ 1 = count(v.getAnAssignedValue()) and
+ v.getType() instanceof TypeString and
+ formatStringValue(v.getAnAssignedValue(), fmtvalue)
+ ) or
+ exists(AddExpr add, string left, string right |
+ add = e and
+ add.getType() instanceof TypeString and
+ formatStringValue(add.getLeftOperand(), left) and
+ formatStringValue(add.getRightOperand(), right) and
+ fmtvalue = left + right
+ ) or
+ formatStringValue(e.(ConditionalExpr).getTrueExpr(), fmtvalue) or
+ formatStringValue(e.(ConditionalExpr).getFalseExpr(), fmtvalue) or
+ exists(Method getprop, MethodAccess ma, string prop |
+ e = ma and
+ ma.getMethod() = getprop and
+ getprop.hasName("getProperty") and
+ getprop.getDeclaringType().hasQualifiedName("java.lang", "System") and
+ getprop.getNumberOfParameters() = 1 and
+ ma.getAnArgument().(StringLiteral).getRepresentedString() = prop and
+ (prop = "line.separator" or prop = "file.separator" or prop = "path.separator") and
+ fmtvalue = "x" // dummy value
+ ) or
+ exists(Field f |
+ e = f.getAnAccess() and
+ f.getDeclaringType().hasQualifiedName("java.io", "File") and
+ fmtvalue = "x" // dummy value
+ |
+ f.hasName("pathSeparator") or
+ f.hasName("pathSeparatorChar") or
+ f.hasName("separator") or
+ f.hasName("separatorChar")
+ )
+ )
+}
+
+/**
+ * A string that is used as the format string in a `FormattingCall`.
+ */
+class FormatString extends string {
+ FormatString() {
+ formatStringValue(_, this)
+ }
+
+ /** Gets a `FormattingCall` that uses this as its format string. */
+ FormattingCall getAFormattingUse() {
+ exists(Expr fmt | formatStringValue(fmt, this) |
+ result.getFormatArgument() = fmt or
+ exists(VariableAssign va |
+ defUsePair(va, result.getFormatArgument()) and va.getSource() = fmt
+ ) or
+ result.getFormatArgument().(FieldAccess).getField().getAnAssignedValue() = fmt
+ )
+ }
+
+ /**
+ * Gets the largest argument index (1-indexed) that is referred by a format
+ * specifier. Gets the value 0 if there are no format specifiers.
+ */
+ /*abstract*/ int getMaxFmtSpecIndex() { none() }
+
+ /**
+ * Gets an argument index (1-indexed) less than `getMaxFmtSpecIndex()` that
+ * is not referred by any format specifier.
+ */
+ /*abstract*/ int getASkippedFmtSpecIndex() { none() }
+}
+
+private class PrintfFormatString extends FormatString {
+ PrintfFormatString() {
+ this.getAFormattingUse().getSyntax() = TFmtPrintf()
+ }
+
+ /**
+ * Gets a boolean value that indicates whether the `%` character at index `i`
+ * is an escaped percentage sign or a format specifier.
+ */
+ private boolean isEscapedPct(int i) {
+ this.charAt(i) = "%" and
+ if this.charAt(i-1) = "%" then
+ result = this.isEscapedPct(i-1).booleanNot()
+ else
+ result = false
+ }
+
+ /** Holds if the format specifier at index `i` is a reference to an argument. */
+ private predicate fmtSpecIsRef(int i) {
+ false = this.isEscapedPct(i) and
+ this.charAt(i) = "%" and
+ exists(string c |
+ c = this.charAt(i+1) and
+ c != "%" and
+ c != "n"
+ )
+ }
+
+ /**
+ * Holds if the format specifier at index `i` refers to the same argument as
+ * the preceding format specifier.
+ */
+ private predicate fmtSpecRefersToPrevious(int i) {
+ this.fmtSpecIsRef(i) and
+ "<" = this.charAt(i+1)
+ }
+
+ /**
+ * Gets the index of the specific argument (1-indexed) that the format
+ * specifier at index `i` refers to, if any.
+ */
+ private int fmtSpecRefersToSpecificIndex(int i) {
+ this.fmtSpecIsRef(i) and
+ exists(string num |
+ result = num.toInt()
+ |
+ num = this.charAt(i+1) and "$" = this.charAt(i+2) or
+ num = this.charAt(i+1) + this.charAt(i+2) and "$" = this.charAt(i+3)
+ )
+ }
+
+ /**
+ * Holds if the format specifier at index `i` refers to the next argument in
+ * sequential order.
+ */
+ private predicate fmtSpecRefersToSequentialIndex(int i) {
+ this.fmtSpecIsRef(i) and
+ not exists(this.fmtSpecRefersToSpecificIndex(i)) and
+ not this.fmtSpecRefersToPrevious(i)
+ }
+
+ override int getMaxFmtSpecIndex() {
+ result = max(int ix |
+ ix = fmtSpecRefersToSpecificIndex(_) or
+ ix = count(int i | fmtSpecRefersToSequentialIndex(i))
+ )
+ }
+
+ override int getASkippedFmtSpecIndex() {
+ result in [1..getMaxFmtSpecIndex()] and
+ result > count(int i | fmtSpecRefersToSequentialIndex(i)) and
+ not result = fmtSpecRefersToSpecificIndex(_)
+ }
+}
+
+private class LoggerFormatString extends FormatString {
+ LoggerFormatString() {
+ this.getAFormattingUse().getSyntax() = TFmtLogger()
+ }
+
+ /**
+ * Gets a boolean value that indicates whether the `\` character at index `i`
+ * is an unescaped backslash.
+ */
+ private boolean isUnescapedBackslash(int i) {
+ this.charAt(i) = "\\" and
+ if this.charAt(i-1) = "\\" then
+ result = this.isUnescapedBackslash(i-1).booleanNot()
+ else
+ result = true
+ }
+
+ /** Holds if an unescaped placeholder `{}` occurs at index `i`. */
+ private predicate fmtPlaceholder(int i) {
+ this.charAt(i) = "{" and
+ this.charAt(i+1) = "}" and
+ not true = isUnescapedBackslash(i-1)
+ }
+
+ override int getMaxFmtSpecIndex() {
+ result = count(int i | fmtPlaceholder(i))
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Type.qll b/java/ql/src/semmle/code/java/Type.qll
new file mode 100755
index 00000000000..e8eccbb68a3
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Type.qll
@@ -0,0 +1,1045 @@
+/**
+ * Provides classes and predicates for working with Java types.
+ *
+ * Types can be primitive types (`PrimitiveType`), array types (`Array`), or reference
+ * types (`RefType`), where the latter are either classes (`Class`) or interfaces
+ * (`Interface`).
+ *
+ * Reference types can be at the top level (`TopLevelType`) or nested (`NestedType`).
+ * Classes can also be local (`LocalClass`) or anonymous (`AnonymousClass`).
+ * Enumerated types (`EnumType`) are a special kind of class.
+ */
+
+import Member
+import Modifier
+import JDK
+
+/**
+ * Holds if reference type `t` is an immediate super-type of `sub`.
+ */
+cached
+predicate hasSubtype(RefType t, Type sub) {
+ // Direct subtype.
+ (extendsReftype(sub, t) and t != sub) or
+ implInterface(sub, t) or
+ // A parameterized type `T` is a subtype of the corresponding raw type `T<>`.
+ (parSubtypeRaw(t, sub) and t != sub) or
+ // Array subtyping is covariant.
+ (arraySubtype(t, sub) and t != sub) or
+ // Type parameter containment for parameterized types.
+ (parContainmentSubtype(t, sub) and t != sub) or
+ // Type variables are subtypes of their upper bounds.
+ (typeVarSubtypeBound(t, sub) and t != sub)
+}
+
+private
+predicate typeVarSubtypeBound(RefType t, TypeVariable tv) {
+ if tv.hasTypeBound() then
+ t = tv.getATypeBound().getType()
+ else
+ t instanceof TypeObject
+}
+
+private
+predicate parSubtypeRaw(RefType t, ParameterizedType sub) {
+ t = sub.getErasure().(GenericType).getRawType()
+}
+
+private
+predicate arraySubtype(Array sup, Array sub) {
+ hasSubtype(sup.getComponentType(), sub.getComponentType())
+}
+
+/*
+ * `parContainmentSubtype(pt, psub)` is equivalent to:
+ * ```
+ * pt != psub and
+ * pt.getGenericType() = psub.getGenericType() and
+ * forex(int i | i in [0..pt.getNumberOfTypeArguments()-1] |
+ * typeArgumentContains(_, pt.getTypeArgument(i), psub.getTypeArgument(i), _)
+ * )
+ * ```
+ * For performance several transformations are made. First, the `forex` is
+ * written as a loop where `typeArgumentsContain(_, pt, psub, n)` encode that
+ * the `forex` holds for `i in [0..n]`. Second, the relation is split into two
+ * cases depending on whether `pt.getNumberOfTypeArguments()` is 1 or 2+, as
+ * this allows us to unroll the loop and collapse the first two iterations. The
+ * base case for `typeArgumentsContain` is therefore `n=1` and this allows an
+ * improved join order implemented by `contains01`.
+ */
+private
+predicate parContainmentSubtype(ParameterizedType pt, ParameterizedType psub) {
+ pt != psub and
+ typeArgumentsContain(_, pt, psub, pt.getNumberOfTypeArguments()-1)
+ or
+ typeArgumentsContain0(_, pt, psub)
+}
+
+/**
+ * Gets the `index`-th type parameter of `t`, which is a parameterization of `g`.
+ */
+private
+RefType parameterisationTypeArgument(GenericType g, ParameterizedType t, int index) {
+ g = t.getGenericType() and
+ result = t.getTypeArgument(index)
+}
+
+private predicate varianceCandidate(ParameterizedType pt) {
+ pt.getATypeArgument() instanceof Wildcard
+}
+
+pragma[noinline]
+private RefType parameterisationTypeArgumentVarianceCand(GenericType g, ParameterizedType t, int index) {
+ result = parameterisationTypeArgument(g, t, index) and
+ varianceCandidate(t)
+}
+
+/**
+ * Holds if every type argument of `s` (up to `n` with `n >= 1`) contains the
+ * corresponding type argument of `t`. Both `s` and `t` are constrained to
+ * being parameterizations of `g`.
+ */
+pragma[nomagic]
+private
+predicate typeArgumentsContain(GenericType g, ParameterizedType s, ParameterizedType t, int n) {
+ contains01(g, s, t) and n = 1
+ or
+ contains(g, s, t, n) and
+ typeArgumentsContain(g, s, t, n-1)
+}
+
+private predicate typeArgumentsContain0(GenericType g, ParameterizedType sParm, ParameterizedType tParm) {
+ exists(RefType s, RefType t |
+ containsAux0(g, tParm, s, t) and
+ s = parameterisationTypeArgument(g, sParm, 0) and
+ s != t
+ )
+}
+
+/**
+ * Holds if the `n`-th type argument of `sParm` contain the `n`-th type
+ * argument of `tParm` for both `n = 0` and `n = 1`, where both `sParm` and
+ * `tParm` are parameterizations of the same generic type `g`.
+ *
+ * This is equivalent to
+ * ```
+ * contains(g, sParm, tParm, 0) and
+ * contains(g, sParm, tParm, 1)
+ * ```
+ * except `contains` is restricted to only include `n >= 2`.
+ */
+private predicate contains01(GenericType g, ParameterizedType sParm, ParameterizedType tParm) {
+ exists(RefType s0, RefType t0, RefType s1, RefType t1 |
+ contains01Aux0(g, tParm, s0, t0, t1) and
+ contains01Aux1(g, sParm, s0, s1, t1)
+ )
+}
+
+pragma[nomagic]
+private predicate contains01Aux0(GenericType g, ParameterizedType tParm, RefType s0, RefType t0, RefType t1) {
+ typeArgumentContains(g, s0, t0, 0) and
+ t0 = parameterisationTypeArgument(g, tParm, 0) and
+ t1 = parameterisationTypeArgument(g, tParm, 1)
+}
+
+pragma[nomagic]
+private predicate contains01Aux1(GenericType g, ParameterizedType sParm, RefType s0, RefType s1, RefType t1) {
+ typeArgumentContains(g, s1, t1, 1) and
+ s0 = parameterisationTypeArgumentVarianceCand(g, sParm, 0) and
+ s1 = parameterisationTypeArgumentVarianceCand(g, sParm, 1)
+}
+
+pragma[nomagic]
+private predicate containsAux0(GenericType g, ParameterizedType tParm, RefType s, RefType t) {
+ typeArgumentContains(g, s, t, 0) and
+ t = parameterisationTypeArgument(g, tParm, 0) and
+ g.getNumberOfTypeParameters() = 1
+}
+
+/**
+ * Holds if the `n`-th type argument of `sParm` contain the `n`-th type
+ * argument of `tParm`, where both `sParm` and `tParm` are parameterizations of
+ * the same generic type `g`. The index `n` is restricted to `n >= 2`, the
+ * cases `n < 2` are handled by `contains01`.
+ *
+ * See JLS 4.5.1, Type Arguments of Parameterized Types.
+ */
+private
+predicate contains(GenericType g, ParameterizedType sParm, ParameterizedType tParm, int n) {
+ exists(RefType s, RefType t |
+ containsAux(g, tParm, n, s, t) and
+ s = parameterisationTypeArgumentVarianceCand(g, sParm, n)
+ )
+}
+
+pragma[nomagic]
+private predicate containsAux(GenericType g, ParameterizedType tParm, int n, RefType s, RefType t) {
+ typeArgumentContains(g, s, t, n) and
+ t = parameterisationTypeArgument(g, tParm, n) and
+ n >= 2
+}
+
+/**
+ * Holds if the type argument `s` contains the type argument `t`, where both
+ * type arguments occur as index `n` in an instantiation of `g`.
+ */
+pragma[noinline]
+private predicate typeArgumentContains(GenericType g, RefType s, RefType t, int n) {
+ typeArgumentContainsAux2(g, s, t, n) and
+ s = parameterisationTypeArgumentVarianceCand(g, _, n)
+}
+
+pragma[nomagic]
+private predicate typeArgumentContainsAux2(GenericType g, RefType s, RefType t, int n) {
+ typeArgumentContainsAux1(s, t, n) and
+ t = parameterisationTypeArgument(g, _, n)
+}
+
+/**
+ * Holds if the type argument `s` contains the type argument `t`, where both
+ * type arguments occur as index `n` in some parameterized types.
+ *
+ * See JLS 4.5.1, Type Arguments of Parameterized Types.
+ */
+private
+predicate typeArgumentContainsAux1(RefType s, RefType t, int n) {
+ exists(int i |
+ s = parameterisationTypeArgumentVarianceCand(_, _, i) and
+ t = parameterisationTypeArgument(_, _, n) and
+ i <= n and n <= i
+ |
+ exists(RefType tUpperBound | tUpperBound = t.(Wildcard).getUpperBound().getType() |
+ // ? extends T <= ? extends S if T <: S
+ hasSubtypeStar0(s.(Wildcard).getUpperBound().getType(), tUpperBound) or
+ // ? extends T <= ?
+ s.(Wildcard).isUnconstrained()
+ ) or
+ exists(RefType tLowerBound | tLowerBound = t.(Wildcard).getLowerBound().getType() |
+ // ? super T <= ? super S if s <: T
+ hasSubtypeStar0(tLowerBound, s.(Wildcard).getLowerBound().getType()) or
+ // ? super T <= ?
+ s.(Wildcard).isUnconstrained() or
+ // ? super T <= ? extends Object
+ wildcardExtendsObject(s)
+ ) or
+ // T <= T
+ s = t or
+ // T <= ? extends T
+ hasSubtypeStar0(s.(Wildcard).getUpperBound().getType(), t) or
+ // T <= ? super T
+ hasSubtypeStar0(t, s.(Wildcard).getLowerBound().getType())
+ )
+}
+
+pragma[noinline]
+private predicate wildcardExtendsObject(Wildcard wc) {
+ wc.getUpperBound().getType() instanceof TypeObject
+}
+
+/**
+ * DEPRECATED: Use `hasSubtype*` instead.
+ */
+deprecated predicate hasSubtypeStar(RefType t, RefType sub) {
+ hasSubtype*(t, sub)
+}
+
+private predicate hasSubtypeStar0(RefType t, RefType sub) {
+ sub = t
+ or
+ hasSubtype(t, sub)
+ or
+ exists(RefType mid | hasSubtypeStar0(t, mid) and hasSubtype(mid, sub))
+}
+
+/** Holds if type `t` declares member `m`. */
+predicate declaresMember(Type t, @member m) {
+ methods(m,_,_,_,t,_)
+ or
+ constrs(m,_,_,_,t,_)
+ or
+ fields(m,_,_,t,_)
+ or
+ enclInReftype(m,t) and
+ // Since the type `@member` in the dbscheme includes all `@reftype`s,
+ // anonymous and local classes need to be excluded here.
+ not m instanceof AnonymousClass and
+ not m instanceof LocalClass
+}
+
+/**
+ * A common abstraction for all Java types, including
+ * primitive, class, interface and array types.
+ */
+class Type extends Element, @type {
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ */
+ string getTypeDescriptor() { none() }
+
+ /** Gets the erasure of this type. */
+ Type getErasure() { result = erase(this) }
+}
+
+/**
+ * An array type.
+ *
+ * Array types are implicitly declared when used; there is
+ * an array declaration for each array type used in the system.
+ */
+class Array extends RefType, @array {
+ /**
+ * Gets the type of the components of this array type.
+ *
+ * For example, the component type of `Object[][]` is `Object[]`.
+ */
+ Type getComponentType() { arrays(this, _, _, _, result) }
+
+ /**
+ * Gets the type of the elements used to construct this array type.
+ *
+ * For example, the element type of `Object[][]` is `Object`.
+ */
+ Type getElementType() { arrays(this, _, result, _, _) }
+
+ /**
+ * Gets the arity of this array type.
+ *
+ * For example, the dimension of `Object[][]` is 2.
+ */
+ int getDimension() { arrays(this, _, _, result, _) }
+
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ */
+ override string getTypeDescriptor() {
+ result = "[" + this.getComponentType().getTypeDescriptor()
+ }
+}
+
+/**
+ * A common super-class for various kinds of reference types,
+ * including classes, interfaces, type parameters and arrays.
+ */
+class RefType extends Type, Annotatable, Modifiable, @reftype {
+ /** Gets the package in which this type is declared. */
+ Package getPackage() {
+ classes(this,_,result,_) or
+ interfaces(this,_,result,_)
+ }
+
+ /** Gets the type in which this reference type is enclosed, if any. */
+ RefType getEnclosingType() {
+ enclInReftype(this, result)
+ }
+
+ /** Gets the compilation unit in which this type is declared. */
+ override CompilationUnit getCompilationUnit() { result = this.getFile() }
+
+ /** Holds if `t` is an immediate supertype of this type. */
+ predicate hasSupertype(RefType t) { hasSubtype(t,this) }
+
+ /** Holds if `t` is an immediate subtype of this type. */
+ predicate hasSubtype(RefType t) { hasSubtype(this,t) }
+
+ /** Gets a direct subtype of this type. */
+ RefType getASubtype() { hasSubtype(this,result) }
+
+ /** Gets a direct supertype of this type. */
+ RefType getASupertype() { hasSubtype(result,this) }
+
+ /** Gets a direct or indirect supertype of this type, including itself. */
+ RefType getAnAncestor() { hasSubtype*(result, this) }
+
+ /**
+ * Gets the source declaration of a direct supertype of this type, excluding itself.
+ *
+ * Note, that a generic type is the source declaration of a direct supertype
+ * of itself, namely the corresponding raw type, and this case is thus
+ * explicitly excluded. See also `getSourceDeclaration()`.
+ */
+ pragma[noinline]
+ RefType getASourceSupertype() {
+ result = this.getASupertype().getSourceDeclaration() and
+ result != this
+ }
+
+ /**
+ * Holds if `t` is an immediate super-type of this type using only the immediate
+ * `extends` or `implements` relationships. In particular, this excludes
+ * parameter containment sub-typing for parameterized types.
+ */
+ predicate extendsOrImplements(RefType t) {
+ extendsReftype(this, t) or
+ implInterface(this, t) or
+ typeVarSubtypeBound(t, this)
+ }
+
+ /** Holds if this type declares any members. */
+ predicate hasMember() { exists(getAMember()) }
+
+ /** Gets a member declared in this type. */
+ Member getAMember() { this = result.getDeclaringType() }
+
+ /** Gets a method declared in this type. */
+ Method getAMethod() { this = result.getDeclaringType() }
+
+ /** Gets a constructor declared in this type. */
+ Constructor getAConstructor() { this = result.getDeclaringType() }
+
+ /** Gets a method or constructor declared in this type. */
+ Callable getACallable() { this = result.getDeclaringType() }
+
+ /** Gets a field declared in this type. */
+ Field getAField() { this = result.getDeclaringType() }
+
+ /** Holds if this type declares a method with the specified name. */
+ predicate declaresMethod(string name) { this.getAMethod().getName() = name }
+
+ /** Holds if this type declares a method with the specified name and number of parameters. */
+ predicate declaresMethod(string name, int n) {
+ exists(Method m | m = this.getAMethod() |
+ m.getName() = name and
+ m.getNumberOfParameters() = n
+ )
+ }
+
+ /** Holds if this type declares a field with the specified name. */
+ predicate declaresField(string name) { this.getAField().getName() = name }
+
+ /** Gets the number of methods declared in this type. */
+ int getNumberOfMethods() { result = count(Method m | m.getDeclaringType() = this) }
+
+ /**
+ * Holds if this type declares or inherits method `m`, which is declared
+ * in `declaringType`.
+ */
+ predicate hasMethod(Method m, RefType declaringType) {
+ hasMethod(m, declaringType, false)
+ }
+
+ /**
+ * Holds if this type declares or inherits method `m`, which is declared
+ * in `declaringType`. Methods that would be inherited if they were public,
+ * but are not inherited due to being package protected, are also included
+ * and indicated by `hidden` being true.
+ */
+ cached
+ predicate hasMethod(Method m, RefType declaringType, boolean hidden) {
+ hasNonInterfaceMethod(m, declaringType, hidden) or
+ hasInterfaceMethod(m, declaringType) and hidden = false
+ }
+
+ private predicate noMethodExtraction() {
+ not methods(_,_,_,_,this,_) and
+ exists(Method m | methods(m,_,_,_,getSourceDeclaration(),_) and m.isInheritable())
+ }
+
+ private predicate canInheritFromSupertype(RefType sup) {
+ sup = getASupertype() and
+ (noMethodExtraction() implies supertypeSrcDecl(sup, getSourceDeclaration()))
+ }
+
+ pragma[nomagic]
+ private predicate supertypeSrcDecl(RefType sup, RefType srcDecl) {
+ sup = getASupertype() and
+ srcDecl = sup.getSourceDeclaration()
+ }
+
+ private predicate hasNonInterfaceMethod(Method m, RefType declaringType, boolean hidden) {
+ m = getAMethod() and this = declaringType and not declaringType instanceof Interface and hidden = false or
+ exists(RefType sup, boolean h1, boolean h2 |
+ (if m.isPackageProtected() and sup.getPackage() != this.getPackage() then h1 = true else h1 = false) and
+ (not sup instanceof Interface or this instanceof Interface) and
+ canInheritFromSupertype(sup) and
+ sup.hasNonInterfaceMethod(m, declaringType, h2) and
+ hidden = h1.booleanOr(h2) and
+ exists(string signature | methods(m,_,signature,_,_,_) and not methods(_,_,signature,_,this,_)) and
+ m.isInheritable()
+ )
+ }
+
+ private predicate cannotInheritInterfaceMethod(string signature) {
+ methods(_,_,signature,_,this,_) or
+ exists(Method m | hasNonInterfaceMethod(m, _, false) and methods(m,_,signature,_,_,_))
+ }
+
+ private predicate interfaceMethodCandidateWithSignature(Method m, string signature, RefType declaringType) {
+ m = getAMethod() and this = declaringType and declaringType instanceof Interface and methods(m,_,signature,_,_,_) or
+ exists(RefType sup |
+ sup.interfaceMethodCandidateWithSignature(m, signature, declaringType) and
+ not cannotInheritInterfaceMethod(signature) and
+ canInheritFromSupertype(sup) and
+ m.isInheritable()
+ )
+ }
+
+ pragma[nomagic]
+ private predicate overrideEquivalentInterfaceMethodCandidates(Method m1, Method m2) {
+ exists(string signature |
+ interfaceMethodCandidateWithSignature(m1, signature, _) and
+ interfaceMethodCandidateWithSignature(m2, signature, _) and
+ m1 != m2 and
+ m2.overrides(_) and
+ any(Method m).overrides(m1)
+ )
+ }
+
+ pragma[noinline]
+ private predicate overriddenInterfaceMethodCandidate(Method m) {
+ exists(Method m2 |
+ overrideEquivalentInterfaceMethodCandidates(m, m2) and
+ m2.overrides(m)
+ )
+ }
+
+ private predicate hasInterfaceMethod(Method m, RefType declaringType) {
+ interfaceMethodCandidateWithSignature(m, _, declaringType) and
+ not overriddenInterfaceMethodCandidate(m)
+ }
+
+ /** Holds if this type declares or inherits the specified member. */
+ predicate inherits(Member m) {
+ exists(Field f | f = m |
+ f = getAField() or
+ not f.isPrivate() and not declaresField(f.getName()) and getASupertype().inherits(f) or
+ getSourceDeclaration().inherits(f)
+ )
+ or
+ hasMethod((Method)m, _)
+ }
+
+ /** 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. */
+ predicate hasQualifiedName(string package, string type) {
+ this.getPackage().hasName(package) and type = this.nestedName()
+ }
+
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ */
+ override string getTypeDescriptor() {
+ result = "L" + this.getPackage().getName().replaceAll(".", "/") + "/" +
+ this.getSourceDeclaration().nestedName() + ";"
+ }
+
+ /**
+ * Gets the qualified name of this type.
+ */
+ string getQualifiedName() {
+ exists(string pkgName | pkgName = getPackage().getName() |
+ if pkgName = "" then
+ result = nestedName()
+ else
+ result = pkgName + "." + nestedName()
+ )
+ }
+
+ /** Gets the nested name of this type. */
+ string nestedName() {
+ not this instanceof NestedType and result = this.getName()
+ or
+ this.(NestedType).getEnclosingType().nestedName() + "$" + this.getName() = result
+ }
+
+ /**
+ * Gets the source declaration of this type.
+ *
+ * For parameterized instances of generic types and raw types, the
+ * source declaration is the corresponding generic type.
+ *
+ * For non-parameterized types declared inside a parameterized
+ * instance of a generic type, the source declaration is the
+ * corresponding type in the generic type.
+ *
+ * For all other types, the source declaration is the type itself.
+ */
+ RefType getSourceDeclaration() { result = this }
+
+ /** Holds if this type is the same as its source declaration. */
+ predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
+
+ /** Cast this reference type to a class that provides access to metrics information. */
+ MetricRefType getMetrics() { result = this }
+
+ /**
+ * A common (reflexive, transitive) subtype of the erasures of
+ * types `t1` and `t2`, if it exists.
+ *
+ * If there is no such common subtype, then the two types are disjoint.
+ * However, the converse is not true; for example, the parameterized types
+ * `List` and `Collection` are disjoint,
+ * but their erasures (`List` and `Collection`, respectively)
+ * do have common subtypes (such as `List` itself).
+ *
+ * For the definition of the notion of *erasure* see JLS v8, section 4.6 (Type Erasure).
+ */
+ pragma[inline]
+ RefType commonSubtype(RefType other) {
+ result.getASourceSupertype*() = erase(this) and
+ result.getASourceSupertype*() = erase(other)
+ }
+}
+
+/** A type that is the same as its source declaration. */
+class SrcRefType extends RefType {
+ SrcRefType() { this.isSourceDeclaration() }
+}
+
+/** A class declaration. */
+class Class extends RefType, @class {
+ /** Holds if this class is an anonymous class. */
+ predicate isAnonymous() { isAnonymClass(this,_) }
+
+ /** Holds if this class is a local class. */
+ predicate isLocal() { isLocalClass(this,_) }
+
+ override RefType getSourceDeclaration() { classes(this,_,_,result) }
+
+ /**
+ * Gets an annotation that applies to this class.
+ *
+ * Note that a class may inherit annotations from super-classes.
+ */
+ override Annotation getAnAnnotation() {
+ result = RefType.super.getAnAnnotation() or
+ exists(AnnotationType tp | tp = result.getType() |
+ tp.isInherited() and
+ not exists(Annotation ann | ann = RefType.super.getAnAnnotation() | ann.getType() = tp) and
+ result = this.getASupertype().(Class).getAnAnnotation()
+ )
+ }
+}
+
+/** An intersection type. */
+class IntersectionType extends RefType, @class {
+ IntersectionType() {
+ exists(string shortname |
+ classes(this, shortname, _, _) and
+ shortname.matches("% & ...")
+ )
+ }
+ private RefType superType() { extendsReftype(this, result) }
+ private RefType superInterface() { implInterface(this, result) }
+ string getLongName() {
+ result = superType().toString() + concat(" & " + superInterface().toString())
+ }
+ RefType getFirstBound() {
+ extendsReftype(this, result)
+ }
+}
+
+/** An anonymous class. */
+class AnonymousClass extends NestedClass {
+ AnonymousClass() { this.isAnonymous() }
+
+ /**
+ * Utility method: an integer that is larger for classes that
+ * are defined textually later.
+ */
+ private int rankInParent(RefType parent) {
+ this.getEnclosingType() = parent and
+ exists(Location myLocation, File f, int maxCol | myLocation = this.getLocation() |
+ f = myLocation.getFile() and
+ maxCol = max(Location loc | loc.getFile() = f | loc.getStartColumn()) and
+ result = myLocation.getStartLine() * maxCol + myLocation.getStartColumn()
+ )
+ }
+
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ *
+ * For an anonymous class, the type descriptor is the descriptor of the
+ * enclosing type followed by a (1-based) counter of anonymous classes
+ * declared within that type.
+ */
+ override string getTypeDescriptor() {
+ exists(RefType parent | parent = this.getEnclosingType() |
+ exists(int num | num = 1 + count(AnonymousClass other | other.rankInParent(parent) < rankInParent(parent)) |
+ exists(string parentWithSemi | parentWithSemi = parent.getTypeDescriptor() |
+ result = parentWithSemi.prefix(parentWithSemi.length() - 1) + "$" + num + ";"
+ )
+ )
+ )
+ }
+
+ /** Gets the class instance expression where this anonymous class occurs. */
+ ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this, result) }
+
+ override string toString() { result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }" }
+
+ /**
+ * Gets the qualified name of this type.
+ *
+ * Anonymous classes do not have qualified names, so we use
+ * the string `""` as a placeholder.
+ */
+ override string getQualifiedName() { result = "" }
+}
+
+/** A local class. */
+class LocalClass extends NestedClass {
+ LocalClass() { this.isLocal() }
+
+ /** Gets the statement that declares this local class. */
+ LocalClassDeclStmt getLocalClassDeclStmt() { isLocalClass(this, result) }
+}
+
+/** A top-level type. */
+class TopLevelType extends RefType {
+ TopLevelType() {
+ not enclInReftype(this,_) and
+ (this instanceof Class or this instanceof Interface)
+ }
+}
+
+/** A top-level class. */
+class TopLevelClass extends TopLevelType, Class {
+}
+
+/** A nested type is a type declared within another type. */
+class NestedType extends RefType {
+ NestedType() {
+ enclInReftype(this,_)
+ }
+
+ /** Gets the type enclosing this nested type. */
+ override RefType getEnclosingType() {
+ enclInReftype(this,result)
+ }
+
+ /** Gets the nesting depth of this nested type. Top-level types have nesting depth 0. */
+ int getNestingDepth() {
+ if getEnclosingType() instanceof NestedType then
+ result = getEnclosingType().(NestedType).getNestingDepth() + 1
+ else
+ result = 1
+ }
+
+ override predicate isPublic() {
+ super.isPublic() or
+ // JLS 9.5: A member type declaration in an interface is implicitly public and static
+ exists(Interface i | this = i.getAMember())
+ }
+
+ override predicate isStrictfp() {
+ super.isStrictfp() or
+ // JLS 8.1.1.3, JLS 9.1.1.2
+ getEnclosingType().isStrictfp()
+ }
+
+ /**
+ * Holds if this nested type is static.
+ *
+ * A nested type is static either if it is explicitly declared as such
+ * using the modifier `static`, or if it is implicitly static
+ * because one of the following holds:
+ *
+ * - it is a member type of an interface,
+ * - it is a member interface, or
+ * - it is a nested enum type.
+ *
+ * See JLS v8, section 8.5.1 (Static Member Type Declarations),
+ * section 8.9 (Enums) and section 9.5 (Member Type Declarations).
+ */
+ override predicate isStatic() {
+ super.isStatic() or
+ // JLS 8.5.1: A member interface is implicitly static.
+ this instanceof Interface or
+ // JLS 8.9: A nested enum type is implicitly static.
+ this instanceof EnumType or
+ // JLS 9.5: A member type declaration in an interface is implicitly public and static
+ exists(Interface i | this = i.getAMember())
+ }
+}
+
+/**
+ * A class declared within another type.
+ *
+ * This includes (static and non-static) member classes,
+ * local classes and anonymous classes.
+ */
+class NestedClass extends NestedType, Class {
+}
+
+/**
+ * An inner class is a nested class that is neither
+ * explicitly nor implicitly declared static.
+ */
+class InnerClass extends NestedClass {
+ InnerClass() {
+ not this.isStatic()
+ }
+
+ /**
+ * Holds if an instance of this inner class holds a reference to its
+ * enclosing class.
+ */
+ predicate hasEnclosingInstance() {
+ // JLS 15.9.2. Determining Enclosing Instances
+ not this.(AnonymousClass).getClassInstanceExpr().isInStaticContext() and
+ not this.(LocalClass).getLocalClassDeclStmt().getEnclosingCallable().isStatic()
+ }
+}
+
+/** An interface. */
+class Interface extends RefType, @interface {
+ override RefType getSourceDeclaration() { interfaces(this,_,_,result) }
+
+ override predicate isAbstract() {
+ // JLS 9.1.1.1: "Every interface is implicitly abstract"
+ any()
+ }
+}
+
+/** A class or interface. */
+class ClassOrInterface extends RefType {
+ ClassOrInterface() {
+ this instanceof Class or
+ this instanceof Interface
+ }
+}
+
+/**
+ * A primitive type.
+ *
+ * This includes `boolean`, `byte`, `short`,
+ * `char`, `int`, `long`, `float`,
+ * and `double`.
+ */
+class PrimitiveType extends Type, @primitive {
+ PrimitiveType() {
+ this.getName().regexpMatch("float|double|int|boolean|short|byte|char|long")
+ }
+
+ /** Gets the boxed type corresponding to this primitive type. */
+ BoxedType getBoxedType() {
+ result.getPrimitiveType() = this
+ }
+
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ */
+ override string getTypeDescriptor() {
+ (this.hasName("float") and result = "F") or
+ (this.hasName("double") and result = "D") or
+ (this.hasName("int") and result = "I") or
+ (this.hasName("boolean") and result = "Z") or
+ (this.hasName("short") and result = "S") or
+ (this.hasName("byte") and result = "B") or
+ (this.hasName("char") and result = "C") or
+ (this.hasName("long") and result = "J")
+ }
+
+ /**
+ * Gets a default value for this primitive type, as assigned by the compiler
+ * for variables that are declared but not initialized explicitly.
+ * Typically zero for numeric and character types and `false` for `boolean`.
+ *
+ * For numeric primitive types, default literals of one numeric type are also
+ * considered to be default values of all other numeric types, even if they
+ * require an explicit cast.
+ */
+ Literal getADefaultValue() {
+ getName() = "boolean" and result.getLiteral() = "false" or
+ getName() = "char" and (result.getLiteral() = "'\\0'" or result.getLiteral() = "'\\u0000'") or
+ getName().regexpMatch("(float|double|int|short|byte|long)") and result.getLiteral().regexpMatch("0(\\.0)?+[lLfFdD]?+")
+ }
+}
+
+/** The type of the `null` literal. */
+class NullType extends Type, @primitive {
+ NullType() { this.hasName("") }
+}
+
+/** The `void` type. */
+class VoidType extends Type, @primitive {
+ VoidType() { this.hasName("void") }
+
+ /**
+ * Gets the JVM descriptor for this type, as used in bytecode.
+ */
+ override string getTypeDescriptor() {
+ result = "V"
+ }
+}
+
+/**
+ * A boxed type.
+ *
+ * This includes `Boolean`, `Byte`, `Short`,
+ * `Character`, `Integer`, `Long`, `Float`,
+ * and `Double`.
+ */
+class BoxedType extends RefType {
+ BoxedType() {
+ this.hasQualifiedName("java.lang", "Float") or
+ this.hasQualifiedName("java.lang", "Double") or
+ this.hasQualifiedName("java.lang", "Integer") or
+ this.hasQualifiedName("java.lang", "Boolean") or
+ this.hasQualifiedName("java.lang", "Short") or
+ this.hasQualifiedName("java.lang", "Byte") or
+ this.hasQualifiedName("java.lang", "Character") or
+ this.hasQualifiedName("java.lang", "Long")
+ }
+
+ /** Gets the primitive type corresponding to this boxed type. */
+ PrimitiveType getPrimitiveType() {
+ (this.hasName("Float") and result.hasName("float")) or
+ (this.hasName("Double") and result.hasName("double")) or
+ (this.hasName("Integer") and result.hasName("int")) or
+ (this.hasName("Boolean") and result.hasName("boolean")) or
+ (this.hasName("Short") and result.hasName("short")) or
+ (this.hasName("Byte") and result.hasName("byte")) or
+ (this.hasName("Character") and result.hasName("char")) or
+ (this.hasName("Long") and result.hasName("long"))
+ }
+}
+
+/**
+ * An enumerated type.
+ *
+ * Each enum type has zero or more enum constants which can
+ * be enumerated over.
+ * The type of an enum constant is the enum type itself.
+ *
+ * For example,
+ *
+ * ```
+ * enum X { A, B, C }
+ * ```
+ * is an enum type declaration, where the type of the enum
+ * constant `X.A` is `X`.
+ */
+class EnumType extends Class {
+ EnumType() { isEnumType(this) }
+
+ /** Gets the enum constant with the specified name. */
+ EnumConstant getEnumConstant(string name) {
+ fields(result,_,_,this,_) and result.hasName(name)
+ }
+
+ /** Gets an enum constant declared in this enum type. */
+ EnumConstant getAnEnumConstant() {
+ fields(result,_,_,this,_)
+ }
+
+ override predicate isFinal() {
+ // JLS 8.9: An enum declaration is implicitly `final` unless it contains
+ // at least one enum constant that has a class body.
+ not getAnEnumConstant().getAnAssignedValue().getType() instanceof AnonymousClass
+ }
+}
+
+/** An enum constant is a member of a enum type. */
+class EnumConstant extends Field {
+ EnumConstant() { isEnumConst(this) }
+
+ // JLS 8.9.3: For each enum constant `c` in the body of the declaration of
+ // [enum type] `E`, `E` has an implicitly declared `public static final`
+ // field of type `E` that has the same name as `c`.
+ override predicate isPublic() { any() }
+ override predicate isStatic() { any() }
+ override predicate isFinal() { any() }
+}
+
+/**
+ * Gets the erasure of a type.
+ *
+ * See JLS v8, section 4.6 (Type Erasure).
+ */
+private cached Type erase(Type t) {
+ result = t.(Class).getSourceDeclaration() and not t instanceof IntersectionType or
+ result = erase(t.(IntersectionType).getFirstBound()) or
+ result = t.(Interface).getSourceDeclaration() or
+ result.(Array).getComponentType() = erase(t.(Array).getComponentType()) or
+ result = erase(t.(BoundedType).getFirstUpperBoundType()) or
+ result = (NullType)t or
+ result = (VoidType)t or
+ result = (PrimitiveType)t
+}
+
+/**
+ * Is there a common (reflexive, transitive) subtype of the erasures of
+ * types `t1` and `t2`?
+ *
+ * If there is no such common subtype, then the two types are disjoint.
+ * However, the converse is not true; for example, the parameterized types
+ * `List` and `Collection` are disjoint,
+ * but their erasures (`List` and `Collection`, respectively)
+ * do have common subtypes (such as `List` itself).
+ *
+ * For the definition of the notion of *erasure* see JLS v8, section 4.6 (Type Erasure).
+ */
+pragma[inline]
+predicate haveIntersection(RefType t1, RefType t2) {
+ exists(RefType e1, RefType e2 | e1 = erase(t1) and e2 = erase(t2) |
+ erasedHaveIntersection(e1, e2)
+ )
+}
+
+/**
+ * Holds if there is a common (reflexive, transitive) subtype of the erased
+ * types `t1` and `t2`.
+ */
+predicate erasedHaveIntersection(RefType t1, RefType t2) {
+ exists(SrcRefType commonSub | commonSub.getASourceSupertype*() = t1 and commonSub.getASourceSupertype*() = t2) and
+ t1 = erase(_) and
+ t2 = erase(_)
+}
+
+/** An integral type, which may be either a primitive or a boxed type. */
+class IntegralType extends Type {
+ IntegralType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name.regexpMatch("byte|char|short|int|long")
+ )
+ }
+}
+
+/** A boolean type, which may be either a primitive or a boxed type. */
+class BooleanType extends Type {
+ BooleanType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name = "boolean"
+ )
+ }
+}
+
+/** A character type, which may be either a primitive or a boxed type. */
+class CharacterType extends Type {
+ CharacterType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name = "char"
+ )
+ }
+}
+
+/** A numeric or character type, which may be either a primitive or a boxed type. */
+class NumericOrCharType extends Type {
+ NumericOrCharType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name.regexpMatch("byte|char|short|int|long|double|float")
+ )
+ }
+}
+
+/** A floating point type, which may be either a primitive or a boxed type. */
+class FloatingPointType extends Type {
+ FloatingPointType() {
+ exists(string name |
+ name = this.(PrimitiveType).getName() or name = this.(BoxedType).getPrimitiveType().getName()
+ |
+ name.regexpMatch("float|double")
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/UnitTests.qll b/java/ql/src/semmle/code/java/UnitTests.qll
new file mode 100644
index 00000000000..b058c1f1789
--- /dev/null
+++ b/java/ql/src/semmle/code/java/UnitTests.qll
@@ -0,0 +1,325 @@
+/**
+ * Provides classes and predicates for working with test classes and methods.
+ */
+
+import Type
+import Member
+import semmle.code.java.frameworks.JUnitAnnotations
+
+/** The Java class `junit.framework.TestCase`. */
+class TypeJUnitTestCase extends RefType {
+ TypeJUnitTestCase() {
+ this.hasQualifiedName("junit.framework", "TestCase")
+ }
+}
+
+/** The Java interface `junit.framework.Test`. */
+class TypeJUnitTest extends RefType {
+ TypeJUnitTest() {
+ this.hasQualifiedName("junit.framework", "Test")
+ }
+}
+
+/** The Java class `junit.framework.TestSuite`. */
+class TypeJUnitTestSuite extends RefType {
+ TypeJUnitTestSuite() {
+ this.hasQualifiedName("junit.framework", "TestSuite")
+ }
+}
+
+/** A JUnit 3.8 test class. */
+class JUnit38TestClass extends Class {
+ JUnit38TestClass() {
+ exists(TypeJUnitTestCase tc | this.hasSupertype+(tc))
+ }
+}
+
+/** A JUnit 3.8 `tearDown` method. */
+class TearDownMethod extends Method {
+ TearDownMethod() {
+ this.hasName("tearDown") and
+ this.hasNoParameters() and
+ this.getReturnType().hasName("void") and
+ exists(Method m | m.getDeclaringType() instanceof TypeJUnitTestCase |
+ this.overrides*(m)
+ )
+ }
+}
+
+/**
+ * A class detected to be a test class, either because it is a JUnit test class
+ * or because its name or the name of one of its super-types contains the substring "Test".
+ */
+class TestClass extends Class {
+ TestClass() {
+ this instanceof JUnit38TestClass or
+ this.getASupertype*().getSourceDeclaration().getName().matches("%Test%")
+ }
+}
+
+/**
+ * A test method declared within a JUnit 3.8 test class.
+ */
+class JUnit3TestMethod extends Method {
+ JUnit3TestMethod() {
+ this.isPublic() and
+ this.getDeclaringType() instanceof JUnit38TestClass and
+ this.getName().matches("test%") and
+ this.getReturnType().hasName("void") and
+ this.hasNoParameters()
+ }
+}
+
+/**
+ * A JUnit 3.8 test suite method.
+ */
+class JUnit3TestSuite extends Method {
+ JUnit3TestSuite() {
+ this.isPublic() and
+ this.isStatic() and
+ (
+ this.getDeclaringType() instanceof JUnit38TestClass or
+ this.getDeclaringType().getAnAncestor() instanceof TypeJUnitTestSuite
+ ) and
+ this.hasName("suite") and
+ this.getReturnType() instanceof TypeJUnitTest and
+ this.hasNoParameters()
+ }
+}
+
+
+/**
+ * A JUnit test method that is annotated with the `org.junit.Test` annotation.
+ */
+class JUnit4TestMethod extends Method {
+ JUnit4TestMethod() {
+ this.getAnAnnotation().getType().hasQualifiedName("org.junit", "Test")
+ }
+}
+
+/**
+ * A JUnit test method that is annotated with the `org.junit.jupiter.api.Test` annotation.
+ */
+class JUnitJupiterTestMethod extends Method {
+ JUnitJupiterTestMethod() {
+ this.getAnAnnotation().getType().hasQualifiedName("org.junit.jupiter.api", "Test")
+ }
+}
+
+/**
+ * A JUnit `@Ignore` annotation.
+ */
+class JUnitIgnoreAnnotation extends Annotation {
+ JUnitIgnoreAnnotation() {
+ getType().hasQualifiedName("org.junit", "Ignore")
+ }
+}
+
+/**
+ * A method which, directly or indirectly, is treated as ignored by JUnit due to a `@Ignore`
+ * annotation.
+ */
+class JUnitIgnoredMethod extends Method {
+ JUnitIgnoredMethod() {
+ getAnAnnotation() instanceof JUnitIgnoreAnnotation or
+ exists(Class c |
+ c = this.getDeclaringType()
+ |
+ c.getAnAnnotation() instanceof JUnitIgnoreAnnotation
+ )
+ }
+}
+
+/**
+ * An annotation in TestNG.
+ */
+class TestNGAnnotation extends Annotation {
+ TestNGAnnotation() {
+ getType().getPackage().hasName("org.testng.annotations")
+ }
+}
+
+/**
+ * An annotation of type `org.test.ng.annotations.Test`.
+ */
+class TestNGTestAnnotation extends TestNGAnnotation {
+ TestNGTestAnnotation() {
+ getType().hasName("Test")
+ }
+}
+
+/**
+ * A TestNG test method, annotated with the `org.testng.annotations.Test` annotation.
+ */
+class TestNGTestMethod extends Method {
+ TestNGTestMethod() {
+ this.getAnAnnotation() instanceof TestNGTestAnnotation
+ }
+
+ /**
+ * Identify a possible `DataProvider` for this method, if the annotation includes a `dataProvider`
+ * value.
+ */
+ TestNGDataProviderMethod getADataProvider() {
+ exists(TestNGTestAnnotation testAnnotation |
+ testAnnotation = getAnAnnotation() and
+ // The data provider must have the same name as the referenced data provider
+ result.getDataProviderName() = testAnnotation.getValue("dataProvider").(StringLiteral).getRepresentedString()
+ |
+ // Either the data provider should be on the current class, or a supertype
+ getDeclaringType().getAnAncestor() = result.getDeclaringType() or
+ // Or the data provider class should be declared
+ result.getDeclaringType() = testAnnotation.getValue("dataProviderClass").(TypeLiteral).getTypeName().getType()
+ )
+ }
+}
+
+/**
+ * Any method detected to be a test method of a common testing framework,
+ * including JUnit and TestNG.
+ */
+class TestMethod extends Method {
+ TestMethod() {
+ this instanceof JUnit3TestMethod or
+ this instanceof JUnit4TestMethod or
+ this instanceof JUnitJupiterTestMethod or
+ this instanceof TestNGTestMethod
+ }
+}
+
+/**
+ * A TestNG annotation used to mark a method that runs "before".
+ */
+class TestNGBeforeAnnotation extends TestNGAnnotation {
+ TestNGBeforeAnnotation() {
+ getType().getName().matches("Before%")
+ }
+}
+
+/**
+ * A TestNG annotation used to mark a method that runs "after".
+ */
+class TestNGAfterAnnotation extends TestNGAnnotation {
+ TestNGAfterAnnotation() {
+ getType().getName().matches("After%")
+ }
+}
+
+/**
+ * An annotation of type `org.testng.annotations.DataProvider` which is applied to methods to mark
+ * them as data provider methods for TestNG.
+ */
+class TestNGDataProviderAnnotation extends TestNGAnnotation {
+ TestNGDataProviderAnnotation() {
+ getType().hasName("DataProvider")
+ }
+}
+
+/**
+ * An annotation of type `org.testng.annotations.Factory` which is applied to methods to mark
+ * them as factory methods for TestNG.
+ */
+class TestNGFactoryAnnotation extends TestNGAnnotation {
+ TestNGFactoryAnnotation() {
+ getType().hasName("Factory")
+ }
+}
+
+/**
+ * An annotation of type `org.testng.annotations.Listeners` which is applied to classes to define
+ * which listeners apply to them.
+ */
+class TestNGListenersAnnotation extends TestNGAnnotation {
+ TestNGListenersAnnotation() {
+ getType().hasName("Listeners")
+ }
+
+ /**
+ * Gets a listener defined in this annotation.
+ */
+ TestNGListenerImpl getAListener() {
+ result = getAValue("value").(TypeLiteral).getTypeName().getType()
+ }
+}
+
+/**
+ * A concrete implementation class of one or more of the TestNG listener interfaces.
+ */
+class TestNGListenerImpl extends Class {
+ TestNGListenerImpl() {
+ getAnAncestor().hasQualifiedName("org.testng", "ITestNGListener")
+ }
+}
+
+/**
+ * A method annotated with `org.testng.annotations.DataProvider` marking it as a data provider method
+ * for TestNG.
+ *
+ * This data provider method can be referenced by "name", and used by the test framework to provide
+ * an instance of a particular value when running a test method.
+ */
+class TestNGDataProviderMethod extends Method {
+ TestNGDataProviderMethod() {
+ getAnAnnotation() instanceof TestNGDataProviderAnnotation
+ }
+
+ /**
+ * Gets the name associated with this data provider.
+ */
+ string getDataProviderName() {
+ result = getAnAnnotation().(TestNGDataProviderAnnotation).getValue("name").(StringLiteral).getRepresentedString()
+ }
+}
+
+/**
+ * A constructor or method annotated with `org.testng.annotations.Factory` marking it as a factory
+ * for TestNG.
+ *
+ * This factory callable is used to generate instances of parameterized test classes.
+ */
+class TestNGFactoryCallable extends Callable {
+ TestNGFactoryCallable() {
+ getAnAnnotation() instanceof TestNGFactoryAnnotation
+ }
+}
+
+/**
+ * A class that will be run using the `org.junit.runners.Parameterized` JUnit runner.
+ */
+class ParameterizedJUnitTest extends Class {
+ ParameterizedJUnitTest() {
+ getAnAnnotation().(RunWithAnnotation).getRunner().(Class).hasQualifiedName("org.junit.runners", "Parameterized")
+ }
+}
+
+/**
+ * A `@Category` annotation on a class or method, that categorizes the annotated test.
+ */
+class JUnitCategoryAnnotation extends Annotation {
+ JUnitCategoryAnnotation() {
+ getType().hasQualifiedName("org.junit.experimental.categories", "Category")
+ }
+
+ /**
+ * One of the categories that this test is categorized as.
+ */
+ Type getACategory() {
+ exists(TypeLiteral literal, Expr value |
+ value = getValue("value") and
+ (
+ literal = value or
+ literal = value.(ArrayCreationExpr).getInit().getAnInit()
+ ) |
+ result = literal.getTypeName().getType()
+ )
+ }
+}
+
+/**
+ * A test class that will be run with theories.
+ */
+class JUnitTheoryTest extends Class {
+ JUnitTheoryTest() {
+ getAnAnnotation().(RunWithAnnotation).getRunner().(Class).hasQualifiedName("org.junit.experimental.theories", "Theories")
+ }
+}
diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll
new file mode 100755
index 00000000000..0147c19121b
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Variable.qll
@@ -0,0 +1,106 @@
+/**
+ * Provides classes and predicates for working with Java variables and their declarations.
+ */
+
+import Element
+
+/** A variable is a field, a local variable or a parameter. */
+class Variable extends @variable, Annotatable, Element, Modifiable {
+ /** Gets the type of this variable. */
+ /*abstract*/ Type getType() { none() }
+
+ /** Gets an access to this variable. */
+ VarAccess getAnAccess() { variableBinding(result,this) }
+
+ /** Gets an expression on the right-hand side of an assignment to this variable. */
+ Expr getAnAssignedValue() {
+ exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit())
+ or
+ exists(AssignExpr e | e.getDest().getProperExpr() = this.getAnAccess() and result = e.getSource())
+ }
+
+ /** Gets the initializer expression of this variable. */
+ Expr getInitializer() {
+ none()
+ }
+
+ /** Gets a printable representation of this variable together with its type. */
+ string pp() {
+ result = this.getType().getName() + " " + this.getName()
+ }
+}
+
+/** A locally scoped variable, that is, either a local variable or a parameter. */
+class LocalScopeVariable extends Variable, @localscopevariable {
+ /** Gets the callable in which this variable is declared. */
+ abstract Callable getCallable();
+}
+
+/** A local variable declaration */
+class LocalVariableDecl extends @localvar, LocalScopeVariable {
+ /** Gets the type of this local variable. */
+ override Type getType() { localvars(this,_,result,_) }
+
+ /** Gets the expression declaring this variable. */
+ LocalVariableDeclExpr getDeclExpr() { localvars(this, _, _, result) }
+
+ /** Gets the parent of this declaration. */
+ Expr getParent() { localvars(this,_,_,result) }
+
+ /** Gets the callable in which this declaration occurs. */
+ override Callable getCallable() { result = this.getParent().getEnclosingCallable() }
+
+ /** Gets the callable in which this declaration occurs. */
+ Callable getEnclosingCallable() { result = getCallable() }
+
+ override string toString() { result = this.getType().getName() + " " + this.getName() }
+
+ /** Gets the initializer expression of this local variable declaration. */
+ override Expr getInitializer() {
+ result = getDeclExpr().getInit()
+ }
+}
+
+/** A formal parameter of a callable. */
+class Parameter extends Element, @param, LocalScopeVariable {
+ /** Gets the type of this formal parameter. */
+ override Type getType() { params(this,result,_,_,_) }
+
+ /** Holds if the parameter is never assigned a value in the body of the callable. */
+ predicate isEffectivelyFinal() { not exists(getAnAssignedValue()) }
+
+ /** Gets the (zero-based) index of this formal parameter. */
+ int getPosition() { params(this,_,result,_,_) }
+
+ /** Gets the callable that declares this formal parameter. */
+ override Callable getCallable() { params(this,_,_,result,_) }
+
+ /** Gets the source declaration of this formal parameter. */
+ Parameter getSourceDeclaration() { params(this,_,_,_,result) }
+
+ /** Holds if this formal parameter is the same as its source declaration. */
+ predicate isSourceDeclaration() { this.getSourceDeclaration() = this }
+
+ /** Holds if this formal parameter is a variable arity parameter. */
+ predicate isVarargs() {
+ isVarargsParam(this)
+ }
+
+ /**
+ * Gets an argument for this parameter in any call to the callable that declares this formal
+ * parameter.
+ *
+ * Varargs parameters will have no results for this method.
+ */
+ Expr getAnArgument() {
+ not isVarargs() and
+ result = getACallArgument(getPosition())
+ }
+
+ private pragma[noinline] Expr getACallArgument(int i) {
+ exists(Call call |
+ result = call.getArgument(i) and
+ call.getCallee().getSourceDeclaration().getAParameter() = this
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/arithmetic/Overflow.qll b/java/ql/src/semmle/code/java/arithmetic/Overflow.qll
new file mode 100644
index 00000000000..9a8c7e77f82
--- /dev/null
+++ b/java/ql/src/semmle/code/java/arithmetic/Overflow.qll
@@ -0,0 +1,124 @@
+import java
+
+/** A subclass of `PrimitiveType` with width-based ordering methods. */
+class OrdPrimitiveType extends PrimitiveType {
+
+ predicate widerThan(OrdPrimitiveType that) {
+ getWidthRank() > that.getWidthRank()
+ }
+
+ predicate widerThanOrEqualTo(OrdPrimitiveType that) {
+ getWidthRank() >= that.getWidthRank()
+ }
+
+ OrdPrimitiveType maxType(OrdPrimitiveType that) {
+ (this.widerThan(that) and result = this)
+ or
+ (not this.widerThan(that) and result = that)
+ }
+
+ int getWidthRank() {
+ (this.getName() = "byte" and result = 1)
+ or
+ (this.getName() = "short" and result = 2)
+ or
+ (this.getName() = "int" and result = 3)
+ or
+ (this.getName() = "long" and result = 4)
+ or
+ (this.getName() = "float" and result = 5)
+ or
+ (this.getName() = "double" and result = 6)
+ }
+
+ float getMaxValue() {
+ (this.getName() = "byte" and result = 127.0)
+ or
+ (this.getName() = "short" and result = 32767.0)
+ or
+ (this.getName() = "int" and result = 2147483647.0)
+ or
+ (this.getName() = "long" and result = 9223372036854775807.0)
+ // don't try for floats and doubles
+ }
+
+ float getMinValue() {
+ (this.getName() = "byte" and result = -128.0)
+ or
+ (this.getName() = "short" and result = -32768.0)
+ or
+ (this.getName() = "int" and result = -2147483648.0)
+ or
+ (this.getName() = "long" and result = -9223372036854775808.0)
+ // don't try for floats and doubles
+ }
+}
+
+class NumType extends Type {
+ NumType() {
+ this instanceof PrimitiveType or
+ this instanceof BoxedType
+ }
+
+ /** Gets the width-ordered primitive type corresponding to this type. */
+ OrdPrimitiveType getOrdPrimitiveType() {
+ (this instanceof PrimitiveType and result = this)
+ or
+ (this instanceof BoxedType and result = this.(BoxedType).getPrimitiveType())
+ }
+
+ predicate widerThan(NumType that) {
+ this.getOrdPrimitiveType().widerThan(that.getOrdPrimitiveType())
+ }
+
+ predicate widerThanOrEqualTo(NumType that) {
+ this.getOrdPrimitiveType().widerThanOrEqualTo(that.getOrdPrimitiveType())
+ }
+
+ int getWidthRank() {
+ result = this.getOrdPrimitiveType().getWidthRank()
+ }
+}
+
+class ArithExpr extends Expr {
+ ArithExpr() {
+ (
+ this instanceof UnaryAssignExpr or
+ this instanceof AddExpr or this instanceof MulExpr or
+ this instanceof SubExpr or this instanceof DivExpr
+ ) and
+ forall(Expr e | e = this.(BinaryExpr).getAnOperand() or e = this.(UnaryAssignExpr).getExpr() |
+ e.getType() instanceof NumType
+ )
+ }
+
+ OrdPrimitiveType getOrdPrimitiveType() {
+ exists(OrdPrimitiveType t1, OrdPrimitiveType t2 |
+ t1 = this.getLeftOperand().getType().(NumType).getOrdPrimitiveType() and
+ t2 = this.getRightOperand().getType().(NumType).getOrdPrimitiveType() and
+ result = t1.maxType(t2)
+ )
+ }
+
+ /**
+ * Gets the left-hand operand of a binary expression
+ * or the operand of a unary assignment expression.
+ */
+ Expr getLeftOperand() {
+ result = this.(BinaryExpr).getLeftOperand() or
+ result = this.(UnaryAssignExpr).getExpr()
+ }
+
+ /**
+ * Gets the right-hand operand if this is a binary expression.
+ */
+ Expr getRightOperand() {
+ result = this.(BinaryExpr).getRightOperand()
+ }
+
+ /** Gets an operand of this arithmetic expression. */
+ Expr getAnOperand() {
+ result = this.(BinaryExpr).getAnOperand() or
+ result = this.(UnaryAssignExpr).getExpr()
+ }
+}
diff --git a/java/ql/src/semmle/code/java/comparison/Comparison.qll b/java/ql/src/semmle/code/java/comparison/Comparison.qll
new file mode 100644
index 00000000000..c6546fd715c
--- /dev/null
+++ b/java/ql/src/semmle/code/java/comparison/Comparison.qll
@@ -0,0 +1,23 @@
+import java
+
+/**
+ * If `e1` evaluates to `b1` then the direct subexpression `e2` evaluates to `b2`.
+ *
+ * Used as basis for the transitive closure in `exprImplies`.
+ */
+private predicate exprImpliesStep(Expr e1, boolean b1, Expr e2, boolean b2) {
+ e1.(ParExpr).getProperExpr() = e2 and b2 = b1 and (b1 = true or b1 = false)
+ or
+ e1.(LogNotExpr).getExpr() = e2 and b2 = b1.booleanNot() and (b1 = true or b1 = false)
+ or
+ b1 = true and e1.(AndLogicalExpr).getAnOperand() = e2 and b2 = true
+ or
+ b1 = false and e1.(OrLogicalExpr).getAnOperand() = e2 and b2 = false
+}
+
+/** If `e1` evaluates to `b1` then the subexpression `e2` evaluates to `b2`. */
+predicate exprImplies(Expr e1, boolean b1, Expr e2, boolean b2) {
+ e1 = e2 and b1 = b2 and (b1 = true or b1 = false)
+ or
+ exists(Expr emid, boolean bmid | exprImplies(e1, b1, emid, bmid) and exprImpliesStep(emid, bmid, e2, b2))
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll b/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll
new file mode 100644
index 00000000000..ed1e092c7f3
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/BasicBlocks.qll
@@ -0,0 +1,69 @@
+/**
+ * Provides classes and predicates for working with basic blocks in Java.
+ */
+
+import java
+import Dominance
+import semmle.code.java.ControlFlowGraph
+
+/**
+ * A control-flow node that represents the start of a basic block.
+ *
+ * A basic block is a series of nodes with no control-flow branching, which can
+ * often be treated as a unit in analyses.
+ */
+class BasicBlock extends ControlFlowNode {
+ BasicBlock() {
+ not exists(this.getAPredecessor()) and exists(this.getASuccessor()) or
+ strictcount(this.getAPredecessor()) > 1 or
+ exists(ControlFlowNode pred | pred = this.getAPredecessor() | strictcount(pred.getASuccessor()) > 1)
+ }
+
+ /** Gets an immediate successor of this basic block. */
+ cached
+ BasicBlock getABBSuccessor() {
+ result = getLastNode().getASuccessor()
+ }
+
+ /** Gets an immediate predecessor of this basic block. */
+ BasicBlock getABBPredecessor() {
+ result.getABBSuccessor() = this
+ }
+
+ /** Gets a control-flow node contained in this basic block. */
+ ControlFlowNode getANode() { result = getNode(_) }
+
+ /** Gets the control-flow node at a specific (zero-indexed) position in this basic block. */
+ cached
+ ControlFlowNode getNode(int pos) {
+ result = this and pos = 0
+ or
+ exists(ControlFlowNode mid, int mid_pos | pos = mid_pos + 1 |
+ getNode(mid_pos) = mid and
+ mid.getASuccessor() = result and
+ not result instanceof BasicBlock
+ )
+ }
+
+ /** Gets the first control-flow node in this basic block. */
+ ControlFlowNode getFirstNode() { result = this }
+
+ /** Gets the last control-flow node in this basic block. */
+ ControlFlowNode getLastNode() { result = getNode(length()-1) }
+
+ /** Gets the number of control-flow nodes contained in this basic block. */
+ cached
+ int length() { result = strictcount(getANode()) }
+
+ /** Holds if this basic block strictly dominates `node`. */
+ predicate bbStrictlyDominates(BasicBlock node) { bbStrictlyDominates(this, node) }
+
+ /** Holds if this basic block dominates `node`. (This is reflexive.) */
+ predicate bbDominates(BasicBlock node) { bbDominates(this, node) }
+
+ /** Holds if this basic block strictly post-dominates `node`. */
+ predicate bbStrictlyPostDominates(BasicBlock node) { bbStrictlyPostDominates(this, node) }
+
+ /** Holds if this basic block post-dominates `node`. (This is reflexive.) */
+ predicate bbPostDominates(BasicBlock node) { bbPostDominates(this, node) }
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/Dominance.qll b/java/ql/src/semmle/code/java/controlflow/Dominance.qll
new file mode 100644
index 00000000000..8f36030d812
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/Dominance.qll
@@ -0,0 +1,139 @@
+/**
+ * Provides classes and predicates for control-flow graph dominance.
+ */
+import java
+private import semmle.code.java.ControlFlowGraph
+
+
+/*
+ * Predicates for basic-block-level dominance.
+ */
+
+/** Entry points for control-flow. */
+private predicate flowEntry(Stmt entry) {
+ exists(Callable c | entry = c.getBody())
+ or
+ // This disjunct is technically superfluous, but safeguards against extractor problems.
+ entry instanceof Block and
+ not exists(entry.getEnclosingCallable()) and
+ not entry.getParent() instanceof Stmt
+}
+
+/** The successor relation for basic blocks. */
+private predicate bbSucc(BasicBlock pre, BasicBlock post) {
+ post = pre.getABBSuccessor()
+}
+
+/** The immediate dominance relation for basic blocks. */
+cached predicate bbIDominates(BasicBlock dom, BasicBlock node) = idominance(flowEntry/1, bbSucc/2)(_, dom, node)
+
+/** Holds if the dominance relation is calculated for `bb`. */
+predicate hasDominanceInformation(BasicBlock bb) {
+ exists(BasicBlock entry | flowEntry(entry) and bbSucc*(entry, bb))
+}
+
+/** Exit points for control-flow. */
+private predicate flowExit(Callable exit) {
+ exists(ControlFlowNode s | s.getASuccessor() = exit)
+}
+
+/** Exit points for basic-block control-flow. */
+private predicate bbSink(BasicBlock exit) {
+ flowExit(exit.getLastNode())
+}
+
+/** Reversed `bbSucc`. */
+private predicate bbPred(BasicBlock post, BasicBlock pre) {
+ post = pre.getABBSuccessor()
+}
+
+/** The immediate post-dominance relation on basic blocks. */
+cached predicate bbIPostDominates(BasicBlock dominator, BasicBlock node) = idominance(bbSink/1,bbPred/2)(_, dominator, node)
+
+/** Holds if `dom` strictly dominates `node`. */
+predicate bbStrictlyDominates(BasicBlock dom, BasicBlock node) { bbIDominates+(dom, node) }
+
+/** Holds if `dom` dominates `node`. (This is reflexive.) */
+predicate bbDominates(BasicBlock dom, BasicBlock node) { bbStrictlyDominates(dom, node) or dom = node }
+
+/** Holds if `dom` strictly post-dominates `node`. */
+predicate bbStrictlyPostDominates(BasicBlock dom, BasicBlock node) { bbIPostDominates+(dom, node) }
+
+/** Holds if `dom` post-dominates `node`. (This is reflexive.) */
+predicate bbPostDominates(BasicBlock dom, BasicBlock node) { bbStrictlyPostDominates(dom, node) or dom = node }
+
+/**
+ * The dominance frontier relation for basic blocks.
+ *
+ * This is equivalent to:
+ *
+ * ```
+ * bbDominates(x, w.getABBPredecessor()) and not bbStrictlyDominates(x, w)
+ * ```
+ */
+predicate dominanceFrontier(BasicBlock x, BasicBlock w) {
+ x = w.getABBPredecessor() and not bbIDominates(x, w)
+ or
+ exists(BasicBlock prev | dominanceFrontier(prev, w) |
+ bbIDominates(x, prev) and
+ not bbIDominates(x, w)
+ )
+}
+
+/*
+ * Predicates for expression-level dominance.
+ */
+
+/** Immediate dominance relation on control-flow graph nodes. */
+predicate iDominates(ControlFlowNode dominator, ControlFlowNode node) {
+ exists(BasicBlock bb, int i | dominator = bb.getNode(i) and node = bb.getNode(i+1)) or
+ exists(BasicBlock dom, BasicBlock bb |
+ bbIDominates(dom, bb) and
+ dominator = dom.getLastNode() and
+ node = bb.getFirstNode()
+ )
+}
+
+/** Holds if `dom` strictly dominates `node`. */
+pragma[inline]
+predicate strictlyDominates(ControlFlowNode dom, ControlFlowNode node) {
+ // This predicate is gigantic, so it must be inlined.
+ bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock())
+ or
+ exists(BasicBlock b, int i, int j |
+ dom = b.getNode(i) and node = b.getNode(j) and i < j
+ )
+}
+
+/** Holds if `dom` dominates `node`. (This is reflexive.) */
+pragma[inline]
+predicate dominates(ControlFlowNode dom, ControlFlowNode node) {
+ // This predicate is gigantic, so it must be inlined.
+ bbStrictlyDominates(dom.getBasicBlock(), node.getBasicBlock())
+ or
+ exists(BasicBlock b, int i, int j |
+ dom = b.getNode(i) and node = b.getNode(j) and i <= j
+ )
+}
+
+/** Holds if `dom` strictly post-dominates `node`. */
+pragma[inline]
+predicate strictlyPostDominates(ControlFlowNode dom, ControlFlowNode node) {
+ // This predicate is gigantic, so it must be inlined.
+ bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock())
+ or
+ exists(BasicBlock b, int i, int j |
+ dom = b.getNode(i) and node = b.getNode(j) and i > j
+ )
+}
+
+/** Holds if `dom` post-dominates `node`. (This is reflexive.) */
+pragma[inline]
+predicate postDominates(ControlFlowNode dom, ControlFlowNode node) {
+ // This predicate is gigantic, so it must be inlined.
+ bbStrictlyPostDominates(dom.getBasicBlock(), node.getBasicBlock())
+ or
+ exists(BasicBlock b, int i, int j |
+ dom = b.getNode(i) and node = b.getNode(j) and i >= j
+ )
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/Guards.qll b/java/ql/src/semmle/code/java/controlflow/Guards.qll
new file mode 100644
index 00000000000..59e3ed37a67
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/Guards.qll
@@ -0,0 +1,233 @@
+import java
+private import semmle.code.java.controlflow.Dominance
+private import semmle.code.java.controlflow.internal.GuardsLogic
+
+/**
+ * A basic block that terminates in a condition, splitting the subsequent control flow.
+ */
+class ConditionBlock extends BasicBlock {
+ ConditionBlock() {
+ this.getLastNode() instanceof ConditionNode
+ }
+
+ /** Gets the last node of this basic block. */
+ ConditionNode getConditionNode() {
+ result = this.getLastNode()
+ }
+
+ /** Gets the condition of the last node of this basic block. */
+ Expr getCondition() {
+ result = this.getConditionNode().getCondition()
+ }
+
+ /** Gets a `true`- or `false`-successor of the last node of this basic block. */
+ BasicBlock getTestSuccessor(boolean testIsTrue) {
+ result = this.getConditionNode().getABranchSuccessor(testIsTrue)
+ }
+
+ /**
+ * Holds if `controlled` is a basic block controlled by this condition, that
+ * is, a basic blocks for which the condition is `testIsTrue`.
+ */
+ predicate controls(BasicBlock controlled, boolean testIsTrue) {
+ /*
+ * For this block to control the block `controlled` with `testIsTrue` the following must be true:
+ * Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
+ * Execution must have passed through the `testIsTrue` edge leaving `this`.
+ *
+ * Although "passed through the true edge" implies that `this.getATrueSuccessor()` dominates `controlled`,
+ * the reverse is not true, as flow may have passed through another edge to get to `this.getATrueSuccessor()`
+ * so we need to assert that `this.getATrueSuccessor()` dominates `controlled` *and* that
+ * all predecessors of `this.getATrueSuccessor()` are either `this` or dominated by `this.getATrueSuccessor()`.
+ *
+ * For example, in the following java snippet:
+ * ```
+ * if (x)
+ * controlled;
+ * false_successor;
+ * uncontrolled;
+ * ```
+ * `false_successor` dominates `uncontrolled`, but not all of its predecessors are `this` (`if (x)`)
+ * or dominated by itself. Whereas in the following code:
+ * ```
+ * if (x)
+ * while (controlled)
+ * also_controlled;
+ * false_successor;
+ * uncontrolled;
+ * ```
+ * the block `while controlled` is controlled because all of its predecessors are `this` (`if (x)`)
+ * or (in the case of `also_controlled`) dominated by itself.
+ *
+ * The additional constraint on the predecessors of the test successor implies
+ * that `this` strictly dominates `controlled` so that isn't necessary to check
+ * directly.
+ */
+ exists(BasicBlock succ |
+ succ = this.getTestSuccessor(testIsTrue) and
+ succ.bbDominates(controlled) and
+ forall(BasicBlock pred | pred = succ.getABBPredecessor() and pred != this |
+ succ.bbDominates(pred)
+ )
+ )
+ }
+}
+
+/**
+ * A condition that can be evaluated to either true or false. This can either
+ * be an `Expr` of boolean type that isn't a boolean literal, or a case of a
+ * switch statement.
+ *
+ * Evaluating a switch case to true corresponds to taking that switch case, and
+ * evaluating it to false corresponds to taking some other branch.
+ */
+class Guard extends ExprParent {
+ Guard() {
+ this.(Expr).getType() instanceof BooleanType and not this instanceof BooleanLiteral or
+ this instanceof SwitchCase
+ }
+
+ /** Gets the immediately enclosing callable whose body contains this guard. */
+ Callable getEnclosingCallable() {
+ result = this.(Expr).getEnclosingCallable() or
+ result = this.(SwitchCase).getEnclosingCallable()
+ }
+
+ /** Gets the statement containing this guard. */
+ Stmt getEnclosingStmt() {
+ result = this.(Expr).getEnclosingStmt() or
+ result = this.(SwitchCase).getSwitch()
+ }
+
+ /**
+ * Holds if this guard is an equality test between `e1` and `e2`. The test
+ * can be either `==`, `!=`, `.equals`, or a switch case. If the test is
+ * negated, that is `!=`, then `polarity` is false, otherwise `polarity` is
+ * true.
+ */
+ predicate isEquality(Expr e1, Expr e2, boolean polarity) {
+ exists(Expr exp1, Expr exp2 |
+ equalityGuard(this, exp1, exp2, polarity)
+ |
+ e1 = exp1.getProperExpr() and e2 = exp2.getProperExpr() or
+ e2 = exp1.getProperExpr() and e1 = exp2.getProperExpr()
+ )
+ }
+
+ /**
+ * Holds if the evaluation of this guard to `branch` corresponds to the edge
+ * from `bb1` to `bb2`.
+ */
+ predicate hasBranchEdge(BasicBlock bb1, BasicBlock bb2, boolean branch) {
+ exists(ConditionBlock cb |
+ cb = bb1 and
+ cb.getCondition() = this and
+ bb2 = cb.getTestSuccessor(branch)
+ ) or
+ exists(SwitchCase sc, ControlFlowNode pred |
+ sc = this and
+ branch = true and
+ bb2.getFirstNode() = sc.getControlFlowNode() and
+ pred = sc.getControlFlowNode().getAPredecessor() and
+ pred.(Expr).getParent*() = sc.getSwitch().getExpr() and
+ bb1 = pred.getBasicBlock()
+ )
+ }
+
+ /**
+ * Holds if this guard evaluating to `branch` directly controls the block
+ * `controlled`. That is, the `true`- or `false`-successor of this guard (as
+ * given by `branch`) dominates `controlled`.
+ */
+ predicate directlyControls(BasicBlock controlled, boolean branch) {
+ exists(ConditionBlock cb |
+ cb.getCondition() = this and
+ cb.controls(controlled, branch)
+ ) or
+ switchCaseControls(this, controlled) and branch = true
+ }
+
+ /**
+ * Holds if this guard evaluating to `branch` directly or indirectly controls
+ * the block `controlled`. That is, the evaluation of `controlled` is
+ * dominated by this guard evaluating to `branch`.
+ */
+ predicate controls(BasicBlock controlled, boolean branch) {
+ guardControls_v3(this, controlled, branch)
+ }
+}
+
+private predicate switchCaseControls(SwitchCase sc, BasicBlock bb) {
+ exists(BasicBlock caseblock, SwitchStmt ss |
+ ss.getACase() = sc and
+ caseblock.getFirstNode() = sc.getControlFlowNode() and
+ caseblock.bbDominates(bb) and
+ forall(ControlFlowNode pred | pred = sc.getControlFlowNode().getAPredecessor() |
+ pred.(Expr).getParent*() = ss.getExpr()
+ )
+ )
+}
+
+/**
+ * INTERNAL: Use `Guards.controls` instead.
+ *
+ * Holds if `guard.controls(controlled, branch)`, except this only relies on
+ * BaseSSA-based reasoning.
+ */
+predicate guardControls_v1(Guard guard, BasicBlock controlled, boolean branch) {
+ guard.directlyControls(controlled, branch) or
+ exists(Guard g, boolean b |
+ guardControls_v1(g, controlled, b) and
+ implies_v1(g, b, guard, branch)
+ )
+}
+
+/**
+ * INTERNAL: Use `Guards.controls` instead.
+ *
+ * Holds if `guard.controls(controlled, branch)`, except this doesn't rely on
+ * RangeAnalysis.
+ */
+predicate guardControls_v2(Guard guard, BasicBlock controlled, boolean branch) {
+ guard.directlyControls(controlled, branch) or
+ exists(Guard g, boolean b |
+ guardControls_v2(g, controlled, b) and
+ implies_v2(g, b, guard, branch)
+ )
+}
+
+private predicate guardControls_v3(Guard guard, BasicBlock controlled, boolean branch) {
+ guard.directlyControls(controlled, branch) or
+ exists(Guard g, boolean b |
+ guardControls_v3(g, controlled, b) and
+ implies_v3(g, b, guard, branch)
+ )
+}
+
+private predicate equalityGuard(Guard g, Expr e1, Expr e2, boolean polarity) {
+ exists(EqualityTest eqtest |
+ eqtest = g and
+ polarity = eqtest.polarity() and
+ eqtest.hasOperands(e1, e2)
+ ) or
+ exists(MethodAccess ma |
+ ma = g and
+ ma.getMethod() instanceof EqualsMethod and
+ polarity = true and
+ ma.getAnArgument() = e1 and ma.getQualifier() = e2
+ ) or
+ exists(MethodAccess ma, Method equals |
+ ma = g and
+ ma.getMethod() = equals and
+ polarity = true and
+ equals.hasName("equals") and
+ equals.getNumberOfParameters() = 2 and
+ equals.getDeclaringType().hasQualifiedName("java.util", "Objects") and
+ ma.getArgument(0) = e1 and ma.getArgument(1) = e2
+ ) or
+ exists(ConstCase cc |
+ cc = g and
+ polarity = true and
+ cc.getSwitch().getExpr().getProperExpr() = e1 and cc.getValue() = e2
+ )
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/Paths.qll b/java/ql/src/semmle/code/java/controlflow/Paths.qll
new file mode 100644
index 00000000000..237b663b74f
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/Paths.qll
@@ -0,0 +1,85 @@
+/**
+ * This library provides predicates for reasoning about the set of all paths
+ * through a callable.
+ */
+
+import java
+import semmle.code.java.dispatch.VirtualDispatch
+
+/**
+ * A configuration to define an "action". The member predicates
+ * `callableAlwaysPerformsAction` and `callAlwaysPerformsAction` then gives all
+ * the callables and calls that always performs an action taking
+ * inter-procedural flow into account.
+ */
+abstract class ActionConfiguration extends string {
+ bindingset[this]
+ ActionConfiguration() { any() }
+
+ /** Holds if `node` is an action. */
+ abstract predicate isAction(ControlFlowNode node);
+
+ /** Holds if every path through `callable` goes through at least one action node. */
+ final predicate callableAlwaysPerformsAction(Callable callable) {
+ callableAlwaysPerformsAction(callable, this)
+ }
+
+ /** Holds if every path through `call` goes through at least one action node. */
+ final predicate callAlwaysPerformsAction(Call call) {
+ callAlwaysPerformsAction(call, this)
+ }
+}
+
+/** Gets a `BasicBlock` that contains an action. */
+private BasicBlock actionBlock(ActionConfiguration conf) {
+ exists(ControlFlowNode node | result = node.getBasicBlock() |
+ conf.isAction(node) or
+ callAlwaysPerformsAction(node, conf)
+ )
+}
+
+/** Holds if every path through `call` goes through at least one action node. */
+private predicate callAlwaysPerformsAction(Call call, ActionConfiguration conf) {
+ forex(Callable callable | callable = viableCallable(call) | callableAlwaysPerformsAction(callable, conf))
+}
+
+/** Holds if an action dominates the exit of the callable. */
+private predicate actionDominatesExit(Callable callable, ActionConfiguration conf) {
+ exists(BasicBlock exit |
+ exit.getLastNode() = callable and
+ actionBlock(conf).bbDominates(exit)
+ )
+}
+
+/** Gets a `BasicBlock` that contains an action that does not dominate the exit. */
+private BasicBlock nonDominatingActionBlock(ActionConfiguration conf) {
+ exists(BasicBlock exit |
+ result = actionBlock(conf) and
+ exit.getLastNode() = result.getEnclosingCallable() and
+ not result.bbDominates(exit)
+ )
+}
+
+private class JoinBlock extends BasicBlock { JoinBlock() { 2 <= strictcount(this.getABBPredecessor()) } }
+
+/**
+ * Holds if `bb` is a block that is collectively dominated by a set of one or
+ * more actions that individually does not dominate the exit.
+ */
+private predicate postActionBlock(BasicBlock bb, ActionConfiguration conf) {
+ bb = nonDominatingActionBlock(conf) or
+ if bb instanceof JoinBlock then
+ forall(BasicBlock pred | pred = bb.getABBPredecessor() | postActionBlock(pred, conf))
+ else
+ postActionBlock(bb.getABBPredecessor(), conf)
+}
+
+/** Holds if every path through `callable` goes through at least one action node. */
+private predicate callableAlwaysPerformsAction(Callable callable, ActionConfiguration conf) {
+ actionDominatesExit(callable, conf)
+ or
+ exists(BasicBlock exit |
+ exit.getLastNode() = callable and
+ postActionBlock(exit, conf)
+ )
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll
new file mode 100644
index 00000000000..26423c1c4fc
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll
@@ -0,0 +1,241 @@
+/**
+ * Provides classes and predicates for identifying unreachable blocks under a "closed-world" assumption.
+ */
+import java
+import semmle.code.java.controlflow.Guards
+
+/**
+ * A field which contains a constant of an immutable type.
+ *
+ * This only considers fields which are assigned once.
+ */
+class ConstantField extends Field {
+ ConstantField() {
+ getType() instanceof ImmutableType and
+ // Assigned once
+ count(getAnAssignedValue()) = 1 and
+ /*
+ * And that assignment is either in the appropriate initializer, or, for instance fields on
+ * classes with one constructor, in the constructor.
+ */
+ forall(FieldWrite fa |
+ fa = getAnAccess()
+ |
+ if isStatic() then
+ fa.getEnclosingCallable() instanceof StaticInitializer
+ else (
+ // Defined in the instance initializer.
+ fa.getEnclosingCallable() instanceof InstanceInitializer or
+ // It can be defined in the constructor if there is only one constructor.
+ (
+ fa.getEnclosingCallable() instanceof Constructor and
+ count(getDeclaringType().getAConstructor()) = 1
+ )
+ )
+ )
+ }
+
+ /**
+ * Gets the constant value assigned to the field.
+ *
+ * Note: although this value is constant, we may not be able to statically determine the value.
+ */
+ ConstantExpr getConstantValue() {
+ result = getAnAssignedValue()
+ }
+}
+
+/**
+ * A method that returns a single constant value, and is not overridden.
+ */
+class ConstantMethod extends Method {
+ ConstantMethod() {
+ // Just one return statement
+ count(ReturnStmt rs | rs.getEnclosingCallable() = this) = 1 and
+ // Which returns a constant expr
+ exists(ReturnStmt rs | rs.getEnclosingCallable() = this | rs.getResult() instanceof ConstantExpr) and
+ // And this method is not overridden
+ not exists(Method m | m.overrides(this))
+ }
+
+ /**
+ * Gets the expression representing the constant value returned.
+ */
+ ConstantExpr getConstantValue() {
+ exists(ReturnStmt returnStmt |
+ returnStmt.getEnclosingCallable() = this
+ |
+ result = returnStmt.getResult())
+ }
+}
+
+/**
+ * A field that appears constant, but should not be considered constant when determining
+ * `ConstantExpr`, and, consequently, in the unreachable blocks analysis.
+ */
+abstract class ExcludedConstantField extends ConstantField {
+}
+
+/**
+ * An expression that evaluates to a constant at runtime.
+ *
+ * This includes all JLS compile-time constants, plus expressions that can be deduced to be
+ * constant by making a closed world assumption.
+ */
+class ConstantExpr extends Expr {
+ ConstantExpr() {
+ /*
+ * Ignore reads of excluded fields.
+ */
+ not this.(FieldRead).getField() instanceof ExcludedConstantField and
+ (
+ // A JLS compile time constant expr
+ this instanceof CompileTimeConstantExpr or
+ // A call to a constant method
+ this.(Call).getCallee() instanceof ConstantMethod or
+ // A read of a constant field
+ exists(this.(FieldRead).getField().(ConstantField).getConstantValue()) or
+ // A binary expression where both sides are constant
+ (
+ this.(BinaryExpr).getLeftOperand() instanceof ConstantExpr and
+ this.(BinaryExpr).getRightOperand() instanceof ConstantExpr
+ ) or
+ this.(ParExpr).getExpr() instanceof ConstantExpr
+ )
+ }
+
+ /**
+ * Gets the inferred boolean value for this constant boolean expression.
+ */
+ boolean getBooleanValue() {
+ result = this.(CompileTimeConstantExpr).getBooleanValue() or
+ result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getBooleanValue() or
+ result = this.(FieldRead).getField().(ConstantField).getConstantValue().getBooleanValue() or
+ result = this.(ParExpr).getExpr().(ConstantExpr).getBooleanValue() or
+ // Handle binary expressions that have integer operands and a boolean result.
+ exists(BinaryExpr b, int left, int right |
+ b = this and
+ left = b.getLeftOperand().(ConstantExpr).getIntValue() and
+ right = b.getRightOperand().(ConstantExpr).getIntValue()
+ |
+ (b instanceof LTExpr and if left < right then result = true else result = false) or
+ (b instanceof LEExpr and if left <= right then result = true else result = false) or
+ (b instanceof GTExpr and if left > right then result = true else result = false) or
+ (b instanceof GEExpr and if left >= right then result = true else result = false) or
+ (b instanceof EQExpr and if left = right then result = true else result = false) or
+ (b instanceof NEExpr and if left != right then result = true else result = false)
+ )
+ }
+
+ /**
+ * Gets the inferred int value for this constant int expression.
+ */
+ int getIntValue() {
+ result = this.(CompileTimeConstantExpr).getIntValue() or
+ result = this.(Call).getCallee().(ConstantMethod).getConstantValue().getIntValue() or
+ result = this.(FieldRead).getField().(ConstantField).getConstantValue().getIntValue()
+ }
+}
+
+/**
+ * A switch statement that always selects the same case.
+ */
+class ConstSwitchStmt extends SwitchStmt {
+ ConstSwitchStmt() {
+ this.getExpr() instanceof ConstantExpr
+ }
+
+ /** Gets the `ConstCase` that matches, if any. */
+ ConstCase getMatchingConstCase() {
+ result = getAConstCase() and
+ // Only handle the int case for now
+ result.getValue().(ConstantExpr).getIntValue() = getExpr().(ConstantExpr).getIntValue()
+ }
+
+ /** Gets the matching case, if it can be deduced. */
+ SwitchCase getMatchingCase() {
+ // Must be a value we can deduce
+ exists(getExpr().(ConstantExpr).getIntValue()) and
+ if (exists(getMatchingConstCase())) then
+ result = getMatchingConstCase()
+ else
+ result = getDefaultCase()
+ }
+
+ /**
+ * Gets a case that never matches.
+ *
+ * This only has values if we found the matching case.
+ */
+ SwitchCase getAFailingCase() {
+ exists(SwitchCase matchingCase |
+ // We must have found the matching case, otherwise we can't deduce which cases are not matched
+ matchingCase = getMatchingCase() and
+ result = getACase() and
+ result != matchingCase
+ )
+ }
+}
+
+/**
+ * An unreachable basic block is one that is dominated by a condition that never holds.
+ */
+class UnreachableBasicBlock extends BasicBlock {
+ UnreachableBasicBlock() {
+ /*
+ * Condition blocks with a constant condition that causes a true/false successor to be
+ * unreachable. Note: conditions including a single boolean literal e.g. if (false) are not
+ * modeled as a ConditionBlock - this case is covered by the blocks-without-a-predecessor
+ * check below.
+ */
+ exists(ConditionBlock conditionBlock, boolean constant |
+ constant = conditionBlock.getCondition().(ConstantExpr).getBooleanValue()
+ |
+ conditionBlock.controls(this, constant.booleanNot())
+ ) or
+ /*
+ * This block is not reachable in the CFG, and is not a callable, a body of a callable, an
+ * expression in an annotation, an expression in an assert statement, or a catch clause.
+ */
+ (
+ forall(BasicBlock bb | bb = getABBPredecessor() | bb instanceof UnreachableBasicBlock) and
+ not exists(Callable c |
+ c.getBody() = this) and
+ not this instanceof Callable and
+ not exists(Annotation a |
+ a.getAChildExpr*() = this
+ ) and
+ not exists(AssertStmt a |
+ a = this.(Expr).getEnclosingStmt()
+ ) and
+ not this instanceof CatchClause
+ ) or
+ // Switch statements with a constant comparison expression may have unreachable cases.
+ exists(ConstSwitchStmt constSwitchStmt, BasicBlock failingCaseBlock |
+ failingCaseBlock = constSwitchStmt.getAFailingCase().getBasicBlock()
+ |
+ // Not accessible from the successful case
+ not constSwitchStmt.getMatchingCase().getBasicBlock().getABBSuccessor*() = failingCaseBlock and
+ // Blocks dominated by the failing case block are unreachable
+ constSwitchStmt.getAFailingCase().getBasicBlock().bbDominates(this)
+ )
+ }
+}
+
+/**
+ * An unreachable expression is an expression contained in an `UnreachableBasicBlock`.
+ */
+class UnreachableExpr extends Expr {
+ UnreachableExpr() {
+ getBasicBlock() instanceof UnreachableBasicBlock
+ }
+}
+
+/**
+ * An unreachable statement is a statement contained in an `UnreachableBasicBlock`.
+ */
+class UnreachableStmt extends Stmt {
+ UnreachableStmt() {
+ getBasicBlock() instanceof UnreachableBasicBlock
+ }
+}
diff --git a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll
new file mode 100644
index 00000000000..423aa6f42e4
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll
@@ -0,0 +1,306 @@
+/**
+ * Provides predicates for working with the internal logic of the `Guards`
+ * library.
+ */
+import java
+import semmle.code.java.controlflow.Guards
+private import semmle.code.java.dataflow.SSA
+private import semmle.code.java.dataflow.internal.BaseSSA
+private import semmle.code.java.dataflow.NullGuards
+private import semmle.code.java.dataflow.IntegerGuards
+
+/**
+ * Holds if the assumption that `g1` has been evaluated to `b1` implies that
+ * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
+ * dominates the evaluation of `g1` to `b1`.
+ *
+ * Restricted to BaseSSA-based reasoning.
+ */
+predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) {
+ g1.(ParExpr).getExpr() = g2 and b1 = b2 and (b1 = true or b1 = false) or
+ g1.(AndBitwiseExpr).getAnOperand() = g2 and b1 = true and b2 = true or
+ g1.(OrBitwiseExpr).getAnOperand() = g2 and b1 = false and b2 = false or
+ g1.(AndLogicalExpr).getAnOperand() = g2 and b1 = true and b2 = true or
+ g1.(OrLogicalExpr).getAnOperand() = g2 and b1 = false and b2 = false or
+ g1.(LogNotExpr).getExpr() = g2 and b1 = b2.booleanNot() and (b1 = true or b1 = false) or
+ exists(EqualityTest eqtest, boolean polarity, BooleanLiteral boollit |
+ eqtest = g1 and
+ eqtest.hasOperands(g2, boollit) and
+ eqtest.polarity() = polarity and
+ (b1 = true or b1 = false) and
+ b2 = b1.booleanXor(polarity).booleanXor(boollit.getBooleanValue())
+ ) or
+ exists(ConditionalExpr cond, boolean branch, BooleanLiteral boollit, boolean boolval |
+ cond.getTrueExpr() = boollit and branch = true or
+ cond.getFalseExpr() = boollit and branch = false
+ |
+ cond = g1 and
+ boolval = boollit.getBooleanValue() and
+ b1 = boolval.booleanNot() and
+ (
+ g2 = cond.getCondition() and b2 = branch.booleanNot() or
+ g2 = cond.getTrueExpr() and b2 = b1 or
+ g2 = cond.getFalseExpr() and b2 = b1
+ )
+ ) or
+ g1.(DefaultCase).getSwitch().getAConstCase() = g2 and b1 = true and b2 = false or
+ exists(BaseSsaUpdate vbool |
+ vbool.getAUse() = g1 and
+ vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
+ (b1 = true or b1 = false) and
+ b2 = b1
+ )
+}
+
+/**
+ * Holds if the assumption that `g1` has been evaluated to `b1` implies that
+ * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
+ * dominates the evaluation of `g1` to `b1`.
+ *
+ * Allows full use of SSA but is restricted to pre-RangeAnalysis reasoning.
+ */
+predicate implies_v2(Guard g1, boolean b1, Guard g2, boolean b2) {
+ implies_v1(g1, b1, g2, b2) or
+ exists(SsaExplicitUpdate vbool |
+ vbool.getAUse() = g1 and
+ vbool.getDefiningExpr().(VariableAssign).getSource() = g2 and
+ (b1 = true or b1 = false) and
+ b2 = b1
+ ) or
+ exists(SsaVariable v, AbstractValue k |
+ // If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard
+ // proving `v != k` ensures that `g2` evaluates to `b2`.
+ conditionalAssignVal(v, g2, b2.booleanNot(), k) and
+ guardImpliesNotEqual1(g1, b1, v, k)
+ ) or
+ exists(SsaVariable v, Expr e, AbstractValue k |
+ // If `v = g2 ? k : ...` and all other assignments to `v` are different from
+ // `k` then a guard proving `v == k` ensures that `g2` evaluates to `b2`.
+ uniqueValue(v, e, k) and
+ guardImpliesEqual(g1, b1, v, k) and
+ g2.directlyControls(e.getBasicBlock(), b2) and
+ not g2.directlyControls(getBasicBlockOfGuard(g1), b2)
+ )
+}
+
+/**
+ * Holds if the assumption that `g1` has been evaluated to `b1` implies that
+ * `g2` has been evaluated to `b2`, that is, the evaluation of `g2` to `b2`
+ * dominates the evaluation of `g1` to `b1`.
+ */
+cached
+predicate implies_v3(Guard g1, boolean b1, Guard g2, boolean b2) {
+ implies_v2(g1, b1, g2, b2) or
+ exists(SsaVariable v, AbstractValue k |
+ // If `v = g2 ? k : ...` or `v = g2 ? ... : k` then a guard
+ // proving `v != k` ensures that `g2` evaluates to `b2`.
+ conditionalAssignVal(v, g2, b2.booleanNot(), k) and
+ guardImpliesNotEqual2(g1, b1, v, k)
+ ) or
+ exists(SsaVariable v |
+ conditionalAssign(v, g2, b2.booleanNot(), clearlyNotNullExpr()) and
+ guardImpliesEqual(g1, b1, v, TAbsValNull())
+ )
+}
+
+private newtype TAbstractValue =
+ TAbsValNull() or
+ TAbsValInt(int i) { exists(CompileTimeConstantExpr c | c.getIntValue() = i) } or
+ TAbsValChar(string c) { exists(CharacterLiteral lit | lit.getValue() = c) } or
+ TAbsValString(string s) { exists(StringLiteral lit | lit.getValue() = s) } or
+ TAbsValEnum(EnumConstant c)
+
+/** The value of a constant expression. */
+private abstract class AbstractValue extends TAbstractValue {
+ abstract string toString();
+ /** Gets an expression whose value is this abstract value. */
+ abstract Expr getExpr();
+}
+
+private class AbsValNull extends AbstractValue, TAbsValNull {
+ override string toString() { result = "null" }
+ override Expr getExpr() { result = alwaysNullExpr() }
+}
+
+private class AbsValInt extends AbstractValue, TAbsValInt {
+ int i;
+ AbsValInt() { this = TAbsValInt(i) }
+ override string toString() { result = i.toString() }
+ override Expr getExpr() { result.(CompileTimeConstantExpr).getIntValue() = i }
+}
+
+private class AbsValChar extends AbstractValue, TAbsValChar {
+ string c;
+ AbsValChar() { this = TAbsValChar(c) }
+ override string toString() { result = c }
+ override Expr getExpr() { result.(CharacterLiteral).getValue() = c }
+}
+
+private class AbsValString extends AbstractValue, TAbsValString {
+ string s;
+ AbsValString() { this = TAbsValString(s) }
+ override string toString() { result = s }
+ override Expr getExpr() { result.(CompileTimeConstantExpr).getStringValue() = s }
+}
+
+private class AbsValEnum extends AbstractValue, TAbsValEnum {
+ EnumConstant c;
+ AbsValEnum() { this = TAbsValEnum(c) }
+ override string toString() { result = c.toString() }
+ override Expr getExpr() { result = c.getAnAccess() }
+}
+
+/**
+ * Holds if `v` can have a value that is not representable as an `AbstractValue`.
+ */
+private predicate hasPossibleUnknownValue(SsaVariable v) {
+ exists(SsaVariable def | v.getAnUltimateDefinition() = def |
+ def instanceof SsaImplicitUpdate or
+ def instanceof SsaImplicitInit or
+ exists(VariableUpdate upd | upd = def.(SsaExplicitUpdate).getDefiningExpr() |
+ not exists(upd.(VariableAssign).getSource())
+ ) or
+ exists(VariableAssign a, Expr e |
+ a = def.(SsaExplicitUpdate).getDefiningExpr() and
+ e = possibleValue(a.getSource()) and
+ not exists(AbstractValue val | val.getExpr() = e)
+ )
+ )
+}
+
+/**
+ * Gets a sub-expression of `e` whose value can flow to `e` through
+ * `ConditionalExpr`s. Parentheses are also removed.
+ */
+private Expr possibleValue(Expr e) {
+ result = possibleValue(e.(ParExpr).getExpr()) or
+ result = possibleValue(e.(ConditionalExpr).getTrueExpr()) or
+ result = possibleValue(e.(ConditionalExpr).getFalseExpr()) or
+ result = e and not e instanceof ParExpr and not e instanceof ConditionalExpr
+}
+
+/**
+ * Gets an ultimate definition of `v` that is not itself a phi node. The
+ * boolean `fromBackEdge` indicates whether the flow from `result` to `v` goes
+ * through a back edge.
+ */
+SsaVariable getADefinition(SsaVariable v, boolean fromBackEdge) {
+ result = v and not v instanceof SsaPhiNode and fromBackEdge = false or
+ exists(SsaVariable inp, BasicBlock bb, boolean fbe |
+ v.(SsaPhiNode).hasInputFromBlock(inp, bb) and
+ result = getADefinition(inp, fbe) and
+ (if v.getBasicBlock().bbDominates(bb) then fromBackEdge = true else fromBackEdge = fbe)
+ )
+}
+
+/**
+ * Holds if `e` equals `k` and may be assigned to `v`. The boolean
+ * `fromBackEdge` indicates whether the flow from `e` to `v` goes through a
+ * back edge.
+ */
+private predicate possibleValue(SsaVariable v, boolean fromBackEdge, Expr e, AbstractValue k) {
+ not hasPossibleUnknownValue(v) and
+ exists(SsaExplicitUpdate def |
+ def = getADefinition(v, fromBackEdge) and
+ e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource().getProperExpr()) and
+ k.getExpr() = e
+ )
+}
+
+/**
+ * Holds if `e` equals `k` and may be assigned to `v` without going through
+ * back edges, and all other possible ultimate definitions of `v` are different
+ * from `k`. The trivial case where `v` is an `SsaExplicitUpdate` with `e` as
+ * the only possible value is excluded.
+ */
+private predicate uniqueValue(SsaVariable v, Expr e, AbstractValue k) {
+ possibleValue(v, false, e, k) and
+ forex(Expr other, AbstractValue otherval | possibleValue(v, _, other, otherval) and other != e | otherval != k)
+}
+
+/**
+ * Holds if `guard` evaluating to `branch` implies that `v` equals `k`.
+ */
+private predicate guardImpliesEqual(Guard guard, boolean branch, SsaVariable v, AbstractValue k) {
+ guard.isEquality(v.getAUse(), k.getExpr(), branch)
+}
+
+private BasicBlock getBasicBlockOfGuard(Guard g) {
+ result = g.(Expr).getControlFlowNode().getBasicBlock() or
+ result = g.(SwitchCase).getSwitch().getExpr().getProperExpr().getControlFlowNode().getBasicBlock()
+}
+
+private ControlFlowNode getAGuardBranchSuccessor(Guard g, boolean branch) {
+ result = g.(Expr).getControlFlowNode().(ConditionNode).getABranchSuccessor(branch) or
+ result = g.(SwitchCase).getControlFlowNode() and branch = true
+}
+
+/**
+ * Holds if `v` is conditionally assigned `e` under the condition that `guard` evaluates to `branch`.
+ */
+private predicate conditionalAssign(SsaVariable v, Guard guard, boolean branch, Expr e) {
+ exists(ConditionalExpr c |
+ v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().getProperExpr() = c and
+ guard = c.getCondition().getProperExpr()
+ |
+ branch = true and e = c.getTrueExpr().getProperExpr() or
+ branch = false and e = c.getFalseExpr().getProperExpr()
+ ) or
+ exists(SsaExplicitUpdate upd, SsaPhiNode phi |
+ guard.directlyControls(upd.getBasicBlock(), branch) and
+ upd.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = e and
+ phi = v and
+ upd = phi.getAPhiInput() and
+ getBasicBlockOfGuard(guard).bbStrictlyDominates(phi.getBasicBlock()) and
+ not guard.directlyControls(phi.getBasicBlock(), branch) and
+ forall(SsaVariable other | other != upd and other = phi.getAPhiInput() |
+ guard.directlyControls(other.getBasicBlock(), branch.booleanNot()) or
+ other.getBasicBlock().bbDominates(getBasicBlockOfGuard(guard)) and
+ not other.isLiveAtEndOfBlock(getAGuardBranchSuccessor(guard, branch))
+ )
+ )
+}
+
+/**
+ * Holds if `v` is conditionally assigned `val` under the condition that `guard` evaluates to `branch`.
+ */
+private predicate conditionalAssignVal(SsaVariable v, Guard guard, boolean branch, AbstractValue val) {
+ conditionalAssign(v, guard, branch, val.getExpr())
+}
+
+private predicate relevantEq(SsaVariable v, AbstractValue val) {
+ conditionalAssignVal(v, _, _, val)
+}
+
+/**
+ * Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`.
+ */
+private predicate guardImpliesNotEqual1(Guard guard, boolean branch, SsaVariable v, AbstractValue val) {
+ relevantEq(v, val) and
+ (
+ guard.isEquality(v.getAUse(), val.getExpr(), branch.booleanNot())
+ or
+ exists(AbstractValue val2 |
+ guard.isEquality(v.getAUse(), val2.getExpr(), branch) and
+ val != val2
+ )
+ or
+ guard.(InstanceOfExpr).getExpr() = sameValue(v, _) and branch = true and val = TAbsValNull()
+ )
+}
+
+/**
+ * Holds if the evaluation of `guard` to `branch` implies that `v` does not have the value `val`.
+ */
+private predicate guardImpliesNotEqual2(Guard guard, boolean branch, SsaVariable v, AbstractValue val) {
+ relevantEq(v, val) and
+ (
+ guard = directNullGuard(v, branch, false) and val = TAbsValNull()
+ or
+ exists(int k |
+ guard = integerGuard(v.getAUse(), branch, k, false) and
+ val = TAbsValInt(k)
+ )
+ )
+}
+
diff --git a/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll b/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll
new file mode 100644
index 00000000000..444eb93bb9a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/controlflow/unreachableblocks/ExcludeDebuggingProfilingLogging.qll
@@ -0,0 +1,39 @@
+import java
+import semmle.code.java.controlflow.UnreachableBlocks
+
+/**
+ * Exclude from the unreachable block analysis constant fields that look like they are flags for
+ * controlling debugging, profiling or logging features.
+ *
+ * Debugging, profiling and logging flags that are compile time constants are usually intended to be
+ * toggled by the developer at compile time to provide extra information when developing the
+ * application, or when triaging a problem. By including this sub-class, blocks that are unreachable
+ * because they are guarded by a check of such a flag are considered reachable.
+ *
+ * Note: we explicitly limit this to debugging, profiling and logging flags. True feature toggles
+ * are treated as constant true/false, because it is much less likely that they are toggled in
+ * practice.
+ */
+class ExcludeDebuggingProfilingLogging extends ExcludedConstantField {
+ ExcludeDebuggingProfilingLogging() {
+ exists(string validFieldName |
+ validFieldName = "debug" or
+ validFieldName = "profiling" or
+ validFieldName = "profile" or
+ validFieldName = "time" or
+ validFieldName = "verbose" or
+ validFieldName = "report" or
+ validFieldName = "dbg" or
+ validFieldName = "timing" or
+ validFieldName = "assert" or
+ validFieldName = "log"
+ |
+ getName().regexpMatch(".*(?i)" + validFieldName + ".*")
+ ) and
+ // Boolean type
+ (
+ getType().hasName("boolean") or
+ getType().(BoxedType).hasQualifiedName("java.lang", "Boolean")
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow.qll
new file mode 100644
index 00000000000..fec7ba422ec
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DataFlow.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+
+import java
+
+module DataFlow {
+ import semmle.code.java.dataflow.internal.DataFlowImpl
+
+ /**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ * Four copies are available: `DataFlow` through `DataFlow4`.
+ */
+ private abstract
+ class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll
new file mode 100644
index 00000000000..93cd4e6dbe4
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DataFlow2.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+
+import java
+
+module DataFlow2 {
+ import semmle.code.java.dataflow.internal.DataFlowImpl2
+
+ /**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ * Four copies are available: `DataFlow` through `DataFlow4`.
+ */
+ private abstract
+ class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll
new file mode 100644
index 00000000000..0f68a15f45d
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DataFlow3.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+
+import java
+
+module DataFlow3 {
+ import semmle.code.java.dataflow.internal.DataFlowImpl3
+
+ /**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ * Four copies are available: `DataFlow` through `DataFlow4`.
+ */
+ private abstract
+ class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll
new file mode 100644
index 00000000000..29b8d97c616
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DataFlow4.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+
+import java
+
+module DataFlow4 {
+ import semmle.code.java.dataflow.internal.DataFlowImpl4
+
+ /**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ * Four copies are available: `DataFlow` through `DataFlow4`.
+ */
+ private abstract
+ class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll b/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll
new file mode 100644
index 00000000000..2d2c5afc2af
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DataFlow5.qll
@@ -0,0 +1,33 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) data flow analyses.
+ */
+
+import java
+
+module DataFlow5 {
+ import semmle.code.java.dataflow.internal.DataFlowImpl5
+
+ /**
+ * This class exists to prevent mutual recursion between the user-overridden
+ * member predicates of `Configuration` and the rest of the data-flow library.
+ * Good performance cannot be guaranteed in the presence of such recursion, so
+ * it should be replaced by using more than one copy of the data flow library.
+ * Four copies are available: `DataFlow` through `DataFlow4`.
+ */
+ private abstract
+ class ConfigurationRecursionPrevention extends Configuration {
+ bindingset[this]
+ ConfigurationRecursionPrevention() { any() }
+
+ override predicate hasFlow(Node source, Node sink) {
+ strictcount(Node n | this.isSource(n)) < 0
+ or
+ strictcount(Node n | this.isSink(n)) < 0
+ or
+ strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
+ or
+ super.hasFlow(source, sink)
+ }
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/DefUse.qll b/java/ql/src/semmle/code/java/dataflow/DefUse.qll
new file mode 100644
index 00000000000..11af7d88c7e
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/DefUse.qll
@@ -0,0 +1,54 @@
+/**
+ * Provides classes and predicates for def-use and use-use pairs. Built on top of the SSA library for
+ * maximal precision.
+ */
+
+import java
+private import SSA
+
+/**
+ * Holds if `use1` and `use2` form a use-use-pair of the same SSA variable,
+ * that is, the value read in `use1` can reach `use2` without passing through
+ * any SSA definition of the variable.
+ *
+ * This is the transitive closure of `adjacentUseUseSameVar`.
+ */
+predicate useUsePairSameVar(RValue use1, RValue use2) {
+ adjacentUseUseSameVar+(use1, use2)
+}
+
+/**
+ * Holds if `use1` and `use2` form a use-use-pair of the same
+ * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2`
+ * without passing through any SSA definition of the variable except for phi
+ * nodes and uncertain implicit updates.
+ *
+ * This is the transitive closure of `adjacentUseUse`.
+ */
+predicate useUsePair(RValue use1, RValue use2) {
+ adjacentUseUse+(use1, use2)
+}
+
+/**
+ * Holds if there exists a path from `def` to `use` without passing through another
+ * `VariableUpdate` of the `LocalScopeVariable` that they both refer to.
+ *
+ * Other paths may also exist, so the SSA variables in `def` and `use` can be different.
+ */
+predicate defUsePair(VariableUpdate def, RValue use) {
+ exists(SsaVariable v |
+ v.getAUse() = use and v.getAnUltimateDefinition().(SsaExplicitUpdate).getDefiningExpr() = def
+ )
+}
+
+/**
+ * Holds if there exists a path from the entry-point of the callable to `use` without
+ * passing through a `VariableUpdate` of the parameter `p` that `use` refers to.
+ *
+ * Other paths may also exist, so the SSA variables can be different.
+ */
+predicate parameterDefUsePair(Parameter p, RValue use) {
+ exists(SsaVariable v |
+ v.getAUse() = use and v.getAnUltimateDefinition().(SsaImplicitInit).isParameterDefinition(p)
+ )
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/FlowSources.qll b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll
new file mode 100644
index 00000000000..85f4d8677e7
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/FlowSources.qll
@@ -0,0 +1,225 @@
+import java
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DefUse
+import semmle.code.java.frameworks.Jdbc
+import semmle.code.java.frameworks.Networking
+import semmle.code.java.frameworks.Properties
+import semmle.code.java.frameworks.Rmi
+import semmle.code.java.frameworks.Servlets
+import semmle.code.java.frameworks.ApacheHttp
+import semmle.code.java.frameworks.android.XmlParsing
+import semmle.code.java.frameworks.android.WebView
+import semmle.code.java.frameworks.JaxWS
+import semmle.code.java.frameworks.android.Intent
+
+/** Class for `tainted` user input. */
+abstract class UserInput extends DataFlow::Node {}
+
+private predicate variableStep(Expr tracked, VarAccess sink) {
+ exists(VariableAssign def |
+ def.getSource() = tracked and
+ defUsePair(def, sink)
+ )
+}
+
+/** Input that may be controlled by a remote user. */
+class RemoteUserInput extends UserInput {
+ RemoteUserInput() {
+ this.asExpr().(MethodAccess).getMethod() instanceof RemoteTaintedMethod
+ or
+ // Parameters to RMI methods.
+ exists(RemoteCallableMethod method |
+ method.getAParameter() = this.asParameter() and
+ (
+ getType() instanceof PrimitiveType or
+ getType() instanceof TypeString
+ )
+ )
+ or
+ // Parameters to Jax WS methods.
+ exists(JaxWsEndpoint endpoint |
+ endpoint.getARemoteMethod().getAParameter() = this.asParameter()
+ )
+ or
+ // Parameters to Jax Rs methods.
+ exists(JaxRsResourceClass service |
+ service.getAnInjectableCallable().getAParameter() = this.asParameter() or
+ service.getAnInjectableField().getAnAccess() = this.asExpr()
+ )
+ or
+ // Reverse DNS. Try not to trigger on `localhost`.
+ exists(MethodAccess m | m = this.asExpr() |
+ m.getMethod() instanceof ReverseDNSMethod and
+ not exists(MethodAccess l |
+ (variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
+ l.getMethod().getName() = "getLocalHost"
+ )
+ )
+ or
+ //MessageBodyReader
+ exists(MessageBodyReaderRead m |
+ m.getParameter(4) = this.asParameter() or
+ m.getParameter(5) = this.asParameter()
+ )
+ }
+
+ /**
+ * DEPRECATED: Use a configuration with a defined sink instead.
+ *
+ * Holds if taint can flow from this `RemoteUserInput` to `sink`.
+ *
+ * In addition to the basic taint flow, this allows a path to end in a number
+ * of steps through instance fields.
+ */
+ deprecated
+ predicate flowsTo(DataFlow::Node sink) {
+ remoteUserInputFlow(this, sink)
+ }
+}
+
+/**
+ * Holds if taint can flow from `node1` to `node2` in either one local step or
+ * through an instance field.
+ */
+private predicate localInstanceFieldStep(DataFlow::Node node1, DataFlow::Node node2) {
+ TaintTracking::localTaintStep(node1, node2) or
+ exists(InstanceField field |
+ node1.asExpr() = field.getAnAssignedValue() or
+ exists(Assignment assign | assign.getRhs() = node1.asExpr() |
+ assign.getDest().(ArrayAccess).getArray() = field.getAnAccess()
+ )
+ |
+ node2.asExpr() = field.getAnAccess()
+ )
+}
+
+private module RemoteUserInputFlow {
+ private import semmle.code.java.dataflow.internal.DataFlowImplDepr
+ private import semmle.code.java.security.SecurityTests
+ private import semmle.code.java.security.Validation
+
+ deprecated
+ class RemoteUserInputConfig extends Configuration {
+ RemoteUserInputConfig() { this = "FlowSources.qll:RemoteUserInputConfig" }
+ override
+ predicate isSource(DataFlow::Node source) { source instanceof RemoteUserInput }
+ override
+ predicate isSink(DataFlow::Node sink) { any() }
+ override int fieldFlowBranchLimit() { result = 0 }
+ override predicate isBarrier(DataFlow::Node node) {
+ // Ignore paths through test code.
+ node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
+ exists(ValidatedVariable var | node.asExpr() = var.getAnAccess())
+ }
+ override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ TaintTracking::localAdditionalTaintStep(node1, node2)
+ }
+ }
+}
+
+cached
+deprecated
+private predicate remoteUserInputFlow(RemoteUserInput src, DataFlow::Node sink) {
+ any(RemoteUserInputFlow::RemoteUserInputConfig config).hasFlow(src, sink) or
+ exists(DataFlow::Node mid |
+ remoteUserInputFlow(src, mid) and
+ localInstanceFieldStep(mid, sink)
+ )
+}
+
+/** Input that may be controlled by a local user. */
+abstract class LocalUserInput extends UserInput {}
+
+class EnvInput extends LocalUserInput {
+ EnvInput() {
+ // Parameters to a main method.
+ exists(MainMethod main | this.asParameter() = main.getParameter(0))
+ or
+ // Args4j arguments.
+ exists(Field f | this.asExpr() = f.getAnAccess() | f.getAnAnnotation().getType().getQualifiedName() = "org.kohsuke.args4j.Argument")
+ or
+ // Results from various specific methods.
+ this.asExpr().(MethodAccess).getMethod() instanceof EnvTaintedMethod
+ or
+ // Access to `System.in`.
+ exists(Field f | this.asExpr() = f.getAnAccess() | f instanceof SystemIn)
+ or
+ // Access to files.
+ this.asExpr().(ConstructorCall).getConstructedType().hasQualifiedName("java.io", "FileInputStream")
+ }
+}
+
+class DatabaseInput extends LocalUserInput {
+ DatabaseInput() {
+ this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod
+ }
+}
+
+private
+class RemoteTaintedMethod extends Method {
+ RemoteTaintedMethod() {
+ this instanceof ServletRequestGetParameterMethod or
+ this instanceof ServletRequestGetParameterMapMethod or
+ this instanceof ServletRequestGetParameterNamesMethod or
+ this instanceof HttpServletRequestGetQueryStringMethod or
+ this instanceof HttpServletRequestGetHeaderMethod or
+ this instanceof HttpServletRequestGetPathMethod or
+ this instanceof HttpServletRequestGetHeadersMethod or
+ this instanceof HttpServletRequestGetHeaderNamesMethod or
+ this instanceof HttpServletRequestGetRequestURIMethod or
+ this instanceof HttpServletRequestGetRequestURLMethod or
+ this instanceof HttpServletRequestGetRemoteUserMethod or
+ this instanceof ServletRequestGetBodyMethod or
+ this instanceof CookieGetValueMethod or
+ this instanceof CookieGetNameMethod or
+ this instanceof CookieGetCommentMethod or
+ this instanceof URLConnectionGetInputStreamMethod or
+ this instanceof SocketGetInputStreamMethod or
+ this instanceof ApacheHttpGetParams or
+ this instanceof ApacheHttpEntityGetContent or
+ // In the setting of Android we assume that XML has been transmitted over
+ // the network, so may be tainted.
+ this instanceof XmlPullGetMethod or
+ this instanceof XmlAttrSetGetMethod or
+ // The current URL in a browser may be untrusted or uncontrolled.
+ this instanceof WebViewGetUrlMethod
+ }
+}
+
+private
+class EnvTaintedMethod extends Method {
+ EnvTaintedMethod() {
+ this instanceof MethodSystemGetenv or
+ this instanceof PropertiesGetPropertyMethod or
+ this instanceof MethodSystemGetProperty
+ }
+}
+
+class TypeInetAddr extends RefType {
+ TypeInetAddr() {
+ this.getQualifiedName() = "java.net.InetAddress"
+ }
+}
+
+class ReverseDNSMethod extends Method {
+ ReverseDNSMethod() {
+ this.getDeclaringType() instanceof TypeInetAddr and
+ (
+ this.getName() = "getHostName" or
+ this.getName() = "getCanonicalHostName"
+ )
+ }
+}
+
+/** Android `Intent` that may have come from a hostile application. */
+class AndroidIntentInput extends DataFlow::Node {
+ AndroidIntentInput() {
+ exists(MethodAccess ma, AndroidGetIntentMethod m | ma.getMethod().overrides*(m) and
+ this.asExpr() = ma
+ ) or
+ exists(Method m, AndroidReceiveIntentMethod rI | m.overrides*(rI) and
+ this.asParameter() = m.getParameter(1)
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/Guards.qll b/java/ql/src/semmle/code/java/dataflow/Guards.qll
new file mode 100644
index 00000000000..90685bfbfac
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/Guards.qll
@@ -0,0 +1,65 @@
+import java
+private import semmle.code.java.controlflow.Guards as Guards
+private import semmle.code.java.controlflow.Dominance
+
+/**
+ * DEPRECATED: Use semmle.code.java.controlflow.Guards instead.
+ *
+ * A basic block that terminates in a condition, splitting the subsequent control flow.
+ */
+deprecated
+class ConditionBlock = Guards::ConditionBlock;
+
+/** Holds if `n` updates the locally scoped variable `v`. */
+deprecated
+predicate variableUpdate(ControlFlowNode n, LocalScopeVariable v) {
+ exists(VariableUpdate a | a = n | a.getDestVar() = v)
+}
+
+/** Holds if `bb` updates the locally scoped variable `v`. */
+deprecated private predicate variableUpdateBB(BasicBlock bb, LocalScopeVariable v) {
+ variableUpdate(bb.getANode(), v)
+}
+
+/** Indicates the position of phi-nodes in an SSA representation. */
+deprecated private predicate needPhiNode(BasicBlock bb, LocalScopeVariable v) {
+ exists(BasicBlock def | dominanceFrontier(def, bb) |
+ variableUpdateBB(def, v) or needPhiNode(def, v)
+ )
+}
+
+/** Locally scoped variable `v` occurs in the condition of `cb`. */
+deprecated private predicate relevantVar(ConditionBlock cb, LocalScopeVariable v) {
+ v.getAnAccess() = cb.getCondition().getAChildExpr*()
+}
+
+/** Blocks controlled by the condition in `cb` for which `v` is unchanged. */
+deprecated private predicate controlsBlockWithSameVar(ConditionBlock cb, boolean testIsTrue, LocalScopeVariable v, BasicBlock controlled) {
+ cb.controls(controlled, testIsTrue) and
+ relevantVar(cb, v) and
+ not needPhiNode(controlled, v) and
+ (
+ controlled = cb.getTestSuccessor(testIsTrue)
+ or
+ exists(BasicBlock mid |
+ controlsBlockWithSameVar(cb, testIsTrue, v, mid) and
+ not variableUpdateBB(mid, v) and
+ controlled = mid.getABBSuccessor()
+ )
+ )
+}
+
+/**
+ * DEPRECATED: Use semmle.code.java.dataflow.SSA instead.
+ *
+ * Statements controlled by the condition in `s` for which `v` is unchanged (`v` is the same SSA
+ * variable in both `s` and `controlled`). The condition in `s` must contain an access of `v`.
+ */
+deprecated
+predicate controlsNodeWithSameVar(ConditionNode cn, boolean testIsTrue, LocalScopeVariable v, ControlFlowNode controlled) {
+ exists(ConditionBlock cb, BasicBlock controlledBB, int i |
+ cb.getConditionNode() = cn and
+ controlsBlockWithSameVar(cb, testIsTrue, v, controlledBB) and
+ controlled = controlledBB.getNode(i) and
+ not exists(ControlFlowNode update, int j | update = controlledBB.getNode(j) and j < i and variableUpdate(update, v)))
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll b/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll
new file mode 100644
index 00000000000..4ac73ebbaf5
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/InstanceAccess.qll
@@ -0,0 +1,259 @@
+/**
+ * Provides classes and predicates for reasoning about explicit and implicit
+ * instance accesses.
+ */
+import java
+
+/**
+ * Holds if `cc` constructs an inner class that holds a reference to its
+ * enclosing class `t` and the enclosing instance is not given explicitly as a
+ * qualifier of the constructor.
+ */
+private predicate implicitSetEnclosingInstance(ConstructorCall cc, RefType t) {
+ exists(InnerClass ic |
+ ic = cc.getConstructedType().getSourceDeclaration() and
+ ic.hasEnclosingInstance() and
+ ic.getEnclosingType() = t and
+ not cc instanceof ThisConstructorInvocationStmt and
+ not exists(cc.getQualifier())
+ )
+}
+
+/**
+ * Holds if `cc` implicitly sets the enclosing instance of the constructed
+ * inner class to `this`.
+ */
+private predicate implicitSetEnclosingInstanceToThis(ConstructorCall cc) {
+ exists(RefType t |
+ implicitSetEnclosingInstance(cc, t) and
+ cc.getEnclosingCallable().getDeclaringType().getASourceSupertype*() = t
+ )
+}
+
+/**
+ * Gets the closest enclosing type of `ic` that is also a subtype of `t`.
+ */
+private RefType getEnclosing(InnerClass ic, RefType t) {
+ exists(RefType enclosing | enclosing = ic.getEnclosingType() |
+ if enclosing.getASourceSupertype*() = t then
+ result = enclosing
+ else
+ result = getEnclosing(enclosing, t)
+ )
+}
+
+/**
+ * Holds if `cc` implicitly sets the enclosing instance of type `t2` of the
+ * constructed inner class to `t1.this`.
+ */
+private predicate implicitEnclosingThisCopy(ConstructorCall cc, RefType t1, RefType t2) {
+ implicitSetEnclosingInstance(cc, t2) and
+ not implicitSetEnclosingInstanceToThis(cc) and
+ t1 = getEnclosing(cc.getEnclosingCallable().getDeclaringType(), t2)
+}
+
+/**
+ * Holds if an enclosing instance of the form `t.this` is accessed by `e`.
+ */
+private predicate enclosingInstanceAccess(ExprParent e, RefType t) {
+ e.(InstanceAccess).isEnclosingInstanceAccess(t) or
+ exists(MethodAccess ma | ma.isEnclosingMethodAccess(t) and ma = e and not exists(ma.getQualifier())) or
+ exists(FieldAccess fa | fa.isEnclosingFieldAccess(t) and fa = e and not exists(fa.getQualifier())) or
+ implicitEnclosingThisCopy(e, t, _)
+}
+
+/**
+ * Holds if an enclosing instance of the form `t2.this` is accessed by `e`, and
+ * this desugars into `this.enclosing.enclosing...enclosing`. The prefix of the
+ * desugared access with `i` enclosing instance field accesses has type `t1`.
+ */
+private predicate derivedInstanceAccess(ExprParent e, int i, RefType t1, RefType t2) {
+ enclosingInstanceAccess(e, t2) and
+ i = 0 and
+ exists(Callable c | c = e.(Expr).getEnclosingCallable() or c = e.(Stmt).getEnclosingCallable() | t1 = c.getDeclaringType())
+ or
+ exists(InnerClass ic |
+ derivedInstanceAccess(e, i - 1, ic, t2) and
+ ic.getEnclosingType() = t1 and
+ ic != t2
+ )
+}
+
+cached
+private newtype TInstanceAccessExt =
+ TExplicitInstanceAccess(InstanceAccess ia) or
+ TThisQualifier(FieldAccess fa) {
+ fa.isOwnFieldAccess() and not exists(fa.getQualifier())
+ } or
+ TThisArgument(Call c) {
+ c instanceof ThisConstructorInvocationStmt or
+ c instanceof SuperConstructorInvocationStmt or
+ c.(MethodAccess).isOwnMethodAccess() and not exists(c.getQualifier())
+ } or
+ TThisEnclosingInstanceCapture(ConstructorCall cc) {
+ implicitSetEnclosingInstanceToThis(cc)
+ } or
+ TEnclosingInstanceAccess(ExprParent e, RefType t) {
+ enclosingInstanceAccess(e, t) and not e instanceof InstanceAccess
+ } or
+ TInstanceAccessQualifier(ExprParent e, int i, RefType t1, RefType t2) {
+ derivedInstanceAccess(e, i, t1, t2) and t1 != t2
+ }
+
+/**
+ * A generalization of `InstanceAccess` that includes implicit accesses.
+ *
+ * The accesses can be divided into 6 kinds:
+ * - Explicit: Represented by an `InstanceAccess`.
+ * - Implicit field qualifier: The implicit access associated with an
+ * unqualified `FieldAccess` to a non-static field.
+ * - Implicit method qualifier: The implicit access associated with an
+ * unqualified `MethodAccess` to a non-static method.
+ * - Implicit this constructor argument: The implicit argument of the value of
+ * `this` to a constructor call of the form `this()` or `super()`.
+ * - Implicit enclosing instance capture: The implicit capture of the value of
+ * the directly enclosing instance of a constructed inner class. This is
+ * associated with an unqualified constructor call.
+ * - Implicit enclosing instance qualifier: The instance access that occurs as
+ * the implicit qualifier of a desugared enclosing instance access.
+ *
+ * Of these 6 kinds, the fourth (implicit this constructor argument) is always
+ * an `OwnInstanceAccess`, whereas the other 5 can be either `OwnInstanceAccess`
+ * or `EnclosingInstanceAccess`.
+ */
+class InstanceAccessExt extends TInstanceAccessExt {
+ private string ppBase() {
+ exists(EnclosingInstanceAccess enc | enc = this |
+ result = enc.getQualifier().toString() + "(" + enc.getType() + ")enclosing"
+ ) or
+ isOwnInstanceAccess() and result = "this"
+ }
+
+ private string ppKind() {
+ isExplicit(_) and result = " <" + getAssociatedExprOrStmt().toString() + ">" or
+ isImplicitFieldQualifier(_) and result = " <.field>" or
+ isImplicitMethodQualifier(_) and result = " <.method>" or
+ isImplicitThisConstructorArgument(_) and result = " " or
+ isImplicitEnclosingInstanceCapture(_) and result = " <.new>" or
+ isImplicitEnclosingInstanceQualifier(_) and result = "."
+ }
+
+ /** Gets a textual representation of this element. */
+ string toString() {
+ result = ppBase() + ppKind()
+ }
+
+ /** Gets the source location for this element. */
+ Location getLocation() { result = getAssociatedExprOrStmt().getLocation() }
+
+ private ExprParent getAssociatedExprOrStmt() {
+ this = TExplicitInstanceAccess(result) or
+ this = TThisQualifier(result) or
+ this = TThisArgument(result) or
+ this = TThisEnclosingInstanceCapture(result) or
+ this = TEnclosingInstanceAccess(result, _) or
+ this = TInstanceAccessQualifier(result, _, _, _)
+ }
+
+ /** Gets the callable in which this instance access occurs. */
+ Callable getEnclosingCallable() {
+ result = getAssociatedExprOrStmt().(Expr).getEnclosingCallable() or
+ result = getAssociatedExprOrStmt().(Stmt).getEnclosingCallable()
+ }
+
+ /** Holds if this is the explicit instance access `ia`. */
+ predicate isExplicit(InstanceAccess ia) { this = TExplicitInstanceAccess(ia) }
+
+ /** Holds if this is the implicit qualifier of `fa`. */
+ predicate isImplicitFieldQualifier(FieldAccess fa) {
+ this = TThisQualifier(fa) or
+ this = TEnclosingInstanceAccess(fa, _)
+ }
+
+ /** Holds if this is the implicit qualifier of `ma`. */
+ predicate isImplicitMethodQualifier(MethodAccess ma) {
+ this = TThisArgument(ma) or
+ this = TEnclosingInstanceAccess(ma, _)
+ }
+
+ /**
+ * Holds if this is the implicit `this` argument of `cc`, which is either a
+ * `ThisConstructorInvocationStmt` or a `SuperConstructorInvocationStmt`.
+ */
+ predicate isImplicitThisConstructorArgument(ConstructorCall cc) {
+ this = TThisArgument(cc)
+ }
+
+ /** Holds if this is the implicit qualifier of `cc`.*/
+ predicate isImplicitEnclosingInstanceCapture(ConstructorCall cc) {
+ this = TThisEnclosingInstanceCapture(cc) or
+ this = TEnclosingInstanceAccess(cc, _)
+ }
+
+ /**
+ * Holds if this is the implicit qualifier of the desugared enclosing
+ * instance access `enc`.
+ */
+ predicate isImplicitEnclosingInstanceQualifier(EnclosingInstanceAccess enc) {
+ enc.getQualifier() = this
+ }
+
+ /** Holds if this is an access to an object's own instance. */
+ predicate isOwnInstanceAccess() {
+ not isEnclosingInstanceAccess(_)
+ }
+
+ /** Holds if this is an access to an enclosing instance. */
+ predicate isEnclosingInstanceAccess(RefType t) {
+ exists(InstanceAccess ia | this = TExplicitInstanceAccess(ia) and ia.isEnclosingInstanceAccess(t)) or
+ this = TEnclosingInstanceAccess(_, t) or
+ exists(int i | this = TInstanceAccessQualifier(_, i, t, _) and i > 0)
+ }
+
+ /** Gets the type of this instance access. */
+ RefType getType() {
+ isEnclosingInstanceAccess(result) or
+ isOwnInstanceAccess() and result = getEnclosingCallable().getDeclaringType()
+ }
+
+ /** Gets the control flow node associated with this instance access. */
+ ControlFlowNode getCfgNode() {
+ exists(ExprParent e | e = getAssociatedExprOrStmt() |
+ e instanceof Call and result = e or
+ e instanceof InstanceAccess and result = e or
+ exists(FieldAccess fa | fa = e |
+ if fa instanceof RValue then fa = result
+ else result.(AssignExpr).getDest() = fa
+ )
+ )
+ }
+}
+
+/**
+ * An access to an object's own instance.
+ */
+class OwnInstanceAccess extends InstanceAccessExt {
+ OwnInstanceAccess() { isOwnInstanceAccess() }
+}
+
+/**
+ * An access to an enclosing instance.
+ */
+class EnclosingInstanceAccess extends InstanceAccessExt {
+ EnclosingInstanceAccess() { isEnclosingInstanceAccess(_) }
+
+ /** Gets the implicit qualifier of this in the desugared representation. */
+ InstanceAccessExt getQualifier() {
+ exists(ExprParent e, int i |
+ result = TInstanceAccessQualifier(e, i, _, _)
+ |
+ this = TInstanceAccessQualifier(e, i + 1, _, _) or
+ exists(RefType t |
+ derivedInstanceAccess(e, i + 1, t, t)
+ |
+ this = TEnclosingInstanceAccess(e, t) or
+ this = TExplicitInstanceAccess(e)
+ )
+ )
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll
new file mode 100644
index 00000000000..8bc7ba9a871
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll
@@ -0,0 +1,101 @@
+/**
+ * Provides classes and predicates for integer guards.
+ */
+
+import java
+private import SSA
+private import RangeUtils
+private import RangeAnalysis
+
+/** Gets an expression that might have the value `i`. */
+private Expr exprWithIntValue(int i) {
+ result.(ConstantIntegerExpr).getIntValue() = i or
+ result.(ParExpr).getExpr() = exprWithIntValue(i) or
+ result.(ConditionalExpr).getTrueExpr() = exprWithIntValue(i) or
+ result.(ConditionalExpr).getFalseExpr() = exprWithIntValue(i)
+}
+
+/**
+ * An expression for which the predicate `integerGuard` is relevant.
+ * This includes `RValue` and `MethodAccess`.
+ */
+class IntComparableExpr extends Expr {
+ IntComparableExpr() {
+ this instanceof RValue or this instanceof MethodAccess
+ }
+
+ /** Gets an integer that is directly assigned to the expression in case of a variable; or zero. */
+ int relevantInt() {
+ exists(SsaExplicitUpdate ssa, SsaSourceVariable v |
+ this = v.getAnAccess() and
+ ssa.getSourceVariable() = v and
+ ssa.getDefiningExpr().(VariableAssign).getSource() = exprWithIntValue(result)
+ ) or
+ result = 0
+ }
+}
+
+/**
+ * An expression that directly tests whether a given expression is equal to `k` or not.
+ * The set of `k`s is restricted to those that are relevant for the expression or
+ * have a direct comparison with the expression.
+ *
+ * If `result` evaluates to `branch`, then `e` is guaranteed to be equal to `k` if `is_k`
+ * is true, and different from `k` if `is_k` is false.
+ */
+pragma[nomagic]
+Expr integerGuard(IntComparableExpr e, boolean branch, int k, boolean is_k) {
+ exists(EqualityTest eqtest, boolean polarity |
+ eqtest = result and
+ eqtest.hasOperands(e, any(ConstantIntegerExpr c | c.getIntValue() = k)) and
+ polarity = eqtest.polarity() and
+ (branch = true and is_k = polarity or branch = false and is_k = polarity.booleanNot())
+ ) or
+ exists(EqualityTest eqtest, int val, Expr c, boolean upper |
+ k = e.relevantInt() and
+ eqtest = result and
+ eqtest.hasOperands(e, c) and
+ bounded(c, any(ZeroBound zb), val, upper, _) and
+ is_k = false and
+ (upper = true and val < k or upper = false and val > k) and
+ branch = eqtest.polarity()
+ ) or
+ exists(ComparisonExpr comp, Expr c, int val, boolean upper |
+ k = e.relevantInt() and
+ comp = result and
+ comp.hasOperands(e, c) and
+ bounded(c, any(ZeroBound zb), val, upper, _) and
+ is_k = false
+ |
+ comp.getLesserOperand() = c and comp.isStrict() and branch = true and val >= k and upper = false or // k <= val <= c < e, so e != k
+ comp.getLesserOperand() = c and comp.isStrict() and branch = false and val < k and upper = true or
+ comp.getLesserOperand() = c and not comp.isStrict() and branch = true and val > k and upper = false or
+ comp.getLesserOperand() = c and not comp.isStrict() and branch = false and val <= k and upper = true or
+ comp.getGreaterOperand() = c and comp.isStrict() and branch = true and val <= k and upper = true or
+ comp.getGreaterOperand() = c and comp.isStrict() and branch = false and val > k and upper = false or
+ comp.getGreaterOperand() = c and not comp.isStrict() and branch = true and val < k and upper = true or
+ comp.getGreaterOperand() = c and not comp.isStrict() and branch = false and val >= k and upper = false
+ )
+}
+
+/**
+ * A guard that splits the values of a variable into one range with an upper bound of `k-1`
+ * and one with a lower bound of `k`.
+ *
+ * If `branch_with_lower_bound_k` is true then `result` is equivalent to `k <= x`
+ * and if it is false then `result` is equivalent to `k > x`.
+ */
+Expr intBoundGuard(RValue x, boolean branch_with_lower_bound_k, int k) {
+ exists(ComparisonExpr comp, ConstantIntegerExpr c, int val |
+ comp = result and
+ comp.hasOperands(x, c) and
+ c.getIntValue() = val and
+ x.getVariable().getType() instanceof IntegralType
+ |
+ comp.getLesserOperand().getProperExpr() = c and comp.isStrict() and branch_with_lower_bound_k = true and val + 1 = k or // c < x
+ comp.getLesserOperand().getProperExpr() = c and not comp.isStrict() and branch_with_lower_bound_k = true and val = k or // c <= x
+ comp.getGreaterOperand().getProperExpr() = c and comp.isStrict() and branch_with_lower_bound_k = false and val = k or // x < c
+ comp.getGreaterOperand().getProperExpr() = c and not comp.isStrict() and branch_with_lower_bound_k = false and val + 1 = k // x <= c
+ )
+}
+
diff --git a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll
new file mode 100644
index 00000000000..92ed6d4b683
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll
@@ -0,0 +1,217 @@
+/**
+ * Provides classes and predicates for null guards.
+ */
+
+import java
+import SSA
+private import semmle.code.java.controlflow.internal.GuardsLogic
+private import RangeUtils
+private import IntegerGuards
+
+/** Gets an expression that is always `null`. */
+Expr alwaysNullExpr() {
+ result instanceof NullLiteral or
+ result.(ParExpr).getExpr() = alwaysNullExpr() or
+ result.(CastExpr).getExpr() = alwaysNullExpr()
+}
+
+/** Gets an equality test between an expression `e` and an enum constant `c`. */
+Expr enumConstEquality(Expr e, boolean polarity, EnumConstant c) {
+ exists(EqualityTest eqtest |
+ eqtest = result and
+ eqtest.hasOperands(e, c.getAnAccess()) and
+ polarity = eqtest.polarity()
+ )
+}
+
+/** Gets an expression that is provably not `null`. */
+Expr clearlyNotNullExpr(Expr reason) {
+ result instanceof ClassInstanceExpr and reason = result or
+ result instanceof ArrayCreationExpr and reason = result or
+ result instanceof TypeLiteral and reason = result or
+ result instanceof ThisAccess and reason = result or
+ result instanceof StringLiteral and reason = result or
+ result instanceof AddExpr and result.getType() instanceof TypeString and reason = result or
+ exists(Field f |
+ result = f.getAnAccess() and
+ (f.hasName("TRUE") or f.hasName("FALSE")) and
+ f.getDeclaringType().hasQualifiedName("java.lang", "Boolean") and
+ reason = result
+ ) or
+ result.(ParExpr).getExpr() = clearlyNotNullExpr(reason) or
+ result.(CastExpr).getExpr() = clearlyNotNullExpr(reason) or
+ result.(AssignExpr).getSource() = clearlyNotNullExpr(reason) or
+ exists(ConditionalExpr c, Expr r1, Expr r2 |
+ c = result and
+ c.getTrueExpr() = clearlyNotNullExpr(r1) and
+ c.getFalseExpr() = clearlyNotNullExpr(r2) and
+ (reason = r1 or reason = r2)
+ ) or
+ exists(SsaVariable v, boolean branch, RValue rval, Guard guard |
+ guard = directNullGuard(v, branch, false) and
+ guard.controls(rval.getBasicBlock(), branch) and
+ reason = guard and
+ rval = v.getAUse() and
+ result = rval
+ ) or
+ exists(SsaVariable v | clearlyNotNull(v, reason) and result = v.getAUse())
+}
+
+/** Holds if `v` is an SSA variable that is provably not `null`. */
+predicate clearlyNotNull(SsaVariable v, Expr reason) {
+ exists(Expr src |
+ src = v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() and
+ src = clearlyNotNullExpr(reason)
+ ) or
+ exists(CatchClause cc, LocalVariableDeclExpr decl |
+ decl = cc.getVariable() and
+ decl = v.(SsaExplicitUpdate).getDefiningExpr() and
+ reason = decl
+ ) or
+ exists(SsaVariable captured |
+ v.(SsaImplicitInit).captures(captured) and
+ clearlyNotNull(captured, reason)
+ )
+}
+
+/** Gets an expression that is provably not `null`. */
+Expr clearlyNotNullExpr() {
+ result = clearlyNotNullExpr(_)
+}
+
+/** Holds if `v` is an SSA variable that is provably not `null`. */
+predicate clearlyNotNull(SsaVariable v) {
+ clearlyNotNull(v, _)
+}
+
+/**
+ * Gets an expression that directly tests whether a given expression, `e`, is null or not.
+ *
+ * If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull`
+ * is true, and non-null if `isnull` is false.
+ */
+Expr basicNullGuard(Expr e, boolean branch, boolean isnull) {
+ exists(EqualityTest eqtest, boolean polarity |
+ eqtest = result and
+ eqtest.hasOperands(e, any(NullLiteral n)) and
+ polarity = eqtest.polarity() and
+ (branch = true and isnull = polarity or branch = false and isnull = polarity.booleanNot())
+ ) or
+ result.(InstanceOfExpr).getExpr() = e and branch = true and isnull = false or
+ exists(MethodAccess call, Method m, boolean polarity |
+ call = result and
+ call.getAnArgument() = e and
+ call.getMethod() = m and
+ m.getDeclaringType().hasQualifiedName("java.util", "Objects") and
+ (m.hasName("isNull") and polarity = true or m.hasName("nonNull") and polarity = false) and
+ (branch = true and isnull = polarity or branch = false and isnull = polarity.booleanNot())
+ ) or
+ exists(MethodAccess call |
+ call = result and
+ call.getAnArgument() = e and
+ call.getMethod() instanceof EqualsMethod and
+ branch = true and
+ isnull = false
+ ) or
+ exists(EqualityTest eqtest |
+ eqtest = result and
+ eqtest.hasOperands(e, clearlyNotNullExpr()) and
+ isnull = false and
+ branch = eqtest.polarity()
+ ) or
+ result = enumConstEquality(e, branch, _) and isnull = false
+}
+
+/**
+ * Gets an expression that directly tests whether a given expression, `e`, is null or not.
+ *
+ * If `result` evaluates to `branch`, then `e` is guaranteed to be null if `isnull`
+ * is true, and non-null if `isnull` is false.
+ */
+Expr basicOrCustomNullGuard(Expr e, boolean branch, boolean isnull) {
+ result = basicNullGuard(e, branch, isnull) or
+ exists(MethodAccess call, Method m, int ix |
+ call = result and
+ call.getArgument(ix) = e and
+ call.getMethod().getSourceDeclaration() = m and
+ m = customNullGuard(ix, branch, isnull)
+ )
+}
+
+/**
+ * Gets an expression that directly tests whether a given SSA variable is null or not.
+ *
+ * If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
+ * is true, and non-null if `isnull` is false.
+ */
+Expr directNullGuard(SsaVariable v, boolean branch, boolean isnull) {
+ result = basicOrCustomNullGuard(sameValue(v, _), branch, isnull)
+}
+
+/**
+ * Gets a `Guard` that tests (possibly indirectly) whether a given SSA variable is null or not.
+ *
+ * If `result` evaluates to `branch`, then `v` is guaranteed to be null if `isnull`
+ * is true, and non-null if `isnull` is false.
+ */
+Guard nullGuard(SsaVariable v, boolean branch, boolean isnull) {
+ result = directNullGuard(v, branch, isnull) or
+ exists(boolean branch0 | implies_v3(result, branch, nullGuard(v, branch0, isnull), branch0))
+}
+
+/**
+ * A return statement that on a return value of `retval` allows the conclusion that the
+ * parameter `p` either is null or non-null as specified by `isnull`.
+ */
+private predicate validReturnInCustomNullGuard(ReturnStmt ret, Parameter p, boolean retval, boolean isnull) {
+ exists(Method m |
+ ret.getEnclosingCallable() = m and
+ p.getCallable() = m and
+ m.getReturnType().(PrimitiveType).hasName("boolean")
+ ) and
+ exists(SsaImplicitInit ssa | ssa.isParameterDefinition(p) |
+ nullGuardedReturn(ret, ssa, isnull) and
+ (retval = true or retval = false)
+ or
+ exists(Expr res | res = ret.getResult() |
+ res = nullGuard(ssa, retval, isnull)
+ )
+ )
+}
+
+private predicate nullGuardedReturn(ReturnStmt ret, SsaImplicitInit ssa, boolean isnull) {
+ exists(boolean branch |
+ nullGuard(ssa, branch, isnull).directlyControls(ret.getBasicBlock(), branch)
+ )
+}
+
+/**
+ * Gets a non-overridable method with a boolean return value that performs a null-check
+ * on the `index`th parameter. A return value equal to `retval` allows us to conclude
+ * that the argument either is null or non-null as specified by `isnull`.
+ */
+private Method customNullGuard(int index, boolean retval, boolean isnull) {
+ exists(Parameter p |
+ result.getReturnType().(PrimitiveType).hasName("boolean") and
+ not result.isOverridable() and
+ p.getCallable() = result and
+ not p.isVarargs() and
+ p.getType() instanceof RefType and
+ p.getPosition() = index and
+ forex(ReturnStmt ret |
+ ret.getEnclosingCallable() = result and
+ exists(Expr res | res = ret.getResult() | not res.(BooleanLiteral).getBooleanValue() = retval.booleanNot())
+ |
+ validReturnInCustomNullGuard(ret, p, retval, isnull)
+ )
+ )
+}
+
+/**
+ * `guard` is a guard expression that suggests that `v` might be null.
+ *
+ * This is equivalent to `guard = basicNullGuard(sameValue(v, _), _, true)`.
+ */
+predicate guardSuggestsVarMaybeNull(Expr guard, SsaVariable v) {
+ guard = basicNullGuard(sameValue(v, _), _, true)
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/Nullness.qll b/java/ql/src/semmle/code/java/dataflow/Nullness.qll
new file mode 100644
index 00000000000..4932af01eb5
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/Nullness.qll
@@ -0,0 +1,673 @@
+/**
+ * Provides classes and predicates for nullness analysis.
+ *
+ * Local variables that may be null are tracked to see if they might reach
+ * a dereference and cause a NullPointerException. Assertions are assumed to
+ * hold, so results guarded by, for example, `assert x != null;` or
+ * `if (x == null) { assert false; }` are excluded.
+ */
+
+/*
+ * Implementation details:
+ *
+ * The three exported predicates, `nullDeref`, `alwaysNullDeref`, and
+ * `superfluousNullGuard`, compute potential null dereferences, definite null
+ * dereferences, and superfluous null checks, respectively. The bulk of the
+ * library supports `nullDeref`, while the latter two are fairly simple in
+ * comparison.
+ *
+ * The NPE (NullPointerException) candidates are computed by
+ * `nullDerefCandidate` and consist of three parts: A variable definition that
+ * might be null as computed by `varMaybeNull`, a dereference that can cause a
+ * NPE as computed by `firstVarDereferenceInBlock`, and a control flow path
+ * between the two points. The path is computed by `varMaybeNullInBlock`,
+ * which is the transitive closure of the step relation `nullVarStep`
+ * originating in a definition given by `varMaybeNull`. The step relation
+ * `nullVarStep` is essentially just the successor relation on basic blocks
+ * restricted to exclude edges along which the variable cannot be null.
+ *
+ * The step relation `nullVarStep` is then reused twice to produce two
+ * refinements of the path reachability predicate `varMaybeNullInBlock` in
+ * order to prune impossible paths that would otherwise lead to a potential
+ * NPE. These two refinements are `varMaybeNullInBlock_corrCond` and
+ * `varMaybeNullInBlock_trackVar` and are described in further detail below.
+ */
+
+import java
+private import SSA
+private import semmle.code.java.controlflow.Guards
+private import RangeUtils
+private import IntegerGuards
+private import NullGuards
+private import semmle.code.java.Collections
+private import semmle.code.java.frameworks.Assertions
+
+/** Gets an expression that may be `null`. */
+Expr nullExpr() {
+ result instanceof NullLiteral or
+ result.(ParExpr).getExpr() = nullExpr() or
+ result.(ConditionalExpr).getTrueExpr() = nullExpr() or
+ result.(ConditionalExpr).getFalseExpr() = nullExpr() or
+ result.(AssignExpr).getSource() = nullExpr() or
+ result.(CastExpr).getExpr() = nullExpr()
+}
+
+/** An expression of a boxed type that is implicitly unboxed. */
+private predicate unboxed(Expr e) {
+ e.getType() instanceof BoxedType and
+ (
+ exists(ArrayAccess aa | aa.getIndexExpr() = e) or
+ exists(ArrayCreationExpr ace | ace.getADimension() = e) or
+ exists(LocalVariableDeclExpr decl | decl.getVariable().getType() instanceof PrimitiveType and decl.getInit() = e) or
+ exists(AssignExpr assign | assign.getDest().getType() instanceof PrimitiveType and assign.getSource() = e) or
+ exists(AssignOp assign | assign.getSource() = e and assign.getType() instanceof PrimitiveType) or
+ exists(EqualityTest eq | eq.getAnOperand() = e and eq.getAnOperand().getType() instanceof PrimitiveType) or
+ exists(BinaryExpr bin | bin.getAnOperand() = e and not bin instanceof EqualityTest and bin.getType() instanceof PrimitiveType) or
+ exists(UnaryExpr un | un.getExpr() = e) or
+ exists(ConditionalExpr cond | cond.getType() instanceof PrimitiveType | cond.getTrueExpr() = e or cond.getFalseExpr() = e) or
+ exists(ConditionNode cond | cond.getCondition() = e) or
+ exists(Parameter p | p.getType() instanceof PrimitiveType and p.getAnArgument() = e) or
+ exists(ReturnStmt ret | ret.getEnclosingCallable().getReturnType() instanceof PrimitiveType and ret.getResult() = e)
+ )
+}
+
+/** An expression that is being dereferenced. These are the points where `NullPointerException`s can occur. */
+predicate dereference(Expr e) {
+ exists(EnhancedForStmt for | for.getExpr() = e) or
+ exists(SynchronizedStmt synch | synch.getExpr() = e) or
+ exists(SwitchStmt switch | switch.getExpr() = e) or
+ exists(FieldAccess fa, Field f | fa.getQualifier() = e and fa.getField() = f and not f.isStatic()) or
+ exists(MethodAccess ma, Method m | ma.getQualifier() = e and ma.getMethod() = m and not m.isStatic()) or
+ exists(ClassInstanceExpr cie | cie.getQualifier() = e) or
+ exists(ArrayAccess aa | aa.getArray() = e) or
+ exists(CastExpr cast | cast.getExpr() = e and e.getType() instanceof BoxedType and cast.getType() instanceof PrimitiveType) or
+ unboxed(e)
+}
+
+/**
+ * Gets the `ControlFlowNode` in which the given SSA variable is being dereferenced.
+ *
+ * The `VarAccess` is included for nicer error reporting.
+ */
+private ControlFlowNode varDereference(SsaVariable v, VarAccess va) {
+ exists(Expr e |
+ dereference(e) and
+ e = sameValue(v, va) and
+ result = e.getProperExpr()
+ )
+}
+
+/**
+ * A `ControlFlowNode` that ensures that the SSA variable is not null in any
+ * subsequent use, either by dereferencing it or by an assertion.
+ */
+private ControlFlowNode ensureNotNull(SsaVariable v) {
+ result = varDereference(v, _) or
+ result.(AssertStmt).getExpr() = nullGuard(v, true, false) or
+ exists(AssertTrueMethod m | result = m.getACheck(nullGuard(v, true, false))) or
+ exists(AssertFalseMethod m | result = m.getACheck(nullGuard(v, false, false))) or
+ exists(AssertNotNullMethod m | result = m.getACheck(v.getAUse()))
+}
+
+/**
+ * A variable dereference that cannot be reached by a `null` value, because of an earlier
+ * dereference or assertion in the same `BasicBlock`.
+ */
+private predicate unreachableVarDereference(BasicBlock bb, SsaVariable v, ControlFlowNode varDeref) {
+ exists(ControlFlowNode n, int i, int j |
+ (n = ensureNotNull(v) or assertFail(bb, n)) and
+ varDeref = varDereference(v, _) and
+ bb.getNode(i) = n and
+ bb.getNode(j) = varDeref and
+ i < j
+ )
+}
+
+/**
+ * The first dereference of a variable in a given `BasicBlock` excluding those dereferences
+ * that are preceded by a not-null assertion or a trivially failing assertion.
+ */
+private predicate firstVarDereferenceInBlock(BasicBlock bb, SsaVariable v, VarAccess va) {
+ exists(ControlFlowNode n |
+ varDereference(v, va) = n and
+ n.getBasicBlock() = bb and
+ not unreachableVarDereference(bb, v, n)
+ )
+}
+
+/** A variable suspected of being `null`. */
+private predicate varMaybeNull(SsaVariable v, string msg, Expr reason) {
+ // A variable compared to null might be null.
+ exists(Expr e |
+ reason = e and
+ msg = "as suggested by $@ null guard" and
+ guardSuggestsVarMaybeNull(e, v) and
+ not v instanceof SsaPhiNode and
+ not clearlyNotNull(v) and
+ // Comparisons in finally blocks are excluded since missing exception edges in the CFG could otherwise yield FPs.
+ not exists(TryStmt try |
+ try.getFinally() = e.getEnclosingStmt().getParent*()
+ ) and
+ (
+ exists(ConditionalExpr c | c.getCondition().getAChildExpr*() = e) or
+ not exists(MethodAccess ma |
+ ma.getAnArgument().getAChildExpr*() = e
+ )
+ ) and
+ // Don't use a guard as reason if there is a null assignment.
+ not v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nullExpr()
+ ) or
+ // A parameter might be null if there is a null argument somewhere.
+ exists(Parameter p, Expr arg |
+ v.(SsaImplicitInit).isParameterDefinition(p) and
+ p.getAnArgument() = arg and
+ reason = arg and
+ msg = "because of $@ null argument" and
+ arg = nullExpr() and
+ not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod
+ ) or
+ // If the source of a variable is null then the variable may be null.
+ exists(VariableAssign def |
+ v.(SsaExplicitUpdate).getDefiningExpr() = def and
+ def.getSource() = nullExpr() and
+ reason = def and
+ msg = "because of $@ assignment"
+ )
+}
+
+/** Gets an array or collection that contains at least one element. */
+private Expr nonEmptyExpr() {
+ // An array creation with a known positive size is trivially non-empty.
+ result.(ArrayCreationExpr).getFirstDimensionSize() > 0 or
+ exists(SsaVariable v |
+ // A use of an array variable is non-empty if...
+ result = v.getAUse() and
+ v.getSourceVariable().getType() instanceof Array
+ |
+ // ...its definition is non-empty...
+ v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = nonEmptyExpr() or
+ // ...or it is guarded by a condition proving its length to be non-zero.
+ exists(ConditionBlock cond, boolean branch, FieldAccess length |
+ cond.controls(result.getBasicBlock(), branch) and
+ cond.getCondition() = integerGuard(length, branch, 0, false) and
+ length.getField().hasName("length") and
+ length.getQualifier() = v.getAUse()
+ )
+ ) or
+ exists(SsaVariable v |
+ // A use of a Collection variable is non-empty if...
+ result = v.getAUse() and
+ v.getSourceVariable().getType() instanceof CollectionType and
+ exists(ConditionBlock cond, boolean branch, Expr c |
+ // ...it is guarded by a condition...
+ cond.controls(result.getBasicBlock(), branch) and
+ // ...and it isn't modified in the scope of the condition...
+ forall(MethodAccess ma, Method m |
+ m = ma.getMethod() and
+ ma.getQualifier() = v.getSourceVariable().getAnAccess() and
+ cond.controls(ma.getBasicBlock(), branch)
+ |
+ m instanceof CollectionQueryMethod
+ ) and
+ cond.getCondition() = c
+ |
+ // ...and the condition proves that it is non-empty, either by using the `isEmpty` method...
+ c.(MethodAccess).getMethod().hasName("isEmpty") and branch = false and c.(MethodAccess).getQualifier() = v.getAUse() or
+ // ...or a check on its `size`.
+ exists(MethodAccess size |
+ c = integerGuard(size, branch, 0, false) and
+ size.getMethod().hasName("size") and
+ size.getQualifier() = v.getAUse()
+ )
+ )
+ )
+}
+
+/** The control flow edge that exits an enhanced for loop if the `Iterable` is empty. */
+private predicate enhancedForEarlyExit(EnhancedForStmt for, ControlFlowNode n1, ControlFlowNode n2) {
+ exists(Expr forExpr |
+ n1.getANormalSuccessor() = n2 and
+ for.getExpr() = forExpr and
+ forExpr.getAChildExpr*() = n1 and
+ not forExpr.getAChildExpr*() = n2 and
+ n1.getANormalSuccessor() = for.getVariable() and
+ not n2 = for.getVariable()
+ )
+}
+
+/** A control flow edge that cannot be taken. */
+private predicate impossibleEdge(BasicBlock bb1, BasicBlock bb2) {
+ exists(EnhancedForStmt for |
+ enhancedForEarlyExit(for, bb1.getANode(), bb2.getANode()) and
+ for.getExpr() = nonEmptyExpr()
+ )
+}
+
+/** A control flow edge that leaves a finally-block. */
+private predicate leavingFinally(BasicBlock bb1, BasicBlock bb2, boolean normaledge) {
+ exists(TryStmt try, Block finally |
+ try.getFinally() = finally and
+ bb1.getABBSuccessor() = bb2 and
+ bb1.getEnclosingStmt().getParent*() = finally and
+ not bb2.getEnclosingStmt().getParent*() = finally and
+ if bb1.getLastNode().getANormalSuccessor() = bb2.getFirstNode() then normaledge = true else normaledge = false
+ )
+}
+
+private predicate ssaSourceVarMaybeNull(SsaSourceVariable v) {
+ varMaybeNull(v.getAnSsaVariable(), _, _)
+}
+
+/**
+ * The step relation for propagating that a given SSA variable might be `null` in a given `BasicBlock`.
+ *
+ * If `midssa` is null in `mid` then `ssa` might be null in `bb`. The SSA variables share the same
+ * `SsaSourceVariable`.
+ *
+ * A boolean flag tracks whether a non-normal completion is waiting to resume upon the exit of a finally-block.
+ * If the flag is set, then the normal edge out of the finally-block is prohibited, but if it is not set then
+ * no knowledge is assumed of any potentially waiting completions. `midstoredcompletion` is the flag before
+ * the step and `storedcompletion` is the flag after the step.
+ */
+private predicate nullVarStep(SsaVariable midssa, BasicBlock mid, boolean midstoredcompletion, SsaVariable ssa, BasicBlock bb, boolean storedcompletion) {
+ exists(SsaSourceVariable v |
+ ssaSourceVarMaybeNull(v) and
+ midssa.getSourceVariable() = v
+ |
+ ssa.(SsaPhiNode).getAPhiInput() = midssa and ssa.getBasicBlock() = bb or
+ ssa = midssa and not exists(SsaPhiNode phi | phi.getSourceVariable() = v and phi.getBasicBlock() = bb)
+ ) and
+ (midstoredcompletion = true or midstoredcompletion = false) and
+ midssa.isLiveAtEndOfBlock(mid) and
+ not ensureNotNull(midssa).getBasicBlock() = mid and
+ not assertFail(mid, _) and
+ bb = mid.getABBSuccessor() and
+ not impossibleEdge(mid, bb) and
+ not exists(boolean branch |
+ nullGuard(midssa, branch, false).hasBranchEdge(mid, bb, branch)
+ ) and
+ not (leavingFinally(mid, bb, true) and midstoredcompletion = true) and
+ if bb.getFirstNode() = any(TryStmt try | | try.getFinally()) then
+ (if bb.getFirstNode() = mid.getLastNode().getANormalSuccessor() then storedcompletion = false else storedcompletion = true)
+ else if leavingFinally(mid, bb, _) then
+ storedcompletion = false
+ else
+ storedcompletion = midstoredcompletion
+}
+
+/**
+ * The transitive closure of `nullVarStep` originating from `varMaybeNull`. That is, those `BasicBlock`s
+ * for which the SSA variable is suspected of being `null`.
+ */
+private predicate varMaybeNullInBlock(SsaVariable ssa, SsaSourceVariable v, BasicBlock bb, boolean storedcompletion) {
+ varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and v = ssa.getSourceVariable() or
+ exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
+ varMaybeNullInBlock(midssa, v, mid, midstoredcompletion) and
+ nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
+ )
+}
+
+/**
+ * Holds if `v` is a source variable that might reach a potential `null`
+ * dereference.
+ */
+private predicate nullDerefCandidateVariable(SsaSourceVariable v) {
+ exists(SsaVariable ssa, BasicBlock bb |
+ firstVarDereferenceInBlock(bb, ssa, _) and
+ varMaybeNullInBlock(ssa, v, bb, _)
+ )
+}
+
+private predicate varMaybeNullInBlock_origin(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion) {
+ nullDerefCandidateVariable(ssa.getSourceVariable()) and
+ varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa
+ or
+ exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
+ varMaybeNullInBlock_origin(origin, midssa, mid, midstoredcompletion) and
+ nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
+ )
+}
+
+/**
+ * A potential `null` dereference. That is, the first dereference of a variable in a block
+ * where it is suspected of being `null`.
+ */
+private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) {
+ exists(SsaVariable ssa, BasicBlock bb |
+ firstVarDereferenceInBlock(bb, ssa, va) and
+ varMaybeNullInBlock_origin(origin, ssa, bb, _)
+ )
+}
+
+/*
+ * In the following, the potential `null` dereference candidates are pruned by proving that
+ * a `NullPointerException` (NPE) cannot occur. This is done by pruning the control-flow paths
+ * that lead to the NPE candidate in two ways:
+ *
+ * 1. For each set of correlated conditions that are passed by the path, consistent
+ * branches must be taken. For example, the following code is safe due to the two tests on
+ * `flag` begin correlated.
+ * ```
+ * x = null;
+ * if (flag) x = new A();
+ * if (flag) x.m();
+ * ```
+ *
+ * 2. For each other variable that changes its value alongside the potential NPE candidate,
+ * the passed conditions must be consistent with its value. For example, the following
+ * code is safe due to the value of `t`.
+ * ```
+ * x = null;
+ * t = null;
+ * if (...) { x = new A(); t = new B(); }
+ * if (t != null) x.m();
+ * ```
+ * We call such a variable a _tracking variable_ as it tracks the null-ness of `x`.
+ */
+
+/** A variable that is assigned `null` if the given condition takes the given branch. */
+private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) {
+ exists(ConditionalExpr condexpr |
+ v.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = condexpr and
+ condexpr.getCondition().getProperExpr() = cond.getCondition()
+ |
+ condexpr.getTrueExpr() = nullExpr() and branch = true and not condexpr.getFalseExpr() = nullExpr() or
+ condexpr.getFalseExpr() = nullExpr() and branch = false and not condexpr.getTrueExpr() = nullExpr()
+ )
+ or
+ v.getDefiningExpr().(VariableAssign).getSource() = nullExpr() and
+ cond.controls(v.getBasicBlock(), branch)
+}
+
+/**
+ * A condition that might be useful in proving an NPE candidate safe.
+ *
+ * This is a condition along the path that found the NPE candidate.
+ */
+private predicate interestingCond(SsaSourceVariable npecand, ConditionBlock cond) {
+ nullDerefCandidateVariable(npecand) and
+ (varMaybeNullInBlock(_, npecand, cond, _) or varConditionallyNull(npecand.getAnSsaVariable(), cond, _)) and
+ not cond.getCondition().getAChildExpr*() = npecand.getAnAccess()
+}
+
+/** A pair of correlated conditions for a given NPE candidate. */
+private predicate correlatedConditions(SsaSourceVariable npecand, ConditionBlock cond1, ConditionBlock cond2, boolean inverted) {
+ interestingCond(npecand, cond1) and
+ interestingCond(npecand, cond2) and
+ cond1 != cond2 and
+ (
+ exists(SsaVariable v |
+ cond1.getCondition() = v.getAUse() and
+ cond2.getCondition() = v.getAUse() and
+ inverted = false
+ ) or
+ exists(SsaVariable v, boolean branch1, boolean branch2 |
+ cond1.getCondition() = nullGuard(v, branch1, true) and
+ cond1.getCondition() = nullGuard(v, branch1.booleanNot(), false) and
+ cond2.getCondition() = nullGuard(v, branch2, true) and
+ cond2.getCondition() = nullGuard(v, branch2.booleanNot(), false) and
+ inverted = branch1.booleanXor(branch2)
+ ) or
+ exists(SsaVariable v, RValue rv1, RValue rv2, int k, boolean branch1, boolean branch2 |
+ rv1 = v.getAUse() and
+ rv2 = v.getAUse() and
+ cond1.getCondition() = integerGuard(rv1, branch1, k, true) and
+ cond1.getCondition() = integerGuard(rv1, branch1.booleanNot(), k, false) and
+ cond2.getCondition() = integerGuard(rv2, branch2, k, true) and
+ cond2.getCondition() = integerGuard(rv2, branch2.booleanNot(), k, false) and
+ inverted = branch1.booleanXor(branch2)
+ ) or
+ exists(SsaVariable v, int k, boolean branch1, boolean branch2 |
+ cond1.getCondition() = intBoundGuard(v.getAUse(), branch1, k) and
+ cond2.getCondition() = intBoundGuard(v.getAUse(), branch2, k) and
+ inverted = branch1.booleanXor(branch2)
+ ) or
+ exists(SsaVariable v, EnumConstant c, boolean pol1, boolean pol2 |
+ cond1.getCondition() = enumConstEquality(v.getAUse(), pol1, c) and
+ cond2.getCondition() = enumConstEquality(v.getAUse(), pol2, c) and
+ inverted = pol1.booleanXor(pol2)
+ )
+ )
+}
+
+/**
+ * This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
+ * this time restricted based on pairs of correlated conditions consistent with `cond1`
+ * evaluating to `branch`.
+ */
+private predicate varMaybeNullInBlock_corrCond(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion, ConditionBlock cond1, boolean branch) {
+ exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
+ nullDerefCandidateVariable(npecand) and correlatedConditions(npecand, cond1, _, _)
+ ) and
+ (
+ varConditionallyNull(ssa, cond1, branch) or
+ not varConditionallyNull(ssa, cond1, _) and (branch = true or branch = false)
+ ) and
+ varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa
+ or
+ exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion |
+ varMaybeNullInBlock_corrCond(origin, midssa, mid, midstoredcompletion, cond1, branch) and
+ (
+ cond1 = mid and cond1.getTestSuccessor(branch) = bb or
+ exists(ConditionBlock cond2, boolean inverted, boolean branch2 |
+ cond2 = mid and
+ correlatedConditions(_, cond1, cond2, inverted) and
+ cond2.getTestSuccessor(branch2) = bb and
+ branch = branch2.booleanXor(inverted)
+ ) or
+ cond1 != mid and not exists(ConditionBlock cond2 | cond2 = mid and correlatedConditions(_, cond1, cond2, _))
+ ) and
+ nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion)
+ )
+}
+
+/*
+ * A tracking variable has its possible values divided into two sets, A and B, for
+ * which we can attribute at least one direct assignment to be contained in either
+ * A or B.
+ * Four kinds are supported:
+ * - null: A means null and B means non-null.
+ * - boolean: A means true and B means false.
+ * - enum: A means a specific enum constant and B means any other value.
+ * - int: A means a specific integer value and B means any other value.
+ */
+
+newtype TrackVarKind =
+ TrackVarKindNull() or
+ TrackVarKindBool() or
+ TrackVarKindEnum() or
+ TrackVarKindInt()
+
+/** A variable that might be relevant as a tracking variable for the NPE candidate. */
+private predicate trackingVar(SsaSourceVariable npecand, SsaExplicitUpdate trackssa, SsaSourceVariable trackvar, TrackVarKind kind, Expr init) {
+ exists(ConditionBlock cond |
+ interestingCond(npecand, cond) and
+ varMaybeNullInBlock(_, npecand, cond, _) and
+ cond.getCondition().getAChildExpr*() = trackvar.getAnAccess() and
+ trackssa.getSourceVariable() = trackvar and
+ trackssa.getDefiningExpr().(VariableAssign).getSource() = init
+ |
+ init instanceof NullLiteral and kind = TrackVarKindNull() or
+ init = clearlyNotNullExpr() and kind = TrackVarKindNull() or
+ init instanceof BooleanLiteral and kind = TrackVarKindBool() or
+ init.(VarAccess).getVariable() instanceof EnumConstant and kind = TrackVarKindEnum() or
+ exists(init.(ConstantIntegerExpr).getIntValue()) and kind = TrackVarKindInt()
+ )
+}
+
+/** Gets an expression that tests the value of a given tracking variable. */
+private Expr trackingVarGuard(SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean branch, boolean isA) {
+ exists(Expr init | trackingVar(_, trackssa, trackvar, kind, init) |
+ result = basicOrCustomNullGuard(trackvar.getAnAccess(), branch, isA) and kind = TrackVarKindNull() or
+ result = trackvar.getAnAccess() and kind = TrackVarKindBool() and (branch = true or branch = false) and isA = branch or
+ exists(boolean polarity, EnumConstant c, EnumConstant initc |
+ initc.getAnAccess() = init and
+ kind = TrackVarKindEnum() and
+ result = enumConstEquality(trackvar.getAnAccess(), polarity, c) and
+ (
+ initc = c and branch = polarity.booleanNot() and isA = false or
+ initc = c and branch = polarity and isA = true or
+ initc != c and branch = polarity and isA = false
+ )
+ ) or
+ exists(int k |
+ init.(ConstantIntegerExpr).getIntValue() = k and
+ kind = TrackVarKindInt()
+ |
+ result = integerGuard(trackvar.getAnAccess(), branch, k, isA) or
+ exists(int k2 |
+ result = integerGuard(trackvar.getAnAccess(), branch.booleanNot(), k2, true) and
+ isA = false and
+ k2 != k
+ ) or
+ exists(int bound, boolean branch_with_lower_bound |
+ result = intBoundGuard(trackvar.getAnAccess(), branch_with_lower_bound, bound) and
+ isA = false
+ |
+ branch = branch_with_lower_bound and k < bound or
+ branch = branch_with_lower_bound.booleanNot() and bound <= k
+ )
+ )
+ ) or
+ exists(EqualityTest eqtest, boolean branch0, boolean polarity, BooleanLiteral boollit |
+ eqtest = result and
+ eqtest.hasOperands(trackingVarGuard(trackssa, trackvar, kind, branch0, isA), boollit) and
+ eqtest.polarity() = polarity and
+ branch = branch0.booleanXor(polarity).booleanXor(boollit.getBooleanValue())
+ )
+}
+
+/** An update to a tracking variable that is contained fully in either A or B. */
+private predicate isReset(SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, SsaExplicitUpdate update, boolean isA) {
+ exists(Expr init, Expr e |
+ trackingVar(_, trackssa, trackvar, kind, init) and
+ update.getSourceVariable() = trackvar and
+ e = update.getDefiningExpr().(VariableAssign).getSource()
+ |
+ e instanceof NullLiteral and kind = TrackVarKindNull() and isA = true or
+ e = clearlyNotNullExpr() and kind = TrackVarKindNull() and isA = false or
+ e.(BooleanLiteral).getBooleanValue() = isA and kind = TrackVarKindBool() or
+ e.(VarAccess).getVariable().(EnumConstant) = init.(VarAccess).getVariable() and kind = TrackVarKindEnum() and isA = true or
+ e.(VarAccess).getVariable().(EnumConstant) != init.(VarAccess).getVariable() and kind = TrackVarKindEnum() and isA = false or
+ e.(ConstantIntegerExpr).getIntValue() = init.(ConstantIntegerExpr).getIntValue() and kind = TrackVarKindInt() and isA = true or
+ e.(ConstantIntegerExpr).getIntValue() != init.(ConstantIntegerExpr).getIntValue() and kind = TrackVarKindInt() and isA = false
+ )
+}
+
+/** The abstract value of the tracked variable. */
+newtype TrackedValue =
+ TrackedValueA() or
+ TrackedValueB() or
+ TrackedValueUnknown()
+
+private TrackedValue trackValAorB(boolean isA) { isA = true and result = TrackedValueA() or isA = false and result = TrackedValueB() }
+
+/** A control flow edge passing through a condition that implies a specific value for a tracking variable. */
+private predicate stepImplies(BasicBlock bb1, BasicBlock bb2, SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, boolean isA) {
+ exists(ConditionBlock cond, boolean branch |
+ cond = bb1 and
+ cond.getTestSuccessor(branch) = bb2 and
+ cond.getCondition() = trackingVarGuard(trackssa, trackvar, kind, branch, isA)
+ )
+}
+
+/**
+ * This is again the transitive closure of `nullVarStep` similarly to `varMaybeNullInBlock`, but
+ * this time restricted based on a tracking variable.
+ */
+private predicate varMaybeNullInBlock_trackVar(SsaVariable origin, SsaVariable ssa, BasicBlock bb, boolean storedcompletion, SsaVariable trackssa, SsaSourceVariable trackvar, TrackVarKind kind, TrackedValue trackvalue) {
+ exists(SsaSourceVariable npecand | npecand = ssa.getSourceVariable() |
+ nullDerefCandidateVariable(npecand) and trackingVar(npecand, trackssa, trackvar, kind, _)
+ ) and
+ (
+ exists(SsaVariable init, boolean isA |
+ init.getSourceVariable() = trackvar and
+ init.isLiveAtEndOfBlock(bb) and
+ isReset(trackssa, trackvar, kind, init, isA) and trackvalue = trackValAorB(isA)
+ ) or
+ trackvalue = TrackedValueUnknown() and
+ not exists(SsaVariable init |
+ init.getSourceVariable() = trackvar and
+ init.isLiveAtEndOfBlock(bb) and
+ isReset(trackssa, trackvar, kind, init, _)
+ )
+ ) and
+ varMaybeNull(ssa, _, _) and bb = ssa.getBasicBlock() and storedcompletion = false and origin = ssa
+ or
+ exists(BasicBlock mid, SsaVariable midssa, boolean midstoredcompletion, TrackedValue trackvalue0 |
+ varMaybeNullInBlock_trackVar(origin, midssa, mid, midstoredcompletion, trackssa, trackvar, kind, trackvalue0) and
+ nullVarStep(midssa, mid, midstoredcompletion, ssa, bb, storedcompletion) and
+ (
+ trackvalue0 = TrackedValueUnknown() or
+ // A step that implies a value that contradicts the current value is not allowed.
+ exists(boolean isA | trackvalue0 = trackValAorB(isA) |
+ not stepImplies(mid, bb, trackssa, trackvar, kind, isA.booleanNot())
+ )
+ ) and
+ (
+ // If no update occurs then the tracked value is unchanged unless the step implies a given value via a condition.
+ not exists(SsaUpdate update |
+ update.getSourceVariable() = trackvar and
+ update.getBasicBlock() = bb
+ ) and
+ (
+ exists(boolean isA | stepImplies(mid, bb, trackssa, trackvar, kind, isA) | trackvalue = trackValAorB(isA)) or
+ not stepImplies(mid, bb, trackssa, trackvar, kind, _) and trackvalue = trackvalue0
+ )
+ or
+ // If an update occurs then the tracked value is set accordingly.
+ exists(SsaUpdate update |
+ update.getSourceVariable() = trackvar and
+ update.getBasicBlock() = bb
+ |
+ exists(boolean isA | isReset(trackssa, trackvar, kind, update, isA) | trackvalue = trackValAorB(isA)) or
+ not isReset(trackssa, trackvar, kind, update, _) and trackvalue = TrackedValueUnknown()
+ )
+ )
+ )
+}
+
+/**
+ * A potential `null` dereference that has not been proven safe.
+ */
+predicate nullDeref(SsaSourceVariable v, VarAccess va, string msg, Expr reason) {
+ exists(SsaVariable origin, SsaVariable ssa, BasicBlock bb |
+ nullDerefCandidate(origin, va) and
+ varMaybeNull(origin, msg, reason) and
+ ssa.getSourceVariable() = v and
+ firstVarDereferenceInBlock(bb, ssa, va) and
+ forall(ConditionBlock cond |
+ correlatedConditions(v, cond, _, _)
+ |
+ varMaybeNullInBlock_corrCond(origin, ssa, bb, _, cond, _)
+ ) and
+ forall(SsaVariable guardssa, SsaSourceVariable guardvar, TrackVarKind kind |
+ trackingVar(v, guardssa, guardvar, kind, _)
+ |
+ varMaybeNullInBlock_trackVar(origin, ssa, bb, _, guardssa, guardvar, kind, _)
+ )
+ )
+}
+
+/**
+ * A dereference of a variable that is always `null`.
+ */
+predicate alwaysNullDeref(SsaSourceVariable v, VarAccess va) {
+ exists(BasicBlock bb, SsaVariable ssa |
+ forall(SsaVariable def | def = ssa.getAnUltimateDefinition() |
+ def.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = alwaysNullExpr()
+ ) or
+ exists(boolean branch |
+ nullGuard(ssa, branch, true).directlyControls(bb, branch) and
+ not clearlyNotNull(ssa)
+ )
+ |
+ // Exclude fields as they might not have an accurate ssa representation.
+ not v.getVariable() instanceof Field and
+ firstVarDereferenceInBlock(bb, ssa, va) and
+ ssa.getSourceVariable() = v and
+ not exists(boolean branch |
+ nullGuard(ssa, branch, false).directlyControls(bb, branch)
+ )
+ )
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
new file mode 100644
index 00000000000..152f240914d
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
@@ -0,0 +1,227 @@
+/**
+ * Parity Analysis.
+ *
+ * The analysis is implemented as an abstract interpretation over the
+ * two-valued domain `{even, odd}`.
+ */
+import java
+private import SSA
+private import RangeUtils
+private import semmle.code.java.controlflow.Guards
+private import SignAnalysis
+private import semmle.code.java.Reflection
+
+/** Gets an expression that is the remainder modulo 2 of `arg`. */
+private Expr mod2(Expr arg) {
+ exists(RemExpr rem |
+ result = rem and
+ arg = rem.getLeftOperand() and
+ rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = 2
+ ) or
+ result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) or
+ result.(ParExpr).getExpr() = mod2(arg)
+}
+
+/** An expression that calculates remainder modulo 2. */
+private class Mod2 extends Expr {
+ Mod2() {
+ this = mod2(_)
+ }
+
+ /** Gets the argument of this remainder operation. */
+ Expr getArg() {
+ this = mod2(result)
+ }
+}
+
+/**
+ * Parity represented as booleans. Even corresponds to `false` and odd
+ * corresponds to `true`.
+ */
+class Parity extends boolean {
+ Parity() { this = true or this = false }
+ predicate isEven() { this = false }
+ predicate isOdd() { this = true }
+}
+
+/**
+ * Gets a condition that performs a parity check on `v`, such that `v` has
+ * the given parity if the condition evaluates to `testIsTrue`.
+ */
+private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
+ exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity |
+ result.isEquality(rem, c, polarity) and
+ c.getIntValue() = r and
+ (r = 0 or r = 1) and
+ rem.getArg() = v.getAUse() and
+ (testIsTrue = true or testIsTrue = false) and
+ (
+ r = 0 and parity = testIsTrue.booleanXor(polarity) or
+ r = 1 and parity = testIsTrue.booleanXor(polarity).booleanNot()
+ )
+ )
+}
+
+/**
+ * Gets the parity of `e` if it can be directly determined.
+ */
+private Parity certainExprParity(Expr e) {
+ exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
+ if i % 2 = 0 then result.isEven() else result.isOdd()
+ ) or
+ e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() or
+ e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() or
+ not exists(e.(ConstantIntegerExpr).getIntValue()) and
+ (
+ result = certainExprParity(e.(ParExpr).getExpr()) or
+ exists(Guard guard, SsaVariable v, boolean testIsTrue |
+ guard = parityCheck(v, result, testIsTrue) and
+ e = v.getAUse() and
+ guardControls_v2(guard, e.getBasicBlock(), testIsTrue)
+ ) or
+ exists(SsaVariable arr, int arrlen, FieldAccess len |
+ e = len and
+ len.getField() instanceof ArrayLengthField and
+ len.getQualifier() = arr.getAUse() and
+ arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = arrlen and
+ if arrlen % 2 = 0 then result.isEven() else result.isOdd()
+ )
+ )
+}
+
+/**
+ * Gets the expression that defines the array length that equals `len`, if any.
+ */
+private Expr arrLenDef(FieldAccess len) {
+ exists(SsaVariable arr |
+ len.getField() instanceof ArrayLengthField and
+ len.getQualifier() = arr.getAUse() and
+ arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = result
+ )
+}
+
+/** Gets a possible parity for `v`. */
+private Parity ssaParity(SsaVariable v) {
+ exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() |
+ result = exprParity(def.(VariableAssign).getSource()) or
+ exists(EnhancedForStmt for | def = for.getVariable()) and (result = true or result = false) or
+ result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() or
+ exists(AssignOp a | a = def and result = exprParity(a))
+ ) or
+ result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or
+ result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) or
+ exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p) and (result = true or result = false)) or
+ result = ssaParity(v.(SsaPhiNode).getAPhiInput())
+}
+
+/** Gets a possible parity for `f`. */
+private Parity fieldParity(Field f) {
+ result = exprParity(f.getAnAssignedValue()) or
+ exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or
+ exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) or
+ exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f and (result = true or result = false))
+ or
+ if f.fromSource() then
+ not exists(f.getInitializer()) and result.isEven()
+ else
+ (result = true or result = false)
+}
+
+/** Holds if the parity of `e` is too complicated to determine. */
+private predicate unknownParity(Expr e) {
+ e instanceof AssignDivExpr or
+ e instanceof AssignRShiftExpr or
+ e instanceof AssignURShiftExpr or
+ e instanceof DivExpr or
+ e instanceof RShiftExpr or
+ e instanceof URShiftExpr or
+ exists(Type fromtyp | e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType) or
+ e instanceof ArrayAccess and e.getType() instanceof IntegralType or
+ e instanceof MethodAccess and e.getType() instanceof IntegralType or
+ e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType or
+ e.getType() instanceof FloatingPointType or
+ e.getType() instanceof CharacterType
+}
+
+/** Gets a possible parity for `e`. */
+private Parity exprParity(Expr e) {
+ result = certainExprParity(e) or
+ not exists(certainExprParity(e)) and
+ (
+ result = exprParity(e.(ParExpr).getExpr()) or
+ result = exprParity(arrLenDef(e)) or
+ exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and not exists(arrLenDef(e)) or
+ exists(FieldAccess fa | fa = e |
+ not exists(SsaVariable v | v.getAUse() = fa) and
+ not exists(arrLenDef(e)) and
+ result = fieldParity(fa.getField())
+ ) or
+ exists(VarAccess va | va = e |
+ not exists(SsaVariable v | v.getAUse() = va) and
+ not va instanceof FieldAccess and
+ (result = true or result = false)
+ ) or
+ result = exprParity(e.(AssignExpr).getSource()) or
+ result = exprParity(e.(PlusExpr).getExpr()) or
+ result = exprParity(e.(PostIncExpr).getExpr()) or
+ result = exprParity(e.(PostDecExpr).getExpr()) or
+ result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() or
+ result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() or
+ result = exprParity(e.(MinusExpr).getExpr()) or
+ result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() or
+ unknownParity(e) and (result = true or result = false) or
+ exists(Parity p1, Parity p2, AssignOp a |
+ a = e and
+ p1 = exprParity(a.getDest()) and
+ p2 = exprParity(a.getRhs())
+ |
+ a instanceof AssignAddExpr and result = p1.booleanXor(p2) or
+ a instanceof AssignSubExpr and result = p1.booleanXor(p2) or
+ a instanceof AssignMulExpr and result = p1.booleanAnd(p2) or
+ a instanceof AssignRemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
+ a instanceof AssignAndExpr and result = p1.booleanAnd(p2) or
+ a instanceof AssignOrExpr and result = p1.booleanOr(p2) or
+ a instanceof AssignXorExpr and result = p1.booleanXor(p2) or
+ a instanceof AssignLShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(a.getRhs()))
+ ) or
+ exists(Parity p1, Parity p2, BinaryExpr bin |
+ bin = e and
+ p1 = exprParity(bin.getLeftOperand()) and
+ p2 = exprParity(bin.getRightOperand())
+ |
+ bin instanceof AddExpr and result = p1.booleanXor(p2) or
+ bin instanceof SubExpr and result = p1.booleanXor(p2) or
+ bin instanceof MulExpr and result = p1.booleanAnd(p2) or
+ bin instanceof RemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
+ bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) or
+ bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) or
+ bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) or
+ bin instanceof LShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(bin.getRightOperand()))
+ ) or
+ result = exprParity(e.(ConditionalExpr).getTrueExpr()) or
+ result = exprParity(e.(ConditionalExpr).getFalseExpr()) or
+ result = exprParity(e.(CastExpr).getExpr())
+ )
+}
+
+/**
+ * Gets the parity of `e` if it can be uniquely determined.
+ */
+Parity getExprParity(Expr e) {
+ result = exprParity(e) and 1 = count(exprParity(e))
+}
+
+/**
+ * Holds if the parity can be determined for both sides of `comp`. The boolean
+ * `eqparity` indicates whether the two sides have equal or opposite parity.
+ */
+predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
+ exists(Expr left, Expr right, boolean lpar, boolean rpar |
+ comp.getLeftOperand() = left and
+ comp.getRightOperand() = right and
+ lpar = getExprParity(left) and
+ rpar = getExprParity(right) and
+ eqparity = lpar.booleanXor(rpar).booleanNot()
+ )
+}
+
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
new file mode 100644
index 00000000000..d6d396e8c6a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -0,0 +1,708 @@
+/**
+ * Provides classes and predicates for range analysis.
+ *
+ * An inferred bound can either be a specific integer, the abstract value of an
+ * SSA variable, or the abstract value of an interesting expression. The latter
+ * category includes array lengths that are not SSA variables.
+ *
+ * If an inferred bound relies directly on a condition, then this condition is
+ * reported as the reason for the bound.
+ */
+
+/*
+ * This library tackles range analysis as a flow problem. Consider e.g.:
+ * ```
+ * len = arr.length;
+ * if (x < len) { ... y = x-1; ... y ... }
+ * ```
+ * In this case we would like to infer `y <= arr.length - 2`, and this is
+ * accomplished by tracking the bound through a sequence of steps:
+ * ```
+ * arr.length --> len = .. --> x < len --> x-1 --> y = .. --> y
+ * ```
+ *
+ * In its simplest form the step relation `E1 --> E2` relates two expressions
+ * such that `E1 <= B` implies `E2 <= B` for any `B` (with a second separate
+ * step relation handling lower bounds). Examples of such steps include
+ * assignments `E2 = E1` and conditions `x <= E1` where `E2` is a use of `x`
+ * guarded by the condition.
+ *
+ * In order to handle subtractions and additions with constants, and strict
+ * comparisons, the step relation is augmented with an integer delta. With this
+ * generalization `E1 --(delta)--> E2` relates two expressions and an integer
+ * such that `E1 <= B` implies `E2 <= B + delta` for any `B`. This corresponds
+ * to the predicate `boundFlowStep`.
+ *
+ * The complete range analysis is then implemented as the transitive closure of
+ * the step relation summing the deltas along the way. If `E1` transitively
+ * steps to `E2`, `delta` is the sum of deltas along the path, and `B` is an
+ * interesting bound equal to the value of `E1` then `E2 <= B + delta`. This
+ * corresponds to the predicate `bounded`.
+ *
+ * Phi nodes need a little bit of extra handling. Consider `x0 = phi(x1, x2)`.
+ * There are essentially two cases:
+ * - If `x1 <= B + d1` and `x2 <= B + d2` then `x0 <= B + max(d1,d2)`.
+ * - If `x1 <= B + d1` and `x2 <= x0 + d2` with `d2 <= 0` then `x0 <= B + d1`.
+ * The first case is for whenever a bound can be proven without taking looping
+ * into account. The second case is relevant when `x2` comes from a back-edge
+ * where we can prove that the variable has been non-increasing through the
+ * loop-iteration as this means that any upper bound that holds prior to the
+ * loop also holds for the variable during the loop.
+ * This generalizes to a phi node with `n` inputs, so if
+ * `x0 = phi(x1, ..., xn)` and `xi <= B + delta` for one of the inputs, then we
+ * also have `x0 <= B + delta` if we can prove either:
+ * - `xj <= B + d` with `d <= delta` or
+ * - `xj <= x0 + d` with `d <= 0`
+ * for each input `xj`.
+ *
+ * As all inferred bounds can be related directly to a path in the source code
+ * the only source of non-termination is if successive redundant (and thereby
+ * increasingly worse) bounds are calculated along a loop in the source code.
+ * We prevent this by weakening the bound to a small finite set of bounds when
+ * a path follows a second back-edge (we postpone weakening till the second
+ * back-edge as a precise bound might require traversing a loop once).
+ */
+
+import java
+private import SSA
+private import RangeUtils
+private import semmle.code.java.controlflow.internal.GuardsLogic
+private import SignAnalysis
+private import ParityAnalysis
+private import semmle.code.java.Reflection
+private import semmle.code.java.Collections
+private import semmle.code.java.Maps
+
+cached private module RangeAnalysisCache {
+
+ cached module RangeAnalysisPublic {
+ /**
+ * Holds if `b + delta` is a valid bound for `e`.
+ * - `upper = true` : `e <= b + delta`
+ * - `upper = false` : `e >= b + delta`
+ *
+ * The reason for the bound is given by `reason` and may be either a condition
+ * or `NoReason` if the bound was proven directly without the use of a bounding
+ * condition.
+ */
+ cached predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
+ bounded(e, b, delta, upper, _, _, reason)
+ }
+ }
+
+ /**
+ * Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`.
+ */
+ cached predicate possibleReason(Guard guard) { guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) }
+
+}
+private import RangeAnalysisCache
+import RangeAnalysisPublic
+
+/**
+ * Gets a condition that tests whether `v` equals `e + delta`.
+ *
+ * If the condition evaluates to `testIsTrue`:
+ * - `isEq = true` : `v == e + delta`
+ * - `isEq = false` : `v != e + delta`
+ */
+private Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
+ exists(boolean eqpolarity |
+ result.isEquality(ssaRead(v, delta), e, eqpolarity) and
+ (testIsTrue = true or testIsTrue = false) and
+ eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
+ )
+ or
+ exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0))
+}
+
+/**
+ * Holds if `comp` corresponds to:
+ * - `upper = true` : `v <= e + delta` or `v < e + delta`
+ * - `upper = false` : `v >= e + delta` or `v > e + delta`
+ */
+private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper) {
+ comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true
+ or
+ comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false
+ or
+ exists(SubExpr sub, ConstantIntegerExpr c, int d |
+ // (v - d) - e < c
+ comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
+ sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
+ upper = true and delta = d + c.getIntValue()
+ or
+ // (v - d) - e > c
+ comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
+ sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
+ upper = false and delta = d + c.getIntValue()
+ or
+ // e - (v - d) < c
+ comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
+ sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
+ upper = false and delta = d - c.getIntValue()
+ or
+ // e - (v - d) > c
+ comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
+ sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
+ upper = true and delta = d - c.getIntValue()
+ )
+}
+
+/**
+ * Gets a condition that tests whether `v` is bounded by `e + delta`.
+ *
+ * If the condition evaluates to `testIsTrue`:
+ * - `upper = true` : `v <= e + delta`
+ * - `upper = false` : `v >= e + delta`
+ */
+private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) {
+ exists(ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, boolean resultIsStrict |
+ comp = result and
+ boundCondition(comp, v, e, d1, compIsUpper) and
+ (testIsTrue = true or testIsTrue = false) and
+ upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and
+ (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
+ (if v.getSourceVariable().getType() instanceof IntegralType then
+ (upper = true and strengthen = -1 or
+ upper = false and strengthen = 1)
+ else
+ strengthen = 0) and
+ // A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if
+ // `x` and `y` have opposite parities, and a strict inequality `x < y` can
+ // be similarly strengthened if `x` and `y` have equal parities.
+ (if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and
+ // A strict inequality `x < y` can be strengthened to `x <= y - 1`.
+ (resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and
+ delta = d1 + d2 + d3
+ ) or
+ exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)) or
+ result = eqFlowCond(v, e, delta, true, testIsTrue) and (upper = true or upper = false)
+}
+
+private newtype TReason =
+ TNoReason() or
+ TCondReason(Guard guard) { possibleReason(guard) }
+
+/**
+ * A reason for an inferred bound. This can either be `CondReason` if the bound
+ * is due to a specific condition, or `NoReason` if the bound is inferred
+ * without going through a bounding condition.
+ */
+abstract class Reason extends TReason {
+ abstract string toString();
+}
+class NoReason extends Reason, TNoReason {
+ override string toString() { result = "NoReason" }
+}
+class CondReason extends Reason, TCondReason {
+ Guard getCond() { this = TCondReason(result) }
+ override string toString() { result = getCond().toString() }
+}
+
+/**
+ * Holds if `e + delta` is a valid bound for `v` at `pos`.
+ * - `upper = true` : `v <= e + delta`
+ * - `upper = false` : `v >= e + delta`
+ */
+private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) {
+ exists(SsaExplicitUpdate upd | v = upd and pos.hasReadOfVar(v) and reason = TNoReason() |
+ upd.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 and (upper = true or upper = false) or
+ upd.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
+ upd.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
+ upd.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
+ upd.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
+ upd.getDefiningExpr().(AssignOp) = e and delta = 0 and (upper = true or upper = false)
+ ) or
+ exists(Guard guard, boolean testIsTrue |
+ pos.hasReadOfVar(v) and
+ guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
+ guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
+ reason = TCondReason(guard)
+ )
+}
+
+/** Holds if `v != e + delta` at `pos`. */
+private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason) {
+ exists(Guard guard, boolean testIsTrue |
+ pos.hasReadOfVar(v) and
+ guard = eqFlowCond(v, e, delta, false, testIsTrue) and
+ guardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
+ reason = TCondReason(guard)
+ )
+}
+
+/**
+ * Holds if a cast from `fromtyp` to `totyp` can be ignored for the purpose of
+ * range analysis.
+ */
+private predicate safeCast(Type fromtyp, Type totyp) {
+ exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp |
+ pfrom = pto or
+ pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") or
+ pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") or
+ pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") or
+ pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") or
+ pfrom.hasName("long") and pto.getName().regexpMatch("float|double") or
+ pfrom.hasName("float") and pto.hasName("double") or
+ pfrom.hasName("double") and pto.hasName("float")
+ ) or
+ safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) or
+ safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType())
+}
+
+/**
+ * A cast that can be ignored for the purpose of range analysis.
+ */
+private class SafeCastExpr extends CastExpr {
+ SafeCastExpr() {
+ safeCast(getExpr().getType(), getType())
+ }
+}
+
+/**
+ * Holds if `typ` is a small integral type with the given lower and upper bounds.
+ */
+private predicate typeBound(Type typ, int lowerbound, int upperbound) {
+ typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 or
+ typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 or
+ typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 or
+ typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound)
+}
+
+/**
+ * A cast to a small integral type that may overflow or underflow.
+ */
+private class NarrowingCastExpr extends CastExpr {
+ NarrowingCastExpr() {
+ not this instanceof SafeCastExpr and
+ typeBound(getType(), _, _)
+ }
+ /** Gets the lower bound of the resulting type. */
+ int getLowerBound() { typeBound(getType(), result, _) }
+ /** Gets the upper bound of the resulting type. */
+ int getUpperBound() { typeBound(getType(), _, result) }
+}
+
+/**
+ * Holds if `e1 + delta` is a valid bound for `e2`.
+ * - `upper = true` : `e2 <= e1 + delta`
+ * - `upper = false` : `e2 >= e1 + delta`
+ */
+private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
+ e2.(ParExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ e2.(AssignExpr).getSource() = e1 and delta = 0 and (upper = true or upper = false) or
+ e2.(PlusExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ e2.(PostIncExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ e2.(PostDecExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ e2.(PreIncExpr).getExpr() = e1 and delta = 1 and (upper = true or upper = false) or
+ e2.(PreDecExpr).getExpr() = e1 and delta = -1 and (upper = true or upper = false) or
+ e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ exists(SsaExplicitUpdate v, FieldRead arrlen |
+ e2 = arrlen and
+ arrlen.getField() instanceof ArrayLengthField and
+ arrlen.getQualifier() = v.getAUse() and
+ v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
+ delta = 0 and
+ (upper = true or upper = false)
+ ) or
+ exists(Expr x |
+ e2.(AddExpr).hasOperands(e1, x) or
+ exists(AssignAddExpr add | add = e2 |
+ add.getDest() = e1 and add.getRhs() = x or
+ add.getDest() = x and add.getRhs() = e1
+ )
+ |
+ x.(ConstantIntegerExpr).getIntValue() = delta and (upper = true or upper = false)
+ or
+ not x instanceof ConstantIntegerExpr and
+ not e1 instanceof ConstantIntegerExpr and
+ if strictlyPositive(x) then
+ (upper = false and delta = 1)
+ else if positive(x) then
+ (upper = false and delta = 0)
+ else if strictlyNegative(x) then
+ (upper = true and delta = -1)
+ else if negative(x) then
+ (upper = true and delta = 0)
+ else
+ none()
+ ) or
+ exists(Expr x |
+ exists(SubExpr sub |
+ e2 = sub and
+ sub.getLeftOperand() = e1 and
+ sub.getRightOperand() = x
+ ) or
+ exists(AssignSubExpr sub |
+ e2 = sub and
+ sub.getDest() = e1 and
+ sub.getRhs() = x
+ )
+ |
+ x.(ConstantIntegerExpr).getIntValue() = -delta and (upper = true or upper = false)
+ or
+ not x instanceof ConstantIntegerExpr and
+ if strictlyPositive(x) then
+ (upper = true and delta = -1)
+ else if positive(x) then
+ (upper = true and delta = 0)
+ else if strictlyNegative(x) then
+ (upper = false and delta = 1)
+ else if negative(x) then
+ (upper = false and delta = 0)
+ else
+ none()
+ ) or
+ e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true or
+ e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true or
+ e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true or
+ e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true or
+ e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true or
+ e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true or
+ e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false or
+ e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false or
+ exists(MethodAccess ma, Method m |
+ e2 = ma and
+ ma.getMethod() = m and
+ m.hasName("nextInt") and
+ m.getDeclaringType().hasQualifiedName("java.util", "Random") and
+ e1 = ma.getAnArgument() and
+ delta = -1 and
+ upper = true
+ ) or
+ exists(MethodAccess ma, Method m |
+ e2 = ma and
+ ma.getMethod() = m and
+ (m.hasName("max") and upper = false or m.hasName("min") and upper = true) and
+ m.getDeclaringType().hasQualifiedName("java.lang", "Math") and
+ e1 = ma.getAnArgument() and
+ delta = 0
+ )
+}
+
+/** Holds if `e2 = e1 * factor` and `factor > 0`. */
+private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
+ exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
+ e2.(MulExpr).hasOperands(e1, c) and factor = k or
+ exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
+ exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) or
+ exists(LShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
+ exists(AssignLShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
+ )
+}
+
+/**
+ * Holds if `e2 = e1 / factor` and `factor > 0`.
+ *
+ * This conflates division, right shift, and unsigned right shift and is
+ * therefore only valid for non-negative numbers.
+ */
+private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
+ exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
+ exists(DivExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k) or
+ exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
+ exists(RShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
+ exists(AssignRShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) or
+ exists(URShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
+ exists(AssignURShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
+ )
+}
+
+private newtype TBound =
+ TBoundZero() or
+ TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
+ TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) }
+
+/**
+ * A bound that may be inferred for an expression plus/minus an integer delta.
+ */
+abstract class Bound extends TBound {
+ abstract string toString();
+ /** Gets an expression that equals this bound plus `delta`. */
+ abstract Expr getExpr(int delta);
+ /** Gets an expression that equals this bound. */
+ Expr getExpr() {
+ result = getExpr(0)
+ }
+ predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
+ }
+}
+
+/**
+ * The bound that corresponds to the integer 0. This is used to represent all
+ * integer bounds as bounds are always accompanied by an added integer delta.
+ */
+class ZeroBound extends Bound, TBoundZero {
+ override string toString() { result = "0" }
+ override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
+}
+
+/**
+ * A bound corresponding to the value of an SSA variable.
+ */
+class SsaBound extends Bound, TBoundSsa {
+ /** Gets the SSA variable that equals this bound. */
+ SsaVariable getSsa() { this = TBoundSsa(result) }
+ override string toString() { result = getSsa().toString() }
+ override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
+ }
+}
+
+/**
+ * A bound that corresponds to the value of a specific expression that might be
+ * interesting, but isn't otherwise represented by the value of an SSA variable.
+ */
+class ExprBound extends Bound, TBoundExpr {
+ override string toString() { result = getExpr().toString() }
+ override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ getExpr().hasLocationInfo(path, sl, sc, el, ec)
+ }
+}
+
+/**
+ * Holds if `b + delta` is a valid bound for `v` at `pos`.
+ * - `upper = true` : `v <= b + delta`
+ * - `upper = false` : `v >= b + delta`
+ */
+private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+ exists(Expr mid, int d1, int d2, Reason r1, Reason r2 |
+ boundFlowStepSsa(v, pos, mid, d1, upper, r1) and
+ bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and
+ // upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta
+ // upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
+ delta = d1 + d2 and
+ (if r1 instanceof NoReason then reason = r2 else reason = r1)
+ ) or
+ exists(int d, Reason r1, Reason r2 |
+ boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or
+ boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2)
+ |
+ unequalSsa(v, pos, b, d, r1) and
+ (upper = true and delta = d - 1 or upper = false and delta = d + 1) and
+ (reason = r1 or reason = r2 and not r2 instanceof NoReason)
+ )
+}
+
+/**
+ * Holds if `v != b + delta` at `pos`.
+ */
+private predicate unequalSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, Reason reason) {
+ exists(Expr e, int d1, int d2 |
+ unequalFlowStepSsa(v, pos, e, d1, reason) and
+ bounded(e, b, d2, true, _, _, _) and
+ bounded(e, b, d2, false, _, _, _) and
+ delta = d2 + d1
+ )
+}
+
+/**
+ * Holds if `inp` is an input to `phi` along a back edge.
+ */
+private predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
+ edge.phiInput(phi, inp) and
+ // Conservatively assume that every edge is a back edge if we don't have dominance information.
+ (phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or not hasDominanceInformation(edge.getOrigBlock()))
+}
+
+/** Weakens a delta to lie in the range `[-1..1]`. */
+bindingset[delta, upper]
+private int weakenDelta(boolean upper, int delta) {
+ delta in [-1..1] and result = delta or
+ upper = true and result = -1 and delta < -1 or
+ upper = false and result = 1 and delta > 1
+}
+
+/**
+ * Holds if `b + delta` is a valid bound for `inp` when used as an input to
+ * `phi` along `edge`.
+ * - `upper = true` : `inp <= b + delta`
+ * - `upper = false` : `inp >= b + delta`
+ */
+private predicate boundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+ edge.phiInput(phi, inp) and
+ exists(int d, boolean fromBackEdge0 |
+ boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) or
+ boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) or
+ b.(SsaBound).getSsa() = inp and d = 0 and (upper = true or upper = false) and fromBackEdge0 = false and origdelta = 0 and reason = TNoReason()
+ |
+ if backEdge(phi, inp, edge) then
+ fromBackEdge = true and
+ (
+ fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta or
+ fromBackEdge0 = false and delta = d
+ )
+ else
+ (delta = d and fromBackEdge = fromBackEdge0)
+ )
+}
+
+/** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */
+pragma[noinline]
+private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int delta) {
+ boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)
+}
+
+/**
+ * Holds if `phi` is a valid bound for `inp` when used as an input to `phi`
+ * along `edge`.
+ * - `upper = true` : `inp <= phi`
+ * - `upper = false` : `inp >= phi`
+ */
+private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper) {
+ exists(int d, SsaBound phibound |
+ phibound.getSsa() = phi and
+ boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and
+ (upper = true and d <= 0 or upper = false and d >= 0)
+ )
+}
+
+/**
+ * Holds if `b + delta` is a valid bound for some input, `inp`, to `phi`, and
+ * thus a candidate bound for `phi`.
+ * - `upper = true` : `inp <= b + delta`
+ * - `upper = false` : `inp >= b + delta`
+ */
+pragma[noinline]
+private predicate boundedPhiCand(SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
+ exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason))
+}
+
+/**
+ * Holds if the candidate bound `b + delta` for `phi` is valid for the phi input
+ * `inp` along `edge`.
+ */
+private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
+ boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and
+ (
+ exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) or
+ exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) or
+ selfBoundedPhiInp(phi, inp, edge, upper)
+ )
+}
+
+/**
+ * Holds if `b + delta` is a valid bound for `phi`.
+ * - `upper = true` : `phi <= b + delta`
+ * - `upper = false` : `phi >= b + delta`
+ */
+private predicate boundedPhi(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+ forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) |
+ boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge)
+ )
+}
+
+/**
+ * Holds if `e` has a lower bound of zero.
+ */
+private predicate lowerBoundZero(Expr e) {
+ e.(MethodAccess).getMethod() instanceof StringLengthMethod or
+ e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or
+ e.(MethodAccess).getMethod() instanceof MapSizeMethod or
+ e.(FieldRead).getField() instanceof ArrayLengthField or
+ positive(e.(AndBitwiseExpr).getAnOperand())
+}
+
+/**
+ * Holds if `e` has an upper (for `upper = true`) or lower
+ * (for `upper = false`) bound of `b`.
+ */
+private predicate baseBound(Expr e, int b, boolean upper) {
+ lowerBoundZero(e) and b = 0 and upper = false or
+ exists(Method read |
+ e.(MethodAccess).getMethod().overrides*(read) and
+ read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
+ read.hasName("read") and
+ read.getNumberOfParameters() = 0
+ |
+ upper = true and b = 255 or
+ upper = false and b = -1
+ )
+}
+
+/**
+ * Holds if the value being cast has an upper (for `upper = true`) or lower
+ * (for `upper = false`) bound within the bounds of the resulting type.
+ * For `upper = true` this means that the cast will not overflow and for
+ * `upper = false` this means that the cast will not underflow.
+ */
+private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
+ exists(int bound |
+ bounded(cast.getExpr(), TBoundZero(), bound, upper, _, _, _)
+ |
+ upper = true and bound <= cast.getUpperBound() or
+ upper = false and bound >= cast.getLowerBound()
+ )
+}
+
+pragma[noinline]
+private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+ bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
+}
+
+/**
+ * Holds if `b + delta` is a valid bound for `e`.
+ * - `upper = true` : `e <= b + delta`
+ * - `upper = false` : `e >= b + delta`
+ */
+private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+ e = b.getExpr(delta) and (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
+ baseBound(e, delta, upper) and b instanceof ZeroBound and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
+ exists(SsaVariable v, SsaReadPositionBlock bb |
+ boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
+ e = v.getAUse() and
+ bb.getBlock() = e.getBasicBlock()
+ ) or
+ exists(Expr mid, int d1, int d2 |
+ boundFlowStep(e, mid, d1, upper) and
+ // Constants have easy, base-case bounds, so let's not infer any recursive bounds.
+ not e instanceof ConstantIntegerExpr and
+ bounded(mid, b, d2, upper, fromBackEdge, origdelta, reason) and
+ // upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
+ // upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
+ delta = d1 + d2
+ ) or
+ exists(SsaPhiNode phi |
+ boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and
+ e = phi.getAUse()
+ ) or
+ exists(Expr mid, int factor, int d |
+ boundFlowStepMul(e, mid, factor) and
+ not e instanceof ConstantIntegerExpr and
+ bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
+ b instanceof ZeroBound and
+ delta = d * factor
+ ) or
+ exists(Expr mid, int factor, int d |
+ boundFlowStepDiv(e, mid, factor) and
+ not e instanceof ConstantIntegerExpr and
+ bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
+ b instanceof ZeroBound and
+ d >= 0 and
+ delta = d / factor
+ ) or
+ exists(NarrowingCastExpr cast |
+ cast = e and
+ safeNarrowingCast(cast, upper.booleanNot()) and
+ boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason)
+ ) or
+ exists(ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, Reason r2 |
+ cond = e and
+ boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
+ boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
+ (delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or
+ delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2)
+ |
+ upper = true and delta = d1.maximum(d2) or
+ upper = false and delta = d1.minimum(d2)
+ )
+}
+
+private predicate boundedConditionalExpr(ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
+ branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) or
+ branch = false and bounded(cond.getFalseExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
new file mode 100644
index 00000000000..9b5e9a257a2
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
@@ -0,0 +1,133 @@
+/**
+ * Provides utility predicates for range analysis.
+ */
+
+import java
+private import SSA
+private import semmle.code.java.controlflow.internal.GuardsLogic
+
+/** An expression that always has the same integer value. */
+pragma[nomagic]
+private predicate constantIntegerExpr(Expr e, int val) {
+ e.(CompileTimeConstantExpr).getIntValue() = val or
+ exists(SsaExplicitUpdate v, Expr src |
+ e = v.getAUse() and
+ src = v.getDefiningExpr().(VariableAssign).getSource() and
+ constantIntegerExpr(src, val)
+ )
+}
+
+/** An expression that always has the same integer value. */
+class ConstantIntegerExpr extends Expr {
+ ConstantIntegerExpr() {
+ constantIntegerExpr(this, _)
+ }
+
+ /** Gets the integer value of this expression. */
+ int getIntValue() {
+ constantIntegerExpr(this, result)
+ }
+}
+
+/**
+ * Gets an expression that equals `v - d`.
+ */
+Expr ssaRead(SsaVariable v, int delta) {
+ result = v.getAUse() and delta = 0 or
+ result.(ParExpr).getExpr() = ssaRead(v, delta) or
+ exists(int d1, ConstantIntegerExpr c |
+ result.(AddExpr).hasOperands(ssaRead(v, d1), c) and
+ delta = d1 - c.getIntValue()
+ ) or
+ exists(SubExpr sub, int d1, ConstantIntegerExpr c |
+ result = sub and
+ sub.getLeftOperand() = ssaRead(v, d1) and
+ sub.getRightOperand() = c and
+ delta = d1 + c.getIntValue()
+ ) or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 or // x++ === ++x - 1
+ v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 or // x-- === --x + 1
+ v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 or
+ result.(AssignExpr).getSource() = ssaRead(v, delta)
+}
+
+private newtype TSsaReadPosition =
+ TSsaReadPositionBlock(BasicBlock bb) { exists(SsaVariable v | bb = v.getAUse().getBasicBlock()) } or
+ TSsaReadPositionPhiInputEdge(BasicBlock bbOrig, BasicBlock bbPhi) {
+ exists(SsaPhiNode phi | phi.hasInputFromBlock(_, bbOrig) and bbPhi = phi.getBasicBlock())
+ }
+
+/**
+ * A position at which an SSA variable is read. This includes both ordinary
+ * reads occurring in basic blocks and input to phi nodes occurring along an
+ * edge between two basic blocks.
+ */
+class SsaReadPosition extends TSsaReadPosition {
+ /** Holds if `v` is read at this position. */
+ abstract predicate hasReadOfVar(SsaVariable v);
+
+ abstract string toString();
+}
+
+/** A basic block in which an SSA variable is read. */
+class SsaReadPositionBlock extends SsaReadPosition, TSsaReadPositionBlock {
+ /** Gets the basic block corresponding to this position. */
+ BasicBlock getBlock() { this = TSsaReadPositionBlock(result) }
+
+ override predicate hasReadOfVar(SsaVariable v) { getBlock() = v.getAUse().getBasicBlock() }
+
+ override string toString() { result = "block" }
+}
+
+/**
+ * An edge between two basic blocks where the latter block has an SSA phi
+ * definition. The edge therefore has a read of an SSA variable serving as the
+ * input to the phi node.
+ */
+class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiInputEdge {
+ /** Gets the head of the edge. */
+ BasicBlock getOrigBlock() { this = TSsaReadPositionPhiInputEdge(result, _) }
+
+ /** Gets the tail of the edge. */
+ BasicBlock getPhiBlock() { this = TSsaReadPositionPhiInputEdge(_, result) }
+
+ override predicate hasReadOfVar(SsaVariable v) {
+ exists(SsaPhiNode phi |
+ phi.hasInputFromBlock(v, getOrigBlock()) and
+ getPhiBlock() = phi.getBasicBlock()
+ )
+ }
+
+ /** Holds if `inp` is an input to `phi` along this edge. */
+ predicate phiInput(SsaPhiNode phi, SsaVariable inp) {
+ phi.hasInputFromBlock(inp, getOrigBlock()) and
+ getPhiBlock() = phi.getBasicBlock()
+ }
+
+ override string toString() { result = "edge" }
+}
+
+/**
+ * Holds if `guard` directly controls the position `controlled` with the
+ * value `testIsTrue`.
+ */
+predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
+ guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) or
+ exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled |
+ guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or
+ guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue)
+ )
+}
+
+/**
+ * Holds if `guard` controls the position `controlled` with the value `testIsTrue`.
+ */
+predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
+ guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) or
+ exists(Guard guard0, boolean testIsTrue0 |
+ implies_v2(guard0, testIsTrue0, guard, testIsTrue) and
+ guardControlsSsaRead(guard0, controlled, testIsTrue0)
+ )
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/SSA.qll b/java/ql/src/semmle/code/java/dataflow/SSA.qll
new file mode 100644
index 00000000000..2266c7034ac
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/SSA.qll
@@ -0,0 +1,1104 @@
+/**
+ * Provides classes and predicates for SSA representation (Static Single Assignment form).
+ *
+ * An SSA variable consists of the pair of a `SsaSourceVariable` and a
+ * `ControlFlowNode` at which it is defined. Each SSA variable is defined
+ * either by a phi node, an implicit initial value (for parameters and fields),
+ * an explicit update, or an implicit update (for fields).
+ * An implicit update occurs either at a `Call` that might modify a field, at
+ * another update that can update the qualifier of a field, or at a `FieldRead`
+ * of the field in case the field is not amenable to a non-trivial SSA
+ * representation.
+ */
+
+import java
+private import semmle.code.java.dispatch.VirtualDispatch
+private import semmle.code.java.dispatch.WrappedInvocation
+
+private predicate fieldAccessInCallable(FieldAccess fa, Field f, Callable c) {
+ f = fa.getField() and
+ c = fa.getEnclosingCallable()
+}
+
+cached
+private newtype TSsaSourceVariable =
+ TLocalVar(Callable c, LocalScopeVariable v) { c = v.getCallable() or c = v.getAnAccess().getEnclosingCallable() } or
+ TPlainField(Callable c, Field f) {
+ exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and (fr.isOwnFieldAccess() or f.isStatic()))
+ } or
+ TEnclosingField(Callable c, Field f, RefType t) {
+ exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.isEnclosingFieldAccess(t))
+ } or
+ TQualifiedField(Callable c, SsaSourceVariable q, InstanceField f) {
+ exists(FieldRead fr | fieldAccessInCallable(fr, f, c) and fr.getQualifier() = q.getAnAccess())
+ }
+
+/**
+ * A fully qualified variable in the context of a `Callable` in which it is
+ * accessed.
+ *
+ * This is either a local variable or a fully qualified field, `q.f1.f2....fn`,
+ * where the base qualifier `q` is either `this`, a local variable, or a type
+ * in case `f1` is static.
+ */
+class SsaSourceVariable extends TSsaSourceVariable {
+ /** Gets the variable corresponding to this `SsaSourceVariable`. */
+ Variable getVariable() {
+ this = TLocalVar(_, result) or
+ this = TPlainField(_, result) or
+ this = TEnclosingField(_, result, _) or
+ this = TQualifiedField(_, _, result)
+ }
+
+ /**
+ * Gets an access of this `SsaSourceVariable`. This access is within
+ * `this.getEnclosingCallable()`. Note that `LocalScopeVariable`s that are
+ * accessed from nested callables are therefore associated with several
+ * `SsaSourceVariable`s.
+ */
+ cached
+ VarAccess getAnAccess() {
+ exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) and result = v.getAnAccess() and result.getEnclosingCallable() = c) or
+ exists(Field f, Callable c | fieldAccessInCallable(result, f, c) |
+ (result.(FieldAccess).isOwnFieldAccess() or f.isStatic()) and this = TPlainField(c, f)
+ or
+ exists(RefType t | this = TEnclosingField(c, f, t) and result.(FieldAccess).isEnclosingFieldAccess(t))
+ or
+ exists(SsaSourceVariable q | result.getQualifier() = q.getAnAccess() and this = TQualifiedField(c, q, f))
+ )
+ }
+
+ /** Gets the `Callable` in which this `SsaSourceVariable` is defined. */
+ Callable getEnclosingCallable() {
+ this = TLocalVar(result, _) or
+ this = TPlainField(result, _) or
+ this = TEnclosingField(result, _, _) or
+ this = TQualifiedField(result, _, _)
+ }
+
+ string toString() {
+ exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) |
+ if c = v.getCallable() then result = v.getName() else result = c.getName() + "(..)." + v.getName()
+ ) or
+ result = this.(SsaSourceField).ppQualifier() + "." + getVariable().toString()
+ }
+
+ /**
+ * Gets the first access to `this` in terms of source code location. This is
+ * used as the representative location for named fields that otherwise would
+ * not have a specific source code location.
+ */
+ private VarAccess getFirstAccess() {
+ result = min(this.getAnAccess() as a order by
+ a.getLocation().getStartLine(), a.getLocation().getStartColumn())
+ }
+
+ Location getLocation() {
+ exists(LocalScopeVariable v | this = TLocalVar(_, v) and result = v.getLocation()) or
+ this instanceof SsaSourceField and result = getFirstAccess().getLocation()
+ }
+
+ /** Gets the type of this variable. */
+ Type getType() {
+ result = this.getVariable().getType()
+ }
+
+ /** Gets the qualifier, if any. */
+ SsaSourceVariable getQualifier() {
+ this = TQualifiedField(_, result, _)
+ }
+
+ /** Gets an SSA variable that has this variable as its underlying source variable. */
+ SsaVariable getAnSsaVariable() { result.getSourceVariable() = this }
+}
+
+/**
+ * A fully qualified field in the context of a `Callable` in which it is
+ * accessed.
+ */
+class SsaSourceField extends SsaSourceVariable {
+ SsaSourceField() {
+ this = TPlainField(_, _) or this = TEnclosingField(_, _, _) or this = TQualifiedField(_, _, _)
+ }
+
+ /** Gets the field corresponding to this named field. */
+ Field getField() {
+ result = getVariable()
+ }
+
+ /** Gets a string representation of the qualifier. */
+ string ppQualifier() {
+ exists(Field f | this = TPlainField(_, f) |
+ if f.isStatic() then
+ result = f.getDeclaringType().getQualifiedName()
+ else
+ result = "this"
+ )
+ or
+ exists(Field f, RefType t | this = TEnclosingField(_, f, t) |
+ result = t.toString() + ".this"
+ )
+ or
+ exists(SsaSourceVariable q | this = TQualifiedField(_, q, _) |
+ result = q.toString()
+ )
+ }
+
+ /** Holds if the field itself or any of the fields part of the qualifier are volatile. */
+ predicate isVolatile() {
+ getField().isVolatile() or
+ getQualifier().(SsaSourceField).isVolatile()
+ }
+}
+
+private module TrackedVariablesImpl {
+
+ /** Gets the number of accesses of `f`. */
+ private int numberOfAccesses(SsaSourceField f) {
+ result = strictcount(FieldAccess fa | fa = f.getAnAccess())
+ }
+
+ /** Holds if `f` is accessed inside a loop. */
+ private predicate loopAccessed(SsaSourceField f) {
+ exists(LoopStmt l, FieldRead fr | fr = f.getAnAccess() |
+ l.getBody() = fr.getEnclosingStmt().getParent*() or
+ l.getCondition() = fr.getParent*() or
+ l.(ForStmt).getAnUpdate() = fr.getParent*()
+ )
+ }
+
+ /** Holds if `f` is accessed more than once or inside a loop. */
+ private predicate multiAccessed(SsaSourceField f) {
+ loopAccessed(f) or 1 < numberOfAccesses(f)
+ }
+
+ /**
+ * Holds if `f` is a field that is interesting as a basis for SSA.
+ *
+ * - A field that is read twice is interesting as we want to know whether the
+ * reads refer to the same value.
+ * - A field that is both written and read is interesting as we want to know
+ * whether the read might get the written value.
+ * - A field that is read in a loop is interesting as we want to know whether
+ * the value is the same in different iterations (that is, whether the SSA
+ * definition can be placed outside the loop).
+ * - A volatile field is never interesting, since all reads must reread from
+ * memory and we are forced to assume that the value can change at any point.
+ */
+ cached
+ predicate trackField(SsaSourceField f) {
+ multiAccessed(f) and not f.isVolatile()
+ }
+
+ /**
+ * The variables that form the basis of the non-trivial SSA construction.
+ * Fields that aren't tracked get a trivial SSA construction (a definition
+ * prior to every read).
+ */
+ class TrackedVar extends SsaSourceVariable {
+ TrackedVar() {
+ this = TLocalVar(_, _) or
+ trackField(this)
+ }
+ }
+
+ class TrackedField extends TrackedVar, SsaSourceField {
+ }
+
+}
+private import TrackedVariablesImpl
+
+private cached module SsaImpl {
+
+ /** Gets the destination variable of an update of a tracked variable. */
+ cached
+ TrackedVar getDestVar(VariableUpdate upd) {
+ result.getAnAccess() = upd.(Assignment).getDest() or
+ exists(LocalVariableDecl v | v = upd.(LocalVariableDeclExpr).getVariable() |
+ result = TLocalVar(v.getCallable(), v)
+ ) or
+ result.getAnAccess() = upd.(UnaryAssignExpr).getExpr()
+ }
+
+ /** Holds if `n` must update the locally tracked variable `v`. */
+ cached
+ predicate certainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) {
+ exists(VariableUpdate a | a = n | getDestVar(a) = v) and b.getNode(i) = n or
+ certainVariableUpdate(v.getQualifier(), n, b, i)
+ }
+
+ /** Gets the definition point of a nested class in the parent scope. */
+ private ControlFlowNode parentDef(NestedClass nc) {
+ nc.(AnonymousClass).getClassInstanceExpr() = result or
+ nc.(LocalClass).getLocalClassDeclStmt() = result
+ }
+
+ /**
+ * Gets the enclosing type of a nested class.
+ *
+ * Differs from `RefType.getEnclosingType()` by including anonymous classes defined by lambdas.
+ */
+ private RefType desugaredGetEnclosingType(NestedClass inner) {
+ exists(ControlFlowNode node |
+ node = parentDef(inner) and
+ node.getEnclosingCallable().getDeclaringType() = result
+ )
+ }
+
+ /**
+ * Gets the control flow node at which the variable is read to get the value for
+ * a `VarAccess` inside a closure. `capturedvar` is the variable in its defining
+ * scope, and `closurevar` is the variable in the closure.
+ */
+ private ControlFlowNode captureNode(TrackedVar capturedvar, TrackedVar closurevar) {
+ exists(LocalScopeVariable v, Callable inner, Callable outer, NestedClass innerclass, VarAccess va |
+ va.getVariable() = v and
+ inner = va.getEnclosingCallable() and
+ outer = v.getCallable() and
+ inner != outer and
+ inner.getDeclaringType() = innerclass and
+ result = parentDef(desugaredGetEnclosingType*(innerclass)) and
+ result.getEnclosingStmt().getEnclosingCallable() = outer and
+ capturedvar = TLocalVar(outer, v) and
+ closurevar = TLocalVar(inner, v)
+ )
+ }
+
+ /** Holds if `VarAccess` `use` of `v` occurs in `b` at index `i`. */
+ private predicate variableUse(TrackedVar v, RValue use, BasicBlock b, int i) {
+ v.getAnAccess() = use and b.getNode(i) = use
+ }
+
+ /** Holds if the value of `v` is captured in `b` at index `i`. */
+ private predicate variableCapture(TrackedVar capturedvar, TrackedVar closurevar, BasicBlock b, int i) {
+ b.getNode(i) = captureNode(capturedvar, closurevar)
+ }
+
+ /** Holds if the value of `v` is read in `b` at index `i`. */
+ private predicate variableUseOrCapture(TrackedVar v, BasicBlock b, int i) {
+ variableUse(v, _, b, i) or variableCapture(v, _, b, i)
+ }
+
+ /*
+ * Liveness analysis to restrict the size of the SSA representation.
+ */
+
+ private predicate liveAtEntry(TrackedVar v, BasicBlock b) {
+ exists(int i | variableUseOrCapture(v, b, i) |
+ not exists(int j | certainVariableUpdate(v, _, b, j) | j < i))
+ or
+ liveAtExit(v, b) and not certainVariableUpdate(v, _, b, _)
+ }
+ private predicate liveAtExit(TrackedVar v, BasicBlock b) {
+ liveAtEntry(v, b.getABBSuccessor())
+ }
+
+ /*
+ * The SSA construction for a field `f` relies on implicit update nodes at
+ * every call site that conceivably could reach an update of the field.
+ *
+ * At a first approximation we need to find update paths of the form:
+ * Callable --(callEdge)-->* Callable(setter of f)
+ *
+ * This can be improved by excluding paths ending in:
+ * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f)
+ * as these updates are guaranteed not to alias with the `f` under
+ * consideration.
+ *
+ * This set of paths can be expressed positively by noting that those
+ * that set `this.f` end in zero or more `intraInstanceCallEdge`s between
+ * methods, and before those is either the originating `Call` or a
+ * `crossInstanceCallEdge`.
+ */
+
+ /**
+ * Holds if `fw` is a field write that is not relevant as an implicit SSA
+ * update, since it is an initialization and therefore cannot alias.
+ */
+ private predicate init(FieldWrite fw) {
+ fw.getEnclosingCallable() instanceof InitializerMethod or
+ fw.getEnclosingCallable() instanceof Constructor and fw.isOwnFieldAccess() or
+ exists(LocalVariableDecl v |
+ v.getAnAccess() = fw.getQualifier() and
+ forex(VariableAssign va | va.getDestVar() = v and exists(va.getSource()) |
+ va.getSource() instanceof ClassInstanceExpr
+ )
+ )
+ }
+
+ /**
+ * Holds if `fw` is an update of `f` in `c` that is relevant for SSA construction.
+ */
+ cached
+ predicate relevantFieldUpdate(Callable c, Field f, FieldWrite fw) {
+ fw = f.getAnAccess() and
+ not init(fw) and
+ fw.getEnclosingCallable() = c and
+ exists(TrackedField nf | nf.getField() = f)
+ }
+
+ /** Holds if `c` can change the value of `this.f` and is relevant for SSA construction. */
+ private predicate setsOwnField(Method c, Field f) {
+ exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and fw.isOwnFieldAccess())
+ }
+
+ /**
+ * Holds if `c` can change the value of `f` and is relevant for SSA
+ * construction excluding those cases covered by `setsOwnField`.
+ */
+ private predicate setsOtherField(Callable c, Field f) {
+ exists(FieldWrite fw | relevantFieldUpdate(c, f, fw) and not fw.isOwnFieldAccess())
+ }
+
+ pragma[nomagic]
+ private predicate innerclassSupertypeStar(InnerClass t1, RefType t2) {
+ t1.getASupertype*().getSourceDeclaration() = t2
+ }
+
+ /**
+ * Holds if `(c1,m2)` is a call edge to a method that does not change the value
+ * of `this`.
+ *
+ * Constructor-to-constructor calls can also be intra-instance, but are not
+ * included, as this does not affect whether a call chain ends in
+ *
+ * ```
+ * Constructor --(intraInstanceCallEdge)-->+ Method(setter of this.f)
+ * ```
+ */
+ private predicate intraInstanceCallEdge(Callable c1, Method m2) {
+ exists(MethodAccess ma, RefType t1 |
+ ma.getCaller() = c1 and
+ m2 = viableImpl(ma) and
+ not m2.isStatic() and
+ (
+ not exists(ma.getQualifier()) or
+ ma.getQualifier() instanceof ThisAccess or
+ ma.getQualifier() instanceof SuperAccess
+ ) and
+ c1.getDeclaringType() = t1 and
+ if t1 instanceof InnerClass then
+ innerclassSupertypeStar(t1, ma.getCallee().getSourceDeclaration().getDeclaringType()) and
+ not exists(ma.getQualifier().(ThisAccess).getQualifier()) and
+ not exists(ma.getQualifier().(SuperAccess).getQualifier())
+ else any()
+ )
+ }
+
+ private Callable tgt(Call c) {
+ result = viableImpl(c) or
+ result = getRunnerTarget(c) or
+ c instanceof ConstructorCall and result = c.getCallee().getSourceDeclaration()
+ }
+
+ /** Holds if `(c1,c2)` is an edge in the call graph. */
+ private predicate callEdge(Callable c1, Callable c2) {
+ exists(Call c | c.getCaller() = c1 and c2 = tgt(c))
+ }
+
+ /** Holds if `(c1,c2)` is an edge in the call graph excluding `intraInstanceCallEdge`. */
+ private predicate crossInstanceCallEdge(Callable c1, Callable c2) {
+ callEdge(c1, c2) and not intraInstanceCallEdge(c1, c2)
+ }
+
+ /** Holds if a call to `x.c` can change the value of `x.f`. The actual update occurs in `setter`. */
+ private predicate setsOwnFieldTransitive(Method c, Field f, Method setter) {
+ setsOwnField(setter, f) and intraInstanceCallEdge*(c, setter)
+ }
+
+ /** Holds if a call to `c` can change the value of `f` on some instance. The actual update occurs in `setter`. */
+ private predicate generalSetter(Callable c, Field f, Callable setter) {
+ exists(Method ownsetter |
+ setsOwnFieldTransitive(ownsetter, f, setter) and
+ crossInstanceCallEdge(c, ownsetter)
+ )
+ or
+ setsOtherField(c, f) and c = setter
+ }
+
+ /**
+ * Holds if `call` occurs in the same basic block, `b`, as `f` at index `i` and
+ * `f` has an update somewhere.
+ */
+ private predicate updateCandidate(TrackedField f, Call call, BasicBlock b, int i) {
+ b.getNode(i) = call and
+ call.getEnclosingCallable() = f.getEnclosingCallable() and
+ relevantFieldUpdate(_, f.getField(), _)
+ }
+
+ /**
+ * Holds if `rankix` is the rank of index `i` at which there is a use, a
+ * certain update, or a potential update of `f` in the basic block `b`.
+ *
+ * Basic block indices are translated to rank indices in order to skip
+ * irrelevant indices at which there is update or use when traversing
+ * basic blocks.
+ */
+ private predicate callDefUseRank(TrackedField f, BasicBlock b, int rankix, int i) {
+ updateCandidate(f, _, b, _) and
+ i = rank[rankix](int j | certainVariableUpdate(f, _, b, j) or variableUseOrCapture(f, b, j) or updateCandidate(f, _, b, j))
+ }
+
+ /**
+ * Holds if `f` is live in `b` at index `i`. The rank of `i` is `rankix` as
+ * defined by `callDefUseRank`.
+ */
+ private predicate liveAtRank(TrackedField f, BasicBlock b, int rankix, int i) {
+ callDefUseRank(f, b, rankix, i) and
+ (
+ rankix = max(int rix | callDefUseRank(f, b, rix, _)) and liveAtExit(f, b)
+ or
+ variableUseOrCapture(f, b, i)
+ or
+ exists(int j | liveAtRank(f, b, rankix+1, j) and not certainVariableUpdate(f, _, b, j))
+ )
+ }
+
+ /**
+ * Holds if `call` is relevant as a potential update of `f`. This requires the
+ * existence of an update to `f` somewhere and that `f` is live at `call`.
+ */
+ private predicate relevantCall(Call call, TrackedField f) {
+ exists(BasicBlock b, int i |
+ updateCandidate(f, call, b, i) and
+ liveAtRank(f, b, _, i)
+ )
+ }
+
+ /**
+ * Holds if `c` is a relevant part of the call graph for
+ * `updatesNamedFieldPart1` based on following edges in forward direction.
+ */
+ private predicate pruneFromLeft(Callable c) {
+ exists(Call call, SsaSourceField f |
+ generalSetter(_, f.getField(), _) and
+ relevantCall(call, f) and
+ c = tgt(call)
+ )
+ or
+ exists(Callable mid | pruneFromLeft(mid) and callEdge(mid, c))
+ }
+
+ /**
+ * Holds if `c` is a relevant part of the call graph for
+ * `updatesNamedFieldPart1` based on following edges in backward direction.
+ */
+ private predicate pruneFromRight(Callable c) {
+ generalSetter(c, _, _)
+ or
+ exists(Callable mid | callEdge(c, mid) and pruneFromRight(mid))
+ }
+
+ /** A restriction of the call graph to the parts that are relevant for `updatesNamedFieldPart1`. */
+ private class PrunedCallable extends Callable {
+ PrunedCallable() {
+ pruneFromLeft(this) and pruneFromRight(this)
+ }
+ }
+
+ private predicate callEdgePruned(PrunedCallable c1, PrunedCallable c2) {
+ callEdge(c1, c2)
+ }
+
+ private predicate callEdgePlus(PrunedCallable c1, PrunedCallable c2) = fastTC(callEdgePruned/2)(c1, c2)
+
+ pragma[noinline]
+ private predicate updatesNamedFieldPrefix(Call call, TrackedField f, Callable c1, Field field) {
+ relevantCall(call, f) and
+ field = f.getField() and
+ c1 = tgt(call)
+ }
+
+ pragma[noinline]
+ private predicate generalSetterProj(Callable c, Field f) {
+ generalSetter(c, f, _)
+ }
+
+ /**
+ * Holds if `call` may change the value of `f` on some instance, which may or
+ * may not alias with `this`. The actual update occurs in `setter`.
+ */
+ pragma[noopt]
+ private predicate updatesNamedFieldPart1(Call call, TrackedField f, Callable setter) {
+ exists(Callable c1, Callable c2, Field field |
+ updatesNamedFieldPrefix(call, f, c1, field) and
+ generalSetterProj(c2, field) and
+ (c1 = c2 or callEdgePlus(c1, c2)) and
+ generalSetter(c2, field, setter)
+ )
+ }
+
+ /** Holds if `call` may change the value of `f` on `this`. The actual update occurs in `setter`. */
+ private predicate updatesNamedFieldPart2(Call call, TrackedField f, Callable setter) {
+ relevantCall(call, f) and
+ setsOwnFieldTransitive(tgt(call), f.getField(), setter)
+ }
+
+ /**
+ * Holds if there exists a call-chain originating in `call` that can update `f` on some instance
+ * where `f` and `call` share the same enclosing callable in which a
+ * `FieldRead` of `f` is reachable from `call`.
+ */
+ cached
+ predicate updatesNamedField(Call call, TrackedField f, Callable setter) {
+ updatesNamedFieldPart1(call, f, setter) or updatesNamedFieldPart2(call, f, setter)
+ }
+
+ /** Holds if `n` might update the locally tracked variable `v`. */
+ cached
+ predicate uncertainVariableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) {
+ exists(Call c | c = n | updatesNamedField(c, v, _)) and b.getNode(i) = n or
+ uncertainVariableUpdate(v.getQualifier(), n, b, i)
+ }
+
+ /** Holds if `n` updates the locally tracked variable `v`. */
+ private predicate variableUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) {
+ certainVariableUpdate(v, n, b, i) or uncertainVariableUpdate(v, n, b, i)
+ }
+
+ /** Holds if a phi node for `v` is needed at the beginning of basic block `b`. */
+ cached
+ predicate phiNode(TrackedVar v, BasicBlock b) {
+ liveAtEntry(v, b) and
+ exists(BasicBlock def | dominanceFrontier(def, b) |
+ variableUpdate(v, _, def, _) or phiNode(v, def)
+ )
+ }
+
+ /** Holds if `v` has an implicit definition at the entry, `b`, of the callable. */
+ cached
+ predicate hasEntryDef(TrackedVar v, BasicBlock b) {
+ exists(LocalScopeVariable l, Callable c |
+ v = TLocalVar(c, l) and c.getBody() = b
+ |
+ l instanceof Parameter or
+ l.getCallable() != c
+ ) or
+ v instanceof SsaSourceField and v.getEnclosingCallable().getBody() = b and liveAtEntry(v, b)
+ }
+
+ /**
+ * The construction of SSA form ensures that each use of a variable is
+ * dominated by its definition. A definition of an SSA variable therefore
+ * reaches a `ControlFlowNode` if it is the _closest_ SSA variable definition
+ * that dominates the node. If two definitions dominate a node then one must
+ * dominate the other, so therefore the definition of _closest_ is given by the
+ * dominator tree. Thus, reaching definitions can be calculated in terms of
+ * dominance.
+ */
+ cached module SsaDefReaches {
+
+ /**
+ * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or use of
+ * `v` in the basic block `b`.
+ *
+ * Basic block indices are translated to rank indices in order to skip
+ * irrelevant indices at which there is no definition or use when traversing
+ * basic blocks.
+ */
+ private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) {
+ i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUseOrCapture(v, b, j))
+ }
+
+ /** Gets the maximum rank index for the given variable and basic block. */
+ private int lastRank(TrackedVar v, BasicBlock b) {
+ result = max(int rankix | defUseRank(v, b, rankix, _))
+ }
+
+ /** Holds if a definition of an SSA variable occurs at the specified rank index in basic block `b`. */
+ private predicate ssaDefRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) {
+ exists(int i |
+ def.definesAt(v, b, i) and
+ defUseRank(v, b, rankix, i)
+ )
+ }
+
+ /** Holds if the SSA definition reaches the rank index `rankix` in its own basic block `b`. */
+ private predicate ssaDefReachesRank(TrackedVar v, TrackedSsaDef def, BasicBlock b, int rankix) {
+ ssaDefRank(v, def, b, rankix) or
+ ssaDefReachesRank(v, def, b, rankix-1) and rankix <= lastRank(v, b) and not ssaDefRank(v, _, b, rankix)
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches the end of a basic block `b`, at
+ * which point it is still live, without crossing another SSA definition of `v`.
+ */
+ cached
+ predicate ssaDefReachesEndOfBlock(TrackedVar v, TrackedSsaDef def, BasicBlock b) {
+ liveAtExit(v, b) and
+ (
+ ssaDefReachesRank(v, def, b, lastRank(v, b)) or
+ exists(BasicBlock idom |
+ bbIDominates(idom, b) and // It is sufficient to traverse the dominator graph, cf. discussion above.
+ ssaDefReachesEndOfBlock(v, def, idom) and
+ not any(TrackedSsaDef other).definesAt(v, b, _)
+ )
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches `use` in the same basic block
+ * without crossing another SSA definition of `v`.
+ */
+ private predicate ssaDefReachesUseWithinBlock(TrackedVar v, TrackedSsaDef def, RValue use) {
+ exists(BasicBlock b, int rankix, int i |
+ ssaDefReachesRank(v, def, b, rankix) and
+ defUseRank(v, b, rankix, i) and
+ variableUse(v, use, b, i)
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches `use` without crossing another
+ * SSA definition of `v`.
+ */
+ cached
+ predicate ssaDefReachesUse(TrackedVar v, TrackedSsaDef def, RValue use) {
+ ssaDefReachesUseWithinBlock(v, def, use) or
+ exists(BasicBlock b |
+ variableUse(v, use, b, _) and
+ ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and
+ not ssaDefReachesUseWithinBlock(v, _, use)
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches the capture point of
+ * `closurevar` in the same basic block without crossing another SSA
+ * definition of `v`.
+ */
+ private predicate ssaDefReachesCaptureWithinBlock(TrackedVar v, TrackedSsaDef def, TrackedVar closurevar) {
+ exists(BasicBlock b, int rankix, int i |
+ ssaDefReachesRank(v, def, b, rankix) and
+ defUseRank(v, b, rankix, i) and
+ variableCapture(v, closurevar, b, i)
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches capture point of
+ * `closurevar` without crossing another SSA definition of `v`.
+ */
+ cached
+ predicate ssaDefReachesCapture(TrackedVar v, TrackedSsaDef def, TrackedVar closurevar) {
+ ssaDefReachesCaptureWithinBlock(v, def, closurevar) or
+ exists(BasicBlock b |
+ variableCapture(v, closurevar, b, _) and
+ ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and
+ not ssaDefReachesCaptureWithinBlock(v, _, closurevar)
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches `redef` in the same basic block
+ * without crossing another SSA definition of `v`.
+ */
+ private predicate ssaDefReachesUncertainDefWithinBlock(TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef) {
+ exists(BasicBlock b, int rankix, int i |
+ ssaDefReachesRank(v, def, b, rankix) and
+ defUseRank(v, b, rankix+1, i) and
+ redef.(TrackedSsaDef).definesAt(v, b, i)
+ )
+ }
+
+ /**
+ * Holds if the SSA definition of `v` at `def` reaches `redef` without crossing another
+ * SSA definition of `v`.
+ */
+ cached
+ predicate ssaDefReachesUncertainDef(TrackedVar v, TrackedSsaDef def, SsaUncertainImplicitUpdate redef) {
+ ssaDefReachesUncertainDefWithinBlock(v, def, redef) or
+ exists(BasicBlock b |
+ redef.(TrackedSsaDef).definesAt(v, b, _) and
+ ssaDefReachesEndOfBlock(v, def, b.getABBPredecessor()) and
+ not ssaDefReachesUncertainDefWithinBlock(v, _, redef)
+ )
+ }
+
+ }
+
+ private module AdjacentUsesImpl {
+
+ /**
+ * Holds if `rankix` is the rank the index `i` at which there is an SSA definition or explicit use of
+ * `v` in the basic block `b`.
+ */
+ private predicate defUseRank(TrackedVar v, BasicBlock b, int rankix, int i) {
+ i = rank[rankix](int j | any(TrackedSsaDef def).definesAt(v, b, j) or variableUse(v, _, b, j))
+ }
+
+ /** Gets the maximum rank index for the given variable and basic block. */
+ private int lastRank(TrackedVar v, BasicBlock b) {
+ result = max(int rankix | defUseRank(v, b, rankix, _))
+ }
+
+ /** Holds if `v` is defined or used in `b`. */
+ private predicate varOccursInBlock(TrackedVar v, BasicBlock b) {
+ defUseRank(v, b, _, _)
+ }
+
+ /** Holds if `v` occurs in `b` or one of `b`'s transitive successors. */
+ private predicate blockPrecedesVar(TrackedVar v, BasicBlock b) {
+ varOccursInBlock(v, b.getABBSuccessor*())
+ }
+
+ /**
+ * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and
+ * in `b2` or one of its transitive successors but not in any block on the path
+ * between `b1` and `b2`.
+ */
+ private predicate varBlockReaches(TrackedVar v, BasicBlock b1, BasicBlock b2) {
+ varOccursInBlock(v, b1) and b2 = b1.getABBSuccessor() or
+ exists(BasicBlock mid |
+ varBlockReaches(v, b1, mid) and
+ b2 = mid.getABBSuccessor() and
+ not varOccursInBlock(v, mid) and
+ blockPrecedesVar(v, b2)
+ )
+ }
+
+ /**
+ * Holds if `b2` is a transitive successor of `b1` and `v` occurs in `b1` and
+ * `b2` but not in any block on the path between `b1` and `b2`.
+ */
+ private predicate varBlockStep(TrackedVar v, BasicBlock b1, BasicBlock b2) {
+ varBlockReaches(v, b1, b2) and
+ varOccursInBlock(v, b2)
+ }
+
+ /**
+ * Holds if `v` occurs at index `i1` in `b1` and at index `i2` in `b2` and
+ * there is a path between them without any occurrence of `v`.
+ */
+ predicate adjacentVarRefs(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2) {
+ exists(int rankix |
+ b1 = b2 and
+ defUseRank(v, b1, rankix, i1) and
+ defUseRank(v, b2, rankix+1, i2)
+ ) or
+ defUseRank(v, b1, lastRank(v, b1), i1) and
+ varBlockStep(v, b1, b2) and
+ defUseRank(v, b2, 1, i2)
+ }
+
+ }
+ private import AdjacentUsesImpl
+
+ /**
+ * Holds if the value defined at `def` can reach `use` without passing through
+ * any other uses, but possibly through phi nodes and uncertain implicit updates.
+ */
+ cached
+ predicate firstUse(TrackedSsaDef def, RValue use) {
+ exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 |
+ adjacentVarRefs(v, b1, i1, b2, i2) and
+ def.definesAt(v, b1, i1) and
+ variableUse(v, use, b2, i2)
+ ) or
+ exists(TrackedVar v, TrackedSsaDef redef, BasicBlock b1, int i1, BasicBlock b2, int i2 |
+ redef instanceof SsaUncertainImplicitUpdate or redef instanceof SsaPhiNode
+ |
+ adjacentVarRefs(v, b1, i1, b2, i2) and
+ def.definesAt(v, b1, i1) and
+ redef.definesAt(v, b2, i2) and
+ firstUse(redef, use)
+ )
+ }
+
+ cached module SsaPublic {
+
+ /**
+ * Holds if `use1` and `use2` form an adjacent use-use-pair of the same SSA
+ * variable, that is, the value read in `use1` can reach `use2` without passing
+ * through any other use or any SSA definition of the variable.
+ */
+ cached
+ predicate adjacentUseUseSameVar(RValue use1, RValue use2) {
+ exists(TrackedVar v, BasicBlock b1, int i1, BasicBlock b2, int i2 |
+ adjacentVarRefs(v, b1, i1, b2, i2) and
+ variableUse(v, use1, b1, i1) and
+ variableUse(v, use2, b2, i2)
+ )
+ }
+
+ /**
+ * Holds if `use1` and `use2` form an adjacent use-use-pair of the same
+ * `SsaSourceVariable`, that is, the value read in `use1` can reach `use2`
+ * without passing through any other use or any SSA definition of the variable
+ * except for phi nodes and uncertain implicit updates.
+ */
+ cached
+ predicate adjacentUseUse(RValue use1, RValue use2) {
+ adjacentUseUseSameVar(use1, use2) or
+ exists(TrackedVar v, TrackedSsaDef def, BasicBlock b1, int i1, BasicBlock b2, int i2 |
+ adjacentVarRefs(v, b1, i1, b2, i2) and
+ variableUse(v, use1, b1, i1) and
+ def.definesAt(v, b2, i2) and
+ firstUse(def, use2) and
+ (def instanceof SsaUncertainImplicitUpdate or def instanceof SsaPhiNode)
+ )
+ }
+
+ }
+
+}
+private import SsaImpl
+private import SsaDefReaches
+import SsaPublic
+
+cached
+private newtype TSsaVariable =
+ TSsaPhiNode(TrackedVar v, BasicBlock b) { phiNode(v, b) } or
+ TSsaCertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { certainVariableUpdate(v, n, b, i) } or
+ TSsaUncertainUpdate(TrackedVar v, ControlFlowNode n, BasicBlock b, int i) { uncertainVariableUpdate(v, n, b, i) } or
+ TSsaEntryDef(TrackedVar v, BasicBlock b) { hasEntryDef(v, b) } or
+ TSsaUntracked(SsaSourceField nf, ControlFlowNode n) {
+ n = nf.getAnAccess().(FieldRead) and not trackField(nf)
+ }
+
+/**
+ * An SSA definition excluding those variables that use a trivial SSA construction.
+ */
+private class TrackedSsaDef extends SsaVariable {
+ TrackedSsaDef() {
+ not this = TSsaUntracked(_, _)
+ }
+
+ /**
+ * Holds if this SSA definition occurs at the specified position.
+ * Phi nodes are placed at index -1.
+ */
+ predicate definesAt(TrackedVar v, BasicBlock b, int i) {
+ this = TSsaPhiNode(v, b) and i = -1 or
+ this = TSsaCertainUpdate(v, _, b, i) or
+ this = TSsaUncertainUpdate(v, _, b, i) or
+ this = TSsaEntryDef(v, b) and i = 0
+ }
+}
+
+/**
+ * An SSA variable.
+ */
+class SsaVariable extends TSsaVariable {
+ /** Gets the SSA source variable underlying this SSA variable. */
+ SsaSourceVariable getSourceVariable() {
+ this = TSsaPhiNode(result, _) or
+ this = TSsaCertainUpdate(result, _, _, _) or
+ this = TSsaUncertainUpdate(result, _, _, _) or
+ this = TSsaEntryDef(result, _) or
+ this = TSsaUntracked(result, _)
+ }
+
+ /** Gets the `ControlFlowNode` at which this SSA variable is defined. */
+ ControlFlowNode getCFGNode() {
+ this = TSsaPhiNode(_, result) or
+ this = TSsaCertainUpdate(_, result, _, _) or
+ this = TSsaUncertainUpdate(_, result, _, _) or
+ this = TSsaEntryDef(_, result) or
+ this = TSsaUntracked(_, result)
+ }
+
+ string toString() { none() }
+
+ Location getLocation() { result = getCFGNode().getLocation() }
+
+ /** Gets the `BasicBlock` in which this SSA variable is defined. */
+ BasicBlock getBasicBlock() { result = getCFGNode().getBasicBlock() }
+
+ /** Gets an access of this SSA variable. */
+ RValue getAUse() {
+ ssaDefReachesUse(_, this, result) or
+ this = TSsaUntracked(_, result)
+ }
+
+ /**
+ * Gets an access of the SSA source variable underlying this SSA variable
+ * that can be reached from this SSA variable without passing through any
+ * other uses, but potentially through phi nodes and uncertain implicit
+ * updates.
+ *
+ * Subsequent uses can be found by following the steps defined by
+ * `adjacentUseUse`.
+ */
+ RValue getAFirstUse() {
+ firstUse(this, result) or
+ this = TSsaUntracked(_, result)
+ }
+
+ /** Holds if this SSA variable is live at the end of `b`. */
+ predicate isLiveAtEndOfBlock(BasicBlock b) {
+ ssaDefReachesEndOfBlock(_, this, b)
+ }
+
+ /**
+ * Gets an SSA variable whose value can flow to this one in one step. This
+ * includes inputs to phi nodes, the prior definition of uncertain updates,
+ * and the captured ssa variable for a closure variable.
+ */
+ private SsaVariable getAPhiInputOrPriorDef() {
+ result = this.(SsaPhiNode).getAPhiInput() or
+ result = this.(SsaUncertainImplicitUpdate).getPriorDef() or
+ this.(SsaImplicitInit).captures(result)
+ }
+
+ /** Gets a definition that ultimately defines this variable and is not itself a phi node. */
+ SsaVariable getAnUltimateDefinition() {
+ result = this.getAPhiInputOrPriorDef*() and not result instanceof SsaPhiNode
+ }
+}
+
+/** An SSA variable that either explicitly or implicitly updates the variable. */
+class SsaUpdate extends SsaVariable {
+ SsaUpdate() {
+ this = TSsaCertainUpdate(_, _, _, _) or
+ this = TSsaUncertainUpdate(_, _, _, _) or
+ this = TSsaUntracked(_, _)
+ }
+}
+
+/** An SSA variable that is defined by a `VariableUpdate`. */
+class SsaExplicitUpdate extends SsaUpdate, TSsaCertainUpdate {
+ SsaExplicitUpdate() {
+ exists(VariableUpdate upd |
+ upd = this.getCFGNode() and getDestVar(upd) = getSourceVariable()
+ )
+ }
+
+ override string toString() {
+ result = "SSA def(" + getSourceVariable() + ")"
+ }
+
+ /** Gets the `VariableUpdate` defining the SSA variable. */
+ VariableUpdate getDefiningExpr() {
+ result = this.getCFGNode() and getDestVar(result) = getSourceVariable()
+ }
+}
+
+/**
+ * An SSA variable that represents any sort of implicit update. This can be a
+ * `Call` that might reach a non-local update of the field, an explicit or
+ * implicit update of the qualifier of the field, or the implicit update that
+ * occurs just prior to a `FieldRead` of an untracked field.
+ */
+class SsaImplicitUpdate extends SsaUpdate {
+ SsaImplicitUpdate() {
+ not this instanceof SsaExplicitUpdate
+ }
+
+ override string toString() {
+ result = "SSA impl upd[" + getKind() + "](" + getSourceVariable() + ")"
+ }
+
+ private string getKind() {
+ this = TSsaUntracked(_, _) and result = "untracked" or
+ certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) and result = "explicit qualifier" or
+ if uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) then
+ if exists(getANonLocalUpdate()) then
+ result = "nonlocal + nonlocal qualifier"
+ else
+ result = "nonlocal qualifier"
+ else
+ (exists(getANonLocalUpdate()) and result = "nonlocal")
+ }
+
+ /**
+ * Gets a reachable `FieldWrite` that might represent this ssa update, if any.
+ */
+ FieldWrite getANonLocalUpdate() {
+ exists(SsaSourceField f, Callable setter |
+ f = getSourceVariable() and
+ relevantFieldUpdate(setter, f.getField(), result) and
+ updatesNamedField(getCFGNode(), f, setter)
+ )
+ }
+
+ /**
+ * Holds if this ssa variable might change the value to something unknown.
+ *
+ * Examples include updates that might change the value of the qualifier, or
+ * reads from untracked variables, for example those where the field or one
+ * of its qualifiers is volatile.
+ */
+ predicate assignsUnknownValue() {
+ this = TSsaUntracked(_, _) or
+ certainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _) or
+ uncertainVariableUpdate(getSourceVariable().getQualifier(), getCFGNode(), _, _)
+ }
+}
+
+/**
+ * An SSA variable that represents an uncertain implicit update of the value.
+ * This is a `Call` that might reach a non-local update of the field or one of
+ * its qualifiers.
+ */
+class SsaUncertainImplicitUpdate extends SsaImplicitUpdate, TSsaUncertainUpdate {
+ /**
+ * Gets the immediately preceding definition. Since this update is uncertain
+ * the value from the preceding definition might still be valid.
+ */
+ SsaVariable getPriorDef() {
+ ssaDefReachesUncertainDef(_, result, this)
+ }
+}
+
+/**
+ * An SSA variable that is defined by its initial value in the callable. This
+ * includes initial values of parameters, fields, and closure variables.
+ */
+class SsaImplicitInit extends SsaVariable, TSsaEntryDef {
+ override string toString() {
+ result = "SSA init(" + getSourceVariable() + ")"
+ }
+
+ /** Holds if this is a closure variable that captures the value of `capturedvar`. */
+ predicate captures(SsaVariable capturedvar) {
+ ssaDefReachesCapture(_, capturedvar, getSourceVariable())
+ }
+
+ /**
+ * Holds if the SSA variable is a parameter defined by its initial value in the callable.
+ */
+ predicate isParameterDefinition(Parameter p) {
+ getSourceVariable() = TLocalVar(p.getCallable(), p) and p.getCallable().getBody() = getCFGNode()
+ }
+}
+
+/** An SSA phi node. */
+class SsaPhiNode extends SsaVariable, TSsaPhiNode {
+ override string toString() {
+ result = "SSA phi(" + getSourceVariable() + ")"
+ }
+
+ /** Gets an input to the phi node defining the SSA variable. */
+ SsaVariable getAPhiInput() {
+ exists(BasicBlock phiPred, TrackedVar v |
+ v = getSourceVariable() and
+ getCFGNode().(BasicBlock).getABBPredecessor() = phiPred and
+ ssaDefReachesEndOfBlock(v, result, phiPred)
+ )
+ }
+
+ /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */
+ predicate hasInputFromBlock(SsaVariable inp, BasicBlock bb) {
+ this.getAPhiInput() = inp and
+ this.getBasicBlock().getABBPredecessor() = bb and
+ inp.isLiveAtEndOfBlock(bb)
+ }
+}
+
+library class RefTypeCastExpr extends CastExpr {
+ RefTypeCastExpr() { this.getType() instanceof RefType }
+}
+
+/**
+ * Gets an expression that has the same value as the given SSA variable.
+ *
+ * The `VarAccess` represents the access to `v` that `result` has the same value as.
+ */
+Expr sameValue(SsaVariable v, VarAccess va) {
+ result = v.getAUse() and result = va or
+ result.(AssignExpr).getDest() = va and result = v.(SsaExplicitUpdate).getDefiningExpr() or
+ result.(AssignExpr).getSource() = sameValue(v, va) or
+ result.(ParExpr).getExpr() = sameValue(v, va) or
+ result.(RefTypeCastExpr).getExpr() = sameValue(v, va)
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll
new file mode 100644
index 00000000000..aa4eb8bb647
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll
@@ -0,0 +1,447 @@
+/**
+ * Provides sign analysis to determine whether expression are always positive
+ * or negative.
+ *
+ * The analysis is implemented as an abstract interpretation over the
+ * three-valued domain `{negative, zero, positive}`.
+ */
+import java
+private import SSA
+private import RangeUtils
+private import semmle.code.java.controlflow.Guards
+private import semmle.code.java.Reflection
+private import semmle.code.java.Collections
+private import semmle.code.java.Maps
+
+private newtype TSign = TNeg() or TZero() or TPos()
+private class Sign extends TSign {
+ string toString() {
+ result = "-" and this = TNeg() or
+ result = "0" and this = TZero() or
+ result = "+" and this = TPos()
+ }
+ Sign inc() {
+ this = TNeg() and result = TNeg() or
+ this = TNeg() and result = TZero() or
+ this = TZero() and result = TPos() or
+ this = TPos() and result = TPos()
+ }
+ Sign dec() {
+ result.inc() = this
+ }
+ Sign neg() {
+ this = TNeg() and result = TPos() or
+ this = TZero() and result = TZero() or
+ this = TPos() and result = TNeg()
+ }
+ Sign bitnot() {
+ this = TNeg() and result = TPos() or
+ this = TNeg() and result = TZero() or
+ this = TZero() and result = TNeg() or
+ this = TPos() and result = TNeg()
+ }
+ Sign add(Sign s) {
+ this = TZero() and result = s or
+ s = TZero() and result = this or
+ this = s and this = result or
+ this = TPos() and s = TNeg() or
+ this = TNeg() and s = TPos()
+ }
+ Sign mul(Sign s) {
+ result = TZero() and this = TZero() or
+ result = TZero() and s = TZero() or
+ result = TNeg() and this = TPos() and s = TNeg() or
+ result = TNeg() and this = TNeg() and s = TPos() or
+ result = TPos() and this = TPos() and s = TPos() or
+ result = TPos() and this = TNeg() and s = TNeg()
+ }
+ Sign div(Sign s) {
+ result = TZero() and s = TNeg() or
+ result = TZero() and s = TPos() or
+ result = TNeg() and this = TPos() and s = TNeg() or
+ result = TNeg() and this = TNeg() and s = TPos() or
+ result = TPos() and this = TPos() and s = TPos() or
+ result = TPos() and this = TNeg() and s = TNeg()
+ }
+ Sign rem(Sign s) {
+ result = TZero() and s = TNeg() or
+ result = TZero() and s = TPos() or
+ result = this and s = TNeg() or
+ result = this and s = TPos()
+ }
+ Sign bitand(Sign s) {
+ result = TZero() and this = TZero() or
+ result = TZero() and s = TZero() or
+ result = TZero() and this = TPos() or
+ result = TZero() and s = TPos() or
+ result = TNeg() and this = TNeg() and s = TNeg() or
+ result = TPos() and this = TNeg() and s = TPos() or
+ result = TPos() and this = TPos() and s = TNeg() or
+ result = TPos() and this = TPos() and s = TPos()
+ }
+ Sign bitor(Sign s) {
+ result = TZero() and this = TZero() and s = TZero() or
+ result = TNeg() and this = TNeg() or
+ result = TNeg() and s = TNeg() or
+ result = TPos() and this = TPos() and s = TZero() or
+ result = TPos() and this = TZero() and s = TPos() or
+ result = TPos() and this = TPos() and s = TPos()
+ }
+ Sign bitxor(Sign s) {
+ result = TZero() and this = s or
+ result = this and s = TZero() or
+ result = s and this = TZero() or
+ result = TPos() and this = TPos() and s = TPos() or
+ result = TNeg() and this = TNeg() and s = TPos() or
+ result = TNeg() and this = TPos() and s = TNeg() or
+ result = TPos() and this = TNeg() and s = TNeg()
+ }
+ Sign lshift(Sign s) {
+ result = TZero() and this = TZero() or
+ result = this and s = TZero() or
+ this != TZero() and s != TZero()
+ }
+ Sign rshift(Sign s) {
+ result = TZero() and this = TZero() or
+ result = this and s = TZero() or
+ result = TNeg() and this = TNeg() or
+ result != TNeg() and this = TPos() and s != TZero()
+ }
+ Sign urshift(Sign s) {
+ result = TZero() and this = TZero() or
+ result = this and s = TZero() or
+ result != TZero() and this = TNeg() and s != TZero() or
+ result != TNeg() and this = TPos() and s != TZero()
+ }
+}
+
+/** Gets the sign of `e` if this can be directly determined. */
+private Sign certainExprSign(Expr e) {
+ exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
+ i < 0 and result = TNeg() or
+ i = 0 and result = TZero() or
+ i > 0 and result = TPos()
+ ) or
+ not exists(e.(ConstantIntegerExpr).getIntValue()) and
+ (
+ exists(float f |
+ f = e.(LongLiteral).getValue().toFloat() or
+ f = e.(FloatingPointLiteral).getValue().toFloat() or
+ f = e.(DoubleLiteral).getValue().toFloat()
+ |
+ f < 0 and result = TNeg() or
+ f = 0 and result = TZero() or
+ f > 0 and result = TPos()
+ ) or
+ exists(string charlit | charlit = e.(CharacterLiteral).getValue() |
+ if charlit = "\\0" or charlit = "\\u0000" then
+ result = TZero()
+ else
+ result = TPos()
+ ) or
+ e.(MethodAccess).getMethod() instanceof StringLengthMethod and (result = TPos() or result = TZero()) or
+ e.(MethodAccess).getMethod() instanceof CollectionSizeMethod and (result = TPos() or result = TZero()) or
+ e.(MethodAccess).getMethod() instanceof MapSizeMethod and (result = TPos() or result = TZero())
+ )
+}
+
+/** Holds if the sign of `e` is too complicated to determine. */
+private predicate unknownSign(Expr e) {
+ not exists(e.(ConstantIntegerExpr).getIntValue()) and
+ (
+ exists(IntegerLiteral lit | lit = e and not exists(lit.getValue().toInt())) or
+ exists(LongLiteral lit | lit = e and not exists(lit.getValue().toFloat())) or
+ exists(CastExpr cast, Type fromtyp |
+ cast = e and
+ fromtyp = cast.getExpr().getType() and
+ not fromtyp instanceof NumericOrCharType
+ ) or
+ e instanceof ArrayAccess and e.getType() instanceof NumericOrCharType or
+ e instanceof MethodAccess and e.getType() instanceof NumericOrCharType or
+ e instanceof ClassInstanceExpr and e.getType() instanceof NumericOrCharType
+ )
+}
+
+/**
+ * Holds if `lowerbound` is a lower bound for `v` at `pos`. This is restricted
+ * to only include bounds for which we might determine a sign.
+ */
+private predicate lowerBound(Expr lowerbound, SsaVariable v, SsaReadPosition pos, boolean isStrict) {
+ exists(boolean testIsTrue, ComparisonExpr comp |
+ pos.hasReadOfVar(v) and
+ guardControlsSsaRead(comp, pos, testIsTrue) and
+ not unknownSign(lowerbound)
+ |
+ testIsTrue = true and
+ comp.getLesserOperand() = lowerbound and
+ comp.getGreaterOperand() = ssaRead(v, 0) and
+ (if comp.isStrict() then isStrict = true else isStrict = false)
+ or
+ testIsTrue = false and
+ comp.getGreaterOperand() = lowerbound and
+ comp.getLesserOperand() = ssaRead(v, 0) and
+ (if comp.isStrict() then isStrict = false else isStrict = true)
+ )
+}
+
+/**
+ * Holds if `upperbound` is an upper bound for `v` at `pos`. This is restricted
+ * to only include bounds for which we might determine a sign.
+ */
+private predicate upperBound(Expr upperbound, SsaVariable v, SsaReadPosition pos, boolean isStrict) {
+ exists(boolean testIsTrue, ComparisonExpr comp |
+ pos.hasReadOfVar(v) and
+ guardControlsSsaRead(comp, pos, testIsTrue) and
+ not unknownSign(upperbound)
+ |
+ testIsTrue = true and
+ comp.getGreaterOperand() = upperbound and
+ comp.getLesserOperand() = ssaRead(v, 0) and
+ (if comp.isStrict() then isStrict = true else isStrict = false)
+ or
+ testIsTrue = false and
+ comp.getLesserOperand() = upperbound and
+ comp.getGreaterOperand() = ssaRead(v, 0) and
+ (if comp.isStrict() then isStrict = false else isStrict = true)
+ )
+}
+
+/**
+ * Holds if `eqbound` is an equality/inequality for `v` at `pos`. This is
+ * restricted to only include bounds for which we might determine a sign. The
+ * boolean `isEq` gives the polarity:
+ * - `isEq = true` : `v = eqbound`
+ * - `isEq = false` : `v != eqbound`
+ */
+private predicate eqBound(Expr eqbound, SsaVariable v, SsaReadPosition pos, boolean isEq) {
+ exists(Guard guard, boolean testIsTrue, boolean polarity |
+ pos.hasReadOfVar(v) and
+ guardControlsSsaRead(guard, pos, testIsTrue) and
+ guard.isEquality(eqbound, ssaRead(v, 0), polarity) and
+ isEq = polarity.booleanXor(testIsTrue).booleanNot() and
+ not unknownSign(eqbound)
+ )
+}
+
+/**
+ * Holds if `bound` is a bound for `v` at `pos` that needs to be positive in
+ * order for `v` to be positive.
+ */
+private predicate posBound(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ upperBound(bound, v, pos, _) or
+ eqBound(bound, v, pos, true)
+}
+
+/**
+ * Holds if `bound` is a bound for `v` at `pos` that needs to be negative in
+ * order for `v` to be negative.
+ */
+private predicate negBound(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ lowerBound(bound, v, pos, _) or
+ eqBound(bound, v, pos, true)
+}
+
+/**
+ * Holds if `bound` is a bound for `v` at `pos` that can restrict whether `v`
+ * can be zero.
+ */
+private predicate zeroBound(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ lowerBound(bound, v, pos, _) or
+ upperBound(bound, v, pos, _) or
+ eqBound(bound, v, pos, _)
+}
+
+/** Holds if `bound` allows `v` to be positive at `pos`. */
+private predicate posBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ posBound(bound, v, pos) and TPos() = exprSign(bound)
+}
+
+/** Holds if `bound` allows `v` to be negative at `pos`. */
+private predicate negBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ negBound(bound, v, pos) and TNeg() = exprSign(bound)
+}
+
+/** Holds if `bound` allows `v` to be zero at `pos`. */
+private predicate zeroBoundOk(Expr bound, SsaVariable v, SsaReadPosition pos) {
+ lowerBound(bound, v, pos, _) and TNeg() = exprSign(bound) or
+ lowerBound(bound, v, pos, false) and TZero() = exprSign(bound) or
+ upperBound(bound, v, pos, _) and TPos() = exprSign(bound) or
+ upperBound(bound, v, pos, false) and TZero() = exprSign(bound) or
+ eqBound(bound, v, pos, true) and TZero() = exprSign(bound) or
+ eqBound(bound, v, pos, false) and TZero() != exprSign(bound)
+}
+
+/**
+ * Holds if there is a bound that might restrict whether `v` has the sign `s`
+ * at `pos`.
+ */
+private predicate hasGuard(SsaVariable v, SsaReadPosition pos, Sign s) {
+ s = TPos() and posBound(_, v, pos) or
+ s = TNeg() and negBound(_, v, pos) or
+ s = TZero() and zeroBound(_, v, pos)
+}
+
+pragma[noinline]
+private Sign guardedSsaSign(SsaVariable v, SsaReadPosition pos) {
+ result = ssaDefSign(v) and
+ pos.hasReadOfVar(v) and
+ hasGuard(v, pos, result)
+}
+
+pragma[noinline]
+private Sign unguardedSsaSign(SsaVariable v, SsaReadPosition pos) {
+ result = ssaDefSign(v) and
+ pos.hasReadOfVar(v) and
+ not hasGuard(v, pos, result)
+}
+
+private Sign guardedSsaSignOk(SsaVariable v, SsaReadPosition pos) {
+ result = TPos() and forex(Expr bound | posBound(bound, v, pos) | posBoundOk(bound, v, pos)) or
+ result = TNeg() and forex(Expr bound | negBound(bound, v, pos) | negBoundOk(bound, v, pos)) or
+ result = TZero() and forex(Expr bound | zeroBound(bound, v, pos) | zeroBoundOk(bound, v, pos))
+}
+
+/** Gets a possible sign for `v` at `pos`. */
+private Sign ssaSign(SsaVariable v, SsaReadPosition pos) {
+ result = unguardedSsaSign(v, pos)
+ or
+ result = guardedSsaSign(v, pos) and
+ result = guardedSsaSignOk(v, pos)
+}
+
+/** Gets a possible sign for `v`. */
+pragma[nomagic]
+private Sign ssaDefSign(SsaVariable v) {
+ exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() |
+ result = exprSign(def.(VariableAssign).getSource()) or
+ exists(EnhancedForStmt for | def = for.getVariable()) or
+ result = exprSign(def.(PostIncExpr).getExpr()).inc() or
+ result = exprSign(def.(PreIncExpr).getExpr()).inc() or
+ result = exprSign(def.(PostDecExpr).getExpr()).dec() or
+ result = exprSign(def.(PreDecExpr).getExpr()).dec() or
+ exists(AssignOp a | a = def and result = exprSign(a))
+ ) or
+ result = fieldSign(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or
+ result = fieldSign(v.(SsaImplicitInit).getSourceVariable().getVariable()) or
+ exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p)) or
+ exists(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge |
+ v = phi and
+ edge.phiInput(phi, inp) and
+ result = ssaSign(inp, edge)
+ )
+}
+
+/** Gets a possible sign for `f`. */
+private Sign fieldSign(Field f) {
+ result = exprSign(f.getAnAssignedValue()) or
+ exists(PostIncExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).inc()) or
+ exists(PreIncExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).inc()) or
+ exists(PostDecExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).dec()) or
+ exists(PreDecExpr inc | inc.getExpr() = f.getAnAccess() and result = fieldSign(f).dec()) or
+ exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprSign(a)) or
+ exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f)
+ or
+ if f.fromSource() then
+ not exists(f.getInitializer()) and result = TZero()
+ else if f instanceof ArrayLengthField then result != TNeg()
+ else if f.hasName("MAX_VALUE") then result = TPos()
+ else if f.hasName("MIN_VALUE") then result = TNeg()
+ else any()
+}
+
+/** Gets a possible sign for `e`. */
+cached
+private Sign exprSign(Expr e) {
+ result = certainExprSign(e) or
+ not exists(certainExprSign(e)) and
+ (
+ unknownSign(e) or
+ result = exprSign(e.(ParExpr).getExpr()) or
+ exists(SsaVariable v | v.getAUse() = e |
+ result = ssaSign(v, any(SsaReadPositionBlock bb | bb.getBlock() = e.getBasicBlock())) or
+ not exists(e.getBasicBlock()) and result = ssaDefSign(v)
+ ) or
+ exists(FieldAccess fa | fa = e |
+ not exists(SsaVariable v | v.getAUse() = fa) and
+ result = fieldSign(fa.getField())
+ ) or
+ exists(VarAccess va | va = e |
+ not exists(SsaVariable v | v.getAUse() = va) and
+ not va instanceof FieldAccess
+ ) or
+ result = exprSign(e.(AssignExpr).getSource()) or
+ result = exprSign(e.(PlusExpr).getExpr()) or
+ result = exprSign(e.(PostIncExpr).getExpr()) or
+ result = exprSign(e.(PostDecExpr).getExpr()) or
+ result = exprSign(e.(PreIncExpr).getExpr()).inc() or
+ result = exprSign(e.(PreDecExpr).getExpr()).dec() or
+ result = exprSign(e.(MinusExpr).getExpr()).neg() or
+ result = exprSign(e.(BitNotExpr).getExpr()).bitnot() or
+ exists(DivExpr div |
+ div = e and
+ result = exprSign(div.getLeftOperand()) and
+ result != TZero()
+ |
+ div.getRightOperand().(FloatingPointLiteral).getValue().toFloat() = 0 or
+ div.getRightOperand().(DoubleLiteral).getValue().toFloat() = 0
+ ) or
+ exists(Sign s1, Sign s2 |
+ binaryOpSigns(e, s1, s2)
+ |
+ (e instanceof AssignAddExpr or e instanceof AddExpr) and result = s1.add(s2) or
+ (e instanceof AssignSubExpr or e instanceof SubExpr) and result = s1.add(s2.neg()) or
+ (e instanceof AssignMulExpr or e instanceof MulExpr) and result = s1.mul(s2) or
+ (e instanceof AssignDivExpr or e instanceof DivExpr) and result = s1.div(s2) or
+ (e instanceof AssignRemExpr or e instanceof RemExpr) and result = s1.rem(s2) or
+ (e instanceof AssignAndExpr or e instanceof AndBitwiseExpr) and result = s1.bitand(s2) or
+ (e instanceof AssignOrExpr or e instanceof OrBitwiseExpr) and result = s1.bitor(s2) or
+ (e instanceof AssignXorExpr or e instanceof XorBitwiseExpr) and result = s1.bitxor(s2) or
+ (e instanceof AssignLShiftExpr or e instanceof LShiftExpr) and result = s1.lshift(s2) or
+ (e instanceof AssignRShiftExpr or e instanceof RShiftExpr) and result = s1.rshift(s2) or
+ (e instanceof AssignURShiftExpr or e instanceof URShiftExpr) and result = s1.urshift(s2)
+ ) or
+ result = exprSign(e.(ConditionalExpr).getTrueExpr()) or
+ result = exprSign(e.(ConditionalExpr).getFalseExpr()) or
+ result = exprSign(e.(CastExpr).getExpr())
+ )
+}
+private Sign binaryOpLhsSign(Expr e) {
+ result = exprSign(e.(BinaryExpr).getLeftOperand()) or
+ result = exprSign(e.(AssignOp).getDest())
+}
+private Sign binaryOpRhsSign(Expr e) {
+ result = exprSign(e.(BinaryExpr).getRightOperand()) or
+ result = exprSign(e.(AssignOp).getRhs())
+}
+pragma[noinline]
+private predicate binaryOpSigns(Expr e, Sign lhs, Sign rhs) {
+ lhs = binaryOpLhsSign(e) and
+ rhs = binaryOpRhsSign(e)
+}
+
+/** Holds if `e` can be positive and cannot be negative. */
+predicate positive(Expr e) {
+ exprSign(e) = TPos() and
+ not exprSign(e) = TNeg()
+}
+
+/** Holds if `e` can be negative and cannot be positive. */
+predicate negative(Expr e) {
+ exprSign(e) = TNeg() and
+ not exprSign(e) = TPos()
+}
+
+/** Holds if `e` is strictly positive. */
+predicate strictlyPositive(Expr e) {
+ exprSign(e) = TPos() and
+ not exprSign(e) = TNeg() and
+ not exprSign(e) = TZero()
+}
+
+/** Holds if `e` is strictly negative. */
+predicate strictlyNegative(Expr e) {
+ exprSign(e) = TNeg() and
+ not exprSign(e) = TPos() and
+ not exprSign(e) = TZero()
+}
+
diff --git a/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll b/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll
new file mode 100644
index 00000000000..3e026b4d338
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/TaintTracking.qll
@@ -0,0 +1,689 @@
+/**
+ * Provides classes for performing local (intra-procedural) and
+ * global (inter-procedural) taint-tracking analyses.
+ */
+
+import java
+import semmle.code.java.dataflow.DataFlow
+import semmle.code.java.dataflow.DataFlow2
+import semmle.code.java.Collections
+
+private import SSA
+private import DefUse
+private import semmle.code.java.security.SecurityTests
+private import semmle.code.java.security.Validation
+private import semmle.code.java.frameworks.android.Intent
+
+module TaintTracking {
+
+ /**
+ * A taint tracking configuration.
+ *
+ * A taint tracking configuration is a special dataflow configuration
+ * (`DataFlow::Configuration`) that allows for flow through nodes that do not
+ * necessarily preserve values, but are still relevant from a taint tracking
+ * perspective. (For example, string concatenation, where one of the operands
+ * is tainted.)
+ *
+ * Each use of the taint tracking library must define its own unique extension
+ * of this abstract class. A configuration defines a set of relevant sources
+ * (`isSource`) and sinks (`isSink`), and may additionally treat intermediate
+ * nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps
+ * (`isAdditionalTaintStep()`).
+ */
+ abstract class Configuration extends DataFlow::Configuration {
+ bindingset[this]
+ Configuration() { any() }
+
+ /**
+ * Holds if `source` is a relevant taint source.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSource(DataFlow::Node source);
+
+ /**
+ * Holds if `sink` is a relevant taint sink.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSink(DataFlow::Node sink);
+
+ /** Holds if the node `node` is a taint sanitizer. */
+ predicate isSanitizer(DataFlow::Node node) { none() }
+
+ final
+ override predicate isBarrier(DataFlow::Node node) {
+ isSanitizer(node) or
+ // Ignore paths through test code.
+ node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
+ exists(ValidatedVariable var | node.asExpr() = var.getAnAccess())
+ }
+
+ /** Holds if the edge from `node1` to `node2` is a taint sanitizer. */
+ predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
+
+ final
+ override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
+ isSanitizerEdge(node1, node2)
+ }
+
+ /**
+ * Holds if the additional taint propagation step from `node1` to `node2`
+ * must be taken into account in the analysis.
+ */
+ predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
+
+ final
+ override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ isAdditionalTaintStep(node1, node2) or
+ localAdditionalTaintStep(node1, node2)
+ }
+
+ /**
+ * Holds if taint may flow from `source` to `sink` for this configuration.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
+ super.hasFlow(source, sink)
+ }
+ }
+
+ /**
+ * A taint tracking configuration.
+ *
+ * A taint tracking configuration is a special dataflow configuration
+ * (`DataFlow::Configuration`) that allows for flow through nodes that do not
+ * necessarily preserve values, but are still relevant from a taint tracking
+ * perspective. (For example, string concatenation, where one of the operands
+ * is tainted.)
+ *
+ * Each use of the taint tracking library must define its own unique extension
+ * of this abstract class. A configuration defines a set of relevant sources
+ * (`isSource`) and sinks (`isSink`), and may additionally treat intermediate
+ * nodes as "sanitizers" (`isSanitizer`) as well as add custom taint flow steps
+ * (`isAdditionalTaintStep()`).
+ */
+ abstract class Configuration2 extends DataFlow2::Configuration {
+ bindingset[this]
+ Configuration2() { any() }
+
+ /**
+ * Holds if `source` is a relevant taint source.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSource(DataFlow::Node source);
+
+ /**
+ * Holds if `sink` is a relevant taint sink.
+ *
+ * The smaller this predicate is, the faster `hasFlow()` will converge.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ abstract override predicate isSink(DataFlow::Node sink);
+
+ /** Holds if the node `node` is a taint sanitizer. */
+ predicate isSanitizer(DataFlow::Node node) { none() }
+
+ final
+ override predicate isBarrier(DataFlow::Node node) {
+ isSanitizer(node) or
+ // Ignore paths through test code.
+ node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
+ exists(ValidatedVariable var | node.asExpr() = var.getAnAccess())
+ }
+
+ /** Holds if the edge from `node1` to `node2` is a taint sanitizer. */
+ predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
+
+ final
+ override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
+ isSanitizerEdge(node1, node2)
+ }
+
+ /**
+ * Holds if the additional taint propagation step from `node1` to `node2`
+ * must be taken into account in the analysis.
+ */
+ predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { none() }
+
+ final
+ override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
+ isAdditionalTaintStep(node1, node2) or
+ localAdditionalTaintStep(node1, node2)
+ }
+
+ /**
+ * Holds if taint may flow from `source` to `sink` for this configuration.
+ */
+ // overridden to provide taint-tracking specific qldoc
+ override predicate hasFlow(DataFlow::Node source, DataFlow::Node sink) {
+ super.hasFlow(source, sink)
+ }
+ }
+
+ /**
+ * Holds if taint can flow from `src` to `sink` in zero or more
+ * local (intra-procedural) steps.
+ */
+ predicate localTaint(DataFlow::Node src, DataFlow::Node sink) {
+ localTaintStep*(src, sink)
+ }
+
+ /**
+ * Holds if taint can flow in one local step from `src` to `sink`.
+ */
+ predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
+ DataFlow::localFlowStep(src, sink) or
+ localAdditionalTaintStep(src, sink)
+ }
+
+ /**
+ * Holds if taint can flow in one local step from `src` to `sink` excluding
+ * local data flow steps. That is, `src` and `sink` are likely to represent
+ * different objects.
+ */
+ predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
+ localAdditionalTaintExprStep(src.asExpr(), sink.asExpr()) or
+ exists(Argument arg | src.asExpr() = arg and arg.isVararg() and sink.(DataFlow::ImplicitVarargsArray).getCall() = arg.getCall())
+ }
+
+ /**
+ * Holds if taint can flow in one local step from `src` to `sink` excluding
+ * local data flow steps. That is, `src` and `sink` are likely to represent
+ * different objects.
+ */
+ private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
+ sink.(AddExpr).getAnOperand() = src and sink.getType() instanceof TypeString or
+ sink.(AssignAddExpr).getSource() = src and sink.getType() instanceof TypeString or
+ sink.(ArrayCreationExpr).getInit() = src or
+ sink.(ArrayInit).getAnInit() = src or
+ sink.(ArrayAccess).getArray() = src or
+ sink.(LogicExpr).getAnOperand() = src or
+ exists(Assignment assign | assign.getSource() = src |
+ sink = assign.getDest().(ArrayAccess).getArray()
+ ) or
+ constructorStep(src, sink) or
+ qualifierToMethodStep(src, sink) or
+ qualifierToArgumentStep(src, sink) or
+ argToMethodStep(src, sink) or
+ argToArgStep(src, sink) or
+ argToQualifierStep(src, sink) or
+ comparisonStep(src, sink) or
+ stringBuilderStep(src, sink) or
+ serializationStep(src, sink) or
+ qualifierToArgStep(src, sink)
+ }
+
+ private class BulkData extends RefType {
+ BulkData() {
+ this.(Array).getElementType().(PrimitiveType).getName().regexpMatch("byte|char") or
+ exists(RefType t | this.getASourceSupertype*() = t |
+ t.hasQualifiedName("java.io", "InputStream") or
+ t.hasQualifiedName("java.nio", "ByteBuffer") or
+ t.hasQualifiedName("java.lang", "Readable") or
+ t.hasQualifiedName("java.io", "DataInput") or
+ t.hasQualifiedName("java.nio.channels", "ReadableByteChannel")
+ )
+ }
+ }
+
+ /**
+ * Holds if `c` is a constructor for a subclass of `java.io.InputStream` that
+ * wraps an underlying data source. The underlying data source is given as a
+ * the `argi`'th parameter to the constructor.
+ *
+ * An object construction of such a wrapper is likely to preserve the data flow
+ * status of its argument.
+ */
+ private predicate inputStreamWrapper(Constructor c, int argi) {
+ c.getParameterType(argi) instanceof BulkData and
+ c.getDeclaringType().getASourceSupertype().hasQualifiedName("java.io", "InputStream")
+ }
+
+ /** An object construction that preserves the data flow status of any of its arguments. */
+ private predicate constructorStep(Expr tracked, ConstructorCall sink) {
+ exists(int argi | sink.getArgument(argi) = tracked |
+ exists(string s | sink.getConstructedType().getQualifiedName() = s |
+ // String constructor does nothing to data
+ s = "java.lang.String" and argi = 0 or
+ // some readers preserve the content of streams
+ s = "java.io.InputStreamReader" and argi = 0 or
+ s = "java.io.BufferedReader" and argi = 0 or
+ s = "java.io.CharArrayReader" and argi = 0 or
+ s = "java.io.StringReader" and argi = 0 or
+ // data preserved through streams
+ s = "java.io.ObjectInputStream" and argi = 0 or
+ s = "java.io.ByteArrayInputStream" and argi = 0 or
+ s = "java.io.DataInputStream" and argi = 0 or
+ s = "java.io.BufferedInputStream" and argi = 0 or
+ s = "com.esotericsoftware.kryo.io.Input" and argi = 0 or
+ s = "java.beans.XMLDecoder" and argi = 0 or
+ // a tokenizer preserves the content of a string
+ s = "java.util.StringTokenizer" and argi = 0 or
+ // unzipping the stream preserves content
+ s = "java.util.zip.ZipInputStream" and argi = 0 or
+ s = "java.util.zip.GZIPInputStream" and argi = 0 or
+ // string builders and buffers
+ s = "java.lang.StringBuilder" and argi = 0 or
+ s = "java.lang.StringBuffer" and argi = 0 or
+ // a cookie with tainted ingredients is tainted
+ s = "javax.servlet.http.Cookie" and argi = 0 or
+ s = "javax.servlet.http.Cookie" and argi = 1 or
+ // various xml stream source constructors.
+ s = "org.xml.sax.InputSource" and argi = 0 or
+ s = "javax.xml.transform.sax.SAXSource" and argi = 0 and sink.getNumArgument() = 1 or
+ s = "javax.xml.transform.sax.SAXSource" and argi = 1 and sink.getNumArgument() = 2 or
+ s = "javax.xml.transform.stream.StreamSource" and argi = 0 or
+ //a URI constructed from a tainted string is tainted.
+ s = "java.net.URI" and argi = 0 and sink.getNumArgument() = 1
+ ) or
+ exists(RefType t | t.getQualifiedName() = "java.lang.Number" |
+ hasSubtype*(t, sink.getConstructedType())
+ ) and argi = 0 or
+ // wrappers constructed by extension
+ exists(Constructor c, Parameter p, SuperConstructorInvocationStmt sup |
+ c = sink.getConstructor() and
+ p = c.getParameter(argi) and
+ sup.getEnclosingCallable() = c and
+ constructorStep(p.getAnAccess(), sup)
+ ) or
+ // a custom InputStream that wraps a tainted data source is tainted
+ inputStreamWrapper(sink.getConstructor(), argi)
+ )
+ }
+
+ /** Access to a method that passes taint from qualifier to argument. */
+ private predicate qualifierToArgumentStep(Expr tracked, RValue sink) {
+ exists(MethodAccess ma, int arg |
+ taintPreservingQualifierToArgument(ma.getMethod(), arg) and
+ tracked = ma.getQualifier() and sink = ma.getArgument(arg)
+ )
+ }
+
+ /** Methods that passes tainted data from qualifier to argument.*/
+ private predicate taintPreservingQualifierToArgument(Method m, int arg) {
+ m instanceof CollectionMethod and
+ m.hasName("toArray") and arg = 1
+ or
+ m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
+ m.hasName("writeTo") and arg = 0
+ }
+
+ /** Access to a method that passes taint from the qualifier. */
+ private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
+ (taintPreservingQualifierToMethod(sink.getMethod()) or unsafeEscape(sink))
+ and
+ tracked = sink.getQualifier()
+ }
+
+ /**
+ * Methods that return tainted data when called on tainted data.
+ */
+ private predicate taintPreservingQualifierToMethod(Method m) {
+ m.getDeclaringType() instanceof TypeString and
+ (
+ m.getName() = "endsWith" or
+ m.getName() = "getBytes" or
+ m.getName() = "split" or
+ m.getName() = "substring" or
+ m.getName() = "toCharArray" or
+ m.getName() = "toLowerCase" or
+ m.getName() = "toString" or
+ m.getName() = "toUpperCase" or
+ m.getName() = "trim"
+ ) or
+ exists(Class c | c.getQualifiedName() = "java.lang.Number" | hasSubtype*(c, m.getDeclaringType())) and
+ (
+ m.getName().matches("to%String") or
+ m.getName() = "toByteArray" or
+ m.getName().matches("%Value")
+ ) or
+ m.getDeclaringType().getQualifiedName().matches("%Reader") and
+ m.getName().matches("read%")
+ or
+ m.getDeclaringType().getQualifiedName().matches("%StringWriter") and
+ m.getName() = "toString"
+ or
+ m.getDeclaringType().hasQualifiedName("java.util", "StringTokenizer") and
+ m.getName().matches("next%")
+ or
+ m.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and
+ (m.getName() = "toByteArray" or m.getName() = "toString")
+ or
+ m.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream") and
+ m.getName().matches("read%")
+ or
+ (
+ m.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
+ m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
+ ) and
+ (m.getName() = "toString" or m.getName() = "append")
+ or
+ m.getDeclaringType().hasQualifiedName("javax.xml.transform.sax", "SAXSource") and
+ m.hasName("getInputSource")
+ or
+ m.getDeclaringType().hasQualifiedName("javax.xml.transform.stream", "StreamSource") and
+ m.hasName("getInputStream")
+ or
+ m instanceof IntentGetExtraMethod
+ or
+ m instanceof CollectionMethod and
+ m.hasName("toArray")
+ or
+ m.getDeclaringType().hasQualifiedName("java.nio", "ByteBuffer") and
+ m.hasName("get")
+ }
+
+ private class StringReplaceMethod extends Method {
+ StringReplaceMethod() {
+ getDeclaringType() instanceof TypeString and
+ (
+ hasName("replace") or
+ hasName("replaceAll") or
+ hasName("replaceFirst")
+ )
+ }
+ }
+
+ private predicate unsafeEscape(MethodAccess ma) {
+ // Removing `