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 checkJavaAgent()130 public void checkJavaAgent() { 131 assumeManagementLibraryIsLoadable(); 132 Assume.assumeFalse("Java Agent found -> skipping", SubprocessUtil.isJavaAgentAttached()); 133 } 134 shouldProcess(String classpathEntry)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 /** 144 * Keep test time down by only sampling a limited number of class files per jar. 145 */ 146 private static final int CLASSES_PER_JAR = 250; 147 148 @Test test()149 public void test() { 150 RuntimeProvider rt = Graal.getRequiredCapability(RuntimeProvider.class); 151 Providers providers = rt.getHostBackend().getProviders(); 152 MetaAccessProvider metaAccess = providers.getMetaAccess(); 153 154 Assume.assumeTrue(VerifyPhase.class.desiredAssertionStatus()); 155 156 String propertyName = Java8OrEarlier ? "sun.boot.class.path" : "jdk.module.path"; 157 String bootclasspath = System.getProperty(propertyName); 158 Assert.assertNotNull("Cannot find value of " + propertyName, bootclasspath); 159 160 for (String path : bootclasspath.split(File.pathSeparator)) { 161 if (shouldProcess(path)) { 162 try { 163 final ZipFile zipFile = new ZipFile(new File(path)); 164 int index = 0; 165 int step = zipFile.size() > CLASSES_PER_JAR ? zipFile.size() / CLASSES_PER_JAR : 1; 166 for (final Enumeration<? extends ZipEntry> entry = zipFile.entries(); entry.hasMoreElements();) { 167 final ZipEntry zipEntry = entry.nextElement(); 168 if ((index % step) == 0) { 169 String name = zipEntry.getName(); 170 if (name.endsWith(".class") && !name.equals("module-info.class") && !name.startsWith("META-INF/versions/")) { 171 String className = name.substring(0, name.length() - ".class".length()).replace('/', '.'); 172 if (isInNativeImage(className)) { 173 /* 174 * Native image requires non-graalsdk classes to be present in 175 * the classpath. 176 */ 177 continue; 178 } 179 if (isGSON(className)) { 180 /* uses old class format */ 181 continue; 182 } 183 try { 184 checkClass(metaAccess, getSnippetReflection(), className); 185 } catch (ClassNotFoundException e) { 186 throw new AssertionError(e); 187 } 188 } 189 } 190 index++; 191 } 192 } catch (IOException ex) { 193 Assert.fail(ex.toString()); 194 } 195 } 196 } 197 } 198 isInNativeImage(String className)199 private static boolean isInNativeImage(String className) { 200 return className.startsWith("org.graalvm.nativeimage"); 201 } 202 isGSON(String className)203 private static boolean isGSON(String className) { 204 return className.contains("com.google.gson"); 205 } 206 checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className)207 protected void checkClass(MetaAccessProvider metaAccess, SnippetReflectionProvider snippetReflection, String className) throws ClassNotFoundException { 208 if (className.equals("jdk.vm.ci.services.JVMCIClassLoaderFactory")) { 209 // JVMCIClassLoaderFactory must only be initialized by the VM 210 return; 211 } 212 Class<?> c = Class.forName(className, true, getClass().getClassLoader()); 213 ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection); 214 for (Method method : c.getDeclaredMethods()) { 215 checkMethod(cbp, metaAccess, method); 216 } 217 } 218 checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable)219 private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) { 220 ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable); 221 if (method.hasBytecodes()) { 222 Bytecode actual = getBytecode(cbp, method); 223 if (actual != null) { 224 ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method); 225 new BytecodeComparer(expected, actual).compare(); 226 } 227 } 228 } 229 getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method)230 protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) { 231 try { 232 return cbp.getBytecode(method); 233 } catch (UnsupportedClassVersionError e) { 234 // This can happen when a library containing old class files 235 // is bundled into a Graal jar (GR-12672). 236 return null; 237 } catch (Throwable e) { 238 throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e); 239 } 240 } 241 242 static class BytecodeComparer { 243 244 private Bytecode expected; 245 private Bytecode actual; 246 private ConstantPool eCp; 247 private ConstantPool aCp; 248 BytecodeStream eStream; 249 BytecodeStream aStream; 250 int bci = -1; 251 BytecodeComparer(Bytecode expected, Bytecode actual)252 BytecodeComparer(Bytecode expected, Bytecode actual) { 253 this.expected = expected; 254 this.actual = actual; 255 this.eCp = expected.getConstantPool(); 256 this.aCp = actual.getConstantPool(); 257 Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize()); 258 this.eStream = new BytecodeStream(expected.getCode()); 259 this.aStream = new BytecodeStream(actual.getCode()); 260 } 261 compare()262 public void compare() { 263 try { 264 compare0(); 265 } catch (Throwable e) { 266 BytecodeDisassembler dis = new BytecodeDisassembler(true, false); 267 Formatter msg = new Formatter(); 268 msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)")); 269 if (bci >= 0) { 270 msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1)); 271 msg.format("%nactual: %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1)); 272 } 273 throw new AssertionError(msg.toString(), e); 274 } 275 } 276 compare0()277 public void compare0() { 278 int opcode = eStream.currentBC(); 279 ResolvedJavaMethod method = expected.getMethod(); 280 while (opcode != Bytecodes.END) { 281 bci = eStream.currentBCI(); 282 int actualOpcode = aStream.currentBC(); 283 if (opcode != actualOpcode) { 284 Assert.assertEquals(opcode, actualOpcode); 285 } 286 if (eStream.nextBCI() > bci + 1) { 287 switch (opcode) { 288 case BIPUSH: 289 Assert.assertEquals(eStream.readByte(), aStream.readByte()); 290 break; 291 case SIPUSH: 292 Assert.assertEquals(eStream.readShort(), aStream.readShort()); 293 break; 294 case NEW: 295 case CHECKCAST: 296 case INSTANCEOF: 297 case ANEWARRAY: { 298 ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode); 299 ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode); 300 assertEqualTypes(e, a); 301 break; 302 } 303 case GETSTATIC: 304 case PUTSTATIC: 305 case GETFIELD: 306 case PUTFIELD: { 307 ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode); 308 ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode); 309 assertEqualFields(e, a); 310 break; 311 } 312 case INVOKEVIRTUAL: 313 case INVOKESPECIAL: 314 case INVOKESTATIC: { 315 ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode); 316 ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode); 317 assertEqualMethods(e, a); 318 break; 319 } 320 case INVOKEINTERFACE: { 321 ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode); 322 ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode); 323 assertEqualMethods(e, a); 324 break; 325 } 326 case INVOKEDYNAMIC: { 327 // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider 328 return; 329 } 330 case LDC: 331 case LDC_W: 332 case LDC2_W: { 333 Object e = lookupConstant(eCp, eStream.readCPI(), opcode); 334 Object a = lookupConstant(aCp, aStream.readCPI(), opcode); 335 assertEqualsConstants(e, a); 336 break; 337 } 338 case RET: 339 case ILOAD: 340 case LLOAD: 341 case FLOAD: 342 case DLOAD: 343 case ALOAD: 344 case ISTORE: 345 case LSTORE: 346 case FSTORE: 347 case DSTORE: 348 case ASTORE: { 349 Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex()); 350 break; 351 } 352 case IFEQ: 353 case IFNE: 354 case IFLT: 355 case IFGE: 356 case IFGT: 357 case IFLE: 358 case IF_ICMPEQ: 359 case IF_ICMPNE: 360 case IF_ICMPLT: 361 case IF_ICMPGE: 362 case IF_ICMPGT: 363 case IF_ICMPLE: 364 case IF_ACMPEQ: 365 case IF_ACMPNE: 366 case GOTO: 367 case JSR: 368 case IFNULL: 369 case IFNONNULL: 370 case GOTO_W: 371 case JSR_W: { 372 Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest()); 373 break; 374 } 375 case LOOKUPSWITCH: 376 case TABLESWITCH: { 377 BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci); 378 BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci); 379 Assert.assertEquals(e.numberOfCases(), a.numberOfCases()); 380 for (int i = 0; i < e.numberOfCases(); i++) { 381 Assert.assertEquals(e.keyAt(i), a.keyAt(i)); 382 Assert.assertEquals(e.targetAt(i), a.targetAt(i)); 383 } 384 Assert.assertEquals(e.defaultTarget(), a.defaultTarget()); 385 Assert.assertEquals(e.defaultOffset(), a.defaultOffset()); 386 break; 387 } 388 case NEWARRAY: { 389 Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex()); 390 break; 391 } 392 case MULTIANEWARRAY: { 393 ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode); 394 ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode); 395 Assert.assertEquals(e, a); 396 break; 397 } 398 } 399 } 400 eStream.next(); 401 aStream.next(); 402 opcode = eStream.currentBC(); 403 } 404 } 405 lookupConstant(ConstantPool cp, int cpi, int opcode)406 static Object lookupConstant(ConstantPool cp, int cpi, int opcode) { 407 cp.loadReferencedType(cpi, opcode); 408 return cp.lookupConstant(cpi); 409 } 410 lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode)411 static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) { 412 cp.loadReferencedType(cpi, opcode); 413 return (ResolvedJavaField) cp.lookupField(cpi, method, opcode); 414 } 415 lookupMethod(ConstantPool cp, int cpi, int opcode)416 static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) { 417 cp.loadReferencedType(cpi, opcode); 418 return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode); 419 } 420 lookupMethodOrNull(ConstantPool cp, int cpi, int opcode)421 static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) { 422 try { 423 return lookupMethod(cp, cpi, opcode); 424 } catch (NoSuchMethodError e) { 425 // A method hidden to reflection 426 return null; 427 } 428 } 429 lookupType(ConstantPool cp, int cpi, int opcode)430 static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) { 431 cp.loadReferencedType(cpi, opcode); 432 return (ResolvedJavaType) cp.lookupType(cpi, opcode); 433 } 434 assertEqualsConstants(Object e, Object a)435 static void assertEqualsConstants(Object e, Object a) { 436 if (!e.equals(a)) { 437 Assert.assertEquals(String.valueOf(e), String.valueOf(a)); 438 } 439 } 440 assertEqualFields(JavaField e, JavaField a)441 static void assertEqualFields(JavaField e, JavaField a) { 442 if (!e.equals(a)) { 443 Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T")); 444 } 445 } 446 assertEqualTypes(JavaType e, JavaType a)447 static void assertEqualTypes(JavaType e, JavaType a) { 448 if (!e.equals(a)) { 449 Assert.assertEquals(e.toJavaName(), a.toJavaName()); 450 } 451 } 452 assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a)453 static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) { 454 if (a != null) { 455 if (!e.equals(a)) { 456 if (!e.equals(a)) { 457 if (!e.getDeclaringClass().equals(a.getDeclaringClass())) { 458 459 if (!typesAreRelated(e, a)) { 460 throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName())); 461 } 462 } 463 Assert.assertEquals(e.getName(), a.getName()); 464 Assert.assertEquals(e.getSignature(), a.getSignature()); 465 } else { 466 Assert.assertEquals(e, a); 467 } 468 } 469 } 470 } 471 472 /** 473 * The VM can resolve references to methods not available via reflection. For example, the 474 * javap output for {@link ProfiledMethod#toString()} includes: 475 * 476 * <pre> 477 * 16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String; 478 * </pre> 479 * 480 * When resolving via {@code HotSpotConstantPool}, we get: 481 * 482 * <pre> 483 * 16: invokeinterface#4, 1 // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String 484 * </pre> 485 * 486 * However resolving via {@code ClassfileConstantPool}, we get: 487 * 488 * <pre> 489 * 16: invokeinterface#40, 1 // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String 490 * </pre> 491 * 492 * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only 493 * returns methods originating from class files. 494 * 495 * We accept such differences for the purpose of this test if the declaring class of two 496 * otherwise similar methods are related (i.e. one is a subclass of the other). 497 */ typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a)498 protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) { 499 return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()); 500 } 501 } 502 } 503