Merge pull request #2152 from yh-semmle/java-alert-suppression-annotations

Java: support LGTM alert suppression using `@SuppressWarnings` annotations
This commit is contained in:
Anders Schack-Mulligen
2019-10-24 15:04:29 +02:00
committed by GitHub
7 changed files with 146 additions and 20 deletions

View File

@@ -0,0 +1,96 @@
/**
* @name Alert suppression using annotations
* @description Generates information about alert suppressions
* using 'SuppressWarnings' annotations.
* @kind alert-suppression
* @id java/alert-suppression-annotations
*/
import java
import Metrics.Internal.Extents
/** Gets the LGTM suppression annotation text in the string `s`, if any. */
bindingset[s]
string getAnnotationText(string s) {
// match `lgtm[...]` anywhere in the comment
result = s.regexpFind("(?i)\\blgtm\\s*\\[[^\\]]*\\]", _, _)
}
/**
* An alert suppression annotation.
*/
class SuppressionAnnotation extends SuppressWarningsAnnotation {
string text;
SuppressionAnnotation() {
text = this.getASuppressedWarningLiteral().getValue() and
exists(getAnnotationText(text))
}
/**
* Gets the text of this suppression annotation.
*/
string getText() { result = text }
private Annotation getASiblingAnnotation() {
result = getAnnotatedElement().(Annotatable).getAnAnnotation() and
(getAnnotatedElement() instanceof Callable or getAnnotatedElement() instanceof RefType)
}
private Annotation firstAnnotation() {
result = min(this.getASiblingAnnotation() as m
order by
m.getLocation().getStartLine(), m.getLocation().getStartColumn()
)
}
/**
* Holds if this annotation applies to the range from column `startcolumn` of line `startline`
* to column `endcolumn` of line `endline` in file `filepath`.
*/
predicate covers(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
if firstAnnotation().hasLocationInfo(filepath, _, _, _, _)
then
getAnnotatedElement().hasLocationInfo(filepath, _, _, endline, endcolumn) and
firstAnnotation().hasLocationInfo(filepath, startline, startcolumn, _, _)
else getAnnotatedElement().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets the scope of this suppression. */
SuppressionScope getScope() { this = result.getSuppressionAnnotation() }
}
/**
* The scope of an alert suppression annotation.
*/
class SuppressionScope extends @annotation {
SuppressionScope() { this instanceof SuppressionAnnotation }
/** Gets a suppression annotation with this scope. */
SuppressionAnnotation getSuppressionAnnotation() { result = this }
/**
* 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
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.(SuppressionAnnotation).covers(filepath, startline, startcolumn, endline, endcolumn)
}
/** Gets a textual representation of this element. */
string toString() { result = "suppression range" }
}
from SuppressionAnnotation c, string text, string annotationText
where
text = c.getText() and
annotationText = getAnnotationText(text)
select c, // suppression entity
text, // full text of suppression string
annotationText, // LGTM suppression annotation text
c.getScope() // scope of suppression

View File

@@ -1,15 +1,19 @@
import java
/*
/**
* Provides classes that modify the default location information
* of `RefType`s and `Callable`s to cover their full extent.
*
* When this library is imported, the `hasLocationInfo` predicate of
* `Callable` and `RefTypes` is overridden to specify their entire range
* instead of just the range of their name. The latter can still be
* obtained by invoking the `getLocation()` predicate.
*
* The full ranges are required for the purpose of associating a violation
* with an individual `Callable` or `RefType` as opposed to a whole `File`.
* The full ranges may be used to determine whether a given location
* (for example, the location of an alert) is contained within the extent
* of a `Callable` or `RefType`.
*/
import java
/**
* A Callable whose `hasLocationInfo` is overridden to specify its entire range
* including the body (if any), as opposed to the location of its name only.
@@ -48,20 +52,9 @@ class RangeRefType extends RefType {
}
private Member lastMember() {
exists(Member m, int i |
result = m and
m = getAMember() and
i = rankOfMember(m) and
not exists(Member other | other = getAMember() and rankOfMember(other) > i)
)
}
private int rankOfMember(Member m) {
this.getAMember() = m and
exists(Location mLoc, File f, int maxCol | mLoc = m.getLocation() |
f = mLoc.getFile() and
maxCol = max(Location loc | loc.getFile() = f | loc.getStartColumn()) and
result = mLoc.getStartLine() * maxCol + mLoc.getStartColumn()
)
result = max(this.getAMember() as m
order by
m.getLocation().getStartLine(), m.getLocation().getStartColumn()
)
}
}

View File

@@ -18,6 +18,12 @@ class OverrideAnnotation extends Annotation {
class SuppressWarningsAnnotation extends Annotation {
SuppressWarningsAnnotation() { this.getType().hasQualifiedName("java.lang", "SuppressWarnings") }
/** Gets the `StringLiteral` of a warning suppressed by this annotation. */
StringLiteral getASuppressedWarningLiteral() {
result = this.getAValue() or
result = this.getAValue().(ArrayInit).getAnInit()
}
/** Gets the name of a warning suppressed by this annotation. */
string getASuppressedWarning() {
result = this.getAValue().(StringLiteral).getLiteral() or

View File

@@ -0,0 +1,6 @@
| TestSuppressWarnings.java:2:1:2:49 | SuppressWarnings | lgtm[java/non-sync-override] | lgtm[java/non-sync-override] | TestSuppressWarnings.java:2:1:21:5 | suppression range |
| TestSuppressWarnings.java:5:5:5:31 | SuppressWarnings | lgtm[] | lgtm[] | TestSuppressWarnings.java:5:5:8:5 | suppression range |
| TestSuppressWarnings.java:10:5:10:104 | SuppressWarnings | lgtm[java/confusing-method-name] not confusing | lgtm[java/confusing-method-name] | TestSuppressWarnings.java:9:5:13:5 | suppression range |
| TestSuppressWarnings.java:10:5:10:104 | SuppressWarnings | lgtm[java/non-sync-override] | lgtm[java/non-sync-override] | TestSuppressWarnings.java:9:5:13:5 | suppression range |
| TestSuppressWarnings.java:18:5:18:98 | SuppressWarnings | lgtm[java/confusing-method-name] blah blah lgtm[java/non-sync-override] | lgtm[java/confusing-method-name] | TestSuppressWarnings.java:18:5:21:5 | suppression range |
| TestSuppressWarnings.java:18:5:18:98 | SuppressWarnings | lgtm[java/confusing-method-name] blah blah lgtm[java/non-sync-override] | lgtm[java/non-sync-override] | TestSuppressWarnings.java:18:5:21:5 | suppression range |

View File

@@ -0,0 +1 @@
AlertSuppressionAnnotations.ql

View File

@@ -0,0 +1,22 @@
@SuppressWarnings("lgtm[java/non-sync-override]")
@Deprecated
class TestSuppressWarnings {
@SuppressWarnings("lgtm[]")
public void test() {
}
@Deprecated
@SuppressWarnings({"lgtm[java/confusing-method-name] not confusing","lgtm[java/non-sync-override]"})
public void test2() {
}
@SuppressWarnings("lgtm")
public void test3() {
}
@SuppressWarnings({"lgtm[java/confusing-method-name] blah blah lgtm[java/non-sync-override]"})
public void test4() {
}
}