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
38package compiler.jsr292;
39
40import jdk.internal.misc.Unsafe;
41import jdk.internal.org.objectweb.asm.ClassWriter;
42import jdk.internal.org.objectweb.asm.Handle;
43import jdk.internal.org.objectweb.asm.MethodVisitor;
44
45import java.lang.invoke.CallSite;
46import java.lang.invoke.MethodHandle;
47import java.lang.invoke.MethodHandleHelper;
48import java.lang.invoke.MethodHandles;
49import java.lang.invoke.MethodType;
50import java.lang.invoke.MutableCallSite;
51import java.lang.ref.PhantomReference;
52import java.lang.ref.Reference;
53import java.lang.ref.ReferenceQueue;
54import java.lang.reflect.Field;
55
56import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
57import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
58import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
59import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
60import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
61
62public class CallSiteDepContextTest {
63    static final Unsafe               UNSAFE = Unsafe.getUnsafe();
64    static final MethodHandles.Lookup LOOKUP = MethodHandleHelper.IMPL_LOOKUP;
65    static final String           CLASS_NAME = "compiler/jsr292/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(CallSiteDepContextTest.class, getClassFile("CS_1"), null);
133        Class<?> cls2 = UNSAFE.defineAnonymousClass(CallSiteDepContextTest.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(CallSiteDepContextTest.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(CallSiteDepContextTest.class, getClassFile("GC_1" + id), null),
174                UNSAFE.defineAnonymousClass(CallSiteDepContextTest.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(CallSiteDepContextTest.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
224