From b5156bb16aade1ff40f20ed6ce6faafe7becb671 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 29 Apr 2025 14:32:38 +0000 Subject: [PATCH] 8355065: ConcurrentModificationException in RichDiagnosticFormatter Reviewed-by: vromero --- .../javac/util/RichDiagnosticFormatter.java | 78 +++++++--- .../RichFormatterWithTypeAnnotationsTest.java | 133 ++++++++++++++++++ 2 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 test/langtools/tools/javac/annotations/typeAnnotations/RichFormatterWithTypeAnnotationsTest.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java index fa81ce7ba75..512fa0e07e3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/RichDiagnosticFormatter.java @@ -83,8 +83,37 @@ public class RichDiagnosticFormatter extends /* type/symbol printer used by this formatter */ private RichPrinter printer; + private static class WhereClauses { + private final Map> whereClauses; + + WhereClauses() { + Map> whereClauses = new EnumMap<>(WhereClauseKind.class); + for (WhereClauseKind kind : WhereClauseKind.values()) { + whereClauses.put(kind, new LinkedHashMap<>()); + } + this.whereClauses = whereClauses; + } + + public Map get(WhereClauseKind kind) { + return whereClauses.get(kind); + } + } + /* map for keeping track of a where clause associated to a given type */ - Map> whereClauses; + WhereClauses whereClauses; + + private void enter() { + if (nameSimplifier != null || whereClauses != null) { + throw new IllegalStateException(); + } + nameSimplifier = new ClassNameSimplifier(); + whereClauses = new WhereClauses(); + } + + private void exit() { + nameSimplifier = null; + whereClauses = null; + } /** Get the DiagnosticFormatter instance for this context. */ public static RichDiagnosticFormatter instance(Context context) { @@ -102,39 +131,42 @@ public class RichDiagnosticFormatter extends this.diags = JCDiagnostic.Factory.instance(context); this.types = Types.instance(context); this.messages = JavacMessages.instance(context); - whereClauses = new EnumMap<>(WhereClauseKind.class); configuration = new RichConfiguration(Options.instance(context), formatter); - for (WhereClauseKind kind : WhereClauseKind.values()) - whereClauses.put(kind, new LinkedHashMap()); } @Override public String format(JCDiagnostic diag, Locale l) { - StringBuilder sb = new StringBuilder(); - nameSimplifier = new ClassNameSimplifier(); - for (WhereClauseKind kind : WhereClauseKind.values()) - whereClauses.get(kind).clear(); - preprocessDiagnostic(diag); - sb.append(formatter.format(diag, l)); - if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) { - List clauses = getWhereClauses(); - String indent = formatter.isRaw() ? "" : - formatter.indentString(DetailsInc); - for (JCDiagnostic d : clauses) { - String whereClause = formatter.format(d, l); - if (whereClause.length() > 0) { - sb.append('\n' + indent + whereClause); + enter(); + try { + StringBuilder sb = new StringBuilder(); + preprocessDiagnostic(diag); + sb.append(formatter.format(diag, l)); + if (getConfiguration().isEnabled(RichFormatterFeature.WHERE_CLAUSES)) { + List clauses = getWhereClauses(); + String indent = formatter.isRaw() ? "" : + formatter.indentString(DetailsInc); + for (JCDiagnostic d : clauses) { + String whereClause = formatter.format(d, l); + if (whereClause.length() > 0) { + sb.append('\n' + indent + whereClause); + } } } + return sb.toString(); + } finally { + exit(); } - return sb.toString(); } @Override public String formatMessage(JCDiagnostic diag, Locale l) { - nameSimplifier = new ClassNameSimplifier(); - preprocessDiagnostic(diag); - return super.formatMessage(diag, l); + enter(); + try { + preprocessDiagnostic(diag); + return super.formatMessage(diag, l); + } finally { + exit(); + } } /** @@ -556,7 +588,7 @@ public class RichDiagnosticFormatter extends @Override public Void visitTypeVar(TypeVar t, Void ignored) { - t = (TypeVar)t.stripMetadataIfNeeded(); + t = (TypeVar)t.stripMetadata(); if (indexOf(t, WhereClauseKind.TYPEVAR) == -1) { //access the bound type and skip error types Type bound = t.getUpperBound(); diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/RichFormatterWithTypeAnnotationsTest.java b/test/langtools/tools/javac/annotations/typeAnnotations/RichFormatterWithTypeAnnotationsTest.java new file mode 100644 index 00000000000..4482bd46854 --- /dev/null +++ b/test/langtools/tools/javac/annotations/typeAnnotations/RichFormatterWithTypeAnnotationsTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025, Google LLC. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8355065 + * @summary ConcurrentModificationException in RichDiagnosticFormatter + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.main jdk.compiler/com.sun.tools.javac.api + * @build toolbox.ToolBox toolbox.JavacTask + * @run main RichFormatterWithTypeAnnotationsTest + */ +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.TestRunner; +import toolbox.ToolBox; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +public class RichFormatterWithTypeAnnotationsTest extends TestRunner { + ToolBox tb; + + public RichFormatterWithTypeAnnotationsTest() { + super(System.err); + tb = new ToolBox(); + } + + public static void main(String[] args) throws Exception { + new RichFormatterWithTypeAnnotationsTest() + .runTests(m -> new Object[] {Paths.get(m.getName())}); + } + + @Test + public void test(Path base) throws Exception { + Path libClasses = base.resolve("libclasses"); + Files.createDirectories(libClasses); + new JavacTask(tb) + .outdir(libClasses) + .sources( + """ + package lib; + enum Bar { + BAZ + } + """, + """ + package lib; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + @Retention(RetentionPolicy.RUNTIME) + @interface Foo { + Bar value(); + } + """, + """ + package lib; + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) + @Foo(Bar.BAZ) + @interface A {} + """, + """ + package lib; + public interface M { + @A + V f(K k, V v); + } + """) + .options() + .run() + .writeAll(); + Files.delete(libClasses.resolve("lib").resolve("Bar.class")); + String code = + """ + import lib.M; + class T { + protected M m; + + public void f() { + m.f(null, 0); + } + } + """; + List output = + new JavacTask(tb) + .classpath(libClasses) + .sources(code) + .options("-Xlint:all", "-Werror", "-XDrawDiagnostics") + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + List expected = + Arrays.asList( + "T.java:3:13: compiler.warn.raw.class.use: lib.M, lib.M", + "T.java:6:8: compiler.warn.unchecked.call.mbr.of.raw.type: f(K,V), lib.M", + "- compiler.err.warnings.and.werror", + "1 error", + "2 warnings"); + tb.checkEqual(expected, output); + } +}