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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.dynalink.test;
26
27import static jdk.dynalink.StandardNamespace.PROPERTY;
28import static jdk.dynalink.StandardOperation.GET;
29
30import java.lang.invoke.CallSite;
31import java.lang.invoke.MethodHandle;
32import java.lang.invoke.MethodHandles;
33import java.lang.invoke.MethodType;
34import java.util.List;
35import java.util.ServiceConfigurationError;
36import javax.script.ScriptEngine;
37import javax.script.ScriptEngineManager;
38import jdk.dynalink.CallSiteDescriptor;
39import jdk.dynalink.DynamicLinker;
40import jdk.dynalink.DynamicLinkerFactory;
41import jdk.dynalink.NoSuchDynamicMethodException;
42import jdk.dynalink.Operation;
43import jdk.dynalink.StandardNamespace;
44import jdk.dynalink.StandardOperation;
45import jdk.dynalink.beans.StaticClass;
46import jdk.dynalink.linker.GuardedInvocation;
47import jdk.dynalink.linker.GuardingDynamicLinker;
48import jdk.dynalink.linker.LinkRequest;
49import jdk.dynalink.linker.LinkerServices;
50import jdk.dynalink.support.SimpleRelinkableCallSite;
51import jdk.nashorn.api.scripting.AbstractJSObject;
52import org.testng.Assert;
53import org.testng.annotations.Test;
54
55@SuppressWarnings("javadoc")
56public class DynamicLinkerFactoryTest {
57
58    private static final Operation GET_PROPERTY = GET.withNamespace(PROPERTY);
59
60    private static DynamicLinkerFactory newDynamicLinkerFactory(final boolean resetClassLoader) {
61        final DynamicLinkerFactory factory = new DynamicLinkerFactory();
62        if (resetClassLoader) {
63            factory.setClassLoader(null);
64        }
65        return factory;
66    }
67
68    @Test
69    public void callSiteCreationTest() {
70        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
71        final DynamicLinker linker = factory.createLinker();
72        final StandardOperation[] operations = StandardOperation.values();
73        final MethodType mt = MethodType.methodType(Object.class, Object.class);
74        for (final Operation op : operations) {
75            final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
76                    MethodHandles.publicLookup(), op, mt)));
77            Assert.assertNotNull(cs);
78            Assert.assertEquals(cs.type(), mt);
79            Assert.assertNotNull(cs.getTarget());
80        }
81    }
82
83    @Test
84    public void fallbackLinkerTest() {
85        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
86        final Operation myOperation = new Operation() {
87        };
88        final boolean[] reachedFallback = { false };
89        factory.setFallbackLinkers((GuardingDynamicLinker) (final LinkRequest linkRequest, final LinkerServices linkerServices) -> {
90            Assert.assertEquals(linkRequest.getCallSiteDescriptor().getOperation(), myOperation);
91            reachedFallback[0] = true;
92            return null;
93        });
94
95        final DynamicLinker linker = factory.createLinker();
96        final MethodType mt = MethodType.methodType(Object.class);
97        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
98                MethodHandles.publicLookup(), myOperation, mt)));
99
100        // linking the call site initially does not invoke the linkers!
101        Assert.assertFalse(reachedFallback[0]);
102        try {
103            cs.getTarget().invoke();
104        } catch (final NoSuchDynamicMethodException nsdm) {
105            // we do expect NoSuchDynamicMethod!
106            // because our dummy fallback linker returns null!
107        } catch (final Throwable th) {
108            throw new RuntimeException("should not reach here with: " + th);
109        }
110
111        // check that the control reached fallback linker!
112        Assert.assertTrue(reachedFallback[0]);
113    }
114
115    @Test
116    public void priorityLinkerTest() {
117        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
118        final Operation myOperation = new Operation() {
119        };
120        final boolean[] reachedProrityLinker = { false };
121        factory.setPrioritizedLinker((GuardingDynamicLinker) (final LinkRequest linkRequest, final LinkerServices linkerServices) -> {
122            Assert.assertEquals(linkRequest.getCallSiteDescriptor().getOperation(), myOperation);
123            reachedProrityLinker[0] = true;
124            return null;
125        });
126
127        final DynamicLinker linker = factory.createLinker();
128        final MethodType mt = MethodType.methodType(Object.class);
129        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
130                MethodHandles.publicLookup(), myOperation, mt)));
131
132        // linking the call site initially does not invoke the linkers!
133        Assert.assertFalse(reachedProrityLinker[0]);
134        try {
135            cs.getTarget().invoke();
136        } catch (final NoSuchDynamicMethodException nsdm) {
137            // we do expect NoSuchDynamicMethod!
138            // because our dummy priority linker returns null!
139        } catch (final Throwable th) {
140            throw new RuntimeException("should not reach here with: " + th);
141        }
142
143        // check that the control reached fallback linker!
144        Assert.assertTrue(reachedProrityLinker[0]);
145    }
146
147    @Test
148    public void priorityAndFallbackLinkerTest() {
149        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
150        final Operation myOperation = new Operation() {
151        };
152        final int[] linkerReachCounter = { 0 };
153        factory.setPrioritizedLinker((GuardingDynamicLinker) (final LinkRequest linkRequest, final LinkerServices linkerServices) -> {
154            Assert.assertEquals(linkRequest.getCallSiteDescriptor().getOperation(), myOperation);
155            linkerReachCounter[0]++;
156            return null;
157        });
158        factory.setFallbackLinkers((GuardingDynamicLinker) (final LinkRequest linkRequest, final LinkerServices linkerServices) -> {
159            Assert.assertEquals(linkRequest.getCallSiteDescriptor().getOperation(), myOperation);
160            Assert.assertEquals(linkerReachCounter[0], 1);
161            linkerReachCounter[0]++;
162            return null;
163        });
164
165        final DynamicLinker linker = factory.createLinker();
166        final MethodType mt = MethodType.methodType(Object.class);
167        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
168                MethodHandles.publicLookup(), myOperation, mt)));
169
170        // linking the call site initially does not invoke the linkers!
171        Assert.assertEquals(linkerReachCounter[0], 0);
172
173        try {
174            cs.getTarget().invoke();
175        } catch (final NoSuchDynamicMethodException nsdm) {
176            // we do expect NoSuchDynamicMethod!
177        } catch (final Throwable th) {
178            throw new RuntimeException("should not reach here with: " + th);
179        }
180
181        Assert.assertEquals(linkerReachCounter[0], 2);
182    }
183
184    @Test
185    public void prelinkTransformerTest() throws Throwable {
186        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
187        final boolean[] reachedPrelinkTransformer = { false };
188
189        factory.setPrelinkTransformer((final GuardedInvocation inv, final LinkRequest linkRequest, final LinkerServices linkerServices) -> {
190            reachedPrelinkTransformer[0] = true;
191            // just identity transformer!
192            return inv;
193        });
194
195        final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
196        final DynamicLinker linker = factory.createLinker();
197        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
198                MethodHandles.publicLookup(), GET_PROPERTY, mt)));
199        Assert.assertFalse(reachedPrelinkTransformer[0]);
200        Assert.assertEquals(cs.getTarget().invoke(new Object(), "class"), Object.class);
201        Assert.assertTrue(reachedPrelinkTransformer[0]);
202    }
203
204    @Test
205    public void internalObjectsFilterTest() throws Throwable {
206        final DynamicLinkerFactory factory = newDynamicLinkerFactory(true);
207        final boolean[] reachedInternalObjectsFilter = { false };
208
209        factory.setInternalObjectsFilter((final MethodHandle mh) -> {
210            reachedInternalObjectsFilter[0] = true;
211            return mh;
212        });
213
214        final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
215        final DynamicLinker linker = factory.createLinker();
216        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
217                MethodHandles.publicLookup(), GET_PROPERTY, mt)));
218        Assert.assertFalse(reachedInternalObjectsFilter[0]);
219        Assert.assertEquals(cs.getTarget().invoke(new Object(), "class"), Object.class);
220        Assert.assertTrue(reachedInternalObjectsFilter[0]);
221    }
222
223    private static void checkOneAutoLoadingError(final DynamicLinkerFactory factory) {
224        // expect one error as we have one untrusted linker exporter in META-INF/services
225        final List<ServiceConfigurationError> autoLoadingErrors = factory.getAutoLoadingErrors();
226        // single error ...
227        Assert.assertFalse(autoLoadingErrors.isEmpty());
228        final Throwable cause = autoLoadingErrors.get(0).getCause();
229        // ..  due to permission check..
230        Assert.assertTrue(cause.toString().contains("dynalink.exportLinkersAutomatically"));
231    }
232
233    @Test
234    public void autoLoadedLinkerNegativeTest() {
235        // enable auto loaded linkers
236        final DynamicLinkerFactory factory = newDynamicLinkerFactory(false);
237        factory.createLinker();
238        checkOneAutoLoadingError(factory);
239    }
240
241    @Test
242    public void autoLoadedLinkerTest() {
243        testAutoLoadedLinkerInvoked(new Object(), "toString");
244    }
245
246    @Test
247    public void autoLoadedLinkerSeesStaticMethod() {
248        testAutoLoadedLinkerInvoked(StaticClass.forClass(System.class), "currentTimeMillis");
249    }
250
251    private static void testAutoLoadedLinkerInvoked(final Object target, final String methodName) {
252        final DynamicLinkerFactory factory = newDynamicLinkerFactory(false);
253        final DynamicLinker linker = factory.createLinker();
254
255        // we should still get one error due to untrusted dynamic linker exporter!
256        checkOneAutoLoadingError(factory);
257
258        final MethodType mt = MethodType.methodType(Object.class, Object.class);
259        final CallSiteDescriptor testDescriptor = new CallSiteDescriptor(MethodHandles.publicLookup(),
260                GET.withNamespace(StandardNamespace.METHOD).named(methodName), mt);
261        final CallSite cs = linker.link(new SimpleRelinkableCallSite(testDescriptor));
262
263        TrustedGuardingDynamicLinkerExporter.enable();
264        try {
265            cs.getTarget().invoke(target);
266            // The linker was loaded and it observed our invocation
267            Assert.assertTrue(TrustedGuardingDynamicLinkerExporter.isLastCallSiteDescriptor(testDescriptor));
268        } catch (final Throwable th) {
269            throw new RuntimeException(th);
270        } finally {
271            TrustedGuardingDynamicLinkerExporter.disable();
272        }
273
274    }
275
276    @Test
277    public void nashornExportedLinkerJSObjectTest() {
278        final DynamicLinkerFactory factory = newDynamicLinkerFactory(false);
279        final DynamicLinker linker = factory.createLinker();
280
281        final MethodType mt = MethodType.methodType(Object.class, Object.class);
282        final Operation op = GET_PROPERTY.named("foo");
283        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
284                MethodHandles.publicLookup(), op, mt)));
285        final boolean[] reachedGetMember = new boolean[1];
286        // check that the nashorn exported linker can be used for user defined JSObject
287        final Object obj = new AbstractJSObject() {
288                @Override
289                public Object getMember(final String name) {
290                    reachedGetMember[0] = true;
291                    return name.equals("foo")? "bar" : "<unknown>";
292                }
293            };
294
295        Object value = null;
296        try {
297            value = cs.getTarget().invoke(obj);
298        } catch (final Throwable th) {
299            throw new RuntimeException(th);
300        }
301
302        Assert.assertTrue(reachedGetMember[0]);
303        Assert.assertEquals(value, "bar");
304    }
305
306    @Test
307    public void nashornExportedLinkerScriptObjectMirrorTest() {
308        final DynamicLinkerFactory factory = newDynamicLinkerFactory(false);
309        final DynamicLinker linker = factory.createLinker();
310
311        // check that the nashorn exported linker can be used for ScriptObjectMirror
312        final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
313        final MethodType mt = MethodType.methodType(Object.class, Object.class);
314        final Operation op = GET_PROPERTY.named("foo");
315        final CallSite cs = linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
316                MethodHandles.publicLookup(), op, mt)));
317        Object value = null;
318        try {
319            final Object obj = engine.eval("({ foo: 'hello' })");
320            value = cs.getTarget().invoke(obj);
321        } catch (final Throwable th) {
322            throw new RuntimeException(th);
323        }
324        Assert.assertEquals(value, "hello");
325    }
326}
327