166 * that is specified should use literal value "<none>", to have all the APIs of the platform written to
167 * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
168 * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
169 * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
170 * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
171 * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
172 *
173 * To generate the .sym.txt files for OpenJDK 7 and 8:
174 * <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
175 * <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
176 * java build.tools.symbolgenerator.CreateSymbols build-description src/jdk.compiler/share/data/symbols
177 * $TOPDIR src/jdk.compiler/share/data/symbols/include.list
178 * 8 OpenJDK8.classes '<none>'
179 * 7 OpenJDK7.classes 8
180 *
181 * Note: the versions are expected to be a single character.
182 *
183 */
184 public class CreateSymbols {
185
186 //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
187 /**Create sig files for ct.sym reading the classes description from the directory that contains
188 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
189 */
190 public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation,
191 long timestamp, String currentVersion, String preReleaseTag, String moduleClasses,
192 String includedModulesFile) throws IOException {
193 LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
194 : null,
195 Paths.get(ctDescriptionFile));
196
197 int currentVersionParsed = Integer.parseInt(currentVersion);
198
199 currentVersion = Integer.toString(currentVersionParsed, Character.MAX_RADIX);
200 currentVersion = currentVersion.toUpperCase(Locale.ROOT);
201
202 String previousVersion = Integer.toString(currentVersionParsed - 1, Character.MAX_RADIX);
203
204 previousVersion = previousVersion.toUpperCase(Locale.ROOT);
205
206 //load current version classes:
207 Path moduleClassPath = Paths.get(moduleClasses);
208 Set<String> includedModules = Files.lines(Paths.get(includedModulesFile))
209 .flatMap(l -> Arrays.stream(l.split(" ")))
210 .collect(Collectors.toSet());
211
212 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
213 includedModules, currentVersion, previousVersion);
214
215 stripNonExistentAnnotations(data);
216 splitHeaders(data.classes);
217
218 Map<String, Map<Character, String>> package2Version2Module = new HashMap<>();
219 Map<String, Set<FileData>> directory2FileData = new TreeMap<>();
220
221 for (ModuleDescription md : data.modules.values()) {
222 for (ModuleHeaderDescription mhd : md.header) {
223 writeModulesForVersions(directory2FileData,
224 md,
225 mhd,
226 mhd.versions,
227 version -> {
228 String versionString = Character.toString(version);
229 int versionNumber = Integer.parseInt(versionString, Character.MAX_RADIX);
230 versionString = Integer.toString(versionNumber);
231 if (versionNumber == currentVersionParsed && !preReleaseTag.isEmpty()) {
232 versionString = versionString + "-" + preReleaseTag;
233 }
234 return versionString;
235 });
236 List<String> packages = new ArrayList<>();
237 mhd.exports.stream()
238 .map(ExportsDescription::packageName)
239 .forEach(packages::add);
240 if (mhd.extraModulePackages != null) {
241 packages.addAll(mhd.extraModulePackages);
242 }
243 packages.stream().forEach(pkg -> {
244 for (char v : mhd.versions.toCharArray()) {
245 package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name);
246 }
247 });
248 }
792 void writeModule(Map<String, Set<FileData>> directory2FileData,
793 ModuleDescription moduleDescription,
794 ModuleHeaderDescription header,
795 char version,
796 Function<Character, String> version2ModuleVersion) throws IOException {
797 var classFile = ClassFile.of().build(ClassDesc.of("module-info"), clb -> {
798 clb.withFlags(header.flags);
799 addAttributes(moduleDescription, header, clb, version2ModuleVersion.apply(version));
800 });
801
802 String versionString = Character.toString(version);
803 doWrite(directory2FileData, versionString, moduleDescription.name, "module-info" + EXTENSION, classFile);
804 }
805
806 void writeClass(Map<String, Set<FileData>> directory2FileData,
807 ClassDescription classDescription,
808 ClassHeaderDescription header,
809 String module,
810 String version) throws IOException {
811 var classFile = ClassFile.of().build(ClassDesc.ofInternalName(classDescription.name), clb -> {
812 if (header.extendsAttr != null)
813 clb.withSuperclass(ClassDesc.ofInternalName(header.extendsAttr));
814 clb.withInterfaceSymbols(header.implementsAttr.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList()))
815 .withFlags(header.flags);
816 for (FieldDescription fieldDesc : classDescription.fields) {
817 if (disjoint(fieldDesc.versions, version))
818 continue;
819 clb.withField(fieldDesc.name, ClassDesc.ofDescriptor(fieldDesc.descriptor), fb -> {
820 addAttributes(fieldDesc, fb);
821 fb.withFlags(fieldDesc.flags);
822 });
823 }
824 for (MethodDescription methDesc : classDescription.methods) {
825 if (disjoint(methDesc.versions, version))
826 continue;
827 clb.withMethod(methDesc.name, MethodTypeDesc.ofDescriptor(methDesc.descriptor), methDesc.flags, mb -> addAttributes(methDesc, mb));
828 }
829 addAttributes(header, clb);
830 });
831 doWrite(directory2FileData, version, module, classDescription.name + EXTENSION, classFile);
1288 return module.exports.stream().filter(ed -> ed.packageName().equals(pack)).findAny().isPresent() ||
1289 module.extraModulePackages.contains(pack);
1290 }
1291
1292 private void loadVersionClassesFromDirectory(ClassList classes,
1293 Map<String, ModuleDescription> modules,
1294 Path modulesDirectory,
1295 Set<String> includedModules,
1296 String version,
1297 String baseline) {
1298 Map<String, ModuleDescription> currentVersionModules =
1299 new HashMap<>();
1300 ClassList currentVersionClasses = new ClassList();
1301 Set<String> privateIncludes = new HashSet<>();
1302 Set<String> includes = new HashSet<>();
1303 ExcludeIncludeList currentEIList = new ExcludeIncludeList(includes,
1304 privateIncludes,
1305 Collections.emptySet());
1306
1307 try {
1308 Map<Path, ModuleHeaderDescription> modulePath2Header = new HashMap<>();
1309 List<Path> pendingExportedDirectories = new ArrayList<>();
1310
1311 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulesDirectory)) {
1312 for (Path p : ds) {
1313 if (!includedModules.contains(p.getFileName().toString())) {
1314 continue;
1315 }
1316
1317 Path moduleInfo = p.resolve("module-info.class");
1318
1319 if (Files.isReadable(moduleInfo)) {
1320 ModuleDescription md = inspectModuleInfoClassFile(Files.readAllBytes(moduleInfo),
1321 currentVersionModules, version);
1322 if (md == null) {
1323 continue;
1324 }
1325
1326 modulePath2Header.put(p, md.header.get(0));
1327
1328 Set<String> currentModuleExports =
1329 md.header.get(0).exports.stream()
1330 .filter(e -> !e.isQualified())
1331 .map(e -> e.packageName + '/')
1332 .collect(Collectors.toSet());
1333
1334 for (String dir : currentModuleExports) {
1335 includes.add(dir);
1336 pendingExportedDirectories.add(p.resolve(dir));
1337 }
1338 } else {
1339 throw new IllegalArgumentException("Included module: " +
1340 p.getFileName() +
1341 " does not have a module-info.class");
1342 }
1343 }
1344 }
1345
1346 List<String> pendingExtraClasses = new ArrayList<>();
1347
1348 for (Path exported : pendingExportedDirectories) {
1349 try (DirectoryStream<Path> ds = Files.newDirectoryStream(exported)) {
1350 for (Path p2 : ds) {
1351 if (!Files.isRegularFile(p2) || !p2.getFileName().toString().endsWith(".class")) {
1352 continue;
1353 }
1354
1355 loadFromDirectoryHandleClassFile(p2, currentVersionClasses,
1356 currentEIList, version,
1357 pendingExtraClasses);
1358 }
1359 }
1360 }
1361
1362 while (!pendingExtraClasses.isEmpty()) {
1363 String current = pendingExtraClasses.remove(pendingExtraClasses.size() - 1);
1364
1365 if (currentVersionClasses.find(current, true) != null) {
1366 continue;
1367 }
1368
1369 for (Entry<Path, ModuleHeaderDescription> e : modulePath2Header.entrySet()) {
1370 Path currentPath = e.getKey().resolve(current + ".class");
1371
1372 if (Files.isReadable(currentPath)) {
1373 String pack = current.substring(0, current.lastIndexOf('/'));
1374
1375 e.getValue().extraModulePackages.add(pack);
1376
1377 loadFromDirectoryHandleClassFile(currentPath, currentVersionClasses,
1378 currentEIList, version,
1379 pendingExtraClasses);
1380 }
1381 }
1382 }
1383 } catch (IOException ex) {
1384 throw new IllegalArgumentException(ex);
1385 }
1386
1387 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1388 }
1389
1390 private void loadFromDirectoryHandleClassFile(Path path, ClassList currentVersionClasses,
1391 ExcludeIncludeList currentEIList, String version,
1392 List<String> todo) throws IOException {
1393 try (InputStream in = Files.newInputStream(path)) {
1394 inspectClassFile(in, currentVersionClasses,
1395 currentEIList, version,
1396 cf -> {
1397 Set<String> superTypes = otherRelevantTypesWithOwners(cf);
1398
1399 currentEIList.privateIncludeList.addAll(superTypes);
1400 todo.addAll(superTypes);
1401 });
1402 }
1403 }
1404
1405 private void finishClassLoading(ClassList classes, Map<String, ModuleDescription> modules, Map<String, ModuleDescription> currentVersionModules, ClassList currentVersionClasses, ExcludeIncludeList currentEIList, String version,
1406 String baseline) {
1407 ModuleDescription unsupported =
1408 currentVersionModules.get("jdk.unsupported");
1409
1410 if (unsupported != null) {
1411 for (ClassDescription cd : currentVersionClasses.classes) {
1412 if (unsupported.header
1413 .get(0)
1414 .exports
1415 .stream()
1416 .map(ed -> ed.packageName)
1417 .anyMatch(pack -> pack.equals(cd.packge().replace('.', '/')))) {
1418 ClassHeaderDescription ch = cd.header.get(0);
1419 if (ch.classAnnotations == null) {
1420 ch.classAnnotations = new ArrayList<>();
1421 }
1422 AnnotationDescription ad;
1423 ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1424 Collections.emptyMap());
1913 inspectClassFile(in, classes, excludesIncludes, version, cf -> {});
1914 }
1915
1916 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version,
1917 Consumer<ClassModel> extraTask) throws IOException {
1918 ClassModel cm = ClassFile.of().parse(in.readAllBytes());
1919
1920 if (cm.isModuleInfo()) {
1921 return ;
1922 }
1923
1924 if (!excludesIncludes.accepts(cm.thisClass().asInternalName(), true)) {
1925 return ;
1926 }
1927
1928 extraTask.accept(cm);
1929
1930 ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1931
1932 headerDesc.flags = cm.flags().flagsMask();
1933
1934 if (cm.superclass().isPresent()) {
1935 headerDesc.extendsAttr = cm.superclass().get().asInternalName();
1936 }
1937 headerDesc.implementsAttr = cm.interfaces().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
1938 for (var attr : cm.attributes()) {
1939 if (!readAttribute(headerDesc, attr))
1940 return ;
1941 }
1942
1943 ClassDescription clazzDesc = null;
1944
1945 for (ClassDescription cd : classes) {
1946 if (cd.name.equals(cm.thisClass().asInternalName())) {
1947 clazzDesc = cd;
1948 break;
1949 }
1950 }
1951
1952 if (clazzDesc == null) {
1979 for (var attr : f.attributes()) {
1980 readAttribute(fieldDesc, attr);
1981 }
1982 addField(clazzDesc, fieldDesc, version, null);
1983 }
1984 }
1985
1986 private ModuleDescription inspectModuleInfoClassFile(byte[] data,
1987 Map<String, ModuleDescription> modules,
1988 String version) {
1989 ClassModel cm = ClassFile.of().parse(data);
1990
1991 if (!cm.flags().has(AccessFlag.MODULE)) {
1992 return null;
1993 }
1994
1995 ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
1996
1997 headerDesc.versions = version;
1998 headerDesc.flags = cm.flags().flagsMask();
1999
2000 for (var attr : cm.attributes()) {
2001 if (!readAttribute(headerDesc, attr))
2002 return null;
2003 }
2004
2005 String name = headerDesc.name;
2006
2007 ModuleDescription moduleDesc = modules.get(name);
2008
2009 if (moduleDesc == null) {
2010 moduleDesc = new ModuleDescription();
2011 moduleDesc.name = name;
2012 modules.put(moduleDesc.name, moduleDesc);
2013 }
2014
2015 addModuleHeader(moduleDesc, headerDesc, version);
2016
2017 return moduleDesc;
2018 }
2246 var rcd = new RecordComponentDescription();
2247 rcd.name = rci.name().stringValue();
2248 rcd.descriptor = rci.descriptor().stringValue();
2249 rci.attributes().forEach(child -> readAttribute(rcd, child));
2250 return rcd;
2251 }).collect(Collectors.toList());
2252 }
2253 case MethodParametersAttribute a -> ((MethodDescription) feature).methodParameters = a.parameters().stream()
2254 .map(mpi -> new MethodDescription.MethodParam(mpi.flagsMask(), mpi.name().map(Utf8Entry::stringValue).orElse(null)))
2255 .collect(Collectors.toList());
2256 case PermittedSubclassesAttribute a -> {
2257 var chd = (ClassHeaderDescription) feature;
2258 chd.isSealed = true;
2259 chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2260 }
2261 case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName();
2262 case RuntimeInvisibleTypeAnnotationsAttribute a ->
2263 feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2264 case RuntimeVisibleTypeAnnotationsAttribute a ->
2265 feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2266 default -> throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName()); // Do nothing
2267 }
2268
2269 return true;
2270 }
2271
2272 public static String INJECTED_VERSION = null;
2273
2274 private static String getVersion(Optional<Utf8Entry> version) {
2275 if (INJECTED_VERSION != null) {
2276 return INJECTED_VERSION;
2277 }
2278 return version.map(Utf8Entry::stringValue).orElse(null);
2279 }
2280
2281 Object convertConstantValue(ConstantValueEntry info, String descriptor) {
2282 if (descriptor.length() == 1 && info instanceof IntegerEntry ie) {
2283 var i = ie.intValue();
2284 return switch (descriptor.charAt(0)) {
2285 case 'I', 'B', 'S' -> i;
2286 case 'C' -> (char) i;
3291 if (recordComponents != null) {
3292 for (RecordComponentDescription rcd : recordComponents) {
3293 rcd.write(output, "", "");
3294 }
3295 }
3296 }
3297
3298 protected void readRecordComponents(LineBasedReader reader) throws IOException {
3299 recordComponents = new ArrayList<>();
3300
3301 while ("recordcomponent".equals(reader.lineKey)) {
3302 RecordComponentDescription rcd = new RecordComponentDescription();
3303 rcd.read(reader);
3304 recordComponents.add(rcd);
3305 }
3306 }
3307 }
3308
3309 static abstract class HeaderDescription extends FeatureDescription {
3310 List<InnerClassInfo> innerClasses;
3311
3312 @Override
3313 public int hashCode() {
3314 int hash = super.hashCode();
3315 hash = 19 * hash + Objects.hashCode(this.innerClasses);
3316 return hash;
3317 }
3318
3319 @Override
3320 public boolean equals(Object obj) {
3321 if (obj == null) {
3322 return false;
3323 }
3324 if (!super.equals(obj)) {
3325 return false;
3326 }
3327 final HeaderDescription other = (HeaderDescription) obj;
3328 if (!listEquals(this.innerClasses, other.innerClasses)) {
3329 return false;
3330 }
3331 return true;
3332 }
3333
3334 protected void writeInnerClasses(Appendable output,
3335 String baselineVersion,
3336 String version) throws IOException {
3337 if (innerClasses != null && !innerClasses.isEmpty()) {
3338 for (InnerClassInfo ici : innerClasses) {
3339 output.append("innerclass");
3340 output.append(" innerClass " + ici.innerClass);
3341 output.append(" outerClass " + ici.outerClass);
3342 output.append(" innerClassName " + ici.innerClassName);
3343 output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3344 output.append("\n");
3345 }
3346 }
3347 }
3348
3349 protected void readInnerClasses(LineBasedReader reader) throws IOException {
3350 innerClasses = new ArrayList<>();
3351
3352 while ("innerclass".equals(reader.lineKey)) {
3353 InnerClassInfo info = new InnerClassInfo();
3354
3355 info.innerClass = reader.attributes.get("innerClass");
3356 info.outerClass = reader.attributes.get("outerClass");
3357 info.innerClassName = reader.attributes.get("innerClassName");
3358
3359 String inFlags = reader.attributes.get("flags");
3360 if (inFlags != null && !inFlags.isEmpty())
3361 info.innerClassFlags = Integer.parseInt(inFlags, 16);
3362
3363 innerClasses.add(info);
3364
3365 reader.moveNext();
3366 }
3367 }
3368
3369 }
3370
3371 static class MethodDescription extends FeatureDescription {
3372 static int METHODS_FLAGS_NORMALIZATION = ~0;
3373 String name;
3374 String descriptor;
3375 List<String> thrownTypes;
3376 Object annotationDefaultValue;
3377 List<List<AnnotationDescription>> classParameterAnnotations;
3378 List<List<AnnotationDescription>> runtimeParameterAnnotations;
3379 List<MethodParam> methodParameters;
3380
3381 public MethodDescription() {
3382 flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3383 }
3384
3385 @Override
3386 public int hashCode() {
3387 int hash = super.hashCode();
3388 hash = 59 * hash + Objects.hashCode(this.name);
|
166 * that is specified should use literal value "<none>", to have all the APIs of the platform written to
167 * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
168 * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
169 * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
170 * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
171 * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
172 *
173 * To generate the .sym.txt files for OpenJDK 7 and 8:
174 * <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
175 * <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
176 * java build.tools.symbolgenerator.CreateSymbols build-description src/jdk.compiler/share/data/symbols
177 * $TOPDIR src/jdk.compiler/share/data/symbols/include.list
178 * 8 OpenJDK8.classes '<none>'
179 * 7 OpenJDK7.classes 8
180 *
181 * Note: the versions are expected to be a single character.
182 *
183 */
184 public class CreateSymbols {
185
186 /**
187 * <p>Support for a "preview version" of classfiles when running with preview
188 * mode. This is modeled as a new version (@) and since preview mode is only
189 * supported for the current version, a single identifier token is sufficient.
190 *
191 * <p>For example, inside ct.sym, 27 will be modeled as 'R', and the preview
192 * for 27 will be '@'. Classfiles unchanged between 27 and 27-preview will
193 * not be duplicated (in the same way classfiles that are common between 26
194 * and 27 are shared).
195 */
196 private static final String PREVIEW_VERSION = "@";
197
198 //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
199 /**Create sig files for ct.sym reading the classes description from the directory that contains
200 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
201 */
202 public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation,
203 long timestamp, String currentVersion, String preReleaseTag, String moduleClasses,
204 String includedModulesFile) throws IOException {
205 LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
206 : null,
207 Paths.get(ctDescriptionFile));
208
209 int currentVersionParsed = Integer.parseInt(currentVersion);
210
211 currentVersion = Integer.toString(currentVersionParsed, Character.MAX_RADIX);
212 currentVersion = currentVersion.toUpperCase(Locale.ROOT);
213
214 String previousVersion = Integer.toString(currentVersionParsed - 1, Character.MAX_RADIX);
215
216 previousVersion = previousVersion.toUpperCase(Locale.ROOT);
217
218 //load current version classes:
219 Path moduleClassPath = Paths.get(moduleClasses);
220 Set<String> includedModules = Files.lines(Paths.get(includedModulesFile))
221 .flatMap(l -> Arrays.stream(l.split(" ")))
222 .collect(Collectors.toSet());
223
224 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
225 includedModules, currentVersion, previousVersion);
226
227 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
228 includedModules, PREVIEW_VERSION, currentVersion);
229
230 stripNonExistentAnnotations(data);
231 splitHeaders(data.classes);
232
233 Map<String, Map<Character, String>> package2Version2Module = new HashMap<>();
234 Map<String, Set<FileData>> directory2FileData = new TreeMap<>();
235
236 String currentVersionFin = currentVersion;
237
238 for (ModuleDescription md : data.modules.values()) {
239 for (ModuleHeaderDescription mhd : md.header) {
240 writeModulesForVersions(directory2FileData,
241 md,
242 mhd,
243 mhd.versions,
244 version -> {
245 String versionString = Character.toString(version);
246 if (PREVIEW_VERSION.equals(versionString)) {
247 versionString = currentVersionFin;
248 }
249 int versionNumber = Integer.parseInt(versionString, Character.MAX_RADIX);
250 versionString = Integer.toString(versionNumber);
251 if (versionNumber == currentVersionParsed && !preReleaseTag.isEmpty()) {
252 versionString = versionString + "-" + preReleaseTag;
253 }
254 return versionString;
255 });
256 List<String> packages = new ArrayList<>();
257 mhd.exports.stream()
258 .map(ExportsDescription::packageName)
259 .forEach(packages::add);
260 if (mhd.extraModulePackages != null) {
261 packages.addAll(mhd.extraModulePackages);
262 }
263 packages.stream().forEach(pkg -> {
264 for (char v : mhd.versions.toCharArray()) {
265 package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name);
266 }
267 });
268 }
812 void writeModule(Map<String, Set<FileData>> directory2FileData,
813 ModuleDescription moduleDescription,
814 ModuleHeaderDescription header,
815 char version,
816 Function<Character, String> version2ModuleVersion) throws IOException {
817 var classFile = ClassFile.of().build(ClassDesc.of("module-info"), clb -> {
818 clb.withFlags(header.flags);
819 addAttributes(moduleDescription, header, clb, version2ModuleVersion.apply(version));
820 });
821
822 String versionString = Character.toString(version);
823 doWrite(directory2FileData, versionString, moduleDescription.name, "module-info" + EXTENSION, classFile);
824 }
825
826 void writeClass(Map<String, Set<FileData>> directory2FileData,
827 ClassDescription classDescription,
828 ClassHeaderDescription header,
829 String module,
830 String version) throws IOException {
831 var classFile = ClassFile.of().build(ClassDesc.ofInternalName(classDescription.name), clb -> {
832 if (header.preview) {
833 clb.withVersion(ClassFile.latestMajorVersion(), ClassFile.PREVIEW_MINOR_VERSION);
834 }
835 if (header.extendsAttr != null)
836 clb.withSuperclass(ClassDesc.ofInternalName(header.extendsAttr));
837 clb.withInterfaceSymbols(header.implementsAttr.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList()))
838 .withFlags(header.flags);
839 for (FieldDescription fieldDesc : classDescription.fields) {
840 if (disjoint(fieldDesc.versions, version))
841 continue;
842 clb.withField(fieldDesc.name, ClassDesc.ofDescriptor(fieldDesc.descriptor), fb -> {
843 addAttributes(fieldDesc, fb);
844 fb.withFlags(fieldDesc.flags);
845 });
846 }
847 for (MethodDescription methDesc : classDescription.methods) {
848 if (disjoint(methDesc.versions, version))
849 continue;
850 clb.withMethod(methDesc.name, MethodTypeDesc.ofDescriptor(methDesc.descriptor), methDesc.flags, mb -> addAttributes(methDesc, mb));
851 }
852 addAttributes(header, clb);
853 });
854 doWrite(directory2FileData, version, module, classDescription.name + EXTENSION, classFile);
1311 return module.exports.stream().filter(ed -> ed.packageName().equals(pack)).findAny().isPresent() ||
1312 module.extraModulePackages.contains(pack);
1313 }
1314
1315 private void loadVersionClassesFromDirectory(ClassList classes,
1316 Map<String, ModuleDescription> modules,
1317 Path modulesDirectory,
1318 Set<String> includedModules,
1319 String version,
1320 String baseline) {
1321 Map<String, ModuleDescription> currentVersionModules =
1322 new HashMap<>();
1323 ClassList currentVersionClasses = new ClassList();
1324 Set<String> privateIncludes = new HashSet<>();
1325 Set<String> includes = new HashSet<>();
1326 ExcludeIncludeList currentEIList = new ExcludeIncludeList(includes,
1327 privateIncludes,
1328 Collections.emptySet());
1329
1330 try {
1331 record ExportedDir(Path modulePath, Path exportedDir) {}
1332 Map<Path, ModuleHeaderDescription> modulePath2Header = new HashMap<>();
1333 List<ExportedDir> pendingExportedDirectories = new ArrayList<>();
1334
1335 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulesDirectory)) {
1336 for (Path p : ds) {
1337 if (!includedModules.contains(p.getFileName().toString())) {
1338 continue;
1339 }
1340
1341 Path moduleInfo = resolvePossiblyPreviewClassFile(version, p, p.resolve("module-info.class"));
1342
1343 if (Files.isReadable(moduleInfo)) {
1344 ModuleDescription md = inspectModuleInfoClassFile(Files.readAllBytes(moduleInfo),
1345 currentVersionModules, version);
1346 if (md == null) {
1347 continue;
1348 }
1349
1350 modulePath2Header.put(p, md.header.get(0));
1351
1352 Set<String> currentModuleExports =
1353 md.header.get(0).exports.stream()
1354 .filter(e -> !e.isQualified())
1355 .map(e -> e.packageName + '/')
1356 .collect(Collectors.toSet());
1357
1358 for (String dir : currentModuleExports) {
1359 includes.add(dir);
1360 pendingExportedDirectories.add(new ExportedDir(p, p.resolve(dir)));
1361 }
1362 } else {
1363 throw new IllegalArgumentException("Included module: " +
1364 p.getFileName() +
1365 " does not have a module-info.class");
1366 }
1367 }
1368 }
1369
1370 List<String> pendingExtraClasses = new ArrayList<>();
1371
1372 for (ExportedDir exported : pendingExportedDirectories) {
1373 try (DirectoryStream<Path> ds = Files.newDirectoryStream(exported.exportedDir())) {
1374 for (Path p2 : ds) {
1375 if (!Files.isRegularFile(p2) || !p2.getFileName().toString().endsWith(".class")) {
1376 continue;
1377 }
1378
1379 p2 = resolvePossiblyPreviewClassFile(version, exported.modulePath(), p2);
1380
1381 loadFromDirectoryHandleClassFile(p2, currentVersionClasses,
1382 currentEIList, version,
1383 pendingExtraClasses);
1384 }
1385 }
1386 }
1387
1388 while (!pendingExtraClasses.isEmpty()) {
1389 String current = pendingExtraClasses.remove(pendingExtraClasses.size() - 1);
1390
1391 if (currentVersionClasses.find(current, true) != null) {
1392 continue;
1393 }
1394
1395 for (Entry<Path, ModuleHeaderDescription> e : modulePath2Header.entrySet()) {
1396 Path currentPath = e.getKey().resolve(current + ".class");
1397
1398 if (Files.isReadable(currentPath)) {
1399 currentPath = resolvePossiblyPreviewClassFile(version, e.getKey(), currentPath);
1400 String pack = current.substring(0, current.lastIndexOf('/'));
1401
1402 e.getValue().extraModulePackages.add(pack);
1403
1404 loadFromDirectoryHandleClassFile(currentPath, currentVersionClasses,
1405 currentEIList, version,
1406 pendingExtraClasses);
1407 }
1408 }
1409 }
1410 } catch (IOException ex) {
1411 throw new IllegalArgumentException(ex);
1412 }
1413
1414 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1415 }
1416
1417 private void loadFromDirectoryHandleClassFile(Path path, ClassList currentVersionClasses,
1418 ExcludeIncludeList currentEIList, String version,
1419 List<String> todo) throws IOException {
1420 try (InputStream in = Files.newInputStream(path)) {
1421 inspectClassFile(in, currentVersionClasses,
1422 currentEIList, version,
1423 cf -> {
1424 Set<String> superTypes = otherRelevantTypesWithOwners(cf);
1425
1426 currentEIList.privateIncludeList.addAll(superTypes);
1427 todo.addAll(superTypes);
1428 });
1429 }
1430 }
1431
1432 private Path resolvePossiblyPreviewClassFile(String version, Path moduleClassDir, Path classfile) {
1433 if (!PREVIEW_VERSION.equals(version)) {
1434 return classfile;
1435 }
1436
1437 Path relativePath = moduleClassDir.relativize(classfile);
1438 Path previewCandidate = moduleClassDir.resolve("META-INF").resolve("preview").resolve(relativePath);
1439
1440 if (Files.exists(previewCandidate)) {
1441 return previewCandidate;
1442 }
1443
1444 return classfile;
1445 }
1446
1447 private void finishClassLoading(ClassList classes, Map<String, ModuleDescription> modules, Map<String, ModuleDescription> currentVersionModules, ClassList currentVersionClasses, ExcludeIncludeList currentEIList, String version,
1448 String baseline) {
1449 ModuleDescription unsupported =
1450 currentVersionModules.get("jdk.unsupported");
1451
1452 if (unsupported != null) {
1453 for (ClassDescription cd : currentVersionClasses.classes) {
1454 if (unsupported.header
1455 .get(0)
1456 .exports
1457 .stream()
1458 .map(ed -> ed.packageName)
1459 .anyMatch(pack -> pack.equals(cd.packge().replace('.', '/')))) {
1460 ClassHeaderDescription ch = cd.header.get(0);
1461 if (ch.classAnnotations == null) {
1462 ch.classAnnotations = new ArrayList<>();
1463 }
1464 AnnotationDescription ad;
1465 ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1466 Collections.emptyMap());
1955 inspectClassFile(in, classes, excludesIncludes, version, cf -> {});
1956 }
1957
1958 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version,
1959 Consumer<ClassModel> extraTask) throws IOException {
1960 ClassModel cm = ClassFile.of().parse(in.readAllBytes());
1961
1962 if (cm.isModuleInfo()) {
1963 return ;
1964 }
1965
1966 if (!excludesIncludes.accepts(cm.thisClass().asInternalName(), true)) {
1967 return ;
1968 }
1969
1970 extraTask.accept(cm);
1971
1972 ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1973
1974 headerDesc.flags = cm.flags().flagsMask();
1975 headerDesc.preview = cm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION;
1976
1977 if (cm.superclass().isPresent()) {
1978 headerDesc.extendsAttr = cm.superclass().get().asInternalName();
1979 }
1980 headerDesc.implementsAttr = cm.interfaces().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
1981 for (var attr : cm.attributes()) {
1982 if (!readAttribute(headerDesc, attr))
1983 return ;
1984 }
1985
1986 ClassDescription clazzDesc = null;
1987
1988 for (ClassDescription cd : classes) {
1989 if (cd.name.equals(cm.thisClass().asInternalName())) {
1990 clazzDesc = cd;
1991 break;
1992 }
1993 }
1994
1995 if (clazzDesc == null) {
2022 for (var attr : f.attributes()) {
2023 readAttribute(fieldDesc, attr);
2024 }
2025 addField(clazzDesc, fieldDesc, version, null);
2026 }
2027 }
2028
2029 private ModuleDescription inspectModuleInfoClassFile(byte[] data,
2030 Map<String, ModuleDescription> modules,
2031 String version) {
2032 ClassModel cm = ClassFile.of().parse(data);
2033
2034 if (!cm.flags().has(AccessFlag.MODULE)) {
2035 return null;
2036 }
2037
2038 ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
2039
2040 headerDesc.versions = version;
2041 headerDesc.flags = cm.flags().flagsMask();
2042 headerDesc.preview = cm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION;
2043
2044 for (var attr : cm.attributes()) {
2045 if (!readAttribute(headerDesc, attr))
2046 return null;
2047 }
2048
2049 String name = headerDesc.name;
2050
2051 ModuleDescription moduleDesc = modules.get(name);
2052
2053 if (moduleDesc == null) {
2054 moduleDesc = new ModuleDescription();
2055 moduleDesc.name = name;
2056 modules.put(moduleDesc.name, moduleDesc);
2057 }
2058
2059 addModuleHeader(moduleDesc, headerDesc, version);
2060
2061 return moduleDesc;
2062 }
2290 var rcd = new RecordComponentDescription();
2291 rcd.name = rci.name().stringValue();
2292 rcd.descriptor = rci.descriptor().stringValue();
2293 rci.attributes().forEach(child -> readAttribute(rcd, child));
2294 return rcd;
2295 }).collect(Collectors.toList());
2296 }
2297 case MethodParametersAttribute a -> ((MethodDescription) feature).methodParameters = a.parameters().stream()
2298 .map(mpi -> new MethodDescription.MethodParam(mpi.flagsMask(), mpi.name().map(Utf8Entry::stringValue).orElse(null)))
2299 .collect(Collectors.toList());
2300 case PermittedSubclassesAttribute a -> {
2301 var chd = (ClassHeaderDescription) feature;
2302 chd.isSealed = true;
2303 chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2304 }
2305 case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName();
2306 case RuntimeInvisibleTypeAnnotationsAttribute a ->
2307 feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2308 case RuntimeVisibleTypeAnnotationsAttribute a ->
2309 feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2310 default -> {
2311 if (attr.attributeName().equalsString("LoadableDescriptors")) {
2312 //OK, do nothing
2313 } else {
2314 throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName());
2315 }
2316 }
2317 }
2318
2319 return true;
2320 }
2321
2322 public static String INJECTED_VERSION = null;
2323
2324 private static String getVersion(Optional<Utf8Entry> version) {
2325 if (INJECTED_VERSION != null) {
2326 return INJECTED_VERSION;
2327 }
2328 return version.map(Utf8Entry::stringValue).orElse(null);
2329 }
2330
2331 Object convertConstantValue(ConstantValueEntry info, String descriptor) {
2332 if (descriptor.length() == 1 && info instanceof IntegerEntry ie) {
2333 var i = ie.intValue();
2334 return switch (descriptor.charAt(0)) {
2335 case 'I', 'B', 'S' -> i;
2336 case 'C' -> (char) i;
3341 if (recordComponents != null) {
3342 for (RecordComponentDescription rcd : recordComponents) {
3343 rcd.write(output, "", "");
3344 }
3345 }
3346 }
3347
3348 protected void readRecordComponents(LineBasedReader reader) throws IOException {
3349 recordComponents = new ArrayList<>();
3350
3351 while ("recordcomponent".equals(reader.lineKey)) {
3352 RecordComponentDescription rcd = new RecordComponentDescription();
3353 rcd.read(reader);
3354 recordComponents.add(rcd);
3355 }
3356 }
3357 }
3358
3359 static abstract class HeaderDescription extends FeatureDescription {
3360 List<InnerClassInfo> innerClasses;
3361 boolean preview;
3362
3363 @Override
3364 public int hashCode() {
3365 int hash = super.hashCode();
3366 hash = 19 * hash + Objects.hashCode(this.innerClasses);
3367 hash = 19 * hash + Objects.hashCode(this.preview);
3368 return hash;
3369 }
3370
3371 @Override
3372 public boolean equals(Object obj) {
3373 if (obj == null) {
3374 return false;
3375 }
3376 if (!super.equals(obj)) {
3377 return false;
3378 }
3379 final HeaderDescription other = (HeaderDescription) obj;
3380 if (!listEquals(this.innerClasses, other.innerClasses)) {
3381 return false;
3382 }
3383 if (this.preview != other.preview) {
3384 return false;
3385 }
3386 return true;
3387 }
3388
3389 protected void writeInnerClasses(Appendable output,
3390 String baselineVersion,
3391 String version) throws IOException {
3392 if (innerClasses != null && !innerClasses.isEmpty()) {
3393 for (InnerClassInfo ici : innerClasses) {
3394 output.append("innerclass");
3395 output.append(" innerClass " + ici.innerClass);
3396 output.append(" outerClass " + ici.outerClass);
3397 output.append(" innerClassName " + ici.innerClassName);
3398 output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3399 output.append("\n");
3400 }
3401 }
3402 }
3403
3404 protected void readInnerClasses(LineBasedReader reader) throws IOException {
3405 innerClasses = new ArrayList<>();
3406
3407 while ("innerclass".equals(reader.lineKey)) {
3408 InnerClassInfo info = new InnerClassInfo();
3409
3410 info.innerClass = reader.attributes.get("innerClass");
3411 info.outerClass = reader.attributes.get("outerClass");
3412 info.innerClassName = reader.attributes.get("innerClassName");
3413
3414 String inFlags = reader.attributes.get("flags");
3415 if (inFlags != null && !inFlags.isEmpty())
3416 info.innerClassFlags = Integer.parseInt(inFlags, 16);
3417
3418 innerClasses.add(info);
3419
3420 reader.moveNext();
3421 }
3422 }
3423
3424 @Override
3425 protected void writeAttributes(Appendable output) throws IOException {
3426 super.writeAttributes(output);
3427 if (preview) {
3428 output.append(" preview true");
3429 }
3430 }
3431
3432 @Override
3433 protected void readAttributes(LineBasedReader reader) {
3434 super.readAttributes(reader);
3435 String inPreview = reader.attributes.get("preview");
3436 if ("true".equals(inPreview)) {
3437 preview = true;
3438 }
3439 }
3440 }
3441
3442 static class MethodDescription extends FeatureDescription {
3443 static int METHODS_FLAGS_NORMALIZATION = ~0;
3444 String name;
3445 String descriptor;
3446 List<String> thrownTypes;
3447 Object annotationDefaultValue;
3448 List<List<AnnotationDescription>> classParameterAnnotations;
3449 List<List<AnnotationDescription>> runtimeParameterAnnotations;
3450 List<MethodParam> methodParameters;
3451
3452 public MethodDescription() {
3453 flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3454 }
3455
3456 @Override
3457 public int hashCode() {
3458 int hash = super.hashCode();
3459 hash = 59 * hash + Objects.hashCode(this.name);
|