Skip to content

CompilationTestHelper.doTest() passes when the checker fails to initialize and the default locale is German, Japanese, or Chinese #6178

Description

@vlsi

When a BugChecker throws from its constructor, a CompilationTestHelper test fails under the en_US locale with ErrorProne suffered an internal crash. Under de_DE the same test passes: the checker never runs, and doTest() returns normally. ja_JP and zh_CN behave like de_DE.

Reproducer

src/test/java/repro/ThrowingCheckerTest.java:

package repro;

import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.bugpatterns.BugChecker;
import java.util.Locale;
import org.junit.Test;

public class ThrowingCheckerTest {

  @BugPattern(summary = "Fails to initialize", severity = BugPattern.SeverityLevel.ERROR)
  public static class ThrowingChecker extends BugChecker {
    public ThrowingChecker() {
      throw new IllegalStateException("checker failed to initialize");
    }
  }

  @Test
  public void checkerThatCannotBeConstructedFailsTheTest() {
    System.out.println("Locale.getDefault() = " + Locale.getDefault());
    CompilationTestHelper.newInstance(ThrowingChecker.class, getClass())
        .addSourceLines("Test.java", "class Test {}")
        .doTest();
  }
}

build.gradle (plus a settings.gradle with rootProject.name = 'ep-locale'):

plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
  testImplementation 'com.google.errorprone:error_prone_check_api:2.50.0'
  testImplementation 'com.google.errorprone:error_prone_test_helpers:2.50.0'
  testImplementation 'junit:junit:4.13.2'
}
test {
  jvmArgs(['api', 'code', 'comp', 'file', 'main', 'model', 'parser', 'processing', 'tree', 'util']
    .collect { "--add-exports=jdk.compiler/com.sun.tools.javac.$it=ALL-UNNAMED" })
  jvmArgs "-Duser.language=${findProperty('lang')}", "-Duser.country=${findProperty('country')}"
  testLogging { events 'passed', 'failed'; showStandardStreams = true }
  outputs.upToDateWhen { false }
}
gradle test -Plang=en -Pcountry=US
gradle test -Plang=de -Pcountry=DE

Under en_US the test fails, as expected:

ThrowingCheckerTest > checkerThatCannotBeConstructedFailsTheTest STANDARD_OUT
    Locale.getDefault() = en_US
ThrowingCheckerTest > checkerThatCannotBeConstructedFailsTheTest FAILED
    com.google.common.truth.AssertionErrorWithFacts at ThrowingCheckerTest.java:23
