[Infer][filtering] Implementing SuppressWarnings filtering and adding tests

Summary:
Modifying the annotation parser to output a JSON .inferconfig_local
and fixing early deletion of the local inferconfig in inferlib.py.
master
Sam Blackshear 9 years ago
parent e46b6d3c9d
commit 7911e7e54d

@ -26,18 +26,60 @@ import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
@SupportedAnnotationTypes({ "java.lang.SuppressWarnings" })
public class AnnotationProcessor extends AbstractProcessor {
private static final String ANNOTATION_ENV_VAR = "INFER_ANNOTATIONS_OUT";
private String mOutputFilename = "suppress_warnings_map.txt";
private String mOutputFilename;
// map of (classes -> methods in class). an empty set means suppress all
// warnings on class
public Map<String, Set<String>> mSuppressMap = new LinkedHashMap<String, Set<String>>();
// total number of classes/methods with a SuppressWarnings annotation
private int mNumToSuppress = 0;
// print a comma between all objects except for the last one
private void outputCommaIfNotLast(PrintWriter out, int elemCount) {
if (elemCount == mNumToSuppress) {
out.println("");
} else {
out.println(",");
}
}
// output a method to suppress in JSON format
// clearly, we should be using an existing JSON output library like Jackson here. however, we
// can't do this because we do not want to add Jackson (or another JSON library) to the classpath
// of the Java program we are building (along with the JAR for this processor). the reason is that
// if there is a different version of the JSON parser library somewhere in the classpath for the
// project, it could cause *very* strange problems. instead, we rolled our own library to avoid
// introducing additional dependencies
private void outputMethod(PrintWriter out, String clazz, String method, int elemCount) {
String TAB1 = " ";
String TAB2 = TAB1 + TAB1;
out.println(TAB1 + "{");
out.println(TAB2 +"\"language\": \"Java\",");
out.print(TAB2 + "\"class\": \"" + clazz + "\"");
if (method != null) {
out.println(",");
out.println(TAB2 + "\"method\": \"" + method + "\"");
} else {
out.println();
}
out.print(TAB1 + "}");
outputCommaIfNotLast(out, elemCount);
}
// output a class to suppress in JSON format
private void outputClass(PrintWriter out, String clazz, int elemCount) {
outputMethod(out, clazz, null, elemCount);
}
// write the methods/classes to suppress to .inferconfig-style JSON
private void exportSuppressMap() throws FileNotFoundException, IOException {
Map<String, String> env = System.getenv();
if (env.get(ANNOTATION_ENV_VAR) == null) {
@ -45,42 +87,66 @@ public class AnnotationProcessor extends AbstractProcessor {
} else {
mOutputFilename = env.get(ANNOTATION_ENV_VAR);
}
// output .inferconfig format file in JSON
try (PrintWriter out = new PrintWriter(mOutputFilename)) {
int elemCount = 0;
out.println("{ \"suppress_procedures\": [");
for (Map.Entry<String, Set<String>> entry : mSuppressMap.entrySet()) {
out.write(entry.getKey() + " " + entry.getValue());
String clazz = entry.getKey();
Set<String> methods = entry.getValue();
if (methods.isEmpty()) { // empty set of methods means annotation is on class
outputClass(out, clazz, ++elemCount);
} else {
for (String method : methods) {
outputMethod(out, clazz, method, ++elemCount);
}
}
}
out.println("] }");
}
}
// we only care about @SuppressWarnings("null") and @SuppressWarnings("infer"); ignore otherwise
private boolean shouldProcess(SuppressWarnings annot) {
for (String value : annot.value()) {
if (value.equals("null") || value.equals("infer")) return true;
}
return false;
}
// collect all of the SuppressWarnings annotations from the Java source files being compiled
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Elements elements = processingEnv.getElementUtils();
for (TypeElement te : annotations) {
for (Element e : env.getElementsAnnotatedWith(te)) {
if (e instanceof TypeElement) { // class
String className = ((TypeElement) e).getQualifiedName().toString();
mSuppressMap.put(className, Collections.EMPTY_SET);
} else if (e instanceof ExecutableElement) { // method
String classname = e.getEnclosingElement().toString();
java.util.Set<String> suppressMethods = mSuppressMap.get(classname);
if (suppressMethods != null && suppressMethods.isEmpty()) {
// empty set means suppress warnings on all methods in class; do
// nothing
continue;
SuppressWarnings annot = e.getAnnotation(SuppressWarnings.class);
if (shouldProcess(annot)) {
if (e instanceof TypeElement) { // class
String className = elements.getBinaryName((TypeElement) e).toString();
mSuppressMap.put(className, Collections.EMPTY_SET);
mNumToSuppress++;
} else if (e instanceof ExecutableElement) { // method
String classname = e.getEnclosingElement().toString();
java.util.Set<String> suppressMethods = mSuppressMap.get(classname);
if (suppressMethods != null && suppressMethods.isEmpty()) {
// empty set means suppress warnings on all methods in class; do nothing
continue;
}
if (suppressMethods == null) {
suppressMethods = new LinkedHashSet<String>();
}
suppressMethods.add(e.getSimpleName().toString());
mSuppressMap.put(classname, suppressMethods);
mNumToSuppress++;
}
if (suppressMethods == null) {
suppressMethods = new LinkedHashSet<String>();
}
suppressMethods.add(e.toString());
mSuppressMap.put(classname, suppressMethods);
}
}
}
if (env.processingOver()) {
if (env.processingOver() && mNumToSuppress > 0) {
try {
exportSuppressMap();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}

@ -214,7 +214,7 @@ def create_results_dir(results_dir):
mkdir_if_not_exists(os.path.join(results_dir, 'sources'))
def clean_infer_out(infer_out):
def clean(infer_out, annotations_out):
directories = ['multicore', 'classnames', 'sources', jwlib.FILELISTS]
extensions = ['.cfg', '.cg']
@ -230,6 +230,8 @@ def clean_infer_out(infer_out):
path = os.path.join(root, f)
os.remove(path)
os.remove(annotations_out)
def help_exit(message):
print(message)
@ -586,7 +588,7 @@ class Infer:
exit_status += make_status
if self.args.buck and exit_status == os.EX_OK:
clean_infer_out(self.args.infer_out)
clean(self.args.infer_out, self.javac.annotations_out)
return exit_status
@ -620,7 +622,6 @@ class Infer:
if self.javac.annotations_out is not None:
infer_print_options += [
'-local_config', self.javac.annotations_out]
exit_status = subprocess.check_call(
infer_print_cmd + infer_print_options
)
@ -666,7 +667,6 @@ class Infer:
def close(self):
if self.args.analyzer != COMPILE:
os.remove(self.javac.verbose_out)
os.remove(self.javac.annotations_out)
def analyze_and_report(self):
if self.args.analyzer not in [COMPILE, CAPTURE]:

@ -0,0 +1,51 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package codetoanalyze.java.eradicate;
import java.lang.SuppressWarnings;
import javax.annotation.Nullable;
public class SuppressWarningsExample {
@SuppressWarnings("null")
static class DoSuppress {
@Nullable Object mFld;
void doSuppress1(@Nullable Object o) {
o.toString();
}
void doSuppress2() {
mFld.toString();
}
}
@SuppressWarnings("null")
public void doSuppress(@Nullable Object o) {
o.toString();
}
@SuppressWarnings("infer")
public void doSuppressInferAnnot(@Nullable Object o) {
o.toString();
}
@SuppressWarnings("unchecked")
public void doNotSuppressWrongAnnot(@Nullable Object o) {
o.toString();
}
public void doNotSuppressNoAnnot(@Nullable Object o) {
o.toString();
}
}

@ -0,0 +1,56 @@
/*
* Copyright (c) 2015 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package endtoend.java.eradicate;
import static org.hamcrest.MatcherAssert.assertThat;
import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain;
import static utils.matchers.ResultContainsExactly.containsExactly;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import utils.InferException;
import utils.InferResults;
public class SuppressWarningsTest {
public static final String SOURCE_FILE =
"infer/tests/codetoanalyze/java/eradicate/SuppressWarningsExample.java";
public static final String NULL_METHOD_CALL = "ERADICATE_NULL_METHOD_CALL";
private static InferResults inferResults;
@BeforeClass
public static void loadResults() throws InterruptedException, IOException {
inferResults = InferResults.loadEradicateResults(
SuppressWarningsTest.class,
SOURCE_FILE);
}
@Test
public void matchErrors()
throws IOException, InterruptedException, InferException {
String[] methods = {
"doNotSuppressWrongAnnot",
"doNotSuppressNoAnnot"
};
assertThat(
"Results should contain " + NULL_METHOD_CALL,
inferResults,
containsExactly(
NULL_METHOD_CALL,
SOURCE_FILE,
methods));
}
}
Loading…
Cancel
Save