8369856: AOT map does not include unregistered classes

Co-authored-by: Ashutosh Mehra <asmehra@openjdk.org>
Reviewed-by: kvn, matsaave
This commit is contained in:
Ioi Lam 2025-10-17 00:36:54 +00:00
parent 4d20f7696c
commit bd7315648f
4 changed files with 120 additions and 28 deletions

View File

@ -1420,6 +1420,10 @@ void SystemDictionaryShared::get_all_archived_classes(bool is_static_archive, Gr
get_archive(is_static_archive)->_builtin_dictionary.iterate([&] (const RunTimeClassInfo* record) {
classes->append(record->klass());
});
get_archive(is_static_archive)->_unregistered_dictionary.iterate([&] (const RunTimeClassInfo* record) {
classes->append(record->klass());
});
}
class SharedDictionaryPrinter : StackObj {

View File

@ -26,6 +26,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -33,7 +34,7 @@ import java.util.regex.Pattern;
This is a simple parser for parsing the output of
java -Xshare:dump -Xlog:aot+map=debug,aot+map+oops=trace:file=cds.map:none:filesize=0
java -Xshare:dump -Xlog:aot+map=debug,aot+map+oops=trace:file=aot.map:none:filesize=0
The map file contains patterns like this for the heap objects:
@ -59,8 +60,9 @@ more analysis on the HeapObjects.
*/
public class CDSMapReader {
public class AOTMapReader {
public static class MapFile {
HashSet<String> classes = new HashSet<>();
ArrayList<HeapObject> heapObjects = new ArrayList<>();
HashMap<Long, HeapObject> oopToObject = new HashMap<>();
HashMap<Long, HeapObject> narrowOopToObject = new HashMap<>();
@ -80,6 +82,20 @@ public class CDSMapReader {
public int heapObjectCount() {
return heapObjects.size();
}
void addClass(String className) {
classes.add(className);
}
public boolean hasClass(String className) {
return classes.contains(className);
}
public void shouldHaveClass(String className) {
if (!hasClass(className)) {
throw new RuntimeException("AOT map file is missing class " + className);
}
}
}
public static class HeapAddress {
@ -140,13 +156,17 @@ public class CDSMapReader {
this.name = name;
this.offset = Integer.parseInt(offset);
this.referentAddress = new HeapAddress(oopStr, narrowOopStr);
this.lineCount = CDSMapReader.lineCount;
this.lineCount = AOTMapReader.lineCount;
}
}
// 0x00000007ffc00000: 4a5b8701 00000063 00010290 00000000 00010100 fff80003
static Pattern rawDataPattern = Pattern.compile("^0x([0-9a-f]+): *( [0-9a-f]+)+ *$");
// -------------------------------------------------------------------------------
// Patterns for heap objects
// -------------------------------------------------------------------------------
// (one address)
// 0x00000007ffc00000: @@ Object java.lang.String
static Pattern objPattern1 = Pattern.compile("^0x([0-9a-f]+): @@ Object ([^ ]*)");
@ -179,6 +199,15 @@ public class CDSMapReader {
// - injected 'module_entry' 'J' @16 0 (0x0000000000000000)
static Pattern moduleEntryPattern = Pattern.compile("- injected 'module_entry' 'J' @[0-9]+[ ]+([0-9]+)");
// -------------------------------------------------------------------------------
// Patterns for metaspace objects
// -------------------------------------------------------------------------------
// 0x00000008000d1698: @@ Class 512 [Ljdk.internal.vm.FillerElement;
// 0x00000008000d18a0: @@ Class 520 java.lang.Cloneable
static Pattern classPattern = Pattern.compile("^0x([0-9a-f]+): @@ Class [ ]*([0-9]+) (.*)");
private static Matcher match(String line, Pattern pattern) {
Matcher m = pattern.matcher(line);
if (m.find()) {
@ -253,6 +282,11 @@ public class CDSMapReader {
}
}
private static void parseClassObject(String className, String addr, String size) throws IOException {
mapFile.addClass(className);
nextLine();
}
static MapFile mapFile;
static BufferedReader reader;
static String line = null; // current line being parsed
@ -277,6 +311,8 @@ public class CDSMapReader {
parseHeapObject(m.group(3), m.group(1), m.group(2));
} else if ((m = match(line, objPattern1)) != null) {
parseHeapObject(m.group(2), m.group(1), null);
} else if ((m = match(line, classPattern)) != null) {
parseClassObject(m.group(3), m.group(1), m.group(2)); // name, addr, size
} else {
nextLine();
}
@ -303,8 +339,15 @@ public class CDSMapReader {
}
}
public static void validate(MapFile mapFile, String classLoadLogFile) throws IOException {
validateOops(mapFile);
if (classLoadLogFile != null) {
validateClasses(mapFile, classLoadLogFile);
}
}
// Check that each oop fields in the HeapObjects must point to a valid HeapObject.
public static void validate(MapFile mapFile) {
static void validateOops(MapFile mapFile) {
int count1 = 0;
int count2 = 0;
for (HeapObject heapObject : mapFile.heapObjects) {
@ -333,10 +376,10 @@ public class CDSMapReader {
if (mapFile.heapObjectCount() > 0) {
// heapObjectCount() may be zero if the selected GC doesn't support heap object archiving.
if (mapFile.stringCount <= 0) {
throw new RuntimeException("CDS map file should contain at least one string");
throw new RuntimeException("AOT map file should contain at least one string");
}
if (count1 < mapFile.stringCount) {
throw new RuntimeException("CDS map file seems incorrect: " + mapFile.heapObjectCount() +
throw new RuntimeException("AOT map file seems incorrect: " + mapFile.heapObjectCount() +
" objects (" + mapFile.stringCount + " strings). Each string should" +
" have one non-null oop field but we found only " + count1 +
" non-null oop field references");
@ -344,8 +387,26 @@ public class CDSMapReader {
}
}
public static void main(String args[]) {
// classLoadLogFile should be generated with -Xlog:class+load:file=<classLoadLogFile>:none:filesize=0
// Check that every class loaded from "source: shared objects file" have an entry inside the mapFile.
static void validateClasses(MapFile mapFile, String classLoadLogFile) throws IOException {
try (BufferedReader r = new BufferedReader(new FileReader(classLoadLogFile))) {
String line;
String suffix = " source: shared objects file";
int suffixLen = suffix.length();
while ((line = r.readLine()) != null) {
if (line.endsWith(suffix)) {
String className = line.substring(0, line.length() - suffixLen);
if (!mapFile.hasClass(className)) {
throw new RuntimeException("AOT map file is missing class " + className);
}
}
}
}
}
public static void main(String args[]) throws IOException {
MapFile mapFile = read(args[0]);
validate(mapFile);
validate(mapFile, null);
}
}

View File

@ -27,7 +27,7 @@
* @summary Test the contents of -Xlog:aot+map
* @requires vm.cds
* @library /test/lib
* @run driver/timeout=240 CDSMapTest
* @run driver/timeout=240 AOTMapTest
*/
import jdk.test.lib.cds.CDSOptions;
@ -37,7 +37,7 @@ import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.util.ArrayList;
public class CDSMapTest {
public class AOTMapTest {
public static void main(String[] args) throws Exception {
doTest(false);
@ -79,8 +79,8 @@ public class CDSMapTest {
.addSuffix(args);
CDSTestUtils.createArchiveAndCheck(opts);
CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName);
CDSMapReader.validate(mapFile);
AOTMapReader.MapFile mapFile = AOTMapReader.read(mapName);
AOTMapReader.validate(mapFile, null);
return archiveName;
}
@ -98,7 +98,7 @@ public class CDSMapTest {
OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "exec");
out.shouldHaveExitValue(0);
CDSMapReader.MapFile mapFile = CDSMapReader.read(mapName);
CDSMapReader.validate(mapFile);
AOTMapReader.MapFile mapFile = AOTMapReader.read(mapName);
AOTMapReader.validate(mapFile, null);
}
}

View File

@ -26,9 +26,10 @@
* @bug 8362566
* @summary Test the contents of -Xlog:aot+map with AOT workflow
* @requires vm.cds.supports.aot.class.linking
* @library /test/lib /test/hotspot/jtreg/runtime/cds
* @build AOTMapTest
* @library /test/lib /test/hotspot/jtreg/runtime/cds /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build AOTMapTest Hello
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTMapTestApp
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar Hello
* @run driver/timeout=240 AOTMapTest AOT --two-step-training
*/
@ -37,15 +38,18 @@
* @bug 8362566
* @summary Test the contents of -Xlog:aot+map with dynamic CDS archive
* @requires vm.cds.supports.aot.class.linking
* @library /test/lib /test/hotspot/jtreg/runtime/cds
* @library /test/lib /test/hotspot/jtreg/runtime/cds /test/hotspot/jtreg/runtime/cds/appcds/test-classes
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @build AOTMapTest
* @build AOTMapTest Hello
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTMapTestApp
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar cust.jar Hello
* @run main/othervm/timeout=240 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. AOTMapTest DYNAMIC
*/
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.helpers.ClassFileInstaller;
@ -54,6 +58,7 @@ import jdk.test.lib.Platform;
public class AOTMapTest {
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
static final String mainClass = "AOTMapTestApp";
static final String classLoadLogFile = "production.class.load.log";
public static void main(String[] args) throws Exception {
doTest(args);
@ -63,13 +68,25 @@ public class AOTMapTest {
Tester tester = new Tester();
tester.run(args);
validate(tester.dumpMapFile);
validate(tester.runMapFile);
if (tester.isDynamicWorkflow()) {
// For dynamic workflow, the AOT map file doesn't include classes in the base archive, so
// AOTMapReader.validateClasses() will fail.
validate(tester.dumpMapFile, false);
} else {
validate(tester.dumpMapFile, true);
}
validate(tester.runMapFile, true);
}
static void validate(String mapFileName) {
CDSMapReader.MapFile mapFile = CDSMapReader.read(mapFileName);
CDSMapReader.validate(mapFile);
static void validate(String mapFileName, boolean checkClases) throws Exception {
AOTMapReader.MapFile mapFile = AOTMapReader.read(mapFileName);
if (checkClases) {
AOTMapReader.validate(mapFile, classLoadLogFile);
} else {
AOTMapReader.validate(mapFile, null);
}
mapFile.shouldHaveClass("AOTMapTestApp"); // built-in class
mapFile.shouldHaveClass("Hello"); // unregistered class
}
static class Tester extends CDSAppTester {
@ -97,12 +114,13 @@ public class AOTMapTest {
// filesize=0 ensures that a large map file not broken up in multiple files.
String logMapPrefix = "-Xlog:aot+map=debug,aot+map+oops=trace:file=";
String logMapSuffix = ":none:filesize=0";
String logSuffix = ":none:filesize=0";
if (runMode == RunMode.ASSEMBLY || runMode == RunMode.DUMP_DYNAMIC) {
vmArgs.add(logMapPrefix + dumpMapFile + logMapSuffix);
vmArgs.add(logMapPrefix + dumpMapFile + logSuffix);
} else if (runMode == RunMode.PRODUCTION) {
vmArgs.add(logMapPrefix + runMapFile + logMapSuffix);
vmArgs.add(logMapPrefix + runMapFile + logSuffix);
vmArgs.add("-Xlog:class+load:file=" + classLoadLogFile + logSuffix);
}
return vmArgs.toArray(new String[vmArgs.size()]);
@ -118,7 +136,16 @@ public class AOTMapTest {
}
class AOTMapTestApp {
public static void main(String[] args) {
public static void main(String[] args) throws Exception {
System.out.println("Hello AOTMapTestApp");
testCustomLoader();
}
static void testCustomLoader() throws Exception {
File custJar = new File("cust.jar");
URL[] urls = new URL[] {custJar.toURI().toURL()};
URLClassLoader loader = new URLClassLoader(urls, AOTMapTestApp.class.getClassLoader());
Class<?> c = loader.loadClass("Hello");
System.out.println(c);
}
}