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