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 =