| /* |
| * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| 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.junit.Assert; |
| import org.junit.Assume; |
| import org.junit.Test; |
| |
| 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 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; |
| |
| /** |
| * Tests that bytecode exposed via {@link ClassfileBytecode} objects is the same as the bytecode |
| * (modulo minor differences in constant pool resolution) obtained directly from |
| * {@link ResolvedJavaMethod} objects. |
| */ |
| public class ClassfileBytecodeProviderTest extends GraalCompilerTest { |
| |
| 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; |
| } |
| |
| @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 propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path"; |
| String bootclasspath = System.getProperty(propertyName); |
| Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath); |
| |
| for (String path : bootclasspath.split(File.pathSeparator)) { |
| if (shouldProcess(path)) { |
| try { |
| final ZipFile zipFile = new ZipFile(new File(path)); |
| for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) { |
| final ZipEntry zipEntry = entry.nextElement(); |
| String name = zipEntry.getName(); |
| if (name.endsWith(".class") && !name.equals("module-info.class")) { |
| String className = name.substring(0, name.length() - ".class".length()).replace('/', '.'); |
| try { |
| checkClass(metaAccess, getSnippetReflection(), className); |
| } catch (ClassNotFoundException e) { |
| throw new AssertionError(e); |
| } |
| } |
| } |
| } catch (IOException ex) { |
| Assert.fail(ex.toString()); |
| } |
| } |
| } |
| } |
| |
| protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException { |
| 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()) { |
| ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method); |
| Bytecode actual = getBytecode(cbp, method); |
| new BytecodeComparer(expected, actual).compare(); |
| } |
| } |
| |
| protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) { |
| try { |
| return cbp.getBytecode(method); |
| } 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: { |
| // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider |
| 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) { |
| // A method hidden to reflection |
| 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); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The VM can resolve references to methods not available via reflection. For example, the |
| * javap output for {@link ProfiledMethod#toString()} includes: |
| * |
| * <pre> |
| * 16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String; |
| * </pre> |
| * |
| * When resolving via {@code HotSpotConstantPool}, we get: |
| * |
| * <pre> |
| * 16: invokeinterface#4, 1 // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String |
| * </pre> |
| * |
| * However resolving via {@code ClassfileConstantPool}, we get: |
| * |
| * <pre> |
| * 16: invokeinterface#40, 1 // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String |
| * </pre> |
| * |
| * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only |
| * returns methods originating from class files. |
| * |
| * We accept such differences for the purpose of this test if the declaring class of two |
| * otherwise similar methods are related (i.e. one is a subclass of the other). |
| */ |
| protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) { |
| return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()); |
| } |
| } |
| } |