Skip to content

Commit a989245

Browse files
committed
8327466: ct.sym zip not reproducible across build environment timezones
Reviewed-by: erikj, jlahoda
1 parent 3270b00 commit a989245

File tree

2 files changed

+182
-6
lines changed

2 files changed

+182
-6
lines changed

make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
import java.nio.file.Path;
6565
import java.nio.file.Paths;
6666
import java.nio.file.attribute.BasicFileAttributes;
67+
import java.time.Instant;
68+
import java.time.LocalDateTime;
69+
import java.time.ZoneOffset;
6770
import java.util.stream.Stream;
6871
import java.util.ArrayList;
6972
import java.util.Arrays;
@@ -101,10 +104,7 @@
101104

102105
import com.sun.source.util.JavacTask;
103106
import com.sun.tools.javac.api.JavacTool;
104-
import com.sun.tools.javac.jvm.Target;
105-
import com.sun.tools.javac.util.Assert;
106107
import com.sun.tools.javac.util.Context;
107-
import com.sun.tools.javac.util.Pair;
108108
import java.nio.file.DirectoryStream;
109109
import java.util.Optional;
110110
import java.util.function.Consumer;
@@ -339,10 +339,10 @@ private void stripNonExistentAnnotations(Set<String> allClasses, List<Annotation
339339
!allClasses.contains(ann.annotationType.substring(1, ann.annotationType.length() - 1)));
340340
}
341341

