1 /*
   2  * Copyright (c) 2013, 2018, 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 package vm.runtime.defmeth.shared.executor;
  25 
  26 import nsk.share.Pair;
  27 import nsk.share.TestFailure;
  28 import nsk.share.test.TestUtils;
  29 import vm.runtime.defmeth.shared.data.AbstractVisitor;
  30 import vm.runtime.defmeth.shared.DefMethTest;
  31 import vm.runtime.defmeth.shared.MemoryClassLoader;
  32 import vm.runtime.defmeth.shared.Util;
  33 import vm.runtime.defmeth.shared.data.Visitor;
  34 import vm.runtime.defmeth.shared.data.Clazz;
  35 import vm.runtime.defmeth.shared.data.Tester;
  36 import vm.runtime.defmeth.shared.data.method.body.CallMethod;
  37 import vm.runtime.defmeth.shared.data.method.param.*;
  38 import vm.runtime.defmeth.shared.data.method.result.IntResult;
  39 import vm.runtime.defmeth.shared.data.method.result.ThrowExResult;
  40 
  41 import java.lang.invoke.MethodHandle;
  42 import java.lang.invoke.MethodHandles;
  43 import java.lang.invoke.MethodType;
  44 import java.lang.reflect.InvocationTargetException;
  45 import java.lang.reflect.Method;
  46 import java.util.Arrays;
  47 import java.util.Collection;
  48 
  49 /**
  50  * Test runner for invocation mode through MethodHandle.invokeWithArguments(...).
  51  */
  52 public class MHInvokeWithArgsTest extends AbstractReflectionTest {
  53 
  54 
  55     public MHInvokeWithArgsTest(MemoryClassLoader cl, DefMethTest testInstance,
  56                                 Collection<? extends Tester> tests) {
  57         super(testInstance, cl, tests);
  58     }
  59 
  60     public MHInvokeWithArgsTest(MemoryClassLoader cl, DefMethTest testInstance, Tester... tests) {
  61         super(testInstance, cl, Arrays.asList(tests));
  62     }
  63 
  64     private MethodType descToMethodType(String desc) throws ClassNotFoundException {
  65         Pair<String[],String> p = Util.parseDesc(desc);
  66         Class rtype = Util.decodeClass(p.second, cl);
  67         if (p.first.length > 0) {
  68             Class[] ptypes = new Class[p.first.length];
  69             for (int i = 0; i < ptypes.length; i++) {
  70                 ptypes[i] = Util.decodeClass(p.first[i], cl);
  71             }
  72             return MethodType.methodType(rtype, ptypes);
  73         } else {
  74             return MethodType.methodType(rtype);
  75         }
  76     }
  77     private class InvokeWithArgsVisitor extends AbstractVisitor implements Visitor {
  78         private CallMethod call;
  79 
  80         private CallMethod.Invoke invokeType;
  81         Class<?> declaringClass;
  82         String methodName;
  83         MethodType methodType;
  84         private Object[] args;
  85 
  86         @Override
  87         public void visitTester(Tester t) {
  88             // Resolve targetMethod & prepare invocation parameters
  89             t.getCall().visit(this);
  90 
  91             // Invoke resolved targetMethod and check returned value
  92             t.getResult().visit(this);
  93         }
  94 
  95         private void prepareForInvocation()
  96                 throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException {
  97             final Clazz staticClass = call.staticClass();
  98             final Clazz receiverClass = call.receiverClass();
  99 
 100             final Param[] params = call.params();
 101 
 102             // MethodHandle construction is skipped deliberately
 103             // Need to perform the lookup in correct context
 104             invokeType = call.invokeInsn();
 105             declaringClass = resolve(staticClass);
 106             methodName = call.methodName();
 107             methodType = descToMethodType(call.methodDesc());
 108 
 109             Object[] values = values(params);
 110 
 111             if (call.invokeInsn() != CallMethod.Invoke.STATIC) {
 112                 // Prepare receiver for non-static call
 113                 args = new Object[params.length+1];
 114                 Class recClass = resolve(receiverClass);
 115                 args[0] = recClass.newInstance();
 116                 System.arraycopy(values, 0, args, 1, values.length);
 117             } else {
 118                 // No need for a receiver for static call
 119                 args = values;
 120             }
 121         }
 122 
 123         @Override
 124         public void visitCallMethod(CallMethod call) {
 125             // Cache call site info for future uses
 126             this.call = call;
 127 
 128             // NB! don't call prepareForInvocation yet - it can throw exceptions expected by a test
 129         }
 130 
 131         @Override
 132         public void visitResultInt(IntResult res) {
 133             try {
 134                 prepareForInvocation();
 135                 int result = (int) invokeInTestContext();
 136                 TestUtils.assertEquals(res.getExpected(), result);
 137             } catch (Throwable e) {
 138                 throw new TestFailure("Unexpected exception", e);
 139             }
 140         }
 141 
 142         @Override
 143         public void visitResultThrowExc(ThrowExResult res) {
 144             String expectedExcName = res.getExc().name();
 145             String originalExpectedExcName = expectedExcName;
 146             // *Error <==> *Exception correspondence for reflection invocation
 147             switch (expectedExcName) {
 148                 case "java.lang.IllegalAccessError":
 149                 case "java.lang.InstantiationError":
 150                     expectedExcName = expectedExcName.replace("Error", "Exception");
 151                     break;
 152             }
 153 
 154             try {
 155                 prepareForInvocation(); // can throw an exception expected by a test
 156                 invokeInTestContext();
 157                 throw new TestFailure("No exception was thrown: " + expectedExcName);
 158             } catch (Throwable ex) {
 159                 // Need to dig 1 level down (MH.invoke* doesn't wrap exceptions), since there are 2 levels of indirection
 160                 // during invocation:
 161                 // invokeInTestContext(...) => TestContext.invoke(...) => Method.invoke(...)
 162                 Throwable target = ex;
 163                 Class<?> actualExc = (target.getCause() != null) ? target.getCause().getClass()
 164                                                               : target.getClass();
 165                 Class<?> expectedExc;
 166                 try {
 167                     expectedExc = cl.loadClass(expectedExcName);
 168                 } catch (ClassNotFoundException e) {
 169                     throw new Error(e);
 170                 }
 171                 if (!expectedExc.isAssignableFrom(actualExc) &&
 172                     !originalExpectedExcName.equals(actualExc.getName()) &&
 173                     !expectedExc.isAssignableFrom(target.getClass())) {
 174                     throw new TestFailure(
 175                             String.format("Caught exception as expected, but it's type is wrong: expected: %s; actual: %s.",
 176                                     expectedExcName, actualExc.getName()), target);
 177                 }
 178             }
 179         }
 180 
 181         @Override
 182         public void visitResultIgnore() {
 183             try {
 184                 prepareForInvocation();
 185                 invokeInTestContext();
 186             } catch (Throwable e) {
 187                 throw new TestFailure("Unexpected exception", e);
 188             }
 189         }
 190 
 191         private Object invokeInTestContext() throws Throwable {
 192             Class<?> context = cl.getTestContext();
 193             // Invoke target method from TestContext using:
 194             // public static Object invokeWithArguments(
 195             //          CallMethod.Invoke invokeType, Class<?> declaringClass, String methodName, MethodType type,
 196             //          Object... arguments) throws Throwable
 197             MethodHandle invoker;
 198             try {
 199                 invoker = MethodHandles.lookup().
 200                             findStatic(context, "invokeWithArguments",
 201                                     MethodType.methodType(Object.class, CallMethod.Invoke.class, Class.class,
 202                                             String.class, MethodType.class, Object[].class));
 203             } catch (NoSuchMethodException | IllegalAccessException e) {
 204                 throw new TestFailure("Exception during reflection invocation", e.getCause());
 205             }
 206 
 207             return invoker.invokeExact(invokeType, declaringClass, methodName, methodType, args);
 208 
 209         }
 210     }
 211 
 212     /**
 213      * Run individual assertion for the test by it's name.
 214      *
 215      * @param test
 216      * @throws Throwable
 217      */
 218     public void run(Tester test) throws Throwable {
 219         test.visit(new InvokeWithArgsVisitor());
 220     }
 221 }