Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.ErrorProneOptions.Severity;
import com.google.errorprone.RefactoringCollection.RefactoringResult;
import com.google.errorprone.scanner.ErrorProneScannerTransformer;
Expand All @@ -51,6 +50,7 @@
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.OptionalInt;
import java.util.Set;
import javax.tools.JavaFileObject;
import org.safere.Pattern;
Expand Down Expand Up @@ -195,6 +195,30 @@ private ErrorProneAnalyzer(
}

private int errorProneErrors = 0;
private int errorProneWarnings = 0;

/**
* Fails the compilation if Error Prone reported more warnings than {@code -XepMaxWarnings}
* allows. Patching runs are exempt: they exist to fix the findings, not to gate on them.
*/
private void enforceMaxWarnings() {
OptionalInt maxWarnings = errorProneOptions.maxWarnings();
if (maxWarnings.isEmpty()
|| errorProneOptions.patchingOptions().doRefactor()
|| errorProneWarnings <= maxWarnings.getAsInt()) {
return;
}
Log.instance(context)
.error(
"error.prone",
String.format(
Locale.ROOT,
"Error Prone reported %d warning%s, which exceeds the maximum of %d set by"
+ " -XepMaxWarnings",
errorProneWarnings,
errorProneWarnings == 1 ? "" : "s",
maxWarnings.getAsInt()));
}

