Commit e2490350 authored by Eric Bruneton's avatar Eric Bruneton

Add unit test cases for nest mates, and fix a ClassWriter bug found with them.

parent cd3f4529
Pipeline #1437 passed with stage
in 3 minutes and 57 seconds
......@@ -523,7 +523,12 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
ClassNode classNode = new ClassNode();
new ClassReader(classParameter.getBytes()).accept(classNode, 0);
for (MethodNode methodNode : classNode.methods) {
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(new SimpleVerifier());
Analyzer<BasicValue> analyzer =
new Analyzer<BasicValue>(
new SimpleVerifier(
Type.getObjectType(classNode.name),
Type.getObjectType(classNode.superName),
(classNode.access & Opcodes.ACC_INTERFACE) != 0));
analyzer.analyze(classNode.name, methodNode);
}
}
......
......@@ -49,7 +49,7 @@ import java.util.stream.Stream;
/**
* Base class for the ASM tests. ASM can be used to read, write or transform any Java class, ranging
* from very old (e.g. JDK 1.3) to very recent classes, containing all possible class file
* structures. ASM can also be used with different variants of its API (ASM4, ASM5 or ASM6). In
* structures. ASM can also be used with different variants of its API (ASM4, ASM5, ASM6, etc). In
* order to test it thoroughly, it is therefore necessary to run read, write and transform tests,
* for each API version, and for each class in a set of classes containing all possible class file
* structures. The purpose of this class is to automate this process. For this it relies on:
......@@ -65,8 +65,8 @@ import java.util.stream.Stream;
* API) tuple.
* </ul>
*
* For instance, to run a test on all the precompiled classes, with both the ASM5 and the ASM6 API,
* use a subclass such as the following:
* <p>For instance, to run a test on all the precompiled classes, with all the APIs, use a subclass
* such as the following:
*
* <pre>
* public class MyParameterizedTest extends AsmTest {
......@@ -124,7 +124,9 @@ public abstract class AsmTest {
JDK8_ARTIFICIAL_STRUCTURES("jdk8.ArtificialStructures"),
JDK8_INNER_CLASS("jdk8.AllStructures$InnerClass"),
JDK8_LARGE_METHOD("jdk8.LargeMethod"),
JDK9_MODULE("jdk9.module-info");
JDK9_MODULE("jdk9.module-info"),
JDK11_ALL_STRUCTURES("jdk11.AllStructures"),
JDK11_ALL_STRUCTURES_NESTED("jdk11.AllStructures$Nested");
private final String name;
......@@ -151,10 +153,13 @@ public abstract class AsmTest {
* @return whether this class was compiled with a JDK which is more recent than api.
*/
public boolean isMoreRecentThan(final Api api) {
if (name.startsWith("jdk8") && api.value() < Api.ASM5.value()) {
if (name.startsWith("jdk8.") && api.value() < Api.ASM5.value()) {
return true;
}
return name.startsWith("jdk9") && api.value() < Api.ASM6.value();
if (name.startsWith("jdk9.") && api.value() < Api.ASM6.value()) {
return true;
}
return name.startsWith("jdk11.") && api.value() < Api.ASM7.value();
}
/**
......@@ -165,9 +170,12 @@ public abstract class AsmTest {
* less than 9.
*/
public boolean isMoreRecentThanCurrentJdk() {
if (name.startsWith("jdk9")) {
if (name.startsWith("jdk9.")) {
return getMajorJavaVersion() < 9;
}
if (name.startsWith("jdk11.")) {
return getMajorJavaVersion() < 11;
}
return false;
}
......@@ -222,7 +230,8 @@ public abstract class AsmTest {
public enum Api {
ASM4("ASM4", 4 << 16),
ASM5("ASM5", 5 << 16),
ASM6("ASM6", 6 << 16);
ASM6("ASM6", 6 << 16),
ASM7("ASM7", 7 << 16);
private final String name;
private final int value;
......@@ -235,7 +244,7 @@ public abstract class AsmTest {
/**
* Returns the int value of this version, as expected by ASM.
*
* @return one of the ASM4, ASM5 or ASM6 constants from the ASM Opcodes interface.
* @return one of the ASM4, ASM5, ASM6 or ASM7 constants from the ASM Opcodes interface.
*/
public int value() {
return value;
......@@ -244,7 +253,7 @@ public abstract class AsmTest {
/**
* Returns a human readable symbol corresponding to this version.
*
* @return one of "ASM4", "ASM5" or "ASM6".
* @return one of "ASM4", "ASM5", "ASM6" or "ASM7".
*/
@Override
public String toString() {
......@@ -269,10 +278,10 @@ public abstract class AsmTest {
* with <tt>@MethodSource("allClassesAndLatestApi")</tt> will be executed on all the precompiled
* classes, with the latest api.
*
* @return all the possible (precompiledClass, ASM6) pairs, for all the precompiled classes.
* @return all the possible (precompiledClass, ASM7) pairs, for all the precompiled classes.
*/
public static Stream<Arguments> allClassesAndLatestApi() {
return classesAndApis(Api.ASM6);
return classesAndApis(Api.ASM7);
}
private static Stream<Arguments> classesAndApis(final Api... apis) {
......
......@@ -150,7 +150,7 @@ class ClassDump {
builder.add("magic: ", parser.u4());
builder.add("minor_version: ", parser.u2());
int majorVersion = parser.u2();
if (majorVersion >= /* V10 = */ 54) {
if (majorVersion > /* V11 = */ 55) {
throw new IOException("Unsupported class version");
}
builder.add("major_version: ", majorVersion);
......@@ -825,6 +825,10 @@ class ClassDump {
dumpModulePackagesAttribute(parser, builder);
} else if (attributeName.equals("ModuleMainClass")) {
dumpModuleMainClassAttribute(parser, builder);
} else if (attributeName.equals("NestHost")) {
dumpNestHostAttribute(parser, builder);
} else if (attributeName.equals("NestMembers")) {
dumpNestMembersAttribute(parser, builder);
} else if (attributeName.equals("StackMap")) {
dumpStackMapAttribute(parser, builder);
} else {
......@@ -2005,6 +2009,37 @@ class ClassDump {
builder.addCpInfo("main_class: ", parser.u2());
}
/**
* Parses and dumps a NestHost attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.27">JVMS
* 4.7.28</a>
*/
private static void dumpNestHostAttribute(final Parser parser, final Builder builder)
throws IOException {
builder.addCpInfo("host_class: ", parser.u2());
}
/**
* Parses and dumps a NestMembers attribute.
*
* @param parser a class parser.
* @param builder a dump builder.
* @throws IOException if the class can't be parsed.
* @see <a href="https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.27">JVMS
* 4.7.29</a>
*/
private static void dumpNestMembersAttribute(final Parser parser, final Builder builder)
throws IOException {
int numberOfClasses = builder.add("number_of_classes: ", parser.u2());
for (int i = 0; i < numberOfClasses; ++i) {
builder.addCpInfo("class: ", parser.u2());
}
}
/**
* Parses and dumps a StackMap attribute.
*
......
// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holders nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package jdk11;
import java.io.FileOutputStream;
import java.io.IOException;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Generates classes with all the JDK11 specific class file features.
*
* <p>TODO: remove this and use the JDK11 to compile equivalent classes, when it is released.
*
* @author Eric Bruneton
*/
public class DumpAllStructures implements Opcodes {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("AllStructures.class");
fileOutputStream.write(dumpAllStructures());
fileOutputStream.close();
fileOutputStream = new FileOutputStream("AllStructures$Nested.class");
fileOutputStream.write(dumpAllStructuresNested());
fileOutputStream.close();
}
private static byte[] dumpAllStructures() {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classWriter.visit(
V11, ACC_PUBLIC + ACC_SUPER, "jdk11/AllStructures", null, "java/lang/Object", null);
classWriter.visitNestMember("jdk11/AllStructures$Nested");
addDefaultConstructor(classWriter);
classWriter.visitEnd();
return classWriter.toByteArray();
}
private static byte[] dumpAllStructuresNested() {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classWriter.visit(
V11, ACC_PUBLIC + ACC_SUPER, "jdk11/AllStructures$Nested", null, "java/lang/Object", null);
classWriter.visitNestHost("jdk11/AllStructures");
addDefaultConstructor(classWriter);
classWriter.visitEnd();
return classWriter.toByteArray();
}
private static void addDefaultConstructor(final ClassWriter classWriter) {
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitEnd();
}
}
......@@ -60,6 +60,9 @@ public class AsmTestTest extends AsmTest {
case ASM6:
assertEquals(majorVersion > /* V10 = */ 54, isMoreRecent);
break;
case ASM7:
assertEquals(majorVersion > /* V11 = */ 55, isMoreRecent);
break;
default:
fail("Unknown API value");
}
......@@ -69,9 +72,9 @@ public class AsmTestTest extends AsmTest {
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
public void testGetBytes(final PrecompiledClass classParameter, final Api apiParameter) {
assertEquals(Api.ASM6, apiParameter);
assertEquals(0x00060000, apiParameter.value());
assertEquals("ASM6", apiParameter.toString());
assertEquals(Api.ASM7, apiParameter);
assertEquals(0x00070000, apiParameter.value());
assertEquals("ASM7", apiParameter.toString());
byte[] classContent = classParameter.getBytes();
assertThatClass(classContent).contains(classParameter.getInternalName());
assertThatClass(classContent).isEqualTo(classContent);
......
......@@ -500,6 +500,12 @@ public class ClassNodeTest extends AsmTest implements Opcodes {
return null;
}
@Override
public void visitNestHost(final String nestHost) {}
@Override
public void visitNestMember(final String nestMember) {}
@Override
public void visitAttribute(final Attribute attribute) {}
}
......
// class version 55.0 (55)
// access flags 0x21
public class jdk11/AllStructures$Nested {
NESTHOST jdk11/AllStructures
// access flags 0x1
public <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1
}
// class version 55.0 (55)
// access flags 0x21
public class jdk11/AllStructures {
NESTMEMBER jdk11/AllStructures$Nested
// access flags 0x1
public <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1
}
......@@ -118,8 +118,11 @@ public class SAXAdapterTest extends AsmTest {
@MethodSource(ALL_CLASSES_AND_LATEST_API)
public void testSAXAdapter_classUnchanged(PrecompiledClass classParameter, Api apiParameter)
throws TransformerConfigurationException, TransformerFactoryConfigurationError, SAXException {
// Non standard attributes are not supported by the XML API.
if (classParameter == PrecompiledClass.JDK3_ARTIFICIAL_STRUCTURES) return;
// Non standard attributes and features introduced in JDK11 or more are not supported.
if (classParameter == PrecompiledClass.JDK3_ARTIFICIAL_STRUCTURES
|| classParameter.isMoreRecentThan(Api.ASM6)) {
return;
}
byte[] classFile = classParameter.getBytes();
ClassReader classReader = new ClassReader(classFile);
ClassWriter classWriter = new ClassWriter(0);
......
......@@ -158,7 +158,7 @@ public class ClassReader {
this.b = classFileBuffer;
// Check the class' major_version. This field is after the magic and minor_version fields, which
// use 4 and 2 bytes respectively.
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V10) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V11) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
......
......@@ -657,6 +657,9 @@ public class ClassWriter extends ClassVisitor {
lastRuntimeVisibleTypeAnnotation = null;
lastRuntimeInvisibleTypeAnnotation = null;
moduleWriter = null;
nestHostClassIndex = 0;
numberOfNestMemberClasses = 0;
nestMemberClasses = null;
firstAttribute = null;
compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING;
new ClassReader(result.data, 0, /* checkClassVersion = */ false)
......
......@@ -135,7 +135,7 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
assertNotNull(classReader.getInterfaces());
classReader.accept(
new ClassVisitor(Opcodes.ASM6) {
new ClassVisitor(Opcodes.ASM7) {
@Override
public void visit(
final int version,
......@@ -192,12 +192,16 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
final PrecompiledClass classParameter, final Api apiParameter) {
ClassReader classReader = new ClassReader(classParameter.getBytes());
ClassVisitor classVisitor = new ClassVisitor(apiParameter.value()) {};
boolean hasNestHostOrMembers =
classParameter == PrecompiledClass.JDK11_ALL_STRUCTURES
|| classParameter == PrecompiledClass.JDK11_ALL_STRUCTURES_NESTED;
boolean hasModules = classParameter == PrecompiledClass.JDK9_MODULE;
boolean hasTypeAnnotations = classParameter == PrecompiledClass.JDK8_ALL_STRUCTURES;
assertThat(() -> classReader.accept(classVisitor, 0))
.succeedsOrThrows(RuntimeException.class)
.when(
(hasModules && apiParameter.value() < ASM6)
(hasNestHostOrMembers && apiParameter.value() < ASM7)
|| (hasModules && apiParameter.value() < ASM6)
|| (hasTypeAnnotations && apiParameter.value() < ASM5));
}
......@@ -321,7 +325,10 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
.when(classParameter.isMoreRecentThan(apiParameter));
}
/** Tests the ClassReader accept method with a visitor that skips fields, methods and modules. */
/**
* Tests the ClassReader accept method with a visitor that skips fields, methods, modules and nest
* host and members.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_ALL_APIS)
public void testAcceptWithEmptyVisitorAndSkipFieldMethodAndModuleContent(
......@@ -355,6 +362,12 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
final String[] exceptions) {
return null;
}
@Override
public void visitNestHost(final String nestHost) {}
@Override
public void visitNestMember(final String nestMember) {}
};
classReader.accept(classVisitor, 0);
}
......@@ -364,7 +377,7 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
final AtomicBoolean success = new AtomicBoolean(false);
ClassReader classReader = new ClassReader(PrecompiledClass.JDK5_LOCAL_CLASS.getBytes());
classReader.accept(
new ClassVisitor(Opcodes.ASM6) {
new ClassVisitor(Opcodes.ASM7) {
@Override
public MethodVisitor visitMethod(
final int access,
......@@ -372,7 +385,7 @@ public class ClassReaderTest extends AsmTest implements Opcodes {
final String descriptor,
final String signature,
final String[] exceptions) {
return new MethodVisitor(Opcodes.ASM6, null) {
return new MethodVisitor(Opcodes.ASM7, null) {
@Override
public AnnotationVisitor visitParameterAnnotation(
final int parameter, final String descriptor, final boolean visible) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment