[infer][java] add support for @SuppressWarnings, step 1

Summary:
Passing the list of SuppressWarnings annotations detected during the compilation to InferPrint. The next step will be to add support for error filtering in .inferconfig and use the same mechanism. The annotation processor will generate an .inferconfig like config file and use it to suppress the reports.
master
jrm 10 years ago
parent cb52bff366
commit 1a615a467b

@ -9,41 +9,81 @@
package com.facebook.infer.annotprocess; package com.facebook.infer.annotprocess;
import javax.annotation.processing.*; import java.io.FileNotFoundException;
import javax.lang.model.element.*; import java.io.IOException;
import javax.tools.*; import java.io.PrintWriter;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
import java.util.*; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
@SupportedAnnotationTypes({"java.lang.SuppressWarnings"}) @SupportedAnnotationTypes({ "java.lang.SuppressWarnings" })
public class AnnotationProcessor extends AbstractProcessor { public class AnnotationProcessor extends AbstractProcessor {
// map of (classes -> methods in class). an empty set means suppress all warnings on class private static final String ANNOTATION_ENV_VAR = "INFER_ANNOTATIONS_OUT";
public Map<String,Set<String>> suppressMap = new LinkedHashMap<String,Set<String>>();
private String mOutputFilename = "suppress_warnings_map.txt";
// 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>>();
private void exportSuppressMap() throws FileNotFoundException, IOException {
Map<String, String> env = System.getenv();
if (env.get(ANNOTATION_ENV_VAR) == null) {
throw new RuntimeException("Env variable INFER_ANNOTATIONS_OUT not set");
} else {
mOutputFilename = env.get(ANNOTATION_ENV_VAR);
}
try (PrintWriter out = new PrintWriter(mOutputFilename)) {
for (Map.Entry<String, Set<String>> entry : mSuppressMap.entrySet()) {
out.write(entry.getKey() + " " + entry.getValue());
}
}
}
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for (TypeElement te : annotations) { for (TypeElement te : annotations) {
for (Element e : env.getElementsAnnotatedWith(te)) { for (Element e : env.getElementsAnnotatedWith(te)) {
if (e instanceof TypeElement) { // class if (e instanceof TypeElement) { // class
String className = ((TypeElement) e).getQualifiedName().toString(); String className = ((TypeElement) e).getQualifiedName().toString();
suppressMap.put(className, Collections.EMPTY_SET); mSuppressMap.put(className, Collections.EMPTY_SET);
} else if (e instanceof ExecutableElement) { // method } else if (e instanceof ExecutableElement) { // method
String clazz = e.getEnclosingElement().toString();
Set<String> suppressMethods = suppressMap.get(clazz); String classname = e.getEnclosingElement().toString();
if (suppressMethods == null) { java.util.Set<String> suppressMethods = mSuppressMap.get(classname);
suppressMethods = new LinkedHashSet(); if (suppressMethods != null && suppressMethods.isEmpty()) {
suppressMap.put(clazz, suppressMethods); // empty set means suppress warnings on all methods in class; do
} else if (suppressMethods.isEmpty()) { // nothing
// empty set means suppress warnings on all methods in class; do nothing
continue; continue;
} }
suppressMethods.add(clazz); if (suppressMethods == null) {
suppressMethods = new LinkedHashSet<String>();
}
suppressMethods.add(e.toString());
mSuppressMap.put(classname, suppressMethods);
} }
} }
} }
if (env.processingOver()) { if (env.processingOver()) {
// TODO: write suppressMap to a .inferconfig file on disk try {
exportSuppressMap();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
return false; return false;
} }

@ -191,7 +191,7 @@ def create_results_dir(results_dir):
def clean_infer_out(infer_out): def clean_infer_out(infer_out):
directories = ['multicore', 'classnames', 'sources', jwlib.FILELISTS] directories = ['multicore', 'classnames', 'sources', jwlib.FILELISTS]
extensions = ['.cfg', '.cg'] extensions = ['.cfg', '.cg']
@ -590,6 +590,9 @@ class Infer:
'-procs', procs_report, '-procs', procs_report,
'-analyzer', self.args.analyzer '-analyzer', self.args.analyzer
] ]
if self.javac.annotations_out is not None:
infer_print_options += [
'-local_config', self.javac.annotations_out]
exit_status = subprocess.check_call( exit_status = subprocess.check_call(
infer_print_cmd + infer_print_options infer_print_cmd + infer_print_options
@ -631,6 +634,7 @@ class Infer:
def close(self): def close(self):
if self.args.analyzer != COMPILE: if self.args.analyzer != COMPILE:
os.remove(self.javac.verbose_out) os.remove(self.javac.verbose_out)
os.remove(self.javac.annotations_out)
def analyze_and_report(self): def analyze_and_report(self):
if self.args.analyzer not in [COMPILE, CAPTURE]: if self.args.analyzer not in [COMPILE, CAPTURE]:

@ -32,27 +32,44 @@ class CompilerCall:
self.original_arguments = arguments self.original_arguments = arguments
self.args, self.remaining_args = parser.parse_known_args(arguments) self.args, self.remaining_args = parser.parse_known_args(arguments)
self.verbose_out = None self.verbose_out = None
self.annotations_out = None
def run(self): def run(self):
if self.args.version: if self.args.version:
return subprocess.call(['javac'] + self.original_arguments) return subprocess.call(['javac'] + self.original_arguments)
else: else:
javac_cmd = ['javac', '-verbose', '-g'] javac_cmd = ['javac', '-verbose', '-g']
if self.args.bootclasspath is not None: if self.args.bootclasspath is not None:
javac_cmd += ['-bootclasspath', self.args.bootclasspath] javac_cmd += ['-bootclasspath', self.args.bootclasspath]
if self.args.classpath is not None:
javac_cmd += ['-cp', self.args.classpath] if self.args.classpath is None:
classpath = utils.ANNOT_PROCESSOR_JAR
else:
classpath = os.pathsep.join([
utils.ANNOT_PROCESSOR_JAR,
self.args.classpath])
javac_cmd += ['-cp', classpath]
if self.args.classes_out is not None: if self.args.classes_out is not None:
javac_cmd += ['-d', self.args.classes_out] javac_cmd += ['-d', self.args.classes_out]
javac_cmd += self.remaining_args javac_cmd += self.remaining_args
javac_cmd.append('-J-Duser.language=en') javac_cmd.append('-J-Duser.language=en')
with tempfile.NamedTemporaryFile(
mode='w',
suffix='.out',
prefix='annotations_',
delete=False) as annot_out:
self.annotations_out = annot_out.name
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
mode='w', mode='w',
suffix='.out', suffix='.out',
prefix='javac_', prefix='javac_',
delete=False) as file_out: delete=False) as file_out:
self.verbose_out = file_out.name self.verbose_out = file_out.name
os.environ['INFER_ANNOTATIONS_OUT'] = self.annotations_out
try: try:
subprocess.check_call(javac_cmd, stderr=file_out) subprocess.check_call(javac_cmd, stderr=file_out)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:

