1 /*
   2  * Copyright (c) 2015, 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  * @bug 8057967
  27  * @run main/bootclasspath -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+TraceClassUnloading
  28  *                         -XX:+PrintCompilation -XX:+TraceDependencies -XX:+TraceReferenceGC
  29  *                         -verbose:gc java.lang.invoke.CallSiteDepContextTest
  30  */
  31 package java.lang.invoke;
  32 
  33 import java.lang.ref.*;
  34 import java.lang.reflect.Field;
  35 
  36 import jdk.internal.org.objectweb.asm.*;
  37 import sun.misc.Unsafe;
  38 
  39 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  40 
  41 public class CallSiteDepContextTest {
  42     static final Unsafe               UNSAFE = Unsafe.getUnsafe();
  43     static final MethodHandles.Lookup LOOKUP = MethodHandles.Lookup.IMPL_LOOKUP;
  44     static final String           CLASS_NAME = "java/lang/invoke/Test";
  45     static final String          METHOD_NAME = "m";
  46     static final MethodType             TYPE = MethodType.methodType(int.class);
  47 
  48     static MutableCallSite mcs;
  49     static MethodHandle bsmMH;
  50 
  51     static {
  52         try {
  53             bsmMH = LOOKUP.findStatic(
  54                     CallSiteDepContextTest.class, "bootstrap",
  55                     MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class));
  56         } catch(Throwable e) {
  57             throw new InternalError(e);
  58         }
  59     }
  60 
  61     public static CallSite bootstrap(MethodHandles.Lookup caller,
  62                                      String invokedName,
  63                                      MethodType invokedType) {
  64         return mcs;
  65     }
  66 
  67     static class T {
  68         static int f1() { return 1; }
  69         static int f2() { return 2; }
  70     }
  71 
  72     static byte[] getClassFile(String suffix) {
  73         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
  74         MethodVisitor mv;
  75         cw.visit(52, ACC_PUBLIC | ACC_SUPER, CLASS_NAME + suffix, null, "java/lang/Object", null);
  76         {
  77             mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, METHOD_NAME, TYPE.toMethodDescriptorString(), null, null);
  78             mv.visitCode();
  79             Handle bsm = new Handle(H_INVOKESTATIC,
  80                     "java/lang/invoke/CallSiteDepContextTest", "bootstrap",
  81                     bsmMH.type().toMethodDescriptorString());
  82             mv.visitInvokeDynamicInsn("methodName", TYPE.toMethodDescriptorString(), bsm);
  83             mv.visitInsn(IRETURN);
  84             mv.visitMaxs(0, 0);
  85             mv.visitEnd();
  86         }
  87         cw.visitEnd();
  88         return cw.toByteArray();
  89     }
  90 
  91     private static void execute(int expected, MethodHandle... mhs) throws Throwable {
  92         for (int i = 0; i < 20_000; i++) {
  93             for (MethodHandle mh : mhs) {
  94                 int r = (int) mh.invokeExact();
  95                 if (r != expected) {
  96                     throw new Error(r + " != " + expected);
  97                 }
  98             }
  99         }
 100     }
 101 
 102     public static void testHiddenDepField() throws Exception {
 103         try {
 104             Field f = MethodHandleNatives.CallSiteContext.class.getDeclaredField("vmdependencies");
 105             throw new AssertionError("Context.dependencies field should be hidden");
 106         } catch(NoSuchFieldException e) { /* expected */ }
 107     }
 108 
 109     public static void testSharedCallSite() throws Throwable {
 110         Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_1"), null);
 111         Class<?> cls2 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_2"), null);
 112 
 113         MethodHandle[] mhs = new MethodHandle[] {
 114             LOOKUP.findStatic(cls1, METHOD_NAME, TYPE),
 115             LOOKUP.findStatic(cls2, METHOD_NAME, TYPE)
 116         };
 117 
 118         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 119         execute(1, mhs);
 120         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 121         execute(2, mhs);
 122     }
 123 
 124     public static void testNonBoundCallSite() throws Throwable {
 125         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 126 
 127         // mcs.context == null
 128         MethodHandle mh = mcs.dynamicInvoker();
 129         execute(1, mh);
 130 
 131         // mcs.context == cls1
 132         Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("NonBound_1"), null);
 133         MethodHandle mh1 = LOOKUP.findStatic(cls1, METHOD_NAME, TYPE);
 134 
 135         execute(1, mh1);
 136 
 137         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 138 
 139         execute(2, mh, mh1);
 140     }
 141 
 142     static ReferenceQueue rq = new ReferenceQueue();
 143     static PhantomReference ref;
 144 
 145     public static void testGC(boolean clear, boolean precompile) throws Throwable {
 146         String id = "_" + clear + "_" + precompile;
 147 
 148         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 149 
 150         Class<?>[] cls = new Class[] {
 151                 UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_1" + id), null),
 152                 UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_2" + id), null),
 153         };
 154 
 155         MethodHandle[] mhs = new MethodHandle[] {
 156                 LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE),
 157                 LOOKUP.findStatic(cls[1], METHOD_NAME, TYPE),
 158         };
 159 
 160         // mcs.context == cls[0]
 161         int r = (int) mhs[0].invokeExact();
 162 
 163         execute(1, mhs);
 164 
 165         ref = new PhantomReference<>(cls[0], rq);
 166         cls[0] = UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_3" + id), null);
 167         mhs[0] = LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE);
 168 
 169         do {
 170             System.gc();
 171             try {
 172                 Reference ref1 = rq.remove(100);
 173                 if (ref1 == ref) {
 174                     break;
 175                 }
 176             } catch(InterruptedException e) { /* ignore */ }
 177         } while (true);
 178 
 179         if (clear) {
 180             ref.clear();
 181             System.gc(); // Ensure that the stale context is unloaded
 182         }
 183         if (precompile) {
 184             execute(1, mhs);
 185         }
 186         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 187         execute(2, mhs);
 188     }
 189 
 190     public static void main(String[] args) throws Throwable {
 191         testHiddenDepField();
 192         testSharedCallSite();
 193         testNonBoundCallSite();
 194         testGC(false, false);
 195         testGC(false,  true);
 196         testGC( true, false);
 197         testGC( true,  true);
 198         System.out.println("TEST PASSED");
 199     }
 200 }