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  */
  25 package jdk.dynalink.test;
  26 
  27 import static jdk.dynalink.StandardNamespace.PROPERTY;
  28 import static jdk.dynalink.StandardOperation.GET;
  29 
  30 import java.lang.invoke.CallSite;
  31 import java.lang.invoke.MethodHandle;
  32 import java.lang.invoke.MethodHandles;
  33 import java.lang.invoke.MethodType;
  34 import java.util.List;
  35 import java.util.ServiceConfigurationError;
  36 import javax.script.ScriptEngine;
  37 import javax.script.ScriptEngineManager;
  38 import jdk.dynalink.CallSiteDescriptor;
  39 import jdk.dynalink.DynamicLinker;
  40 import jdk.dynalink.DynamicLinkerFactory;
  41 import jdk.dynalink.NoSuchDynamicMethodException;
  42 import jdk.dynalink.Operation;
  43 import jdk.dynalink.StandardNamespace;
  44 import jdk.dynalink.StandardOperation;
  45 import jdk.dynalink.beans.StaticClass;
  46 import jdk.dynalink.linker.GuardedInvocation;
  47 import jdk.dynalink.linker.GuardingDynamicLinker;
  48 import jdk.dynalink.linker.LinkRequest;
  49 import jdk.dynalink.linker.LinkerServices;
  50 import jdk.dynalink.support.SimpleRelinkableCallSite;
  51 import jdk.nashorn.api.scripting.AbstractJSObject;
  52 import org.testng.Assert;
  53 import org.testng.annotations.Test;
  54 
  55 @SuppressWarnings("javadoc")
  56 public 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 }