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 Class<?> c = Class.forName(className, true, getClass().getClassLoader()); 199 ClassfileBytecodeProvider cbp = new ClassfileBytecodeProvider(metaAccess, snippetReflection); 200 for (Method method : c.getDeclaredMethods()) { 201 checkMethod(cbp, metaAccess, method); 202 } 203 } 204 205 private static void checkMethod(ClassfileBytecodeProvider cbp, MetaAccessProvider metaAccess, Executable executable) { 206 ResolvedJavaMethod method = metaAccess.lookupJavaMethod(executable); 207 if (method.hasBytecodes()) { 208 ResolvedJavaMethodBytecode expected = new ResolvedJavaMethodBytecode(method); 209 Bytecode actual = getBytecode(cbp, method); 210 new BytecodeComparer(expected, actual).compare(); 211 } 212 } 213 214 protected static Bytecode getBytecode(ClassfileBytecodeProvider cbp, ResolvedJavaMethod method) { 215 try { 216 return cbp.getBytecode(method); 217 } catch (Throwable e) { 218 throw new AssertionError(String.format("Error getting bytecode for %s", method.format("%H.%n(%p)")), e); 219 } 220 } 221 222 static class BytecodeComparer { 223 224 private Bytecode expected; 225 private Bytecode actual; 226 private ConstantPool eCp; 227 private ConstantPool aCp; 228 BytecodeStream eStream; 229 BytecodeStream aStream; 230 int bci = -1; 231 232 BytecodeComparer(Bytecode expected, Bytecode actual) { 233 this.expected = expected; 234 this.actual = actual; 235 this.eCp = expected.getConstantPool(); 236 this.aCp = actual.getConstantPool(); 237 Assert.assertEquals(expected.getMethod().toString(), expected.getCodeSize(), actual.getCodeSize()); 238 this.eStream = new BytecodeStream(expected.getCode()); 239 this.aStream = new BytecodeStream(actual.getCode()); 240 } 241 242 public void compare() { 243 try { 244 compare0(); 245 } catch (Throwable e) { 246 BytecodeDisassembler dis = new BytecodeDisassembler(true, false); 247 Formatter msg = new Formatter(); 248 msg.format("Error comparing bytecode for %s", expected.getMethod().format("%H.%n(%p)")); 249 if (bci >= 0) { 250 msg.format("%nexpected: %s", dis.disassemble(expected, bci, eStream.nextBCI() - 1)); 251 msg.format("%nactual: %s", dis.disassemble(actual, bci, aStream.nextBCI() - 1)); 252 } 253 throw new AssertionError(msg.toString(), e); 254 } 255 } 256 257 public void compare0() { 258 int opcode = eStream.currentBC(); 259 ResolvedJavaMethod method = expected.getMethod(); 260 while (opcode != Bytecodes.END) { 261 bci = eStream.currentBCI(); 262 int actualOpcode = aStream.currentBC(); 263 if (opcode != actualOpcode) { 264 Assert.assertEquals(opcode, actualOpcode); 265 } 266 if (eStream.nextBCI() > bci + 1) { 267 switch (opcode) { 268 case BIPUSH: 269 Assert.assertEquals(eStream.readByte(), aStream.readByte()); 270 break; 271 case SIPUSH: 272 Assert.assertEquals(eStream.readShort(), aStream.readShort()); 273 break; 274 case NEW: 275 case CHECKCAST: 276 case INSTANCEOF: 277 case ANEWARRAY: { 278 ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode); 279 ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode); 280 assertEqualTypes(e, a); 281 break; 282 } 283 case GETSTATIC: 284 case PUTSTATIC: 285 case GETFIELD: 286 case PUTFIELD: { 287 ResolvedJavaField e = lookupField(eCp, eStream.readCPI(), method, opcode); 288 ResolvedJavaField a = lookupField(aCp, aStream.readCPI(), method, opcode); 289 assertEqualFields(e, a); 290 break; 291 } 292 case INVOKEVIRTUAL: 293 case INVOKESPECIAL: 294 case INVOKESTATIC: { 295 ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode); 296 ResolvedJavaMethod a = lookupMethodOrNull(aCp, aStream.readCPI(), opcode); 297 assertEqualMethods(e, a); 298 break; 299 } 300 case INVOKEINTERFACE: { 301 ResolvedJavaMethod e = lookupMethod(eCp, eStream.readCPI(), opcode); 302 ResolvedJavaMethod a = lookupMethod(aCp, aStream.readCPI(), opcode); 303 assertEqualMethods(e, a); 304 break; 305 } 306 case INVOKEDYNAMIC: { 307 // INVOKEDYNAMIC is not supported by ClassfileBytecodeProvider 308 return; 309 } 310 case LDC: 311 case LDC_W: 312 case LDC2_W: { 313 Object e = lookupConstant(eCp, eStream.readCPI(), opcode); 314 Object a = lookupConstant(aCp, aStream.readCPI(), opcode); 315 assertEqualsConstants(e, a); 316 break; 317 } 318 case RET: 319 case ILOAD: 320 case LLOAD: 321 case FLOAD: 322 case DLOAD: 323 case ALOAD: 324 case ISTORE: 325 case LSTORE: 326 case FSTORE: 327 case DSTORE: 328 case ASTORE: { 329 Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex()); 330 break; 331 } 332 case IFEQ: 333 case IFNE: 334 case IFLT: 335 case IFGE: 336 case IFGT: 337 case IFLE: 338 case IF_ICMPEQ: 339 case IF_ICMPNE: 340 case IF_ICMPLT: 341 case IF_ICMPGE: 342 case IF_ICMPGT: 343 case IF_ICMPLE: 344 case IF_ACMPEQ: 345 case IF_ACMPNE: 346 case GOTO: 347 case JSR: 348 case IFNULL: 349 case IFNONNULL: 350 case GOTO_W: 351 case JSR_W: { 352 Assert.assertEquals(eStream.readBranchDest(), aStream.readBranchDest()); 353 break; 354 } 355 case LOOKUPSWITCH: 356 case TABLESWITCH: { 357 BytecodeSwitch e = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(eStream, bci) : new BytecodeTableSwitch(eStream, bci); 358 BytecodeSwitch a = opcode == LOOKUPSWITCH ? new BytecodeLookupSwitch(aStream, bci) : new BytecodeTableSwitch(aStream, bci); 359 Assert.assertEquals(e.numberOfCases(), a.numberOfCases()); 360 for (int i = 0; i < e.numberOfCases(); i++) { 361 Assert.assertEquals(e.keyAt(i), a.keyAt(i)); 362 Assert.assertEquals(e.targetAt(i), a.targetAt(i)); 363 } 364 Assert.assertEquals(e.defaultTarget(), a.defaultTarget()); 365 Assert.assertEquals(e.defaultOffset(), a.defaultOffset()); 366 break; 367 } 368 case NEWARRAY: { 369 Assert.assertEquals(eStream.readLocalIndex(), aStream.readLocalIndex()); 370 break; 371 } 372 case MULTIANEWARRAY: { 373 ResolvedJavaType e = lookupType(eCp, eStream.readCPI(), opcode); 374 ResolvedJavaType a = lookupType(aCp, aStream.readCPI(), opcode); 375 Assert.assertEquals(e, a); 376 break; 377 } 378 } 379 } 380 eStream.next(); 381 aStream.next(); 382 opcode = eStream.currentBC(); 383 } 384 } 385 386 static Object lookupConstant(ConstantPool cp, int cpi, int opcode) { 387 cp.loadReferencedType(cpi, opcode); 388 return cp.lookupConstant(cpi); 389 } 390 391 static ResolvedJavaField lookupField(ConstantPool cp, int cpi, ResolvedJavaMethod method, int opcode) { 392 cp.loadReferencedType(cpi, opcode); 393 return (ResolvedJavaField) cp.lookupField(cpi, method, opcode); 394 } 395 396 static ResolvedJavaMethod lookupMethod(ConstantPool cp, int cpi, int opcode) { 397 cp.loadReferencedType(cpi, opcode); 398 return (ResolvedJavaMethod) cp.lookupMethod(cpi, opcode); 399 } 400 401 static ResolvedJavaMethod lookupMethodOrNull(ConstantPool cp, int cpi, int opcode) { 402 try { 403 return lookupMethod(cp, cpi, opcode); 404 } catch (NoSuchMethodError e) { 405 // A method hidden to reflection 406 return null; 407 } 408 } 409 410 static ResolvedJavaType lookupType(ConstantPool cp, int cpi, int opcode) { 411 cp.loadReferencedType(cpi, opcode); 412 return (ResolvedJavaType) cp.lookupType(cpi, opcode); 413 } 414 415 static void assertEqualsConstants(Object e, Object a) { 416 if (!e.equals(a)) { 417 Assert.assertEquals(String.valueOf(e), String.valueOf(a)); 418 } 419 } 420 421 static void assertEqualFields(JavaField e, JavaField a) { 422 if (!e.equals(a)) { 423 Assert.assertEquals(e.format("%H.%n %T"), a.format("%H.%n %T")); 424 } 425 } 426 427 static void assertEqualTypes(JavaType e, JavaType a) { 428 if (!e.equals(a)) { 429 Assert.assertEquals(e.toJavaName(), a.toJavaName()); 430 } 431 } 432 433 static void assertEqualMethods(ResolvedJavaMethod e, ResolvedJavaMethod a) { 434 if (a != null) { 435 if (!e.equals(a)) { 436 if (!e.equals(a)) { 437 if (!e.getDeclaringClass().equals(a.getDeclaringClass())) { 438 439 if (!typesAreRelated(e, a)) { 440 throw new AssertionError(String.format("%s and %s are unrelated", a.getDeclaringClass().toJavaName(), e.getDeclaringClass().toJavaName())); 441 } 442 } 443 Assert.assertEquals(e.getName(), a.getName()); 444 Assert.assertEquals(e.getSignature(), a.getSignature()); 445 } else { 446 Assert.assertEquals(e, a); 447 } 448 } 449 } 450 } 451 452 /** 453 * The VM can resolve references to methods not available via reflection. For example, the 454 * javap output for {@link ProfiledMethod#toString()} includes: 455 * 456 * <pre> 457 * 16: invokeinterface #40, 1 // InterfaceMethod jdk/vm/ci/meta/ResolvedJavaMethod.getName:()Ljava/lang/String; 458 * </pre> 459 * 460 * When resolving via {@code HotSpotConstantPool}, we get: 461 * 462 * <pre> 463 * 16: invokeinterface#4, 1 // jdk.vm.ci.meta.ResolvedJavaMethod.getName:()java.lang.String 464 * </pre> 465 * 466 * However resolving via {@code ClassfileConstantPool}, we get: 467 * 468 * <pre> 469 * 16: invokeinterface#40, 1 // jdk.vm.ci.meta.JavaMethod.getName:()java.lang.String 470 * </pre> 471 * 472 * since the latter relies on {@link ResolvedJavaType#getDeclaredMethods()} which only 473 * returns methods originating from class files. 474 * 475 * We accept such differences for the purpose of this test if the declaring class of two 476 * otherwise similar methods are related (i.e. one is a subclass of the other). 477 */ 478 protected static boolean typesAreRelated(ResolvedJavaMethod e, ResolvedJavaMethod a) { 479 return a.getDeclaringClass().isAssignableFrom(e.getDeclaringClass()) || e.getDeclaringClass().isAssignableFrom(a.getDeclaringClass()); 480 } 481 } 482 }