/** Prints how long each check ran, slowest first. */
private void printTimings() {
Expand Down Expand Up @@ -230,6 +254,7 @@ public void finished(TaskEvent taskEvent) {
if (errorProneOptions.printTimings()) {
printTimings();
}
enforceMaxWarnings();
}
if (taskEvent.getKind() != Kind.ANALYZE) {
return;
Expand All @@ -249,8 +274,10 @@ public void finished(TaskEvent taskEvent) {
descriptionListenerFactory.getDescriptionListener(log, compilation);
DescriptionListener countingDescriptionListener =
d -> {
if (d.severity() == SeverityLevel.ERROR) {
errorProneErrors++;
switch (d.severity()) {
case ERROR -> errorProneErrors++;
case WARNING -> errorProneWarnings++;
case SUGGESTION -> {}
}
descriptionListener.onDescribed(d);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalInt;
import java.util.stream.Stream;
import org.safere.Pattern;

Expand Down Expand Up @@ -73,6 +74,7 @@ public final class ErrorProneOptions {
private static final String COMPILING_PUBLICLY_VISIBLE_CODE = "-XepCompilingPubliclyVisibleCode";
private static final String PRINT_TIMINGS = "-XepPrintTimings";
private static final String RECORD_TIMINGS = "-XepRecordTimings";
private static final String MAX_WARNINGS_PREFIX = "-XepMaxWarnings:";
private static final String ARGUMENT_FILE_PREFIX = "@";

/** see {@link javax.tools.OptionChecker#isSupportedOption(String)} */
Expand All @@ -83,6 +85,7 @@ public static int isSupportedOption(String option) {
|| option.startsWith(PATCH_OUTPUT_LOCATION)
|| option.startsWith(PATCH_CHECKS_PREFIX)
|| option.startsWith(EXCLUDED_PATHS_PREFIX)
|| option.startsWith(MAX_WARNINGS_PREFIX)
|| option.equals(IGNORE_UNKNOWN_CHECKS_FLAG)
|| option.equals(DISABLE_WARNINGS_IN_GENERATED_CODE_FLAG)
|| option.equals(ERRORS_AS_WARNINGS_FLAG)
Expand Down Expand Up @@ -169,6 +172,7 @@ abstract static class Builder {
private final boolean ignoreLargeCodeGenerators;
private final boolean printTimings;
private final boolean recordTimings;
private final OptionalInt maxWarnings;

private ErrorProneOptions(
ImmutableMap<String, Severity> severityMap,
Expand All @@ -188,7 +192,8 @@ private ErrorProneOptions(
boolean ignoreSuppressionAnnotations,
boolean ignoreLargeCodeGenerators,
boolean printTimings,
boolean recordTimings) {
boolean recordTimings,
OptionalInt maxWarnings) {
this.severityMap = severityMap;
this.remainingArgs = remainingArgs;
this.ignoreUnknownChecks = ignoreUnknownChecks;
Expand All @@ -207,6 +212,7 @@ private ErrorProneOptions(
this.ignoreLargeCodeGenerators = ignoreLargeCodeGenerators;
this.printTimings = printTimings;
this.recordTimings = recordTimings;
this.maxWarnings = maxWarnings;
}

public ImmutableList<String> getRemainingArgs() {
Expand Down Expand Up @@ -266,6 +272,14 @@ public boolean recordTimings() {
return recordTimings;
}

/**
* The maximum number of Error Prone warnings a compilation may report before Error Prone fails
* it, or empty if the number of warnings is unbounded. Set with {@code -XepMaxWarnings:N}.
*/
public OptionalInt maxWarnings() {
return maxWarnings;
}

public ErrorProneFlags getFlags() {
return flags;
}
Expand All @@ -292,6 +306,7 @@ private static class Builder {
private boolean ignoreLargeCodeGenerators = true;
private boolean printTimings = false;
private boolean recordTimings = false;
private OptionalInt maxWarnings = OptionalInt.empty();
private final Map<String, Severity> severityMap = new LinkedHashMap<>();
private final ErrorProneFlags.Builder flagsBuilder = ErrorProneFlags.builder();
private final PatchingOptions.Builder patchingOptionsBuilder = PatchingOptions.builder();
Expand Down Expand Up @@ -374,6 +389,22 @@ void setRecordTimings(boolean recordTimings) {
this.recordTimings = recordTimings;
}

void parseMaxWarnings(String arg) {
String value = arg.substring(MAX_WARNINGS_PREFIX.length());
int max;
try {
max = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandLineOptionException(
"invalid flag: " + arg + " (expected a non-negative integer)");
}
if (max < 0) {
throw new InvalidCommandLineOptionException(
"invalid flag: " + arg + " (expected a non-negative integer)");
}
this.maxWarnings = OptionalInt.of(max);
}

void setDisableAllChecks(boolean disableAllChecks) {
// Discard previously set severities so that the DisableAllChecks flag is position sensitive.
severityMap.clear();
Expand Down Expand Up @@ -411,7 +442,8 @@ ErrorProneOptions build(ImmutableList<String> remainingArgs) {
ignoreSuppressionAnnotations,
ignoreLargeCodeGenerators,
printTimings,
recordTimings || printTimings);
recordTimings || printTimings,
maxWarnings);
}

void setExcludedPattern(Pattern excludedPattern) {
Expand Down Expand Up @@ -577,6 +609,8 @@ protected ObjectStreamClass readClassDescriptor()
} else if (arg.startsWith(EXCLUDED_PATHS_PREFIX)) {
String pathRegex = arg.substring(EXCLUDED_PATHS_PREFIX.length());
builder.setExcludedPattern(Pattern.compile(pathRegex));
} else if (arg.startsWith(MAX_WARNINGS_PREFIX)) {
builder.parseMaxWarnings(arg);

} else {
if (arg.startsWith(PREFIX)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,30 @@ public void printTimingsIsOffByDefault() {
assertThat(options.recordTimings()).isFalse();
}

@Test
public void recognizesMaxWarnings() {
ErrorProneOptions options = ErrorProneOptions.processArgs(new String[] {"-XepMaxWarnings:3"});
assertThat(options.maxWarnings()).hasValue(3);
assertThat(ErrorProneOptions.isSupportedOption("-XepMaxWarnings:3")).isEqualTo(0);
}

@Test
public void maxWarningsIsUnboundedByDefault() {
ErrorProneOptions options = ErrorProneOptions.processArgs(new String[] {});
assertThat(options.maxWarnings()).isEmpty();
}

@Test
public void maxWarningsRejectsInvalidValues() {
for (String arg : new String[] {"-XepMaxWarnings:", "-XepMaxWarnings:-1", "-XepMaxWarnings:many"}) {
InvalidCommandLineOptionException expected =
assertThrows(
InvalidCommandLineOptionException.class,
() -> ErrorProneOptions.processArgs(new String[] {arg}));
assertThat(expected).hasMessageThat().contains("non-negative integer");
}
}

@Test
public void recognizesCompilingTestOnlyCode() {
ErrorProneOptions options =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -909,4 +909,62 @@ class Test {
.contains(
"com.google.errorprone.SourcePositionException: invalid source position: [35, 29)");
}

@BugPattern(severity = BugPattern.SeverityLevel.WARNING, summary = "")
public static class WarnOnEveryMethod extends BugChecker implements MethodTreeMatcher {
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return describeMatch(tree);
}
}

private Result compileWithTwoWarningsAcrossTwoFiles(String... args) {
compilerBuilder.report(ScannerSupplier.fromBugCheckerClasses(WarnOnEveryMethod.class));
compiler = compilerBuilder.build();
return compiler.compile(
args,
Arrays.asList(
forSourceLines("A.java", "class A {", " void a() {}", "}"),
forSourceLines(
"B.java",
"class B {",
" void b() {}",
" @SuppressWarnings(\"WarnOnEveryMethod\") void suppressed() {}",
"}")));
}

@Test
public void maxWarnings_atLimit_passes() {
Result exitCode = compileWithTwoWarningsAcrossTwoFiles("-XepMaxWarnings:2");
assertWithMessage(outputStream.toString()).that(exitCode).isEqualTo(Result.OK);
}

@Test
public void maxWarnings_overLimit_failsAndCountsAcrossFiles() {
Result exitCode = compileWithTwoWarningsAcrossTwoFiles("-XepMaxWarnings:1");
assertWithMessage(outputStream.toString()).that(exitCode).isEqualTo(Result.ERROR);
assertThat(diagnosticHelper.getDiagnostics())
.comparingElementsUsing(DIAGNOSTIC_CONTAINING)
.contains(
"Error Prone reported 2 warnings, which exceeds the maximum of 1 set by"
+ " -XepMaxWarnings");
}

@Test
public void maxWarnings_zero_failsOnAnyWarning() {
Result exitCode = compileWithTwoWarningsAcrossTwoFiles("-XepMaxWarnings:0");
assertWithMessage(outputStream.toString()).that(exitCode).isEqualTo(Result.ERROR);
}

@Test
public void maxWarnings_unset_warningsDoNotFail() {
Result exitCode = compileWithTwoWarningsAcrossTwoFiles();
assertWithMessage(outputStream.toString()).that(exitCode).isEqualTo(Result.OK);
}

@Test
public void maxWarnings_checkDisabled_doesNotCount() {
Result exitCode = compileWithTwoWarningsAcrossTwoFiles("-XepMaxWarnings:0", "-Xep:WarnOnEveryMethod:OFF");
assertWithMessage(outputStream.toString()).that(exitCode).isEqualTo(Result.OK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,51 @@ public void hello() throws IOException {
assertThat(diagnostic.getMessage(ENGLISH)).contains("[CollectionIncompatibleType]");
}

private static JavacTask maxWarningsTask(String maxWarningsFlag, DiagnosticCollector<JavaFileObject> diagnosticCollector) throws IOException {
FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
Path source = fileSystem.getPath("Test.java");
Files.write(
source,
ImmutableList.of(
"package test;", //
"class Test implements Runnable {",
" public void run() {}",
"}"),
UTF_8);
JavacFileManager fileManager = new JavacFileManager(new Context(), false, UTF_8);
return JavacTool.create()
.getTask(
null,
fileManager,
diagnosticCollector,
ImmutableList.of(
"-Xplugin:ErrorProne -XepDisableAllChecks -Xep:MissingOverride:WARN "
+ maxWarningsFlag,
"-XDcompilePolicy=byfile",
"--should-stop=ifError=FLOW",
"-XDaddTypeAnnotationsToSymbol=true"),
ImmutableList.of(),
fileManager.getJavaFileObjects(source));
}

@Test
public void maxWarnings_exceeded_failsCompilation() throws IOException {
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
assertThat(maxWarningsTask("-XepMaxWarnings:0", diagnosticCollector).call()).isFalse();
Diagnostic<? extends JavaFileObject> diagnostic =
diagnosticCollector.getDiagnostics().stream()
.filter(d -> d.getKind() == Diagnostic.Kind.ERROR)
.collect(onlyElement());
assertThat(diagnostic.getMessage(ENGLISH)).contains("Error Prone reported 1 warning, which exceeds the maximum of 0");
}

@Test
public void maxWarnings_notExceeded_passes() throws IOException {
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
assertThat(maxWarningsTask("-XepMaxWarnings:1", diagnosticCollector).call()).isTrue();
assertThat(diagnosticCollector.getDiagnostics().stream().anyMatch(d -> d.getMessage(ENGLISH).contains("[MissingOverride]"))).isTrue();
}

@Test
public void applyFixes() throws IOException {
// TODO(b/63064865): Test is broken on Windows. Disable for now.
Expand Down
Loading