1 test completed, 1 failed
com.google.common.truth.AssertionErrorWithFacts: ErrorProne suffered an internal crash: An exception has occurred in the compiler (21.0.9). Please file a bug against the Java compiler via the Java bug reporting page (https://bugreport.java.com) after checking the Bug Database (https://bugs.java.com) for duplicates. Include your program, the following diagnostic, and the parameters passed to the Java compiler in your report. Thank you.
java.lang.AssertionError: com.google.errorprone.scanner.ErrorProneInjector$ProvisionException: Failed to initialize repro.ThrowingCheckerTest.ThrowingChecker
	at com.google.errorprone.ErrorProneAnalyzer.lambda$scansPlugins$2(ErrorProneAnalyzer.java:163)

Under de_DE it passes:

ThrowingCheckerTest > checkerThatCannotBeConstructedFailsTheTest STANDARD_OUT
    Locale.getDefault() = de_DE
ThrowingCheckerTest > checkerThatCannotBeConstructedFailsTheTest PASSED
BUILD SUCCESSFUL in 1s

ja_JP and zh_CN pass the same way. fr_FR fails like en_US.

Which tests are affected

Under de_DE, the crash goes unnoticed only where nothing else checks the diagnostics or the compilation result:

  • A source with no // BUG: Diagnostic ... markers, and no expectNoDiagnostics() or expectResult(...), passes. This is the reproducer above.
  • A test that expects doTest() to throw because the checker cannot initialize, written as assertThrows(AssertionError.class, helper::doTest) with JUnit 4.13.2, fails with expected java.lang.AssertionError to be thrown, but nothing was thrown.
  • expectNoDiagnostics() still fails, because the result check catches it: Expected compilation result to be OK, but was ERROR. No diagnostics were emitted.
  • A source with a // BUG: Diagnostic contains: x marker still fails, because the expected diagnostic is missing: Did not see an error on line 3 matching x. There were no errors.

Expected behavior

doTest() throws AssertionError with ErrorProne suffered an internal crash: ... whatever the JVM default locale is, as it does under en_US. Commit 5abc8e5 (from #4112) added that check for this case, a checker that fails in its constructor.

Cause (my reading of the code; the report above stands without it)

A checker constructor that throws is not reported as an error.prone.crash diagnostic: the checker is instantiated from ErrorProneAnalyzer.finished (see the stack trace above), and the exception propagates into javac, which prints its msg.bug banner and returns a failure from JavacTask.call(). doTest() recognizes that banner by its English text (CompilationTestHelper.java#L342-L344 on master):

assertWithMessage("ErrorProne suffered an internal crash: %s", stringifiedOutput)
    .that(stringifiedOutput)
    .doesNotContain("An exception has occurred in the compiler");

javac localizes msg.bug, and JDK 21.0.9 prints it in German, Japanese, and Simplified Chinese. This is the first line of the compiler output for the reproducer under de_DE, ja_JP, and zh_CN, read from the helper's private outputStream field after doTest() returned:

Im Compiler (21.0.9) ist eine Ausnahme aufgetreten. Erstellen Sie auf der Java-Seite zum Melden von Bugs (https://bugreport.java.com) einen Bugbericht, nachdem Sie die Bugdatenbank (https://bugs.java.com) auf Duplikate geprüft haben. Geben Sie in Ihrem Bericht Ihr Programm, die folgende Diagnose und die Parameter an, die Sie dem Java-Compiler übergeben haben. Vielen Dank.
コンパイラで例外が発生しました(21.0.9)。バグ・データベース(https://bugs.java.com)で重複がないかをご確認のうえ、Javaのバグ・レポート・ページ(https://bugreport.java.com)から、Javaコンパイラに対するバグの登録をお願いいたします。レポートには、該当のプログラム、次の診断内容、およびJavaコンパイラに渡されたパラメータをご入力ください。ご協力ありがとうございます。
编译器 (21.0.9) 中出现异常错误。如果在 Bug Database (https://bugs.java.com) 中没有找到有关该错误的 Java 编译器 Bug,请通过 Java Bug 报告页 (https://bugreport.java.com) 提交 Java 编译器 Bug。请在报告中附上您的程序、以下诊断信息以及传递到 Java 编译器的参数。谢谢。

A possible direction

In this case JavacTask.call() returns false and the diagnostic collector holds no diagnostics. I checked that by calling BaseErrorProneJavaCompiler.getTask(...).call() directly under de_DE: call()=false diagnostics=0. A result of ERROR with no error diagnostic would be a locale-independent signal. I have not checked whether a legitimate compilation failure can also produce it.

Setting the task locale does not help: with task.setLocale(Locale.ENGLISH) before call(), the banner under de_DE is still German.

Versions

  • Error Prone 2.50.0 (error_prone_check_api, error_prone_test_helpers). The string check is unchanged on master at f9edd2e.
  • JDK: Amazon Corretto 21.0.9+10-LTS
  • Gradle 9.8.0, JUnit 4.13.2
  • macOS 26.5.1, aarch64

I searched the issues and pull requests for locale, "An exception has occurred in the compiler", CompilationTestHelper, user.language, and "suffered an internal crash" and found no report of this. The nearest is #4112, which added the check.

NullAway found this when it added a locale axis to its CI test matrix (uber/NullAway#1781): four tests in ErrorProneCLIFlagsConfigTest that expect NullAway to fail initialization stopped failing under de_DE.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions