1 /*
   2  * Copyright (c) 2012, 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 
  24 /**
  25  * @test
  26  * @requires vm.jvmci
  27  * @library ../../../../../
  28  * @modules jdk.internal.vm.ci/jdk.vm.ci.meta
  29  *          jdk.internal.vm.ci/jdk.vm.ci.runtime
  30  *          java.base/jdk.internal.misc
  31  * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -Djvmci.Compiler=null jdk.vm.ci.runtime.test.TestResolvedJavaMethod
  32  */
  33 
  34 package jdk.vm.ci.runtime.test;
  35 
  36 import static org.junit.Assert.assertArrayEquals;
  37 import static org.junit.Assert.assertEquals;
  38 import static org.junit.Assert.assertFalse;
  39 import static org.junit.Assert.assertNotNull;
  40 import static org.junit.Assert.assertTrue;
  41 
  42 import java.lang.annotation.Annotation;
  43 import java.lang.annotation.ElementType;
  44 import java.lang.annotation.Retention;
  45 import java.lang.annotation.RetentionPolicy;
  46 import java.lang.annotation.Target;
  47 import java.lang.reflect.Constructor;
  48 import java.lang.reflect.Member;
  49 import java.lang.reflect.Method;
  50 import java.lang.reflect.Modifier;
  51 import java.lang.reflect.Type;
  52 import java.util.Arrays;
  53 import java.util.HashMap;
  54 import java.util.HashSet;
  55 import java.util.Map;
  56 import java.util.Set;
  57 
  58 import org.junit.Assert;
  59 import org.junit.Test;
  60 
  61 import jdk.vm.ci.meta.ConstantPool;
  62 import jdk.vm.ci.meta.ExceptionHandler;
  63 import jdk.vm.ci.meta.ResolvedJavaMethod;
  64 import jdk.vm.ci.meta.ResolvedJavaMethod.Parameter;
  65 import jdk.vm.ci.meta.ResolvedJavaType;
  66 
  67 /**
  68  * Tests for {@link ResolvedJavaMethod}.
  69  */
  70 public class TestResolvedJavaMethod extends MethodUniverse {
  71 
  72     public TestResolvedJavaMethod() {
  73     }
  74 
  75     /**
  76      * @see ResolvedJavaMethod#getCode()
  77      */
  78     @Test
  79     public void getCodeTest() {
  80         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
  81             ResolvedJavaMethod m = e.getValue();
  82             byte[] code = m.getCode();
  83             if (code == null) {
  84                 assertTrue(m.getCodeSize() == 0);
  85             } else {
  86                 if (m.isAbstract()) {
  87                     assertTrue(code.length == 0);
  88                 } else if (!m.isNative()) {
  89                     assertTrue(code.length > 0);
  90                 }
  91             }
  92         }
  93     }
  94 
  95     /**
  96      * @see ResolvedJavaMethod#getCodeSize()
  97      */
  98     @Test
  99     public void getCodeSizeTest() {
 100         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 101             ResolvedJavaMethod m = e.getValue();
 102             int codeSize = m.getCodeSize();
 103             if (m.isAbstract()) {
 104                 assertTrue(codeSize == 0);
 105             } else if (!m.isNative()) {
 106                 assertTrue(codeSize > 0);
 107             }
 108         }
 109     }
 110 
 111     @Test
 112     public void getModifiersTest() {
 113         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 114             ResolvedJavaMethod m = e.getValue();
 115             int expected = e.getKey().getModifiers();
 116             int actual = m.getModifiers();
 117             assertEquals(String.format("%s: 0x%x != 0x%x", m, expected, actual), expected, actual);
 118         }
 119         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 120             ResolvedJavaMethod m = e.getValue();
 121             int expected = e.getKey().getModifiers();
 122             int actual = m.getModifiers();
 123             assertEquals(String.format("%s: 0x%x != 0x%x", m, expected, actual), expected, actual);
 124         }
 125     }
 126 
 127     /**
 128      * @see ResolvedJavaMethod#isClassInitializer()
 129      */
 130     @Test
 131     public void isClassInitializerTest() {
 132         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 133             // Class initializers are hidden from reflection
 134             ResolvedJavaMethod m = e.getValue();
 135             assertFalse(m.isClassInitializer());
 136         }
 137         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 138             ResolvedJavaMethod m = e.getValue();
 139             assertFalse(m.isClassInitializer());
 140         }
 141     }
 142 
 143     @Test
 144     public void isConstructorTest() {
 145         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 146             ResolvedJavaMethod m = e.getValue();
 147             assertFalse(m.isConstructor());
 148         }
 149         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 150             ResolvedJavaMethod m = e.getValue();
 151             assertTrue(m.isConstructor());
 152         }
 153     }
 154 
 155     @Test
 156     public void isSyntheticTest() {
 157         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 158             ResolvedJavaMethod m = e.getValue();
 159             assertEquals(e.getKey().isSynthetic(), m.isSynthetic());
 160         }
 161         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 162             ResolvedJavaMethod m = e.getValue();
 163             assertEquals(e.getKey().isSynthetic(), m.isSynthetic());
 164         }
 165     }
 166 
 167     @Test
 168     public void isBridgeTest() {
 169         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 170             ResolvedJavaMethod m = e.getValue();
 171             assertEquals(e.getKey().isBridge(), m.isBridge());
 172         }
 173         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 174             ResolvedJavaMethod m = e.getValue();
 175             assertEquals(false, m.isBridge());
 176         }
 177     }
 178 
 179     @Test
 180     public void isVarArgsTest() {
 181         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 182             ResolvedJavaMethod m = e.getValue();
 183             assertEquals(e.getKey().isVarArgs(), m.isVarArgs());
 184         }
 185         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 186             ResolvedJavaMethod m = e.getValue();
 187             assertEquals(e.getKey().isVarArgs(), m.isVarArgs());
 188         }
 189     }
 190 
 191     @Test
 192     public void isSynchronizedTest() {
 193         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 194             ResolvedJavaMethod m = e.getValue();
 195             assertEquals(Modifier.isSynchronized(e.getKey().getModifiers()), m.isSynchronized());
 196         }
 197         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 198             ResolvedJavaMethod m = e.getValue();
 199             assertEquals(Modifier.isSynchronized(e.getKey().getModifiers()), m.isSynchronized());
 200         }
 201     }
 202 
 203     @Test
 204     public void canBeStaticallyBoundTest() {
 205         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 206             ResolvedJavaMethod m = e.getValue();
 207             assertEquals(m.canBeStaticallyBound(), canBeStaticallyBound(e.getKey()));
 208         }
 209         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 210             ResolvedJavaMethod m = e.getValue();
 211             assertEquals(m.canBeStaticallyBound(), canBeStaticallyBound(e.getKey()));
 212         }
 213     }
 214 
 215     private static boolean canBeStaticallyBound(Member method) {
 216         int modifiers = method.getModifiers();
 217         return (Modifier.isFinal(modifiers) || Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers) || Modifier.isFinal(method.getDeclaringClass().getModifiers())) &&
 218                         !Modifier.isAbstract(modifiers);
 219     }
 220 
 221     private static String methodWithExceptionHandlers(String p1, Object o2) {
 222         try {
 223             return p1.substring(100) + o2.toString();
 224         } catch (IndexOutOfBoundsException e) {
 225             e.printStackTrace();
 226         } catch (NullPointerException e) {
 227             e.printStackTrace();
 228         } catch (RuntimeException e) {
 229             e.printStackTrace();
 230         }
 231         return null;
 232     }
 233 
 234     @Test
 235     public void getExceptionHandlersTest() throws NoSuchMethodException {
 236         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("methodWithExceptionHandlers", String.class, Object.class));
 237         ExceptionHandler[] handlers = method.getExceptionHandlers();
 238         assertNotNull(handlers);
 239         assertEquals(handlers.length, 3);
 240         handlers[0].getCatchType().equals(metaAccess.lookupJavaType(IndexOutOfBoundsException.class));
 241         handlers[1].getCatchType().equals(metaAccess.lookupJavaType(NullPointerException.class));
 242         handlers[2].getCatchType().equals(metaAccess.lookupJavaType(RuntimeException.class));
 243     }
 244 
 245     private static String nullPointerExceptionOnFirstLine(Object o, String ignored) {
 246         return o.toString() + ignored;
 247     }
 248 
 249     @Test
 250     public void asStackTraceElementTest() throws NoSuchMethodException {
 251         try {
 252             nullPointerExceptionOnFirstLine(null, "ignored");
 253             Assert.fail("should not reach here");
 254         } catch (NullPointerException e) {
 255             StackTraceElement expected = e.getStackTrace()[0];
 256             ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("nullPointerExceptionOnFirstLine", Object.class, String.class));
 257             StackTraceElement actual = method.asStackTraceElement(0);
 258             assertEquals(expected, actual);
 259         }
 260     }
 261 
 262     @Test
 263     public void getConstantPoolTest() {
 264         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 265             ResolvedJavaMethod m = e.getValue();
 266             ConstantPool cp = m.getConstantPool();
 267             assertTrue(cp.length() > 0);
 268         }
 269     }
 270 
 271     @Test
 272     public void getParametersTest() {
 273         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 274             java.lang.reflect.Parameter[] expected = e.getKey().getParameters();
 275             Parameter[] actual = e.getValue().getParameters();
 276             assertEquals(actual.length, expected.length);
 277             for (int i = 0; i < actual.length; i++) {
 278                 java.lang.reflect.Parameter exp = expected[i];
 279                 Parameter act = actual[i];
 280                 assertEquals(exp.getName(), act.getName());
 281                 assertEquals(exp.isNamePresent(), act.isNamePresent());
 282                 assertEquals(exp.getModifiers(), act.getModifiers());
 283                 assertArrayEquals(exp.getAnnotations(), act.getAnnotations());
 284                 assertEquals(exp.getType().getName(), act.getType().toClassName());
 285                 assertEquals(exp.getParameterizedType(), act.getParameterizedType());
 286                 assertEquals(metaAccess.lookupJavaMethod(exp.getDeclaringExecutable()), act.getDeclaringMethod());
 287             }
 288         }
 289     }
 290 
 291     @Retention(RetentionPolicy.RUNTIME)
 292     @Target(ElementType.METHOD)
 293     @interface TestAnnotation {
 294         long value();
 295     }
 296 
 297     @Test
 298     @TestAnnotation(value = 1000L)
 299     public void getAnnotationTest() throws NoSuchMethodException {
 300         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("getAnnotationTest"));
 301         TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
 302         assertNotNull(annotation);
 303         assertEquals(1000L, annotation.value());
 304     }
 305 
 306     @Test
 307     @TestAnnotation(value = 1000L)
 308     public void getAnnotationsTest() throws NoSuchMethodException {
 309         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("getAnnotationsTest"));
 310         Annotation[] annotations = method.getAnnotations();
 311         assertNotNull(annotations);
 312         assertEquals(2, annotations.length);
 313         TestAnnotation annotation = null;
 314         for (Annotation a : annotations) {
 315             if (a instanceof TestAnnotation) {
 316                 annotation = (TestAnnotation) a;
 317                 break;
 318             }
 319         }
 320         assertNotNull(annotation);
 321         assertEquals(1000L, annotation.value());
 322     }
 323 
 324     @Retention(RetentionPolicy.RUNTIME)
 325     @Target(ElementType.PARAMETER)
 326     @interface NonNull {
 327     }
 328 
 329     @Retention(RetentionPolicy.RUNTIME)
 330     @Target(ElementType.PARAMETER)
 331     @interface Special {
 332     }
 333 
 334     private static native void methodWithAnnotatedParameters(@NonNull HashMap<String, String> p1, @Special @NonNull Class<? extends Annotation> p2);
 335 
 336     @Test
 337     public void getParameterAnnotationsTest() throws NoSuchMethodException {
 338         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("methodWithAnnotatedParameters", HashMap.class, Class.class));
 339         Annotation[][] annotations = method.getParameterAnnotations();
 340         assertEquals(2, annotations.length);
 341         assertEquals(1, annotations[0].length);
 342         assertEquals(NonNull.class, annotations[0][0].annotationType());
 343         assertEquals(2, annotations[1].length);
 344         assertEquals(Special.class, annotations[1][0].annotationType());
 345         assertEquals(NonNull.class, annotations[1][1].annotationType());
 346     }
 347 
 348     @Test
 349     public void getGenericParameterTypesTest() throws NoSuchMethodException {
 350         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("methodWithAnnotatedParameters", HashMap.class, Class.class));
 351         Type[] genericParameterTypes = method.getGenericParameterTypes();
 352         assertEquals(2, genericParameterTypes.length);
 353         assertEquals("java.util.HashMap<java.lang.String, java.lang.String>", genericParameterTypes[0].toString());
 354         assertEquals("java.lang.Class<? extends java.lang.annotation.Annotation>", genericParameterTypes[1].toString());
 355     }
 356 
 357     @Test
 358     public void getMaxLocalsTest() throws NoSuchMethodException {
 359         ResolvedJavaMethod method1 = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("methodWithAnnotatedParameters", HashMap.class, Class.class));
 360         ResolvedJavaMethod method2 = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("nullPointerExceptionOnFirstLine", Object.class, String.class));
 361         assertEquals(0, method1.getMaxLocals());
 362         assertEquals(2, method2.getMaxLocals());
 363 
 364     }
 365 
 366     @Test
 367     public void getMaxStackSizeTest() throws NoSuchMethodException {
 368         ResolvedJavaMethod method1 = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("methodWithAnnotatedParameters", HashMap.class, Class.class));
 369         ResolvedJavaMethod method2 = metaAccess.lookupJavaMethod(getClass().getDeclaredMethod("nullPointerExceptionOnFirstLine", Object.class, String.class));
 370         assertEquals(0, method1.getMaxStackSize());
 371         // some versions of javac produce bytecode with a stacksize of 2 for this method
 372         // JSR 292 also sometimes need one more stack slot
 373         int method2StackSize = method2.getMaxStackSize();
 374         assertTrue(2 <= method2StackSize && method2StackSize <= 4);
 375     }
 376 
 377     @Test
 378     public void isDefaultTest() {
 379         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 380             ResolvedJavaMethod m = e.getValue();
 381             assertEquals(e.getKey().isDefault(), m.isDefault());
 382         }
 383         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 384             ResolvedJavaMethod m = e.getValue();
 385             assertFalse(m.isDefault());
 386         }
 387     }
 388 
 389     @Test
 390     public void hasReceiverTest() {
 391         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 392             ResolvedJavaMethod m = e.getValue();
 393             assertTrue(m.hasReceiver() != Modifier.isStatic(e.getKey().getModifiers()));
 394         }
 395         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 396             ResolvedJavaMethod m = e.getValue();
 397             assertTrue(m.hasReceiver());
 398         }
 399     }
 400 
 401     @Test
 402     public void hasBytecodesTest() {
 403         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 404             ResolvedJavaMethod m = e.getValue();
 405             assertTrue(m.hasBytecodes() == (m.isConcrete() && !m.isNative()));
 406         }
 407         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 408             ResolvedJavaMethod m = e.getValue();
 409             assertTrue(m.hasBytecodes());
 410         }
 411     }
 412 
 413     @Test
 414     public void isJavaLangObjectInitTest() throws NoSuchMethodException {
 415         ResolvedJavaMethod method = metaAccess.lookupJavaMethod(Object.class.getConstructor());
 416         assertTrue(method.isJavaLangObjectInit());
 417         for (Map.Entry<Method, ResolvedJavaMethod> e : methods.entrySet()) {
 418             ResolvedJavaMethod m = e.getValue();
 419             assertFalse(m.isJavaLangObjectInit());
 420         }
 421         for (Map.Entry<Constructor<?>, ResolvedJavaMethod> e : constructors.entrySet()) {
 422             ResolvedJavaMethod m = e.getValue();
 423             Constructor<?> key = e.getKey();
 424             if (key.getDeclaringClass() == Object.class && key.getParameters().length == 0) {
 425                 assertTrue(m.isJavaLangObjectInit());
 426             } else {
 427                 assertFalse(m.isJavaLangObjectInit());
 428             }
 429         }
 430     }
 431 
 432     /**
 433      * All public non-final methods should be available in the vtable.
 434      */
 435     @Test
 436     public void testVirtualMethodTableAccess() {
 437         for (Class<?> c : classes) {
 438             if (c.isPrimitive() || c.isInterface()) {
 439                 continue;
 440             }
 441             ResolvedJavaType receiverType = metaAccess.lookupJavaType(c);
 442             for (Method m : c.getMethods()) {
 443                 ResolvedJavaMethod method = metaAccess.lookupJavaMethod(m);
 444                 if (!method.isStatic() && !method.isFinal() && !method.getDeclaringClass().isLeaf() && !method.getDeclaringClass().isInterface()) {
 445                     assertTrue(method + " not available in " + receiverType, method.isInVirtualMethodTable(receiverType));
 446                 }
 447             }
 448         }
 449     }
 450 
 451     private Method findTestMethod(Method apiMethod) {
 452         String testName = apiMethod.getName() + "Test";
 453         for (Method m : getClass().getDeclaredMethods()) {
 454             if (m.getName().equals(testName) && m.getAnnotation(Test.class) != null) {
 455                 return m;
 456             }
 457         }
 458         return null;
 459     }
 460 
 461     // @formatter:off
 462     private static final String[] untestedApiMethods = {
 463         "newInstance",
 464         "getDeclaringClass",
 465         "getEncoding",
 466         "getProfilingInfo",
 467         "reprofile",
 468         "getCompilerStorage",
 469         "hasNeverInlineDirective",
 470         "canBeInlined",
 471         "shouldBeInlined",
 472         "getLineNumberTable",
 473         "getLocalVariableTable",
 474         "isInVirtualMethodTable",
 475         "toParameterTypes",
 476         "getParameterAnnotation",
 477         "getSpeculationLog",
 478         "isFinal",
 479         "invoke",
 480         "$jacocoInit"
 481     };
 482     // @formatter:on
 483 
 484     /**
 485      * Ensures that any new methods added to {@link ResolvedJavaMethod} either have a test written
 486      * for them or are added to {@link #untestedApiMethods}.
 487      */
 488     @Test
 489     public void testCoverage() {
 490         Set<String> known = new HashSet<>(Arrays.asList(untestedApiMethods));
 491         for (Method m : ResolvedJavaMethod.class.getDeclaredMethods()) {
 492             if (Modifier.isStatic(m.getModifiers())) {
 493                 continue;
 494             }
 495             if (findTestMethod(m) == null) {
 496                 assertTrue("test missing for " + m, known.contains(m.getName()));
 497             } else {
 498                 assertFalse("test should be removed from untestedApiMethods" + m, known.contains(m.getName()));
 499             }
 500         }
 501     }
 502 }