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.beans.test;
26
27import static jdk.dynalink.StandardNamespace.ELEMENT;
28import static jdk.dynalink.StandardNamespace.METHOD;
29import static jdk.dynalink.StandardNamespace.PROPERTY;
30import static jdk.dynalink.StandardOperation.CALL;
31import static jdk.dynalink.StandardOperation.GET;
32import static jdk.dynalink.StandardOperation.NEW;
33import static jdk.dynalink.StandardOperation.SET;
34
35import java.lang.invoke.CallSite;
36import java.lang.invoke.MethodHandle;
37import java.lang.invoke.MethodHandles;
38import java.lang.invoke.MethodType;
39import java.security.AccessControlException;
40import java.util.ArrayList;
41import java.util.Date;
42import java.util.List;
43import jdk.dynalink.CallSiteDescriptor;
44import jdk.dynalink.DynamicLinker;
45import jdk.dynalink.DynamicLinkerFactory;
46import jdk.dynalink.NamedOperation;
47import jdk.dynalink.NoSuchDynamicMethodException;
48import jdk.dynalink.Operation;
49import jdk.dynalink.beans.BeansLinker;
50import jdk.dynalink.beans.StaticClass;
51import jdk.dynalink.support.SimpleRelinkableCallSite;
52import org.testng.Assert;
53import org.testng.annotations.AfterTest;
54import org.testng.annotations.BeforeTest;
55import org.testng.annotations.DataProvider;
56import org.testng.annotations.Test;
57
58public class BeanLinkerTest {
59
60    private DynamicLinker linker;
61    private static final MethodHandles.Lookup MY_LOOKUP = MethodHandles.lookup();
62
63    @SuppressWarnings("unused")
64    @DataProvider
65    private static Object[][] flags() {
66        return new Object[][]{
67            {Boolean.FALSE},
68            {Boolean.TRUE}
69        };
70    }
71
72    // helpers to create callsite objects
73    private CallSite createCallSite(final boolean publicLookup, final Operation op, final MethodType mt) {
74        return linker.link(new SimpleRelinkableCallSite(new CallSiteDescriptor(
75                publicLookup ? MethodHandles.publicLookup() : MY_LOOKUP, op, mt)));
76    }
77
78    private CallSite createCallSite(final boolean publicLookup, final Operation op, final Object name, final MethodType mt) {
79        return createCallSite(publicLookup, op.named(name), mt);
80    }
81
82    private CallSite createGetMethodCallSite(final boolean publicLookup, final String name) {
83        return createCallSite(publicLookup, GET_METHOD, name, MethodType.methodType(Object.class, Object.class));
84    }
85
86    private static final MethodHandle throwArrayIndexOutOfBounds = findThrower("throwArrayIndexOutOfBounds");
87    private static final MethodHandle throwIndexOutOfBounds = findThrower("throwIndexOutOfBounds");
88
89    private static final Operation GET_PROPERTY = GET.withNamespace(PROPERTY);
90    private static final Operation GET_ELEMENT = GET.withNamespace(ELEMENT);
91    private static final Operation GET_METHOD = GET.withNamespace(METHOD);
92    private static final Operation SET_ELEMENT = SET.withNamespace(ELEMENT);
93
94    private static final MethodHandle findThrower(final String name) {
95        try {
96            return MethodHandles.lookup().findStatic(BeanLinkerTest.class, name,
97                    MethodType.methodType(Object.class, Object.class, Object.class));
98        } catch (NoSuchMethodException | IllegalAccessException e) {
99            Assert.fail("Unexpected exception", e);
100            return null;
101        }
102    }
103
104    private static Object throwArrayIndexOutOfBounds(final Object receiver, final Object index) {
105        throw new ArrayIndexOutOfBoundsException(String.valueOf(index));
106    }
107
108    private static Object throwIndexOutOfBounds(final Object receiver, final Object index) {
109        throw new IndexOutOfBoundsException(String.valueOf(index));
110    }
111
112    @BeforeTest
113    public void initLinker() {
114        final DynamicLinkerFactory factory = new DynamicLinkerFactory();
115        factory.setFallbackLinkers(new BeansLinker((req, services) -> {
116            // This is a MissingMemberHandlerFactory that creates a missing
117            // member handler for element getters and setters that throw an
118            // ArrayIndexOutOfBoundsException when applied to an array and an
119            // IndexOutOfBoundsException when applied to a list.
120
121            final CallSiteDescriptor desc = req.getCallSiteDescriptor();
122            final Operation op = desc.getOperation();
123            final Operation baseOp = NamedOperation.getBaseOperation(op);
124            if (baseOp != GET_ELEMENT && baseOp != SET_ELEMENT) {
125                // We only handle GET_ELEMENT and SET_ELEMENT.
126                return null;
127            }
128
129            final Object receiver = req.getReceiver();
130            Assert.assertNotNull(receiver);
131
132            final Class<?> clazz = receiver.getClass();
133            final MethodHandle throwerHandle;
134            if (clazz.isArray()) {
135                throwerHandle = throwArrayIndexOutOfBounds;
136            } else if (List.class.isAssignableFrom(clazz)) {
137                throwerHandle = throwIndexOutOfBounds;
138            } else {
139                Assert.fail("Unexpected receiver type " + clazz.getName());
140                return null;
141            }
142
143            final Object name = NamedOperation.getName(op);
144            final MethodHandle nameBoundHandle;
145            if (name == null) {
146                nameBoundHandle = throwerHandle;
147            } else {
148                // If the operation is for a fixed index, bind it
149                nameBoundHandle = MethodHandles.insertArguments(throwerHandle, 1, name);
150            }
151
152            final MethodType callSiteType = desc.getMethodType();
153            final MethodHandle arityMatchedHandle;
154            if (baseOp == SET_ELEMENT) {
155                // Drop "value" parameter for a setter
156                final int handleArity = nameBoundHandle.type().parameterCount();
157                arityMatchedHandle = MethodHandles.dropArguments(nameBoundHandle,
158                        handleArity, callSiteType.parameterType(handleArity));
159            } else {
160                arityMatchedHandle = nameBoundHandle;
161            }
162
163            return arityMatchedHandle.asType(callSiteType);
164        }));
165        this.linker = factory.createLinker();
166    }
167
168    @AfterTest
169    public void afterTest() {
170        this.linker = null;
171    }
172
173    @Test(dataProvider = "flags")
174    public void getPropertyTest(final boolean publicLookup) throws Throwable {
175        final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
176        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
177        Assert.assertEquals(cs.getTarget().invoke(new Object(), "class"), Object.class);
178        Assert.assertEquals(cs.getTarget().invoke(new Date(), "class"), Date.class);
179    }
180
181    @Test(dataProvider = "flags")
182    public void getPropertyNegativeTest(final boolean publicLookup) throws Throwable {
183        final MethodType mt = MethodType.methodType(Object.class, Object.class, String.class);
184        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
185        Assert.assertNull(cs.getTarget().invoke(new Object(), "DOES_NOT_EXIST"));
186    }
187
188    @Test(dataProvider = "flags")
189    public void getPropertyTest2(final boolean publicLookup) throws Throwable {
190        final MethodType mt = MethodType.methodType(Object.class, Object.class);
191        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "class", mt);
192        Assert.assertEquals(cs.getTarget().invoke(new Object()), Object.class);
193        Assert.assertEquals(cs.getTarget().invoke(new Date()), Date.class);
194    }
195
196    @Test(dataProvider = "flags")
197    public void getPropertyNegativeTest2(final boolean publicLookup) throws Throwable {
198        final MethodType mt = MethodType.methodType(Object.class, Object.class);
199        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "DOES_NOT_EXIST", mt);
200
201        try {
202            cs.getTarget().invoke(new Object());
203            throw new RuntimeException("Expected NoSuchDynamicMethodException");
204        } catch (final Throwable th) {
205            Assert.assertTrue(th instanceof NoSuchDynamicMethodException);
206        }
207    }
208
209    @Test(dataProvider = "flags")
210    public void getLengthPropertyTest(final boolean publicLookup) throws Throwable {
211        final MethodType mt = MethodType.methodType(int.class, Object.class, String.class);
212        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, mt);
213
214        Assert.assertEquals((int) cs.getTarget().invoke(new int[10], "length"), 10);
215        Assert.assertEquals((int) cs.getTarget().invoke(new String[33], "length"), 33);
216    }
217
218    @Test(dataProvider = "flags")
219    public void getElementTest(final boolean publicLookup) throws Throwable {
220        final MethodType mt = MethodType.methodType(int.class, Object.class, int.class);
221        final CallSite cs = createCallSite(publicLookup, GET_ELEMENT, mt);
222
223        final int[] arr = {23, 42};
224        Assert.assertEquals((int) cs.getTarget().invoke(arr, 0), 23);
225        Assert.assertEquals((int) cs.getTarget().invoke(arr, 1), 42);
226        try {
227            final int x = (int) cs.getTarget().invoke(arr, -1);
228            throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
229        } catch (final ArrayIndexOutOfBoundsException ex) {
230        }
231
232        try {
233            final int x = (int) cs.getTarget().invoke(arr, arr.length);
234            throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
235        } catch (final ArrayIndexOutOfBoundsException ex) {
236        }
237
238        final List<Integer> list = new ArrayList<>();
239        list.add(23);
240        list.add(430);
241        list.add(-4354);
242        Assert.assertEquals((int) cs.getTarget().invoke(list, 0), (int) list.get(0));
243        Assert.assertEquals((int) cs.getTarget().invoke(list, 1), (int) list.get(1));
244        Assert.assertEquals((int) cs.getTarget().invoke(list, 2), (int) list.get(2));
245        try {
246            final int x = (int) cs.getTarget().invoke(list, -1);
247            throw new RuntimeException("expected IndexOutOfBoundsException");
248        } catch (final IndexOutOfBoundsException ex) {
249        }
250
251        try {
252            final int x = (int) cs.getTarget().invoke(list, list.size());
253            throw new RuntimeException("expected IndexOutOfBoundsException");
254        } catch (final IndexOutOfBoundsException ex) {
255        }
256    }
257
258    @Test(dataProvider = "flags")
259    public void setElementTest(final boolean publicLookup) throws Throwable {
260        final MethodType mt = MethodType.methodType(void.class, Object.class, int.class, int.class);
261        final CallSite cs = createCallSite(publicLookup, SET_ELEMENT, mt);
262
263        final int[] arr = {23, 42};
264        cs.getTarget().invoke(arr, 0, 0);
265        Assert.assertEquals(arr[0], 0);
266        cs.getTarget().invoke(arr, 1, -5);
267        Assert.assertEquals(arr[1], -5);
268
269        try {
270            cs.getTarget().invoke(arr, -1, 12);
271            throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
272        } catch (final ArrayIndexOutOfBoundsException ex) {
273        }
274
275        try {
276            cs.getTarget().invoke(arr, arr.length, 20);
277            throw new RuntimeException("expected ArrayIndexOutOfBoundsException");
278        } catch (final ArrayIndexOutOfBoundsException ex) {
279        }
280
281        final List<Integer> list = new ArrayList<>();
282        list.add(23);
283        list.add(430);
284        list.add(-4354);
285
286        cs.getTarget().invoke(list, 0, -list.get(0));
287        Assert.assertEquals((int) list.get(0), -23);
288        cs.getTarget().invoke(list, 1, -430);
289        cs.getTarget().invoke(list, 2, 4354);
290        try {
291            cs.getTarget().invoke(list, -1, 343);
292            throw new RuntimeException("expected IndexOutOfBoundsException");
293        } catch (final IndexOutOfBoundsException ex) {
294        }
295
296        try {
297            cs.getTarget().invoke(list, list.size(), 43543);
298            throw new RuntimeException("expected IndexOutOfBoundsException");
299        } catch (final IndexOutOfBoundsException ex) {
300        }
301    }
302
303    @Test(dataProvider = "flags")
304    public void newObjectTest(final boolean publicLookup) {
305        final MethodType mt = MethodType.methodType(Object.class, Object.class);
306        final CallSite cs = createCallSite(publicLookup, NEW, mt);
307
308        Object obj = null;
309        try {
310            obj = cs.getTarget().invoke(StaticClass.forClass(Date.class));
311        } catch (final Throwable th) {
312            throw new RuntimeException(th);
313        }
314
315        Assert.assertTrue(obj instanceof Date);
316    }
317
318    @Test(dataProvider = "flags")
319    public void staticPropertyTest(final boolean publicLookup) {
320        final MethodType mt = MethodType.methodType(Object.class, Class.class);
321        final CallSite cs = createCallSite(publicLookup, GET_PROPERTY, "static", mt);
322
323        Object obj = null;
324        try {
325            obj = cs.getTarget().invoke(Object.class);
326        } catch (final Throwable th) {
327            throw new RuntimeException(th);
328        }
329
330        Assert.assertTrue(obj instanceof StaticClass);
331        Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object.class);
332
333        try {
334            obj = cs.getTarget().invoke(Date.class);
335        } catch (final Throwable th) {
336            throw new RuntimeException(th);
337        }
338
339        Assert.assertTrue(obj instanceof StaticClass);
340        Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Date.class);
341
342        try {
343            obj = cs.getTarget().invoke(Object[].class);
344        } catch (final Throwable th) {
345            throw new RuntimeException(th);
346        }
347
348        Assert.assertTrue(obj instanceof StaticClass);
349        Assert.assertEquals(((StaticClass) obj).getRepresentedClass(), Object[].class);
350    }
351
352    @Test(dataProvider = "flags")
353    public void instanceMethodCallTest(final boolean publicLookup) {
354        final CallSite cs = createGetMethodCallSite(publicLookup, "getClass");
355        final MethodType mt2 = MethodType.methodType(Class.class, Object.class, Object.class);
356        final CallSite cs2 = createCallSite(publicLookup, CALL, mt2);
357
358        Object method = null;
359        try {
360            method = cs.getTarget().invoke(new Date());
361        } catch (final Throwable th) {
362            throw new RuntimeException(th);
363        }
364
365        Assert.assertNotNull(method);
366        Assert.assertTrue(BeansLinker.isDynamicMethod(method));
367        Class clz = null;
368        try {
369            clz = (Class) cs2.getTarget().invoke(method, new Date());
370        } catch (final Throwable th) {
371            throw new RuntimeException(th);
372        }
373
374        Assert.assertEquals(clz, Date.class);
375    }
376
377    @Test(dataProvider = "flags")
378    public void staticMethodCallTest(final boolean publicLookup) {
379        final CallSite cs = createGetMethodCallSite(publicLookup, "getProperty");
380        final MethodType mt2 = MethodType.methodType(String.class, Object.class, Object.class, String.class);
381        final CallSite cs2 = createCallSite(publicLookup, CALL, mt2);
382
383        Object method = null;
384        try {
385            method = cs.getTarget().invoke(StaticClass.forClass(System.class));
386        } catch (final Throwable th) {
387            throw new RuntimeException(th);
388        }
389
390        Assert.assertNotNull(method);
391        Assert.assertTrue(BeansLinker.isDynamicMethod(method));
392
393        String str = null;
394        try {
395            str = (String) cs2.getTarget().invoke(method, null, "os.name");
396        } catch (final Throwable th) {
397            throw new RuntimeException(th);
398        }
399        Assert.assertEquals(str, System.getProperty("os.name"));
400    }
401
402    // try calling System.getenv and expect security exception
403    @Test(dataProvider = "flags")
404    public void systemGetenvTest(final boolean publicLookup) {
405        final CallSite cs1 = createGetMethodCallSite(publicLookup, "getenv");
406        final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(Object.class, Object.class, Object.class));
407
408        try {
409            final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
410            cs2.getTarget().invoke(method, StaticClass.forClass(System.class));
411            throw new RuntimeException("should not reach here in any case!");
412        } catch (final Throwable th) {
413            Assert.assertTrue(th instanceof SecurityException);
414        }
415    }
416
417    // try getting a specific sensitive System property and expect security exception
418    @Test(dataProvider = "flags")
419    public void systemGetPropertyTest(final boolean publicLookup) {
420        final CallSite cs1 = createGetMethodCallSite(publicLookup, "getProperty");
421        final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(String.class, Object.class, Object.class, String.class));
422
423        try {
424            final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
425            cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "java.home");
426            throw new RuntimeException("should not reach here in any case!");
427        } catch (final Throwable th) {
428            Assert.assertTrue(th instanceof SecurityException);
429        }
430    }
431
432    // check a @CallerSensitive API and expect appropriate access check exception
433    @Test(dataProvider = "flags")
434    public void systemLoadLibraryTest(final boolean publicLookup) {
435        final CallSite cs1 = createGetMethodCallSite(publicLookup, "loadLibrary");
436        final CallSite cs2 = createCallSite(publicLookup, CALL, MethodType.methodType(void.class, Object.class, Object.class, String.class));
437
438        try {
439            final Object method = cs1.getTarget().invoke(StaticClass.forClass(System.class));
440            cs2.getTarget().invoke(method, StaticClass.forClass(System.class), "foo");
441            throw new RuntimeException("should not reach here in any case!");
442        } catch (final Throwable th) {
443            if (publicLookup) {
444                Assert.assertTrue(th instanceof IllegalAccessError);
445            } else {
446                Assert.assertTrue(th instanceof AccessControlException);
447            }
448        }
449    }
450}
451