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