From: Vladimir Petko Date: Wed, 3 Jun 2026 06:35:57 +0000 (+0000) Subject: [PATCH] 8385738: Javadoc does not produce reproducible output due to the snippet... X-Git-Tag: archive/raspbian/25.0.5_7ea-1+rpi1~3^2~4 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=b432f3cbf1ff4d6d8aafd1e26f89933b0a3b6a8c;p=openjdk-25.git [PATCH] 8385738: Javadoc does not produce reproducible output due to the snippet ids The top level type generation order is not deterministic due to the directory listing being non-deterministic. Snippet ids are monotonically incremented during the javadoc run. This means that the generated snippet ids are not reproducible. This PR changes Set to the Map so that each page uses a separate snippet id counter. Bug: https://bugs.openjdk.org/browse/JDK-8385738 Reviewed-by: nbenalla Gbp-Pq: Name jdk-8385738.diff --- diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index 32bcab0ee..b5bf9f251 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. 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 @@ -49,6 +49,7 @@ import com.sun.source.util.DocTreePath; import jdk.javadoc.doclet.Taglet; import jdk.javadoc.internal.doclets.formats.html.HtmlConfiguration; +import jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyles; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Action; import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.ParseException; @@ -124,7 +125,8 @@ public class SnippetTaglet extends BaseTaglet { if (id != null && !id.isBlank()) { pre.put(HtmlAttr.ID, id); } else { - pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, ids).name()); + var set = ids.computeIfAbsent(tagletWriter.htmlWriter, _ -> new HashSet<>()); + pre.put(HtmlAttr.ID, config.htmlIds.forSnippet(element, set).name()); } var code = HtmlTree.CODE() .addUnchecked(Text.EMPTY); // Make sure the element is always rendered @@ -205,7 +207,7 @@ public class SnippetTaglet extends BaseTaglet { return snippetContainer.add(pre.add(code)); } - private final Set ids = new HashSet<>(); + private final HashMap> ids = new HashMap<>(); private static final class BadSnippetException extends Exception { diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java index 413de03d2..02001fe8a 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetTag.java @@ -39,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -2675,4 +2676,94 @@ public class TestSnippetTag extends SnippetTester { """); checkNoCrashes(); } + + @Test + public void testSnippetIdCounterResetsPerPage(Path base) throws IOException { + Path src = base.resolve("src"); + + tb.writeJavaFiles(src, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ACustomAnnotation { + } + """, + """ + package p; + import java.lang.annotation.Target; + import java.lang.annotation.ElementType; + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface ZCustomAnnotation { + } + """, + """ + package p; + /** Class A. */ + public class A { + /** + * First snippet on A.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on A.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """, + """ + package p; + /** Class B. */ + public class B { + /** + * First snippet on B.foo: + * {@snippet : + * int x = 1; // first + * } + * Second snippet on B.foo: + * {@snippet : + * int y = 2; // second + * } + */ + @ACustomAnnotation + @ZCustomAnnotation + public void foo() {} + } + """); + + javadoc("-d", base.resolve("out").toString(), + "-use", + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + for (String cls : new String[] { + "A.html", + "B.html", + "class-use/ACustomAnnotation.html", + "class-use/ZCustomAnnotation.html"}) { + var file = base + .resolve("out") + .resolve("p") + .resolve(cls); + String content = Files.readString(file); + for (var snippetId : new String[] {"snippet-foo()1", "snippet-foo()2"}) { + checking("id \"" + snippetId + "\" present in " + cls); + if (content.contains("id=\"" + snippetId + "\"")) { + passed("found"); + } else { + failed("" + snippetId + " not found in " + + String.join("\n", Arrays.asList(content.split("\n")) + .stream() + .filter(l -> l.contains("pre class=\"snippet\"")) + .toList())); + } + } + } + } }