342-
private ZipEntry createZipEntry(String name, long timestamp) {
342+
private ZipEntry createZipEntry(String name, long timeMillisSinceEpoch) {
343+
Instant time = Instant.ofEpochMilli(timeMillisSinceEpoch);
343344
ZipEntry ze = new ZipEntry(name);
344-
345-
ze.setTime(timestamp);
345+
ze.setTimeLocal(LocalDateTime.ofInstant(time, ZoneOffset.UTC));
346346
return ze;
347347
}
348348

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.time.Instant;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.Optional;
30+
31+
import javax.lang.model.SourceVersion;
32+
33+
import jdk.test.lib.process.OutputAnalyzer;
34+
import jdk.test.lib.process.ProcessTools;
35+
import org.junit.jupiter.api.BeforeAll;
36+
import org.junit.jupiter.api.Test;
37+
38+
/*
39+
* @test
40+
* @bug 8327466
41+
* @summary verifies that the ct.sym file created by build.tools.symbolgenerator.CreateSymbols
42+
* is reproducible
43+
* @library /test/lib
44+
* @modules java.compiler
45+
* jdk.compiler/com.sun.tools.javac.api
46+
* jdk.compiler/com.sun.tools.javac.jvm
47+
* jdk.compiler/com.sun.tools.javac.main
48+
* jdk.compiler/com.sun.tools.javac.util
49+
*
50+
* @compile ${test.root}/../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java
51+
*
52+
* @run junit CreateSymbolsReproducibleTest
53+
*/
54+
public class CreateSymbolsReproducibleTest {
55+
56+
// the fully qualified class name of the tool that we launch to generate the ct.sym file
57+
private static final String CREATE_SYMBOLS_CLASS_FQN = "build.tools.symbolgenerator.CreateSymbols";
58+
// a reproducible timestamp (in seconds) that we pass to "CreateSymbols build-ctsym" as input
59+
// when generating the ct.sym file
60+
private static final long SOURCE_EPOCH_DATE = Instant.now().getEpochSecond();
61+
// arbitrary set of packages that will be included in a include list file
62+
// that will be given as input to "CreateSymbols build-description-incremental" command
63+
// for generating the symbol text file
64+
private static final String INCLUDE_PKGS = """
65+
+java/io/
66+
+java/lang/
67+
+java/lang/annotation/
68+
+java/lang/instrument/
69+
+java/lang/invoke/
70+
""";
71+
72+
private static Path symTxtFile;
73+
74+
@BeforeAll
75+
static void beforeAll() throws Exception {
76+
symTxtFile = createSymTxtFile();
77+
System.out.println("created sym.txt file at " + symTxtFile);
78+
}
79+
80+
/*
81+
* Launches the "CreateSymbols build-ctsym" tool multiple times to generate ct.sym files.
82+
* Each time with the same inputs and the same timestamp. For each of these attempts, we use
83+
* a different timezone when launching the tool. The test verifies that irrespective of
84+
* what timezone gets used, the generated ct.sym files don't differ.
85+
*/
86+
@Test
87+
void testDifferentTimezone() throws Exception {
88+
final Path destDir = Files.createTempDirectory(Path.of("."), "").toAbsolutePath();
89+
final List<Path> ctSymFiles = new ArrayList<>();
90+
final List<Optional<String>> timezones = List.of(
91+
Optional.empty(), // no explicit timezone
92+
Optional.of("UTC"),
93+
Optional.of("America/Los_Angeles"),
94+
Optional.of("Asia/Tokyo")
95+
);
96+
int num = 0;
97+
// create several ct.sym files by launching the tool with different timezones
98+
// but the same timestamp value as input
99+
for (final Optional<String> timezone : timezones) {
100+
num++;
101+
final String destCtSymFileName = "ct-" + num + ".sym";
102+
final Path destCtSym = destDir.resolve(destCtSymFileName);
103+
System.out.println("using timezone " + timezone + " to create ct.sym file at "
104+
+ destCtSym);
105+
createCtSym(destCtSym, symTxtFile, timezone);
106+
ctSymFiles.add(destCtSym);
107+
}
108+
// verify that each of these generated ct.sym files are exactly the same in content
109+
for (int i = 0; i < ctSymFiles.size() - 1; i++) {
110+
final Path ctSym1 = ctSymFiles.get(i);
111+
final Path ctSym2 = ctSymFiles.get(i + 1);
112+
final long mismatchOffset = Files.mismatch(ctSym1, ctSym2);
113+
if (mismatchOffset != -1) {
114+
throw new AssertionError("contents of files " + ctSym1 + " and " + ctSym2
115+
+ " unexpectedly differ" + " (at " + mismatchOffset + " offset)");
116+
}
117+
}
118+
}
119+
120+
private static Path createSymTxtFile() throws Exception {
121+
final Path tmpDir = Files.createTempDirectory(Path.of("."), "").toAbsolutePath();
122+
final Path destSymTxtFile = tmpDir.resolve("sym.txt");
123+
Files.writeString(destSymTxtFile, "");
124+
final Path includeList = tmpDir.resolve("include.list");
125+
Files.writeString(includeList, INCLUDE_PKGS);
126+
final String[] cmd = new String[]{
127+
"--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
128+
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
129+
CREATE_SYMBOLS_CLASS_FQN,
130+
"build-description-incremental",
131+
destSymTxtFile.toString(),
132+
includeList.toString()
133+
};
134+
final OutputAnalyzer oa = ProcessTools.executeTestJava(cmd);
135+
oa.shouldHaveExitValue(0);
136+
// verify the file was created
137+
if (Files.notExists(destSymTxtFile)) {
138+
oa.reportDiagnosticSummary();
139+
throw new AssertionError(CREATE_SYMBOLS_CLASS_FQN
140+
+ " build-description-incremental failed to create " + destSymTxtFile);
141+
}
142+
return destSymTxtFile;
143+
}
144+
145+
private static void createCtSym(final Path destCtSymFile, final Path symTxtFile,
146+
final Optional<String> timezone) throws Exception {
147+
final Path modulesDir = Path.of(".").resolve("modules");
148+
Files.createDirectories(modulesDir);
149+
final Path modulesList = Path.of(".").resolve("modules-list");
150+
// an empty file
151+
Files.writeString(modulesList, "");
152+
153+
final List<String> cmd = new ArrayList<>();
154+
timezone.ifPresent((tz) -> {
155+
// launch the tool with a specific timezone (if any)
156+
cmd.add("-Duser.timezone=" + tz);
157+
});
158+
cmd.add(CREATE_SYMBOLS_CLASS_FQN);
159+
cmd.add("build-ctsym"); // command to CreateSymbols tool
160+
cmd.add("non-existent-ct-desc-file");
161+
cmd.add(symTxtFile.toString()); // a previously generated a sym.txt file
162+
cmd.add(destCtSymFile.toString()); // target ct.sym file to generate
163+
cmd.add(Long.toString(SOURCE_EPOCH_DATE)); // reproducible timestamp (in seconds)
164+
cmd.add(Integer.toString(SourceVersion.latest().ordinal()));
165+
cmd.add("does-not-matter-pre-release-tag");
166+
cmd.add(modulesDir.toString());
167+
cmd.add(modulesList.toString());
168+
final OutputAnalyzer oa = ProcessTools.executeTestJava(cmd);
169+
oa.shouldHaveExitValue(0);
170+
// verify the ct.sym file was generated
171+
if (Files.notExists(destCtSymFile)) {
172+
oa.reportDiagnosticSummary();
173+
throw new AssertionError("ct.sym file missing at " + destCtSymFile);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)