AnnotatedClassVisitorTest.java
/*
* Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.integration.asm;
import jersey.repackaged.org.objectweb.asm.ClassVisitor;
import jersey.repackaged.org.objectweb.asm.Opcodes;
import org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
public class AnnotatedClassVisitorTest {
@Test
public void testInheritedMethodsFromClassVisitor() {
Class<?> annotatedClassVisitorClass = null;
final Class<?> classVisitorClass = ClassVisitor.class;
final Class<?>[] listenerClasses = AnnotationAcceptingListener.class.getDeclaredClasses();
for (Class<?> c : listenerClasses) {
if (c.getName().contains("AnnotatedClassVisitor")) {
annotatedClassVisitorClass = c;
break;
}
}
final List<Method> classVisitorMethods = Arrays.asList(classVisitorClass.getDeclaredMethods());
final List<Method> annotatedClassVisitorMethods = Arrays.asList(annotatedClassVisitorClass.getDeclaredMethods());
boolean containsAllMethods = true;
for (Method classVisitorMethod : classVisitorMethods) {
boolean foundClassVisitorMethod = false;
for (Method annotatedClassVisitorMethod : annotatedClassVisitorMethods) {
if (annotatedClassVisitorMethod.getName().equals(classVisitorMethod.getName())
&& annotatedClassVisitorMethod.getReturnType() == classVisitorMethod.getReturnType()
&& annotatedClassVisitorMethod.getParameterCount() == classVisitorMethod.getParameterCount()) {
final Class<?>[] annotatedClassVisitorTypes = annotatedClassVisitorMethod.getParameterTypes();
final Class<?>[] classVisitorTypes = classVisitorMethod.getParameterTypes();
boolean typesMatch = true;
for (int i = 0; i != annotatedClassVisitorTypes.length; i++) {
if (annotatedClassVisitorTypes[i] != classVisitorTypes[i]) {
typesMatch = false;
break;
}
}
if (typesMatch) {
foundClassVisitorMethod = true;
//System.out.println("found method " + classVisitorMethod.getName());
break;
}
}
}
if (!foundClassVisitorMethod) {
containsAllMethods = false;
System.out.append("Method ")
.append(classVisitorMethod.getName())
.println(" not implemented by AnnotationAcceptingListener.AnnotatedClassVisitor");
}
}
MatcherAssert.assertThat(containsAllMethods, Matchers.is(true));
}
@Test
public void testCorrectOpcodeAsmIsUsedInAnnotationAcceptingListener() {
final int asmOpcode = getMaxValueOfField("ASM", 6);
final AnnotationAcceptingListener aal = new AnnotationAcceptingListener();
String aalOpcode = null;
try {
final Field classVisitorField = aal.getClass().getDeclaredField("classVisitor");
classVisitorField.setAccessible(true);
final Object classVisitor = classVisitorField.get(aal);
final Field opcodeField = classVisitor.getClass().getSuperclass().getDeclaredField("api");
opcodeField.setAccessible(true);
aalOpcode = String.valueOf(((Integer) opcodeField.get(classVisitor)) >> 16);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
Assertions.assertEquals(String.valueOf(asmOpcode), aalOpcode,
"You need to set: \nAnnotatedClassVisitor() {\n super(Opcodes.ASM" + asmOpcode + ");\n}");
}
@Test
public void testWarningOpcodeInClassReaderWrapperSetCorrectly() {
final Integer jdkVersion = getMaxValueOfField("V", 13);
Class<?> classReaderWrapper = null;
for (Class<?> innerClass : AnnotationAcceptingListener.class.getDeclaredClasses()) {
if (innerClass.getName().contains("ClassReaderWrapper")) {
classReaderWrapper = innerClass;
break;
}
}
Integer warnFieldValue = 0;
try {
final Field warnField = classReaderWrapper.getDeclaredField("WARN_VERSION");
warnField.setAccessible(true);
warnFieldValue = (Integer) warnField.get(null) - (Opcodes.V1_1 & 0x00FF) + 1;
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
Assertions.assertEquals(jdkVersion, warnFieldValue,
"You need to set ClassReaderWrapper.WARN_VERSION=Opcodes.V" + jdkVersion);
}
@Test
public void testLoggerInClassReaderWrapper() throws IOException {
final String warningMsg = "Unsupported class file major version";
final Integer maxOpcode = getMaxValueOfField("V", 13);
final byte[] array = new byte[10];
array[7] = (byte) ((maxOpcode.byteValue() + Opcodes.V1_1) & 0x00FF);
final ByteArrayOutputStream log = new ByteArrayOutputStream(500);
final PrintStream saveErr = System.err;
try {
System.setErr(new PrintStream(log));
try {
new AnnotationAcceptingListener().process("", new ByteArrayInputStream(array));
} catch (ArrayIndexOutOfBoundsException aioobe) {
//expected, given array is too small for a class file
}
} finally {
System.setErr(saveErr);
}
final String message = new String(log.toByteArray());
Assertions.assertTrue(message.contains(warningMsg),
"The WARNING `" + warningMsg + "` has not been printed for a class with byte code version " + array[7]);
}
private static int getMaxValueOfField(String fieldPrefix, int initialValue) {
int value = initialValue;
do {
try {
value++;
Field field = Opcodes.class.getField(fieldPrefix + value);
} catch (NoSuchFieldException e) {
value--;
break;
}
} while (true);
return value;
}
}