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         Class<?> contextClass = Class.forName("java.lang.invoke.CallSite$Context");
 104         try {
 105             Field f = contextClass.getDeclaredField("dependencies");
 106             throw new AssertionError("Context.dependencies field should be hidden");
 107         } catch(NoSuchFieldException e) { /* expected */ }
 108     }
 109 
 110     public static void testSharedCallSite() throws Throwable {
 111         Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_1"), null);
 112         Class<?> cls2 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("CS_2"), null);
 113 
 114         MethodHandle[] mhs = new MethodHandle[] {
 115             LOOKUP.findStatic(cls1, METHOD_NAME, TYPE),
 116             LOOKUP.findStatic(cls2, METHOD_NAME, TYPE)
 117         };
 118 
 119         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 120         execute(1, mhs);
 121         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 122         execute(2, mhs);
 123     }
 124 
 125     public static void testNonBoundCallSite() throws Throwable {
 126         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 127 
 128         // mcs.context == null
 129         MethodHandle mh = mcs.dynamicInvoker();
 130         execute(1, mh);
 131 
 132         // mcs.context == cls1
 133         Class<?> cls1 = UNSAFE.defineAnonymousClass(Object.class, getClassFile("NonBound_1"), null);
 134         MethodHandle mh1 = LOOKUP.findStatic(cls1, METHOD_NAME, TYPE);
 135 
 136         execute(1, mh1);
 137 
 138         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 139 
 140         execute(2, mh, mh1);
 141     }
 142 
 143     static ReferenceQueue rq = new ReferenceQueue();
 144     static PhantomReference ref;
 145 
 146     public static void testGC(boolean clear, boolean precompile) throws Throwable {
 147         String id = "_" + clear + "_" + precompile;
 148 
 149         mcs = new MutableCallSite(LOOKUP.findStatic(T.class, "f1", TYPE));
 150 
 151         Class<?>[] cls = new Class[] {
 152                 UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_1" + id), null),
 153                 UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_2" + id), null),
 154         };
 155 
 156         MethodHandle[] mhs = new MethodHandle[] {
 157                 LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE),
 158                 LOOKUP.findStatic(cls[1], METHOD_NAME, TYPE),
 159         };
 160 
 161         // mcs.context == cls[0]
 162         int r = (int) mhs[0].invokeExact();
 163 
 164         execute(1, mhs);
 165 
 166         ref = new PhantomReference<>(cls[0], rq);
 167         cls[0] = UNSAFE.defineAnonymousClass(Object.class, getClassFile("GC_3" + id), null);
 168         mhs[0] = LOOKUP.findStatic(cls[0], METHOD_NAME, TYPE);
 169 
 170         do {
 171             System.gc();
 172             try {
 173                 Reference ref1 = rq.remove(100);
 174                 if (ref1 == ref) {
 175                     break;
 176                 }
 177             } catch(InterruptedException e) { /* ignore */ }
 178         } while (true);
 179 
 180         if (clear) {
 181             ref.clear();
 182             System.gc(); // Ensure that the stale context is unloaded
 183         }
 184         if (precompile) {
 185             execute(1, mhs);
 186         }
 187         mcs.setTarget(LOOKUP.findStatic(T.class, "f2", TYPE));
 188         execute(2, mhs);
 189     }
 190 
 191     public static void main(String[] args) throws Throwable {
 192         testHiddenDepField();
 193         testSharedCallSite();
 194         testNonBoundCallSite();
 195         testGC(false, false);
 196         testGC(false,  true);
 197         testGC( true, false);
 198         testGC( true,  true);
 199         System.out.println("TEST PASSED");
 200     }
 201 }