1 /*
   2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.replacements.test.classfile;
  26 
  27 import static org.graalvm.compiler.bytecode.Bytecodes.ALOAD;
  28 import static org.graalvm.compiler.bytecode.Bytecodes.ANEWARRAY;
  29 import static org.graalvm.compiler.bytecode.Bytecodes.ASTORE;
  30 import static org.graalvm.compiler.bytecode.Bytecodes.BIPUSH;
  31 import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
  32 import static org.graalvm.compiler.bytecode.Bytecodes.DLOAD;
  33 import static org.graalvm.compiler.bytecode.Bytecodes.DSTORE;
  34 import static org.graalvm.compiler.bytecode.Bytecodes.FLOAD;
  35 import static org.graalvm.compiler.bytecode.Bytecodes.FSTORE;
  36 import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
  37 import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
  38 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
  39 import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
  40 import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
  41 import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
  42 import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
  43 import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
  44 import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
  45 import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
  46 import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
  47 import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
  48 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
  49 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
  50 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
  51 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
  52 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
  53 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
  54 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
  55 import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
  56 import static org.graalvm.compiler.bytecode.Bytecodes.ILOAD;
  57 import static org.graalvm.compiler.bytecode.Bytecodes.INSTANCEOF;
  58 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
  59 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
  60 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
  61 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
  62 import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
  63 import static org.graalvm.compiler.bytecode.Bytecodes.ISTORE;
  64 import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
  65 import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
  66 import static org.graalvm.compiler.bytecode.Bytecodes.LDC;
  67 import static org.graalvm.compiler.bytecode.Bytecodes.LDC2_W;
  68 import static org.graalvm.compiler.bytecode.Bytecodes.LDC_W;
  69 import static org.graalvm.compiler.bytecode.Bytecodes.LLOAD;
  70 import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
  71 import static org.graalvm.compiler.bytecode.Bytecodes.LSTORE;
  72 import static org.graalvm.compiler.bytecode.Bytecodes.MULTIANEWARRAY;
  73 import static org.graalvm.compiler.bytecode.Bytecodes.NEW;
  74 import static org.graalvm.compiler.bytecode.Bytecodes.NEWARRAY;
  75 import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
  76 import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
  77 import static org.graalvm.compiler.bytecode.Bytecodes.RET;
  78 import static org.graalvm.compiler.bytecode.Bytecodes.SIPUSH;
  79 import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
  80 
  81 import java.io.File;
  82 import java.io.IOException;
  83 import java.lang.reflect.Executable;
  84 import java.lang.reflect.Method;
  85 import java.util.Enumeration;
  86 import java.util.Formatter;
  87 import java.util.zip.ZipEntry;
  88 import java.util.zip.ZipFile;
  89 
  90 import org.graalvm.compiler.test.SubprocessUtil;
  91 import org.junit.Assert;
  92 import org.junit.Assume;
  93 import org.junit.Before;
  94 import org.junit.Test;
  95 
  96 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
  97 import org.graalvm.compiler.api.test.Graal;
  98 import org.graalvm.compiler.bytecode.Bytecode;
  99 import org.graalvm.compiler.bytecode.BytecodeDisassembler;
 100 import org.graalvm.compiler.bytecode.BytecodeLookupSwitch;
 101 import org.graalvm.compiler.bytecode.BytecodeStream;
 102 import org.graalvm.compiler.bytecode.BytecodeSwitch;
 103 import org.graalvm.compiler.bytecode.BytecodeTableSwitch;
 104 import org.graalvm.compiler.bytecode.Bytecodes;
 105 import org.graalvm.compiler.bytecode.ResolvedJavaMethodBytecode;
 106 import org.graalvm.compiler.core.test.GraalCompilerTest;
 107 import org.graalvm.compiler.phases.VerifyPhase;
 108 import org.graalvm.compiler.phases.util.Providers;
 109 import org.graalvm.compiler.replacements.classfile.ClassfileBytecode;
 110 import org.graalvm.compiler.replacements.classfile.ClassfileBytecodeProvider;
 111 import org.graalvm.compiler.runtime.RuntimeProvider;
 112 
 113 import jdk.vm.ci.meta.ConstantPool;
 114 import jdk.vm.ci.meta.JavaField;
 115 import jdk.vm.ci.meta.JavaMethodProfile.ProfiledMethod;
 116 import jdk.vm.ci.meta.JavaType;
 117 import jdk.vm.ci.meta.MetaAccessProvider;
 118 import jdk.vm.ci.meta.ResolvedJavaField;
 119 import jdk.vm.ci.meta.ResolvedJavaMethod;
 120 import jdk.vm.ci.meta.ResolvedJavaType;
 121 
 122 /**
 123  * Tests that bytecode exposed via {@link ClassfileBytecode} objects is the same as the bytecode
 124  * (modulo minor differences in constant pool resolution) obtained directly from
 125  * {@link ResolvedJavaMethod} objects.
 126  */
 127 public class ClassfileBytecodeProviderTest extends GraalCompilerTest {
 128 
 129     @Before
 130     public void checkJavaAgent() {
 131         assumeManagementLibraryIsLoadable();
 132         Assume.assumeFalse("Java Agent found -> skipping", SubprocessUtil.isJavaAgentAttached());
 133     }
 134 
 135     private static boolean shouldProcess(String classpathEntry) {
 136         if (classpathEntry.endsWith(".jar")) {
 137             String name = new File(classpathEntry).getName();
 138             return name.contains("jvmci") || name.contains("graal");
 139         }
 140         return false;
 141     }
 142 
 143     @Test
 144     public void test() {
 145         RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class);
 146         Providers providers = rt.getHostBackend().getProviders();
 147         MetaAccessProvider metaAccess = providers.getMetaAccess();
 148 
 149         Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus());
 150 
 151         String propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path";
 152         String bootclasspath = System.getProperty(propertyName);
 153         Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath);
 154 
 155         for (String path : bootclasspath.split(File.pathSeparator)) {
 156             if (shouldProcess(path)) {
 157                 try {
 158                     final ZipFile zipFile = new ZipFile(new File(path));
 159                     for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) {
 160                         final ZipEntry zipEntry = entry.nextElement();
 161                         String name = zipEntry.getName();
 162                         if (name.endsWith(".class") && !name.equals("module-info.class") && !name.startsWith("META-INF/versions/")) {
 163                             String className = name.substring(0, name.length() - ".class".length()).replace('/', '.');
 164                             if (isInNativeImage(className)) {
 165                                 /*
 166                                  * Native image requires non-graalsdk classes to be present in the
 167                                  * classpath.
 168                                  */
 169                                 continue;
 170                             }
 171                             if (isGSON(className)) {
 172                                 /* uses old class format */
 173                                 continue;
 174                             }
 175                             try {
 176                                 checkClass(metaAccess, getSnippetReflection(), className);
 177                             } catch (ClassNotFoundException e) {
 178                                 throw new AssertionError(e);
 179                             }
 180                         }
 181                     }
 182                 } catch (IOException ex) {
 183                     Assert.fail(ex.toString());
 184                 }
 185             }
 186         }
 187     }
 188 
 189     private static boolean isInNativeImage(String className) {
 190         return className.startsWith("org.graalvm.nativeimage");
 191     }
 192 
 193     private static boolean isGSON(String className) {
 194         return className.contains("com.google.gson");
 195     }
 196 
 197     protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException {
 198         if (className.equals("jdk.vm.ci.services.JVMCIClassLoaderFactory")) {
 199             // JVMCIClassLoaderFactory must only be initialized by the VM
 200             return;
 201         }
 202         Class<?> c = Class.forName(className, true, getClass().getClassLoader());
 203         ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection);
 204         for (Method method : c.getDeclaredMethods()) {
 205             checkMethod(cbp, metaAccess, method);
 206         }
 207     }
 208 
 209     private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) {
 210         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable);
 211         if (method.hasBytecodes()) {
 212             Bytecode actual = getBytecode(cbp, method);
 213             if (actual != null) {
 214                 ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method);
 215                 new BytecodeComparer(expected, actual).compare();
 216             }
 217         }
 218     }
 219 
 220     protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) {
 221         try {
 222             return cbp.getBytecode(method);
 223         } catch (UnsupportedClassVersionError e) {
 224             // This can happen when a library containing old class files
 225             // is bundled into a Graal jar (GR-12672).
 226             return null;
 227         } catch (Throwable e) {
 228             throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e);
 229         }
 230     }
 231 
 232     static class BytecodeComparer {
 233 
 234         private Bytecode expected;
 235         private Bytecode actual;
 236         private ConstantPool eCp;
 237         private ConstantPool aCp;
 238         BytecodeStream eStream;
 239         BytecodeStream aStream;
 240         int bci = -1;
 241 
 242         BytecodeComparer(Bytecode expected, Bytecode actual) {
 243             this.expected = expected;
 244             this.actual = actual;
 245             this.eCp = expected.getConstantPool();
 246             this.aCp = actual.getConstantPool();
 247             Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize());
 248             this.eStream = new BytecodeStream(expected.getCode());
 249             this.aStream = new BytecodeStream(actual.getCode());
 250         }
 251 
 252         public void compare() {
 253             try {
 254                 compare0();
 255             } catch (Throwable e) {
 256                 BytecodeDisassembler dis = new BytecodeDisassembler(true, false);
 257                 Formatter msg = new Formatter();
 258                 msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)"));
 259                 if (bci >= 0) {
 260                     msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1));
 261                     msg.format("%nactual:   %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1));
 262                 }
 263                 throw new AssertionError(msg.toString(), e);
 264             }
 265         }
 266 
 267         public void compare0() {
 268             int opcode = eStream.currentBC();
 269             ResolvedJavaMethod method = expected.getMethod();
 270             while (opcode != Bytecodes.END) {
 271                 bci = eStream.currentBCI();
 272                 int actualOpcode = aStream.currentBC();
 273                 if (opcode != actualOpcode) {
 274                     Assert.assertEquals(opcode, actualOpcode);
 275                 }
 276                 if (eStream.nextBCI() > bci + 1) {
 277                     switch (opcode) {
 278                         case BIPUSH:
 279                             Assert.assertEquals(eStream.readByte(), aStream.readByte());
 280                             break;
 281                         case SIPUSH:
 282                             Assert.assertEquals(eStream.readShort(), aStream.readShort());
 283                             break;
 284                         case NEW:
 285                         case CHECKCAST:
 286                         case INSTANCEOF:
 287                         case ANEWARRAY: {
 288                             ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
 289                             ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
 290                             assertEqualTypes(e, a);
 291                             break;
 292                         }
 293                         case GETSTATIC:
 294                         case PUTSTATIC:
 295                         case GETFIELD:
 296                         case PUTFIELD: {
 297                             ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode);
 298                             ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode);
 299                             assertEqualFields(e, a);
 300                             break;
 301                         }
 302                         case INVOKEVIRTUAL:
 303                         case INVOKESPECIAL:
 304                         case INVOKESTATIC: {
 305                             ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
 306                             ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode);
 307                             assertEqualMethods(e, a);
 308                             break;
 309                         }
 310                         case INVOKEINTERFACE: {
 311                             ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode);
 312                             ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode);
 313                             assertEqualMethods(e, a);
 314                             break;
 315                         }
 316                         case INVOKEDYNAMIC: {
 317                             // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider
 318                             return;
 319                         }
 320                         case LDC:
 321                         case LDC_W:
 322                         case LDC2_W: {
 323                             Object e = lookupConstant(eCp, eStream.readCPI(), opcode);
 324                             Object a = lookupConstant(aCp, aStream.readCPI(), opcode);
 325                             assertEqualsConstants(e, a);
 326                             break;
 327                         }
 328                         case RET:
 329                         case ILOAD:
 330                         case LLOAD:
 331                         case FLOAD:
 332                         case DLOAD:
 333                         case ALOAD:
 334                         case ISTORE:
 335                         case LSTORE:
 336                         case FSTORE:
 337                         case DSTORE:
 338                         case ASTORE: {
 339                             Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
 340                             break;
 341                         }
 342                         case IFEQ:
 343                         case IFNE:
 344                         case IFLT:
 345                         case IFGE:
 346                         case IFGT:
 347                         case IFLE:
 348                         case IF_ICMPEQ:
 349                         case IF_ICMPNE:
 350                         case IF_ICMPLT:
 351                         case IF_ICMPGE:
 352                         case IF_ICMPGT:
 353                         case IF_ICMPLE:
 354                         case IF_ACMPEQ:
 355                         case IF_ACMPNE:
 356                         case GOTO:
 357                         case JSR:
 358                         case IFNULL:
 359                         case IFNONNULL:
 360                         case GOTO_W:
 361                         case JSR_W: {
 362                             Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest());
 363                             break;
 364                         }
 365                         case LOOKUPSWITCH:
 366                         case TABLESWITCH: {
 367                             BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci);
 368                             BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci);
 369                             Assert.assertEquals(e.numberOfCases(), a.numberOfCases());
 370                             for (int i = 0; i < e.numberOfCases(); i++) {
 371                                 Assert.assertEquals(e.keyAt(i), a.keyAt(i));
 372                                 Assert.assertEquals(e.targetAt(i), a.targetAt(i));
 373                             }
 374                             Assert.assertEquals(e.defaultTarget(), a.defaultTarget());
 375                             Assert.assertEquals(e.defaultOffset(), a.defaultOffset());
 376                             break;
 377                         }
 378                         case NEWARRAY: {
 379                             Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex());
 380                             break;
 381                         }
 382                         case MULTIANEWARRAY: {
 383                             ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode);
 384                             ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode);
 385                             Assert.assertEquals(e, a);
 386                             break;
 387                         }
 388                     }
 389                 }
 390                 eStream.next();
 391                 aStream.next();
 392                 opcode = eStream.currentBC();
 393             }
 394         }
 395 
 396         static Object lookupConstant(ConstantPool cp, int cpi, int opcode) {
 397             cp.loadReferencedType(cpi, opcode);
 398             return cp.lookupConstant(cpi);
 399         }
 400 
 401         static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) {
 402             cp.loadReferencedType(cpi, opcode);
 403             return (ResolvedJavaField) cp.lookupField(cpi, method, opcode);
 404         }
 405 
 406         static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) {
 407             cp.loadReferencedType(cpi, opcode);
 408             return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode);
 409         }
 410 
 411         static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) {
 412             try {
 413                 return lookupMethod(cp, cpi, opcode);
 414             } catch (NoSuchMethodError e) {
 415                 // A method hidden to reflection
 416                 return null;
 417             }
 418         }
 419 
 420         static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) {
 421             cp.loadReferencedType(cpi, opcode);
 422             return (ResolvedJavaType) cp.lookupType(cpi, opcode);
 423         }
 424 
 425         static void assertEqualsConstants(Object e, Object a) {
 426             if (!e.equals(a)) {
 427                 Assert.assertEquals(String.valueOf(e), String.valueOf(a));
 428             }
 429         }
 430 
 431         static void assertEqualFields(JavaField e, JavaField a) {
 432             if (!e.equals(a)) {
 433                 Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T"));
 434             }
 435         }
 436 
 437         static void assertEqualTypes(JavaType e, JavaType a) {
 438             if (!e.equals(a)) {
 439                 Assert.assertEquals(e.toJavaName(), a.toJavaName());
 440             }
 441         }
 442 
 443         static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) {
 444             if (a != null) {
 445                 if (!e.equals(a)) {
 446                     if (!e.equals(a)) {
 447                         if (!e.getDeclaringClass().equals(a.getDeclaringClass())) {
 448 
 449                             if (!typesAreRelated(e, a)) {
 450                                 throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName()));
 451                             }
 452                         }
 453                         Assert.assertEquals(e.getName(), a.getName());
 454                         Assert.assertEquals(e.getSignature(), a.getSignature());
 455                     } else {
 456                         Assert.assertEquals(e, a);
 457                     }
 458                 }
 459             }
 460         }
 461 
 462         /**
 463          * The VM can resolve references to methods not available via reflection. For example, the
 464          * javap output for {@link ProfiledMethod#toString()} includes:
 465          *
 466          * <pre>
 467          *     16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String;
 468          * </pre>
 469          *
 470          * When resolving via {@code HotSpotConstantPool}, we get:
 471          *
 472          * <pre>
 473          *     16: invokeinterface#4, 1   // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String
 474          * </pre>
 475          *
 476          * However resolving via {@code ClassfileConstantPool}, we get:
 477          *
 478          * <pre>
 479          *     16: invokeinterface#40, 1  // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String
 480          * </pre>
 481          *
 482          * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only
 483          * returns methods originating from class files.
 484          *
 485          * We accept such differences for the purpose of this test if the declaring class of two
 486          * otherwise similar methods are related (i.e. one is a subclass of the other).
 487          */
 488         protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) {
 489             return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass());
 490         }
 491     }
 492 }