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