/* * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package vm.runtime.defmeth.shared.executor; import nsk.share.TestFailure; import nsk.share.test.TestUtils; import vm.runtime.defmeth.shared.builder.TestBuilder; import vm.runtime.defmeth.shared.data.AbstractVisitor; import vm.runtime.defmeth.shared.DefMethTest; import vm.runtime.defmeth.shared.MemoryClassLoader; import vm.runtime.defmeth.shared.data.Clazz; import vm.runtime.defmeth.shared.data.ConcreteClass; import vm.runtime.defmeth.shared.data.Interface; import vm.runtime.defmeth.shared.data.Visitor; import vm.runtime.defmeth.shared.data.Tester; import vm.runtime.defmeth.shared.data.method.AbstractMethod; import vm.runtime.defmeth.shared.data.method.ConcreteMethod; import vm.runtime.defmeth.shared.data.method.DefaultMethod; import vm.runtime.defmeth.shared.data.method.body.CallMethod; import vm.runtime.defmeth.shared.data.method.body.EmptyBody; import vm.runtime.defmeth.shared.data.method.body.ReturnIntBody; import vm.runtime.defmeth.shared.data.method.body.ThrowExBody; import vm.runtime.defmeth.shared.data.method.result.IntResult; import vm.runtime.defmeth.shared.data.method.result.ThrowExResult; import static jdk.internal.org.objectweb.asm.Opcodes.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.List; import static java.lang.String.format; /** * Test runner for invocation mode through Reflection API (Method.invoke). */ public class ReflectionTest extends AbstractReflectionTest { private TestBuilder builder; public ReflectionTest(MemoryClassLoader cl, TestBuilder builder, DefMethTest testInstance, Collection tests) { super(testInstance, cl, tests); this.builder = builder; } public ReflectionTest(MemoryClassLoader cl, TestBuilder builder, DefMethTest testInstance, Tester... tests) { this(cl, builder, testInstance, Arrays.asList(tests)); } private class ReflectionTesterVisitor extends AbstractVisitor implements Visitor { private CallMethod call; private Object receiver; private Method targetMethod; private Object[] values; private Tester tester; @Override public void visitTester(Tester t) { tester = t; try { // Resolve targetMethod & prepare invocation parameters t.getCall().visit(this); // Invoke resolved targetMethod and check returned value t.getResult().visit(this); } finally { tester = null; } } @Override public void visitCallMethod(CallMethod call) { // Cache call site info for future uses this.call = call; // NB! don't call prepareForInvocation yet - it can throw exceptions expected by a test } private void prepareForInvocation() throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException { Class staticClass = resolve(call.staticClass()); String methodName = call.methodName(); Class[] paramTypes = paramType(call.methodDesc()); if (tester.getTestPrivateMethod() != true) { targetMethod = staticClass.getMethod(methodName, paramTypes); } else { try { targetMethod = staticClass.getDeclaredMethod(methodName, paramTypes); } catch (NoSuchMethodException nsme) {} Class clazz = staticClass.getSuperclass(); while ((targetMethod == null) && (clazz != null)) { try { targetMethod = clazz.getDeclaredMethod(methodName, paramTypes); } catch (NoSuchMethodException nsme) {} clazz = clazz.getSuperclass(); } } // Check reflection info for Class.getMethod(...) checkReflectionInfo(targetMethod); // Prepare receiver after resolving target method, because it can throw instantiation exception if (call.invokeInsn() != CallMethod.Invoke.STATIC) { Class receiverClass = resolve(call.receiverClass()); receiver = receiverClass.newInstance(); } else { // receiver == null; Method.invoke ignores first argument when static method is called } // Check reflection info for Class.getDeclaredMethod(...) try { Method m = staticClass.getDeclaredMethod(methodName, paramTypes); checkReflectionInfo(m); } catch (NoSuchMethodException e) {} values = values(call.params()); } /** Calculate Method.isDefault() property for a Method */ private abstract class AbstractResultExtractor extends AbstractVisitor { public boolean isDefault; public void visitConcreteMethod(ConcreteMethod m) { isDefault = false; m.body().visit(this); // extract return value } public void visitAbstractMethod(AbstractMethod m) { isDefault = false; } public void visitDefaultMethod(DefaultMethod m) { isDefault = true; m.body().visit(this); // extract return value } public void visitEmptyBody(EmptyBody body) { // ignore body } public void visitThrowExBody(ThrowExBody body) { // ignore body } public void visitReturnIntBody(ReturnIntBody body) { // ignore body } public void visitResultInt(IntResult res) { // ignore body } public void visitCallMethod(CallMethod call) { // ignore body } } private vm.runtime.defmeth.shared.data.method.Method findMethod(Clazz clz, String name, String desc) { if (clz == null) return null; // Look for the method declaration in current class only for (vm.runtime.defmeth.shared.data.method.Method m : clz.methods()) { if (name.equals(m.name()) && desc.equals(m.desc())) { return m; } } return null; } /** Verify reflection info for a statically known method */ private void checkReflectionInfo(Method method) { vm.runtime.defmeth.shared.data.method.Method m = findMethod(call.staticClass(), call.methodName(), call.methodDesc()); if (m != null) { int flags = m.acc(); boolean shouldBeDefault = (m instanceof DefaultMethod) && ((flags & ACC_PUBLIC) == ACC_PUBLIC) && ((flags & ACC_STATIC) != ACC_STATIC) && ((flags & ACC_ABSTRACT) != ACC_ABSTRACT); boolean shouldBeAbstract = Modifier.isAbstract(flags); // check isDefault property boolean isDefault = method.isDefault(); if (shouldBeDefault != isDefault) { throw new TestFailure(format("Reflection info for Method.isDefault() is invalid:" + " expected: %b; actual: %b. Method info: %s vs %s", shouldBeDefault, isDefault, method.toString(), m.toString())); } boolean isAbstract = Modifier.isAbstract(method.getModifiers()); // check that the method w/ a body shouldn't be abstract if (shouldBeAbstract != isAbstract) { throw new TestFailure(format("Method shouldn't be abstract: %s vs %s", method.toString(), m.toString())); } } } private Object invokeInTestContext(Method m, Object obj, Object... args) throws InvocationTargetException { Class context = cl.getTestContext(); try { // Invoke target method from TestContext using TestContext.invoke(Method, Object, Object...) Method invoker = context.getDeclaredMethod("invoke", Method.class, Object.class, Object[].class); return invoker.invoke(null, m, obj, args); } catch (NoSuchMethodException | IllegalAccessException e) { throw new TestFailure("Exception during reflection invocation", e.getCause()); } } @Override public void visitResultInt(IntResult res) { try { prepareForInvocation(); int result = (int) invokeInTestContext(targetMethod, receiver, values); TestUtils.assertEquals(res.getExpected(), result); } catch (TestFailure e) { throw e; // no need to wrap test exception } catch (Exception e) { throw new TestFailure("Unexpected exception", e); } } private void checkExpectedException(ThrowExResult exceptionInfo, Throwable actualExc, boolean unwrap) { String expectedExcName = exceptionInfo.getExc().name(); String initialExpectedExcName = expectedExcName; // *Error <==> *Exception correspondence for reflection invocation switch (expectedExcName) { case "java.lang.NoSuchMethodError": case "java.lang.InstantiationError": case "java.lang.IllegalAccessError": expectedExcName = expectedExcName.replace("Error", "Exception"); break; } // Need to dig 2 levels down since there are 2 levels of indirection during invocation: // invokeInTestContext(...) => TestContext.invoke(...) => Method.invoke(...) // For some exceptions, it's not the case (like NMSE) Throwable target = actualExc; if (unwrap) { if (target.getCause() != null) target = target.getCause(); if (target.getCause() != null) target = target.getCause(); } Class expectedExc; try { expectedExc = cl.getSystemClassLoader().loadClass(expectedExcName); } catch (ClassNotFoundException e) { throw new Error(e); } String excName = target.getClass().getName(); if (!expectedExc.isAssignableFrom(target.getClass()) && !initialExpectedExcName.equals(excName)) { throw new TestFailure( format("Caught exception as expected, but it's type is wrong: expected: %s; actual: %s.", expectedExcName, excName), target); } } @Override public void visitResultThrowExc(ThrowExResult res) { String expectedExcName = res.getExc().name(); try { prepareForInvocation(); // can throw an exception expected by a test invokeInTestContext(targetMethod, receiver, values); throw new TestFailure("No exception was thrown: " + expectedExcName); } catch (IllegalAccessException | IllegalArgumentException | ClassNotFoundException e) { throw new TestFailure("Exception during reflection invocation", e.getCause()); } catch (InstantiationException | NoSuchMethodException | InvocationTargetException e) { checkExpectedException(res, e, true); } catch (Throwable e) { checkExpectedException(res, e, false); } } @Override public void visitResultIgnore() { try { prepareForInvocation(); invokeInTestContext(targetMethod, receiver, values); } catch (Exception e) { throw new TestFailure("Unexpected exception", e); } } } /** * Run individual assertion for the test by it's name. * * @param test * @throws Throwable */ public void run(Tester test) throws Throwable { test.visit(new ReflectionTesterVisitor()); } }