--- /dev/null 2018-05-14 10:15:02.574213890 -0700 +++ new/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/shared/executor/ReflectionTest.java 2018-05-21 10:53:24.981502807 -0700 @@ -0,0 +1,328 @@ +/* + * 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()); + } +}