package org.graalvm.compiler.replacements.test.classfile;
import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.RET;
import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.api.test.Graal;
import org.graalvm.compiler.bytecode.Bytecode;
import org.graalvm.compiler.bytecode.BytecodeDisassembler;
import org.graalvm.compiler.bytecode.BytecodeLookupSwitch;
import org.graalvm.compiler.bytecode.BytecodeStream;
import org.graalvm.compiler.bytecode.BytecodeSwitch;
import org.graalvm.compiler.bytecode.BytecodeTableSwitch;
import org.graalvm.compiler.bytecode.Bytecodes;
import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode;
import org.graalvm.compiler.core.test.GraalCompilerTest;
import org.graalvm.compiler.phases.VerifyPhase;
import org.graalvm.compiler.phases.util.Providers;
import org.graalvm.compiler.replacements.classfile.ClassfileBytecode;
import org.graalvm.compiler.replacements.classfile.ClassfileBytecodeProvider;
import org.graalvm.compiler.runtime.RuntimeProvider;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
import org.graalvm.compiler.api.test.ModuleSupport;
import org.graalvm.compiler.test.SubprocessUtil;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import jdk.vm.ci.meta.ConstantPool;
import jdk.vm.ci.meta.JavaField;
import jdk.vm.ci.meta.JavaMethodProfile.ProfiledMethod;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
public class ClassfileBytecodeProviderTest extends GraalCompilerTest {
@Before
public void checkJavaAgent() {
assumeManagementLibraryIsLoadable();
Assume.assumeFalse("Java Agent found -> skipping", SubprocessUtil.isJavaAgentAttached());
}
private static boolean shouldProcess(String classpathEntry) {
if (classpathEntry.endsWith(".jar")) {
String name = new File(classpathEntry).getName();
return name.contains("jvmci") || name.contains("graal");
}
return false;
}
private static final int CLASSES_PER_JAR = 250;
public static final String JRT_CLASS_PATH_ENTRY = "<jrt>";
@Test
public void test() {
RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
Providers providers = rt.getHostBackend().getProviders();
MetaAccessProvider metaAccess = providers.getMetaAccess();
Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
String bootclasspath;
if (JavaVersionUtil.JAVA_SPEC <= 8) {
String propertyName = "sun.boot.class.path";
bootclasspath = System.getProperty(propertyName);
Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath);
} else {
bootclasspath = JRT_CLASS_PATH_ENTRY;
}
for (String path : bootclasspath.split(File.pathSeparator)) {
if (shouldProcess(path)) {
try {
if (path.equals(JRT_CLASS_PATH_ENTRY)) {
for (String className : ModuleSupport.getJRTGraalClassNames()) {
if (isGSON(className)) {
continue;
}
try {
checkClass(metaAccess, getSnippetReflection(), className);
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}
} else {
final ZipFile zipFile = new ZipFile(new File(path));
int index = 0;
int step = zipFile.size() > CLASSES_PER_JAR ? zipFile.size() / CLASSES_PER_JAR : 1;
for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
final ZipEntry zipEntry = entry.nextElement();
if ((index % step) == 0) {
String name = zipEntry.getName();
if (name.endsWith(".class") && !name.equals("module-info.class") && !name.startsWith("META-INF/versions/")) {
String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
if (isInNativeImage(className)) {
continue;
}
if (isGSON(className)) {
continue;
}
try {
checkClass(metaAccess, getSnippetReflection(), className);
} catch (UnsupportedClassVersionError e) {
} catch (NoClassDefFoundError e) {
if (!e.getMessage().contains("Could not initialize class")) {
throw e;
} else {
}
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}
}
index++;
}
}
} catch (IOException ex) {
Assert.fail(ex.toString());
}
}
}
}
private static boolean isInNativeImage(String className) {
return className.startsWith("jdk.internal.vm.compiler.nativeimage");
}
private static boolean isGSON(String className) {
return className.contains("com.google.gson");
}
protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException {
if (className.equals("jdk.vm.ci.services.JVMCIClassLoaderFactory")) {
return;
}
Class<?> c = Class.forName(className, true, getClass().getClassLoader());
ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection);
for (Method method : c.getDeclaredMethods()) {
checkMethod(cbp, metaAccess, method);
}
}
private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) {
ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable);
if (method.hasBytecodes()) {
Bytecode actual = getBytecode(cbp, method);
if (actual != null) {
ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method);
new BytecodeComparer(expected, actual).compare();
}
}
}
protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) {
try {
return cbp.getBytecode(method);
} catch (UnsupportedClassVersionError e) {
return null;
} catch (Throwable e) {
throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e);
}
}
static class BytecodeComparer {
private Bytecode expected;
private Bytecode actual;
private ConstantPool eCp;
private ConstantPool aCp;
BytecodeStream eStream;
BytecodeStream aStream;
int bci = -1;
BytecodeComparer(Bytecode expected, Bytecode actual) {
this.expected = expected;
this.actual = actual;
this.eCp = expected.getConstantPool();
this.aCp = actual.getConstantPool();
Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize());
this.eStream = new BytecodeStream(expected.getCode());
this.aStream = new BytecodeStream(actual.getCode());
}
public void compare() {
try {
compare0();
} catch (Throwable e) {
BytecodeDisassembler dis = new BytecodeDisassembler(true, false);
Formatter msg = new Formatter();
msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)"));
if (bci >= 0) {
msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1));
msg.format("%nactual: %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1));
}
throw new AssertionError(msg.toString(), e);
}
}
public void compare0() {
int opcode = eStream.currentBC();
ResolvedJavaMethod method = expected.getMethod();
while (opcode != Bytecodes.END) {
bci = eStream.currentBCI();
int actualOpcode = aStream.currentBC();
if (opcode != actualOpcode) {
Assert.assertEquals(opcode, actualOpcode);
}
if (eStream.nextBCI() > bci + 1) {
switch (opcode) {
case BIPUSH:
Assert.assertEquals(eStream.readByte(), aStream.readByte());
break;
case SIPUSH:
Assert.assertEquals(eStream.readShort(), aStream.readShort());
break;
case NEW:
case CHECKCAST:
case INSTANCEOF:
case ANEWARRAY: {
ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
assertEqualTypes(e, a);
break;
}
case GETSTATIC:
case PUTSTATIC:
case GETFIELD:
case PUTFIELD: {
ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode);
ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode);
assertEqualFields(e, a);
break;
}
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKESTATIC: {
ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode);
assertEqualMethods(e, a);
break;
}
case INVOKEINTERFACE: {
ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode);
assertEqualMethods(e, a);
break;
}
case INVOKEDYNAMIC: {
return;
}
case LDC:
case LDC_W:
case LDC2_W: {
Object e = lookupConstant(eCp, eStream.readCPI(), opcode);
Object a = lookupConstant(aCp, aStream.readCPI(), opcode);
assertEqualsConstants(e, a);
break;
}
case RET:
case ILOAD:
case LLOAD:
case FLOAD:
case DLOAD:
case ALOAD:
case ISTORE:
case LSTORE:
case FSTORE:
case DSTORE:
case ASTORE: {
Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
break;
}
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
case GOTO:
case JSR:
case IFNULL:
case IFNONNULL:
case GOTO_W:
case JSR_W: {
Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest());
break;
}
case LOOKUPSWITCH:
case TABLESWITCH: {
BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci);
BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci);
Assert.assertEquals(e.numberOfCases(), a.numberOfCases());
for (int i = 0; i < e.numberOfCases(); i++) {
Assert.assertEquals(e.keyAt(i), a.keyAt(i));
Assert.assertEquals(e.targetAt(i), a.targetAt(i));
}
Assert.assertEquals(e.defaultTarget(), a.defaultTarget());
Assert.assertEquals(e.defaultOffset(), a.defaultOffset());
break;
}
case NEWARRAY: {
Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
break;
}
case MULTIANEWARRAY: {
ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
Assert.assertEquals(e, a);
break;
}
}
}
eStream.next();
aStream.next();
opcode = eStream.currentBC();
}
}
static Object lookupConstant(ConstantPool cp, int cpi, int opcode) {
cp.loadReferencedType(cpi, opcode);
return cp.lookupConstant(cpi);
}
static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) {
cp.loadReferencedType(cpi, opcode);
return (ResolvedJavaField) cp.lookupField(cpi, method, opcode);
}
static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) {
cp.loadReferencedType(cpi, opcode);
return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode);
}
static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) {
try {
return lookupMethod(cp, cpi, opcode);
} catch (NoSuchMethodError e) {
return null;
}
}
static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) {
cp.loadReferencedType(cpi, opcode);
return (ResolvedJavaType) cp.lookupType(cpi, opcode);
}
static void assertEqualsConstants(Object e, Object a) {
if (!e.equals(a)) {
Assert.assertEquals(String.valueOf(e), String.valueOf(a));
}
}
static void assertEqualFields(JavaField e, JavaField a) {
if (!e.equals(a)) {
Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T"));
}
}
static void assertEqualTypes(JavaType e, JavaType a) {
if (!e.equals(a)) {
Assert.assertEquals(e.toJavaName(), a.toJavaName());
}
}
static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) {
if (a != null) {
if (!e.equals(a)) {
if (!e.equals(a)) {
if (!e.getDeclaringClass().equals(a.getDeclaringClass())) {
if (!typesAreRelated(e, a)) {
throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName()));
}
}
Assert.assertEquals(e.getName(), a.getName());
Assert.assertEquals(e.getSignature(), a.getSignature());
} else {
Assert.assertEquals(e, a);
}
}
}
}
protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) {
return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass());
}
}
}