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