From 1a615a467badf29c34707d9e4f99382621a1ac72 Mon Sep 17 00:00:00 2001 From: jrm Date: Thu, 20 Aug 2015 22:03:39 -0700 Subject: [PATCH] [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. --- .../annotprocess/AnnotationProcessor.java | 74 ++++++++++++++----- infer/bin/inferlib.py | 6 +- infer/bin/jwlib.py | 21 +++++- infer/bin/utils.py | 3 + infer/src/backend/inferconfig.ml | 2 + infer/src/backend/inferconfig.mli | 2 + infer/src/backend/inferprint.ml | 2 + 7 files changed, 90 insertions(+), 20 deletions(-) diff --git a/infer/annotations/com/facebook/infer/annotprocess/AnnotationProcessor.java b/infer/annotations/com/facebook/infer/annotprocess/AnnotationProcessor.java index e1e7a0243..6d1bc165d 100644 --- a/infer/annotations/com/facebook/infer/annotprocess/AnnotationProcessor.java +++ b/infer/annotations/com/facebook/infer/annotprocess/AnnotationProcessor.java @@ -9,41 +9,81 @@ package com.facebook.infer.annotprocess; -import javax.annotation.processing.*; -import javax.lang.model.element.*; -import javax.tools.*; +import java.io.FileNotFoundException; +import java.io.IOException; +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 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 { - // map of (classes -> methods in class). an empty set means suppress all warnings on class - public Map> suppressMap = new LinkedHashMap>(); + private static final String ANNOTATION_ENV_VAR = "INFER_ANNOTATIONS_OUT"; + + private String mOutputFilename = "suppress_warnings_map.txt"; + + // map of (classes -> methods in class). an empty set means suppress all + // warnings on class + public Map> mSuppressMap = new LinkedHashMap>(); + + private void exportSuppressMap() throws FileNotFoundException, IOException { + Map 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> entry : mSuppressMap.entrySet()) { + out.write(entry.getKey() + " " + entry.getValue()); + } + } + } public boolean process(Set annotations, RoundEnvironment env) { for (TypeElement te : annotations) { for (Element e : env.getElementsAnnotatedWith(te)) { if (e instanceof TypeElement) { // class String className = ((TypeElement) e).getQualifiedName().toString(); - suppressMap.put(className, Collections.EMPTY_SET); + mSuppressMap.put(className, Collections.EMPTY_SET); } else if (e instanceof ExecutableElement) { // method - String clazz = e.getEnclosingElement().toString(); - Set suppressMethods = suppressMap.get(clazz); - if (suppressMethods == null) { - suppressMethods = new LinkedHashSet(); - suppressMap.put(clazz, suppressMethods); - } else if (suppressMethods.isEmpty()) { - // empty set means suppress warnings on all methods in class; do nothing + + String classname = e.getEnclosingElement().toString(); + java.util.Set suppressMethods = mSuppressMap.get(classname); + if (suppressMethods != null && suppressMethods.isEmpty()) { + // empty set means suppress warnings on all methods in class; do + // nothing continue; } - suppressMethods.add(clazz); + if (suppressMethods == null) { + suppressMethods = new LinkedHashSet(); + } + suppressMethods.add(e.toString()); + mSuppressMap.put(classname, suppressMethods); } } } 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; } diff --git a/infer/bin/inferlib.py b/infer/bin/inferlib.py index 76725ce7a..bf91a320b 100644 --- a/infer/bin/inferlib.py +++ b/infer/bin/inferlib.py @@ -191,7 +191,7 @@ def create_results_dir(results_dir): def clean_infer_out(infer_out): - + directories = ['multicore', 'classnames', 'sources', jwlib.FILELISTS] extensions = ['.cfg', '.cg'] @@ -590,6 +590,9 @@ class Infer: '-procs', procs_report, '-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( infer_print_cmd + infer_print_options @@ -631,6 +634,7 @@ 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]: diff --git a/infer/bin/jwlib.py b/infer/bin/jwlib.py index dd2c87f3f..8dd94f77b 100644 --- a/infer/bin/jwlib.py +++ b/infer/bin/jwlib.py @@ -32,27 +32,44 @@ class CompilerCall: self.original_arguments = arguments self.args, self.remaining_args = parser.parse_known_args(arguments) self.verbose_out = None + self.annotations_out = None def run(self): if self.args.version: return subprocess.call(['javac'] + self.original_arguments) else: javac_cmd = ['javac', '-verbose', '-g'] + if self.args.bootclasspath is not None: 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: javac_cmd += ['-d', self.args.classes_out] javac_cmd += self.remaining_args 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( mode='w', suffix='.out', prefix='javac_', delete=False) as file_out: self.verbose_out = file_out.name + os.environ['INFER_ANNOTATIONS_OUT'] = self.annotations_out try: subprocess.check_call(javac_cmd, stderr=file_out) except subprocess.CalledProcessError: diff --git a/infer/bin/utils.py b/infer/bin/utils.py index 5b332a890..6416d26a9 100644 --- a/infer/bin/utils.py +++ b/infer/bin/utils.py @@ -29,6 +29,9 @@ LIB_DIRECTORY = os.path.join(BIN_DIRECTORY, '..', 'lib', 'java') TMP_DIRECTORY = tempfile.gettempdir() 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') CSV_PERF_FILENAME = 'performances.csv' diff --git a/infer/src/backend/inferconfig.ml b/infer/src/backend/inferconfig.ml index 344c7edd9..22cbaf4e5 100644 --- a/infer/src/backend/inferconfig.ml +++ b/infer/src/backend/inferconfig.ml @@ -14,6 +14,8 @@ let inferconfig_file = ".inferconfig" let inferconfig_home = ref None +let local_config = ref None + (** Look up a key in a json file containing a list of strings *) let lookup_string_list key json = Yojson.Basic.Util.filter_member key [json] diff --git a/infer/src/backend/inferconfig.mli b/infer/src/backend/inferconfig.mli index 254cd7f46..d127478fc 100644 --- a/infer/src/backend/inferconfig.mli +++ b/infer/src/backend/inferconfig.mli @@ -12,6 +12,8 @@ type path_filter = DB.source_file -> bool val inferconfig_home : string option ref +val local_config : string option ref + (** Filter type for an error name. *) type error_filter = Localise.t -> bool diff --git a/infer/src/backend/inferprint.ml b/infer/src/backend/inferprint.ml index 09939635b..712043f45 100644 --- a/infer/src/backend/inferprint.ml +++ b/infer/src/backend/inferprint.ml @@ -112,6 +112,8 @@ let arg_desc = "setup the analyzer for the path filtering"; "-inferconfig_home", Arg.String (fun s -> Inferconfig.inferconfig_home := Some s), Some "dir", "Path to the .inferconfig file"; + "-local_config", Arg.String (fun s -> Inferconfig.local_config := Some s), Some "Path", + "Path to local config file"; ] in Arg2.create_options_desc false "Options" desc in let reserved_arg =