From 83e2b7ddfd211d9a9d4755af786f81313d9ac072 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sat, 26 Sep 2026 09:20:13 +0200 Subject: [PATCH 1/2] Read module descriptors without ASM --- plexus-java/pom.xml | 5 - .../java/jpms/AsmModuleInfoParser.java | 137 -------- .../java/jpms/BinaryModuleInfoParser.java | 2 +- .../languages/java/jpms/LocationManager.java | 6 +- .../java/jpms/ModuleInfoClassParser.java | 302 ++++++++++++++++++ plexus-java/src/main/java9/module-info.java | 1 - ...st.java => ModuleInfoClassParserTest.java} | 49 ++- 7 files changed, 344 insertions(+), 158 deletions(-) delete mode 100644 plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java create mode 100644 plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java rename plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/{AsmModuleInfoParserTest.java => ModuleInfoClassParserTest.java} (56%) diff --git a/plexus-java/pom.xml b/plexus-java/pom.xml index 6ca4feb..c6b2e35 100644 --- a/plexus-java/pom.xml +++ b/plexus-java/pom.xml @@ -12,11 +12,6 @@ Plexus Languages :: Java - - org.ow2.asm - asm - 9.10.1 - com.github.javaparser javaparser-core diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java deleted file mode 100644 index d14b51d..0000000 --- a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParser.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.codehaus.plexus.languages.java.jpms; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ModuleVisitor; -import org.objectweb.asm.Opcodes; - -/** - * Extract information from module with ASM - * - * - * @author Robert Scholte - * @since 1.0.0 - */ -class AsmModuleInfoParser extends AbstractBinaryModuleInfoParser { - - // The highest class file major version the ASM release on the classpath knows how to read. - // Opcodes.Vxx values encode the minor version in the upper 16 bits, but for these constants - // that is always 0, so the constant itself is the plain major version number. - private static final int NEWEST_MAJOR_VERSION_SUPPORTED_BY_ASM = Opcodes.V27; - - @Override - JavaModuleDescriptor parse(InputStream in) throws IOException { - final JavaModuleDescriptorWrapper wrapper = new JavaModuleDescriptorWrapper(); - - byte[] classBytes = toByteArray(in); - - // A class file compiled by a newer JDK than this ASM release supports (e.g. class file - // major version 72 for JDK 28, while ASM 9.10.1 only understands up to 71/V27) makes - // ClassReader throw IllegalArgumentException before it even looks at the content. The - // module-info.class Module attribute format hasn't changed since Java 9, so it's safe to - // clamp the major version down to the newest one ASM supports before handing it the bytes. - int majorVersion = ((classBytes[6] & 0xFF) << 8) | (classBytes[7] & 0xFF); - if (majorVersion > NEWEST_MAJOR_VERSION_SUPPORTED_BY_ASM) { - classBytes[6] = (byte) (NEWEST_MAJOR_VERSION_SUPPORTED_BY_ASM >>> 8); - classBytes[7] = (byte) NEWEST_MAJOR_VERSION_SUPPORTED_BY_ASM; - } - - ClassReader reader = new ClassReader(classBytes); - reader.accept( - new ClassVisitor(Opcodes.ASM9) { - @Override - public ModuleVisitor visitModule(String name, int arg1, String arg2) { - wrapper.builder = JavaModuleDescriptor.newModule(name); - - return new ModuleVisitor(Opcodes.ASM9) { - @Override - public void visitRequire(String module, int access, String version) { - if ((access & (Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE)) != 0) { - Set modifiers = - new LinkedHashSet<>(); - if ((access & Opcodes.ACC_STATIC_PHASE) != 0) { - modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.STATIC); - } - if ((access & Opcodes.ACC_TRANSITIVE) != 0) { - modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.TRANSITIVE); - } - - wrapper.builder.requires(modifiers, module); - } else { - wrapper.builder.requires(module); - } - } - - @Override - public void visitExport(String pn, int ms, String... targets) { - if (targets == null || targets.length == 0) { - wrapper.builder.exports(pn.replace('/', '.')); - } else { - wrapper.builder.exports( - pn.replace('/', '.'), new HashSet<>(Arrays.asList(targets))); - } - } - - @Override - public void visitUse(String service) { - wrapper.builder.uses(service.replace('/', '.')); - } - - @Override - public void visitProvide(String service, String... providers) { - List renamedProvides = new ArrayList<>(providers.length); - for (String provider : providers) { - renamedProvides.add(provider.replace('/', '.')); - } - wrapper.builder.provides(service.replace('/', '.'), renamedProvides); - } - }; - } - }, - 0); - return wrapper.builder.build(); - } - - private static byte[] toByteArray(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[8192]; - int read; - while ((read = in.read(buffer)) != -1) { - out.write(buffer, 0, read); - } - return out.toByteArray(); - } - - private static class JavaModuleDescriptorWrapper { - private JavaModuleDescriptor.Builder builder; - } -} diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java index e6e67c5..c21d02e 100644 --- a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java +++ b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/BinaryModuleInfoParser.java @@ -18,4 +18,4 @@ * specific language governing permissions and limitations * under the License. */ -class BinaryModuleInfoParser extends AsmModuleInfoParser {} +class BinaryModuleInfoParser extends ModuleInfoClassParser {} diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java index 714df3d..c1869ad 100644 --- a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java +++ b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/LocationManager.java @@ -279,7 +279,9 @@ public ResolvePathsResult resolvePaths(final ResolvePathsRequest reque /** * If the jdkHome is specified, its version it considered higher than the runtime java version. - * In that case ASM must be used to read the module descriptor + * In that case the version-agnostic {@link ModuleInfoClassParser} must be used to read the module + * descriptor, since the runtime's own {@code java.lang.module.ModuleDescriptor} cannot be trusted + * to read a module-info.class produced by a newer JDK than the one it runs on. * * @param jdkHome * @return @@ -289,7 +291,7 @@ ModuleInfoParser getBinaryModuleInfoParser(final Path jdkHome) { if (jdkHome == null) { binaryParser = new BinaryModuleInfoParser(); } else { - binaryParser = new AsmModuleInfoParser(); + binaryParser = new ModuleInfoClassParser(); } return binaryParser; } diff --git a/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java new file mode 100644 index 0000000..f6f117d --- /dev/null +++ b/plexus-java/src/main/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParser.java @@ -0,0 +1,302 @@ +package org.codehaus.plexus.languages.java.jpms; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Reads the {@code Module} attribute (JVMS 4.7.25) directly out of a {@code module-info.class} file, + * without depending on a bytecode library or on the class-file support of the running JDK. + * + *

Only the class file's overall shape (constant pool, then fields, then methods, then attributes) + * needs to be understood to reach the {@code Module} attribute; every other section is skipped using + * its own declared length rather than parsed. The {@code Module} attribute format has been stable + * since it was introduced in Java 9, and this parser never looks at the class file's major/minor + * version, so it reads a {@code module-info.class} produced by any future JDK just as well as one + * from Java 9. + * + * @author Robert Scholte + * @since 1.7.0 + */ +class ModuleInfoClassParser extends AbstractBinaryModuleInfoParser { + + private static final int MAGIC = 0xCAFEBABE; + + // Constant pool tags, JVMS 4.4, Table 4.4-A. + private static final int CONSTANT_UTF8 = 1; + private static final int CONSTANT_INTEGER = 3; + private static final int CONSTANT_FLOAT = 4; + private static final int CONSTANT_LONG = 5; + private static final int CONSTANT_DOUBLE = 6; + private static final int CONSTANT_CLASS = 7; + private static final int CONSTANT_STRING = 8; + private static final int CONSTANT_FIELDREF = 9; + private static final int CONSTANT_METHODREF = 10; + private static final int CONSTANT_INTERFACE_METHODREF = 11; + private static final int CONSTANT_NAME_AND_TYPE = 12; + private static final int CONSTANT_METHOD_HANDLE = 15; + private static final int CONSTANT_METHOD_TYPE = 16; + private static final int CONSTANT_DYNAMIC = 17; + private static final int CONSTANT_INVOKE_DYNAMIC = 18; + private static final int CONSTANT_MODULE = 19; + private static final int CONSTANT_PACKAGE = 20; + + private static final String MODULE_ATTRIBUTE_NAME = "Module"; + + // Module_attribute requires_flags, JVMS 4.7.25. + private static final int ACC_TRANSITIVE = 0x0020; + private static final int ACC_STATIC_PHASE = 0x0040; + + @Override + JavaModuleDescriptor parse(InputStream in) throws IOException { + DataInputStream dis = (in instanceof DataInputStream) ? (DataInputStream) in : new DataInputStream(in); + + int magic = dis.readInt(); + if (magic != MAGIC) { + throw new IOException("Not a class file, bad magic number: 0x" + Integer.toHexString(magic)); + } + + dis.readUnsignedShort(); // minor_version, unused + dis.readUnsignedShort(); // major_version, deliberately unchecked: the Module attribute below + // has the same layout no matter which major version produced the file. + + ConstantPool pool = ConstantPool.read(dis); + + dis.readUnsignedShort(); // access_flags + dis.readUnsignedShort(); // this_class + dis.readUnsignedShort(); // super_class + + int interfacesCount = dis.readUnsignedShort(); + skipFully(dis, 2L * interfacesCount); + + skipMembers(dis); // fields + skipMembers(dis); // methods + + int attributesCount = dis.readUnsignedShort(); + for (int i = 0; i < attributesCount; i++) { + int attributeNameIndex = dis.readUnsignedShort(); + long attributeLength = readU4(dis); + if (MODULE_ATTRIBUTE_NAME.equals(pool.utf8(attributeNameIndex))) { + return readModuleAttribute(dis, pool); + } + skipFully(dis, attributeLength); + } + + throw new IOException("Not a module: no Module attribute found"); + } + + /** + * Skips over a {@code fields} or {@code methods} array: same {@code field_info}/{@code method_info} + * shape, each with its own list of attributes that are skipped by their declared length. + */ + private static void skipMembers(DataInputStream dis) throws IOException { + int count = dis.readUnsignedShort(); + for (int i = 0; i < count; i++) { + skipFully(dis, 6); // access_flags, name_index, descriptor_index + int attributesCount = dis.readUnsignedShort(); + for (int j = 0; j < attributesCount; j++) { + skipFully(dis, 2); // attribute_name_index + long attributeLength = readU4(dis); + skipFully(dis, attributeLength); + } + } + } + + /** + * Reads the body of the {@code Module} attribute (JVMS 4.7.25), mapping it onto a + * {@link JavaModuleDescriptor} the same way {@code java.lang.module.ModuleDescriptor} does. + */ + private static JavaModuleDescriptor readModuleAttribute(DataInputStream dis, ConstantPool pool) throws IOException { + String moduleName = pool.moduleName(dis.readUnsignedShort()); + JavaModuleDescriptor.Builder builder = JavaModuleDescriptor.newModule(moduleName); + + dis.readUnsignedShort(); // module_flags + dis.readUnsignedShort(); // module_version_index + + int requiresCount = dis.readUnsignedShort(); + for (int i = 0; i < requiresCount; i++) { + int requiresIndex = dis.readUnsignedShort(); + int requiresFlags = dis.readUnsignedShort(); + dis.readUnsignedShort(); // requires_version_index + + String requiredModule = pool.moduleName(requiresIndex); + if ((requiresFlags & (ACC_STATIC_PHASE | ACC_TRANSITIVE)) != 0) { + Set modifiers = new LinkedHashSet<>(); + if ((requiresFlags & ACC_STATIC_PHASE) != 0) { + modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.STATIC); + } + if ((requiresFlags & ACC_TRANSITIVE) != 0) { + modifiers.add(JavaModuleDescriptor.JavaRequires.JavaModifier.TRANSITIVE); + } + builder.requires(modifiers, requiredModule); + } else { + builder.requires(requiredModule); + } + } + + int exportsCount = dis.readUnsignedShort(); + for (int i = 0; i < exportsCount; i++) { + int exportsIndex = dis.readUnsignedShort(); + dis.readUnsignedShort(); // exports_flags + int exportsToCount = dis.readUnsignedShort(); + String pkg = pool.packageName(exportsIndex); + if (exportsToCount == 0) { + builder.exports(pkg); + } else { + Set targets = new HashSet<>(); + for (int j = 0; j < exportsToCount; j++) { + targets.add(pool.moduleName(dis.readUnsignedShort())); + } + builder.exports(pkg, targets); + } + } + + int opensCount = dis.readUnsignedShort(); + for (int i = 0; i < opensCount; i++) { + dis.readUnsignedShort(); // opens_index + dis.readUnsignedShort(); // opens_flags + int opensToCount = dis.readUnsignedShort(); + skipFully(dis, 2L * opensToCount); + } + + int usesCount = dis.readUnsignedShort(); + for (int i = 0; i < usesCount; i++) { + builder.uses(pool.className(dis.readUnsignedShort())); + } + + int providesCount = dis.readUnsignedShort(); + for (int i = 0; i < providesCount; i++) { + String service = pool.className(dis.readUnsignedShort()); + int withCount = dis.readUnsignedShort(); + List providers = new ArrayList<>(withCount); + for (int j = 0; j < withCount; j++) { + providers.add(pool.className(dis.readUnsignedShort())); + } + builder.provides(service, providers); + } + + return builder.build(); + } + + private static long readU4(DataInputStream dis) throws IOException { + return dis.readInt() & 0xFFFFFFFFL; + } + + private static void skipFully(DataInputStream dis, long length) throws IOException { + long remaining = length; + while (remaining > 0) { + long skipped = dis.skip(remaining); + if (skipped <= 0) { + // skip() may legitimately return 0 near the end of the stream; fall back to read() + if (dis.read() < 0) { + throw new IOException("Unexpected end of class file"); + } + skipped = 1; + } + remaining -= skipped; + } + } + + /** + * The subset of the constant pool this parser needs: the raw UTF-8 entries, and, for the + * Class/Module/Package entries, which UTF-8 entry holds their name. All three of those tags + * have the same one-field shape (JVMS 4.4.1, 4.4.8, 4.4.9): {@code u1 tag; u2 name_index;}. + */ + private static final class ConstantPool { + private final String[] utf8; + private final int[] classLikeNameIndex; + + private ConstantPool(String[] utf8, int[] classLikeNameIndex) { + this.utf8 = utf8; + this.classLikeNameIndex = classLikeNameIndex; + } + + static ConstantPool read(DataInputStream dis) throws IOException { + int constantPoolCount = dis.readUnsignedShort(); + String[] utf8 = new String[constantPoolCount]; + int[] classLikeNameIndex = new int[constantPoolCount]; + + for (int i = 1; i < constantPoolCount; i++) { + int tag = dis.readUnsignedByte(); + switch (tag) { + case CONSTANT_UTF8: + utf8[i] = dis.readUTF(); + break; + case CONSTANT_CLASS: + case CONSTANT_MODULE: + case CONSTANT_PACKAGE: + classLikeNameIndex[i] = dis.readUnsignedShort(); + break; + case CONSTANT_STRING: + skipFully(dis, 2); + break; + case CONSTANT_INTEGER: + case CONSTANT_FLOAT: + case CONSTANT_FIELDREF: + case CONSTANT_METHODREF: + case CONSTANT_INTERFACE_METHODREF: + case CONSTANT_NAME_AND_TYPE: + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKE_DYNAMIC: + skipFully(dis, 4); + break; + case CONSTANT_LONG: + case CONSTANT_DOUBLE: + skipFully(dis, 8); + // long/double take up two consecutive constant pool entries; the second is unused + i++; + break; + case CONSTANT_METHOD_HANDLE: + skipFully(dis, 3); + break; + case CONSTANT_METHOD_TYPE: + skipFully(dis, 2); + break; + default: + throw new IOException("Unsupported constant pool tag " + tag + " at index " + i); + } + } + return new ConstantPool(utf8, classLikeNameIndex); + } + + String utf8(int index) { + return utf8[index]; + } + + String moduleName(int index) { + return utf8[classLikeNameIndex[index]]; + } + + String packageName(int index) { + return utf8[classLikeNameIndex[index]].replace('/', '.'); + } + + String className(int index) { + return utf8[classLikeNameIndex[index]].replace('/', '.'); + } + } +} diff --git a/plexus-java/src/main/java9/module-info.java b/plexus-java/src/main/java9/module-info.java index b9ce123..1a68b2a 100644 --- a/plexus-java/src/main/java9/module-info.java +++ b/plexus-java/src/main/java9/module-info.java @@ -18,7 +18,6 @@ */ module org.codehaus.plexus.languages.java { requires com.github.javaparser.core; - requires org.objectweb.asm; exports org.codehaus.plexus.languages.java.jpms; exports org.codehaus.plexus.languages.java.version; diff --git a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java similarity index 56% rename from plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java rename to plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java index efc865a..6d3883e 100644 --- a/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java +++ b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/ModuleInfoClassParserTest.java @@ -29,29 +29,32 @@ import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; -class AsmModuleInfoParserTest { - private final AsmModuleInfoParser parser = new AsmModuleInfoParser(); +class ModuleInfoClassParserTest { + private final ModuleInfoClassParser parser = new ModuleInfoClassParser(); /** - * A class file major version newer than what the bundled ASM release supports (e.g. major - * version 72 emitted by JDK 28, see codehaus-plexus/plexus-languages#165) used to make - * ClassReader throw IllegalArgumentException. Patch a known-good module-info.class to claim - * that unsupported major version and verify it still parses, with the same descriptor as the - * unpatched file (see {@code BinaryModuleInfoParserTest#requires()}). + * This parser never looks at the class file's major/minor version, so a module-info.class compiled + * by a JDK newer than any released today (e.g. major version 72 for JDK 28, see + * codehaus-plexus/plexus-languages#165, or an arbitrarily higher one) must still parse, with the same + * descriptor as the unpatched file (see {@code BinaryModuleInfoParserTest#requires()}). */ - @Test - void parsesModuleInfoWithMajorVersionNewerThanAsmSupports() throws Exception { + @ParameterizedTest + @ValueSource(ints = {72, 99}) + void parsesModuleInfoWithMajorVersionNewerThanAnyKnownJdk(int majorVersion) throws Exception { byte[] classBytes = Files.readAllBytes(Paths.get("src/test/test-data/dir.descriptor.requires/out/module-info.class")); - // bytes 6-7 are the big-endian major version; 72 is one past ASM 9.10.1's newest (71/V27) - classBytes[6] = 0x00; - classBytes[7] = 0x48; + // bytes 6-7 are the big-endian major version + classBytes[6] = (byte) (majorVersion >>> 8); + classBytes[7] = (byte) majorVersion; JavaModuleDescriptor descriptor; try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { @@ -75,4 +78,26 @@ void parsesModuleInfoWithMajorVersionNewerThanAsmSupports() throws Exception { assertEquals(expectedRequires, descriptor.requires()); } + + @Test + void rejectsBadMagic() throws Exception { + byte[] classBytes = + Files.readAllBytes(Paths.get("src/test/test-data/dir.descriptor.requires/out/module-info.class")); + classBytes[0] = 0x00; + + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + assertThrows(java.io.IOException.class, () -> parser.parse(is)); + } + } + + @Test + void rejectsClassFileWithoutModuleAttribute() throws Exception { + // an ordinary compiled class, i.e. one without a Module attribute, must be rejected rather than + // silently producing a bogus descriptor + byte[] classBytes = Files.readAllBytes(Paths.get("src/test/test-data/classfile.version/helloworld-17.class")); + + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + assertThrows(java.io.IOException.class, () -> parser.parse(is)); + } + } } From 006a0c17d69c1caf65ad414b26acfae528ee29bf Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sat, 26 Sep 2026 09:28:56 +0200 Subject: [PATCH 2/2] Build on JDK 26, 27 and 28-ea Spotless is skipped on JDK 27+ until palantir-java-format runs there (palantir/palantir-java-format#1785); drop the profile once it does. --- .github/workflows/maven.yml | 7 +++++++ pom.xml | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index faf344f..af403ad 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -23,6 +23,13 @@ jobs: build: name: Build it uses: codehaus-plexus/.github/.github/workflows/maven.yml@master + with: + matrix-include: > + [ + { "os": "ubuntu-latest", "jdk": "26", "distribution": "zulu" }, + { "os": "ubuntu-latest", "jdk": "27", "distribution": "zulu" }, + { "os": "ubuntu-latest", "jdk": "28-ea", "distribution": "temurin" } + ] # deploy: # name: Deploy diff --git a/pom.xml b/pom.xml index c0621f3..72e477c 100644 --- a/pom.xml +++ b/pom.xml @@ -120,5 +120,16 @@ + + + jdk27-skip-spotless + + [27,) + + + true + true + +