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 index c35ebcb..d14b51d 100644 --- 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 @@ -19,6 +19,7 @@ * under the License. */ +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -41,11 +42,30 @@ * @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(); - ClassReader reader = new ClassReader(in); + 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 @@ -101,6 +121,16 @@ public void visitProvide(String service, String... providers) { 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/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java new file mode 100644 index 0000000..efc865a --- /dev/null +++ b/plexus-java/src/test/java/org/codehaus/plexus/languages/java/jpms/AsmModuleInfoParserTest.java @@ -0,0 +1,78 @@ +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.ByteArrayInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor.JavaRequires; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AsmModuleInfoParserTest { + private final AsmModuleInfoParser parser = new AsmModuleInfoParser(); + + /** + * 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()}). + */ + @Test + void parsesModuleInfoWithMajorVersionNewerThanAsmSupports() 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; + + JavaModuleDescriptor descriptor; + try (ByteArrayInputStream is = new ByteArrayInputStream(classBytes)) { + descriptor = parser.parse(is); + } + + assertNotNull(descriptor); + assertThat(descriptor.requires()).hasSize(5); + + Set expectedRequires = JavaModuleDescriptor.newAutomaticModule("_") + .requires("java.base") + .requires("mod_r") + .requires(Collections.singleton(JavaRequires.JavaModifier.STATIC), "mod_r_s") + .requires(Collections.singleton(JavaRequires.JavaModifier.TRANSITIVE), "mod_r_t") + .requires( + new HashSet<>( + Arrays.asList(JavaRequires.JavaModifier.STATIC, JavaRequires.JavaModifier.TRANSITIVE)), + "mod_r_s_t") + .build() + .requires(); + + assertEquals(expectedRequires, descriptor.requires()); + } +}