Simplify the code to collect the list of @SuppressWarnings annotations

Summary:public
Simplifies the code to collect the `SuppressWarnings` annotations and makes the code more robust in the sense that not finding the output of the annotation processor will result in an error directly at the top-level instead of later on when trying to load the output file in the Java frontend.

Reviewed By: sblackshear

Differential Revision: D3034690

fb-gh-sync-id: 60caa0c
shipit-source-id: 60caa0c
master
jrm 9 years ago committed by Facebook Github Bot 9
parent 8d62fd12ca
commit 81a59515e4

@ -16,9 +16,7 @@ import com.sun.source.util.TreePath;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -29,7 +27,6 @@ import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
@SupportedOptions({ "classSourceMapOutputFilename" }) @SupportedOptions({ "classSourceMapOutputFilename" })
// this says: "process all classes, even ones without any annotations" // this says: "process all classes, even ones without any annotations"

@ -9,11 +9,9 @@
package com.facebook.infer.annotprocess; package com.facebook.infer.annotprocess;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
@ -22,39 +20,37 @@ import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements; import javax.lang.model.util.Elements;
@SupportedOptions({ "SuppressWarningsOutputFilename" })
@SupportedAnnotationTypes({ "java.lang.SuppressWarnings" }) @SupportedAnnotationTypes({ "java.lang.SuppressWarnings" })
public class CollectSuppressWarnings extends AbstractProcessor { public class CollectSuppressWarnings extends AbstractProcessor {
private static final String ANNOTATION_ENV_VAR = "INFER_ANNOTATIONS_OUT"; private static final String OUTPUT_FILENAME_OPTION = "SuppressWarningsOutputFilename";
private String mOutputFilename; // map of (classes -> methods in class). an empty set means suppress all warnings on class
// 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>>(); public Map<String, Set<String>> mSuppressMap = new LinkedHashMap<String, Set<String>>();
// total number of classes/methods with a SuppressWarnings annotation // total number of classes/methods with a SuppressWarnings annotation
private int mNumToSuppress = 0; private int mNumToSuppress = 0;
// write the methods/classes to suppress to .inferconfig-style JSON // write the methods/classes to suppress to .inferconfig-style JSON
private void exportSuppressMap() throws FileNotFoundException, IOException { private void exportSuppressMap() throws IOException {
Map<String, String> env = System.getenv();
if (env.get(ANNOTATION_ENV_VAR) == null) { Map<String, String> options = processingEnv.getOptions();
throw new RuntimeException("Env variable INFER_ANNOTATIONS_OUT not set"); String mOutputFilename =
} else { Preconditions.checkNotNull(options.get(OUTPUT_FILENAME_OPTION),
mOutputFilename = env.get(ANNOTATION_ENV_VAR); "The filename should be passed from the Infer top-level script");
}
// output .inferconfig format file in JSON // output .inferconfig format file in JSON
try (PrintWriter out = new PrintWriter(mOutputFilename)) { try (PrintWriter out = new PrintWriter(mOutputFilename)) {
int elemCount = 0; int elemCount = 0;
out.println("{ \"suppress_procedures\": ["); out.println("{ \"suppress_warnings\": [");
for (Map.Entry<String, Set<String>> entry : mSuppressMap.entrySet()) { for (Map.Entry<String, Set<String>> entry : mSuppressMap.entrySet()) {
String clazz = entry.getKey(); String clazz = entry.getKey();
Set<String> methods = entry.getValue(); Set<String> methods = entry.getValue();
@ -79,10 +75,11 @@ public class CollectSuppressWarnings extends AbstractProcessor {
} }
// collect all of the SuppressWarnings annotations from the Java source files being compiled // collect all of the SuppressWarnings annotations from the Java source files being compiled
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Elements elements = processingEnv.getElementUtils(); Elements elements = processingEnv.getElementUtils();
for (TypeElement te : annotations) { for (TypeElement te : annotations) {
for (Element e : env.getElementsAnnotatedWith(te)) { for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
SuppressWarnings annot = e.getAnnotation(SuppressWarnings.class); SuppressWarnings annot = e.getAnnotation(SuppressWarnings.class);
if (annot != null && shouldProcess(annot)) { if (annot != null && shouldProcess(annot)) {
if (e instanceof TypeElement) { // class if (e instanceof TypeElement) { // class
@ -107,13 +104,14 @@ public class CollectSuppressWarnings extends AbstractProcessor {
} }
} }
if (env.processingOver() && mNumToSuppress > 0) { if (roundEnv.processingOver() && mNumToSuppress > 0) {
try { try {
exportSuppressMap(); exportSuppressMap();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
return false; return false;
} }

@ -0,0 +1,21 @@
/*
* 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 com.facebook.infer.annotprocess;
public class Preconditions {
public static <T> T checkNotNull(T reference, String errorMessage) {
if (reference == null) {
throw new NullPointerException(errorMessage);
}
return reference;
}
}

@ -53,6 +53,7 @@ LOG_FILE = 'toplevel.log'
BUCK_INFER_OUT = 'infer' BUCK_INFER_OUT = 'infer'
CLASS_SOURCE_MAP_OUTPUT_FILENAME_OPTION = 'classSourceMapOutputFilename' CLASS_SOURCE_MAP_OUTPUT_FILENAME_OPTION = 'classSourceMapOutputFilename'
SUPRESS_WARNINGS_OUTPUT_FILENAME_OPTION = 'SuppressWarningsOutputFilename'
# exit value when infer finds something to report # exit value when infer finds something to report

@ -80,7 +80,6 @@ class CompilerCall(object):
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:
@ -136,7 +135,10 @@ class CompilerCall(object):
suffix='.out', suffix='.out',
prefix='annotations_', prefix='annotations_',
delete=False) as annot_out: delete=False) as annot_out:
self.annotations_out = annot_out.name self.suppress_warnings_out = annot_out.name
javac_cmd += ['-A%s=%s' %
(config.SUPRESS_WARNINGS_OUTPUT_FILENAME_OPTION,
self.suppress_warnings_out)]
with tempfile.NamedTemporaryFile( with tempfile.NamedTemporaryFile(
mode='w', mode='w',
@ -144,7 +146,6 @@ class CompilerCall(object):
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:
@ -241,14 +242,12 @@ class AnalyzerWithFrontendWrapper(analyze.AnalyzerWrapper):
infer_cmd += [ infer_cmd += [
'-results_dir', self.args.infer_out, '-results_dir', self.args.infer_out,
'-verbose_out', self.javac.verbose_out, '-verbose_out', self.javac.verbose_out,
'-suppress_warnings_out', self.javac.suppress_warnings_out,
] ]
if os.path.isfile(config.MODELS_JAR): if os.path.isfile(config.MODELS_JAR):
infer_cmd += ['-models', config.MODELS_JAR] infer_cmd += ['-models', config.MODELS_JAR]
if self.javac.annotations_out is not None:
infer_cmd += ['-local_config', self.javac.annotations_out]
infer_cmd.append('-no-static_final') infer_cmd.append('-no-static_final')
if self.args.debug: if self.args.debug:
@ -276,4 +275,4 @@ class AnalyzerWithFrontendWrapper(analyze.AnalyzerWrapper):
def _close(self): def _close(self):
os.remove(self.javac.verbose_out) os.remove(self.javac.verbose_out)
os.remove(self.javac.annotations_out) os.remove(self.javac.suppress_warnings_out)

@ -12,7 +12,7 @@ let inferconfig_file = ".inferconfig"
let inferconfig_home = ref None let inferconfig_home = ref None
let local_config = ref None let suppress_warnings_annotations = 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 =
@ -280,8 +280,8 @@ module NeverReturnNull = FileOrProcMatcher(struct
let json_key = "never_returning_null" let json_key = "never_returning_null"
end) end)
module ProcMatcher = FileOrProcMatcher(struct module SuppressWarningsMatcher = FileOrProcMatcher(struct
let json_key = "suppress_procedures" let json_key = "suppress_warnings"
end) end)
module SkipTranslationMatcher = FileOrProcMatcher(struct module SkipTranslationMatcher = FileOrProcMatcher(struct

@ -9,7 +9,7 @@
val inferconfig_home : string option ref val inferconfig_home : string option ref
val local_config : string option ref val suppress_warnings_annotations : string option ref
(** get the path to the .inferconfig file *) (** get the path to the .inferconfig file *)
val inferconfig : unit -> string val inferconfig : unit -> string
@ -45,7 +45,7 @@ module NeverReturnNull : Matcher
module SkipTranslationMatcher : Matcher module SkipTranslationMatcher : Matcher
module ProcMatcher : Matcher module SuppressWarningsMatcher : Matcher
(** Load the config file and list the files to report on *) (** Load the config file and list the files to report on *)
val test: unit -> unit val test: unit -> unit

@ -55,12 +55,11 @@ let log_issue
?(pre = None) ?(pre = None)
exn = exn =
let should_suppress_warnings summary = let should_suppress_warnings summary =
if !Config.curr_language = Config.C_CPP then false !Config.curr_language = Config.Java &&
else let annotated_signature =
let annotated_signature = Annotations.get_annotated_signature summary.Specs.attributes in
Annotations.get_annotated_signature summary.Specs.attributes in let ret_annotation, _ = annotated_signature.Annotations.ret in
let ret_annotation, _ = annotated_signature.Annotations.ret in Annotations.ia_is_suppress_warnings ret_annotation in
Annotations.ia_is_suppress_warnings ret_annotation in
match Specs.get_summary proc_name with match Specs.get_summary proc_name with
| Some summary when should_suppress_warnings summary -> () | Some summary when should_suppress_warnings summary -> ()
| Some summary -> | Some summary ->

@ -17,10 +17,10 @@ let suppress_warnings_lookup = ref None
let load_suppress_warnings_lookup () = let load_suppress_warnings_lookup () =
let default_matcher = fun _ -> false in let default_matcher = fun _ -> false in
let matcher = let matcher =
match !Inferconfig.local_config with match !Inferconfig.suppress_warnings_annotations with
| Some f -> | Some f ->
(try (try
let m = Inferconfig.ProcMatcher.load_matcher f in let m = Inferconfig.SuppressWarningsMatcher.load_matcher f in
(m DB.source_file_empty) (m DB.source_file_empty)
with Yojson.Json_error _ -> with Yojson.Json_error _ ->
default_matcher) default_matcher)

@ -62,10 +62,10 @@ let arg_desc =
None, None,
"Set the path to the javac verbose output" "Set the path to the javac verbose output"
; ;
"-local_config", "-suppress_warnings_out",
Arg.String (fun s -> Inferconfig.local_config := Some s), Arg.String (fun s -> Inferconfig.suppress_warnings_annotations := Some s),
Some "Path", Some "Path",
"Path to local config file" "Path to list of collected @SuppressWarnings annotations"
; ;
] in ] in
Arg.create_options_desc false "Parsing Options" desc Arg.create_options_desc false "Parsing Options" desc

Loading…
Cancel
Save