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