@ -29,6 +29,9 @@ LIB_DIRECTORY = os.path.join(BIN_DIRECTORY, '..', 'lib', 'java')
TMP_DIRECTORY = tempfile.gettempdir() TMP_DIRECTORY = tempfile.gettempdir()
MODELS_JAR = os.path.join(LIB_DIRECTORY, 'models.jar') MODELS_JAR = os.path.join(LIB_DIRECTORY, 'models.jar')
ANNOT_PROCESSOR_JAR = os.path.join(
BIN_DIRECTORY, '..', 'annotations', 'processor.jar')
DEFAULT_INFER_OUT = os.path.join(os.getcwd(), 'infer-out') DEFAULT_INFER_OUT = os.path.join(os.getcwd(), 'infer-out')
CSV_PERF_FILENAME = 'performances.csv' CSV_PERF_FILENAME = 'performances.csv'

@ -14,6 +14,8 @@ let inferconfig_file = ".inferconfig"
let inferconfig_home = ref None let inferconfig_home = ref None
let local_config = ref None
(** Look up a key in a json file containing a list of strings *) (** Look up a key in a json file containing a list of strings *)
let lookup_string_list key json = let lookup_string_list key json =
Yojson.Basic.Util.filter_member key [json] Yojson.Basic.Util.filter_member key [json]

@ -12,6 +12,8 @@ type path_filter = DB.source_file -> bool
val inferconfig_home : string option ref val inferconfig_home : string option ref
val local_config : string option ref
(** Filter type for an error name. *) (** Filter type for an error name. *)
type error_filter = Localise.t -> bool type error_filter = Localise.t -> bool

@ -112,6 +112,8 @@ let arg_desc =
"setup the analyzer for the path filtering"; "setup the analyzer for the path filtering";
"-inferconfig_home", Arg.String (fun s -> Inferconfig.inferconfig_home := Some s), Some "dir", "-inferconfig_home", Arg.String (fun s -> Inferconfig.inferconfig_home := Some s), Some "dir",
"Path to the .inferconfig file"; "Path to the .inferconfig file";
"-local_config", Arg.String (fun s -> Inferconfig.local_config := Some s), Some "Path",
"Path to local config file";
] in ] in
Arg2.create_options_desc false "Options" desc in Arg2.create_options_desc false "Options" desc in
let reserved_arg = let reserved_arg =

Loading…
Cancel
Save