--- /dev/null 2016-05-31 09:42:47.975716356 -0700 +++ new/src/jdk.vm.compiler/share/classes/org.graalvm.compiler.replacements.test/src/org/graalvm/compiler/replacements/test/classfile/ClassfileBytecodeProviderTest.java 2016-12-09 00:56:53.753903745 -0800 @@ -0,0 +1,453 @@ +/* + * 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 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: + * + *
+         *     16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String;
+         * 
+ * + * When resolving via {@code HotSpotConstantPool}, we get: + * + *
+         *     16: invokeinterface#4, 1   // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String
+         * 
+ * + * However resolving via {@code ClassfileConstantPool}, we get: + * + *
+         *     16: invokeinterface#40, 1  // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String
+         * 
+ * + * 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()); + } + } +}