/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.lang.invoke.MethodHandle; import java.lang.invoke.WrongMethodTypeException; import java.util.Objects; public abstract class AbstractInvokeExactTest { public static String STATUS = ""; public final void runAll() throws Throwable { STATUS = ""; Multi.$noinline$testMHFromMain(optionalGet()); $noinline$privateMethods(); $noinline$testNoArgsCalls(); $noinline$nullchecks(); $noinline$testWithArgs(); $noinline$interfaceChecks(); $noinline$abstractClass(); } // There is no privateLookupIn for const-method-handle. abstract void $noinline$privateMethods() throws Throwable; private void $noinline$testNoArgsCalls() throws Throwable { voidMethod().invokeExact(new A()); assertEquals("A.voidMethod", STATUS); int returnedInt = (int) returnInt().invokeExact(new A()); assertEquals(42, returnedInt); double returnedDouble = (double) returnDouble().invokeExact(new A()); assertEquals(42.0d, returnedDouble); try { interfaceDefaultMethod().invokeExact(new A()); unreachable("MethodHandle's type is (Main$I)V, but callsite is (Main$A)V"); } catch (WrongMethodTypeException expected) {} interfaceDefaultMethod().invokeExact((I) new A()); assertEquals("I.defaultMethod", STATUS); overwrittenInterfaceDefaultMethod().invokeExact((I) new A()); assertEquals("A.overrideMe", STATUS); try { exceptionThrowingMethod().invokeExact(new A()); unreachable("Target method always throws"); } catch (MyRuntimeException expected) { assertEquals("A.throwException", STATUS); } try { returnInt().invokeExact(new A()); unreachable("MethodHandle's type is (Main$A)I, but callsite type is (Main$A)V"); } catch (WrongMethodTypeException expected) {} String returnedString = (String) staticMethod().invokeExact(new A()); assertEquals("staticMethod", returnedString); } private void $noinline$nullchecks() throws Throwable { try { voidMethod().invokeExact((A) null); unreachable("Receiver is null, should throw NPE"); } catch (NullPointerException expected) {} try { voidMethod().invokeExact((Main) null); unreachable("Should throw WMTE: input is of wrong type"); } catch (WrongMethodTypeException expected) {} try { interfaceDefaultMethod().invokeExact((I) null); unreachable("Receiver is null, should throw NPE"); } catch (NullPointerException expected) {} try { interfaceDefaultMethod().invokeExact((A) null); unreachable("Should throw WMTE: input is of wrong type"); } catch (WrongMethodTypeException expected) {} try { MethodHandle mh = $noinline$nullMethodHandle(); mh.invokeExact(); unreachable("MethodHandle object is null, should throw NPE"); } catch (NullPointerException expected) {} } private static MethodHandle $noinline$nullMethodHandle() { return null; } private void $noinline$testWithArgs() throws Throwable { int sum = (int) sumI().invokeExact(new Sums(), 1); assertEquals(1, sum); sum = (int) sum2I().invokeExact(new Sums(), 1, 2); assertEquals(3, sum); sum = (int) sum3I().invokeExact(new Sums(), 1, 2, 3); assertEquals(6, sum); sum = (int) sum4I().invokeExact(new Sums(), 1, 2, 3, 4); assertEquals(10, sum); sum = (int) sum5I().invokeExact(new Sums(), 1, 2, 3, 4, 5); assertEquals(15, sum); sum = (int) sum6I().invokeExact(new Sums(), 1, 2, 3, 4, 5, 6); assertEquals(21, sum); sum = (int) sum7I().invokeExact(new Sums(), 1, 2, 3, 4, 5, 6, 7); assertEquals(28, sum); sum = (int) sum8I().invokeExact(new Sums(), 1, 2, 3, 4, 5, 6, 7, 8); assertEquals(36, sum); sum = (int) sum9I().invokeExact(new Sums(), 1, 2, 3, 4, 5, 6, 7, 8, 9); assertEquals(45, sum); sum = (int) sum10I().invokeExact(new Sums(), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); assertEquals(55, sum); long lsum = (long) sumIJ().invokeExact(new Sums(), 1, 2L); assertEquals(3L, lsum); lsum = (long) sum2IJ().invokeExact(new Sums(), 1, 2L, 3, 4L); assertEquals(10L, lsum); lsum = (long) sum3IJ().invokeExact(new Sums(), 1, 2L, 3, 4L, 5, 6L); assertEquals(21L, lsum); lsum = (long) sum4IJ().invokeExact(new Sums(), 1, 2L, 3, 4L, 5, 6L, 7, 8L); assertEquals(36L, lsum); lsum = (long) sum5IJ().invokeExact(new Sums(), 1, 2L, 3, 4L, 5, 6L, 7, 8L, 9, 10L); assertEquals(55L, lsum); } private void $noinline$interfaceChecks() throws Throwable { FooBarImpl instance = new FooBarImpl(); String result = null; result = (String) fooNonDefault().invokeExact((Foo) instance); assertEquals("FooBarImpl.nonDefault", result); result = (String) fooBarImplNonDefault().invokeExact(instance); assertEquals("FooBarImpl.nonDefault", result); result = (String) barDefault().invokeExact((Bar) instance); assertEquals("Bar.defaultToOverride", result); result = (String) fooDefault().invokeExact((Foo) instance); assertEquals("Bar.defaultToOverride", result); result = (String) fooBarImplDefault().invokeExact(instance); assertEquals("Bar.defaultToOverride", result); result = (String) fooNonOverriddenDefault().invokeExact((Foo) instance); assertEquals("Foo.nonOverriddenDefault", result); result = (String) barNonOverriddenDefault().invokeExact((Bar) instance); assertEquals("Foo.nonOverriddenDefault", result); ToStringable toStringable = new ToStringableImpl(); result = (String) toStringDefinedInAnInterface().invokeExact(toStringable); assertEquals("ToStringableImpl", result); try { String ignored = (String) toStringDefinedInAnInterface().invokeExact(instance); unreachable("Should throw WMTE"); } catch (WrongMethodTypeException expected) {} Impl1 impl1 = new Impl1(); result = (String) interfaceOneMethod().invokeExact((Interface1) impl1); assertEquals("Impl1.methodOne", result); Impl2 impl2 = new Impl2(); result = (String) interfaceTwoMethod().invokeExact((Interface2) impl2); assertEquals("Impl2.methodTwo", result); result = (String) interfaceOneMethod().invokeExact((Interface1) impl2); assertEquals("Impl2.methodOne", result); Impl3 impl3 = new Impl3(); result = (String) interfaceThreeMethod().invokeExact((Interface3) impl3); assertEquals("Impl3.methodThree", result); result = (String) interfaceTwoMethod().invokeExact((Interface2) impl3); assertEquals("Impl3.methodTwo", result); result = (String) interfaceOneMethod().invokeExact((Interface1) impl3); assertEquals("Impl3.methodOne", result); Impl4 impl4 = new Impl4(); result = (String) interfaceFourMethod().invokeExact((Interface4) impl4); assertEquals("Impl4.methodFour", result); result = (String) interfaceThreeMethod().invokeExact((Interface3) impl4); assertEquals("Impl4.methodThree", result); result = (String) interfaceTwoMethod().invokeExact((Interface2) impl4); assertEquals("Impl4.methodTwo", result); result = (String) interfaceOneMethod().invokeExact((Interface1) impl4); assertEquals("Impl4.methodOne", result); FooAndFooConflictImpl conflictImpl = new FooAndFooConflictImpl(); result = (String) fooDefault().invokeExact((Foo) conflictImpl); assertEquals("DefaultMethodConflictImpl.defaultToOverride", result); FooAndFooConflict fooAndFooConflict = new FooAndFooConflict() { public String nonDefault() { throw new UnsupportedOperationException(); } }; // TODO(b/297147201): The RI throws AbsractMethodError in these cases, just like plain // fooAndFooConflict.defaultToOverride() call. So RI's invokeExact follows "equivalent of a // particular bytecode behavior". ART throws ICCE in both cases, so current implementation of // invokeExact does not break "equivalent ..." part of the javadoc. // Need to check what the spec says about the error thrown when a conflicting default method is // attempted to be called. try { String ignored = (String) fooAndFooConflictDefault().invokeExact(fooAndFooConflict); unreachable("Non-overridden default conflict method"); } catch (IncompatibleClassChangeError expected) {} try { String ignored = (String) fooDefault().invokeExact((Foo) fooAndFooConflict); unreachable("Non-overridden default conflict method"); } catch (IncompatibleClassChangeError expected) {} BaseInterface baseClassImpl = new BaseClassImpl(); try { String ignored = (String) baseInterface().invokeExact(baseClassImpl); unreachable("Calling unimplemented interface method"); } catch (AbstractMethodError expected) {} } private void $noinline$abstractClass() throws Throwable { FooBarImpl instance = new FooBarImpl(); String result = null; result = (String) fooBarDefinedInAbstract().invokeExact((FooBar) instance); assertEquals("FooBar.definedInAbstract", result); result = (String) fooBarImplDefinedInAbstract().invokeExact(instance); assertEquals("FooBar.definedInAbstract", result); FooBar fooBar = new FooBar() { @Override public String nonDefault() { return "anonymous.nonDefault"; } }; result = (String) fooBarDefinedInAbstract().invokeExact(fooBar); assertEquals("FooBar.definedInAbstract", result); result = (String) fooBarNonDefault().invokeExact(fooBar); assertEquals("anonymous.nonDefault", result); } static void assertEquals(Object expected, Object actual) { if (!Objects.equals(expected, actual)) { throw new AssertionError("Expected: " + expected + ", got: " + actual); } } private static void assertEquals(int expected, int actual) { if (expected != actual) { throw new AssertionError("Expected: " + expected + ", got: " + actual); } } private static void assertEquals(long expected, long actual) { if (expected != actual) { throw new AssertionError("Expected: " + expected + ", got: " + actual); } } private static void assertEquals(double expected, double actual) { if (expected != actual) { throw new AssertionError("Expected: " + expected + ", got: " + actual); } } static void unreachable(String msg) { throw new AssertionError("Unexpectedly reached this point, but shouldn't: " + msg); } public abstract MethodHandle optionalGet(); public abstract MethodHandle voidMethod(); public abstract MethodHandle returnInt(); public abstract MethodHandle returnDouble(); public abstract MethodHandle interfaceDefaultMethod(); public abstract MethodHandle overwrittenInterfaceDefaultMethod(); public abstract MethodHandle exceptionThrowingMethod(); public abstract MethodHandle staticMethod(); public abstract MethodHandle sumI(); public abstract MethodHandle sum2I(); public abstract MethodHandle sum3I(); public abstract MethodHandle sum4I(); public abstract MethodHandle sum5I(); public abstract MethodHandle sum6I(); public abstract MethodHandle sum7I(); public abstract MethodHandle sum8I(); public abstract MethodHandle sum9I(); public abstract MethodHandle sum10I(); public abstract MethodHandle sumIJ(); public abstract MethodHandle sum2IJ(); public abstract MethodHandle sum3IJ(); public abstract MethodHandle sum4IJ(); public abstract MethodHandle sum5IJ(); public abstract MethodHandle fooNonDefault(); public abstract MethodHandle fooBarImplNonDefault(); public abstract MethodHandle barDefault(); public abstract MethodHandle fooDefault(); public abstract MethodHandle fooBarImplDefault(); public abstract MethodHandle fooNonOverriddenDefault(); public abstract MethodHandle barNonOverriddenDefault(); public abstract MethodHandle toStringDefinedInAnInterface(); public abstract MethodHandle interfaceOneMethod(); public abstract MethodHandle interfaceTwoMethod(); public abstract MethodHandle interfaceThreeMethod(); public abstract MethodHandle interfaceFourMethod(); public abstract MethodHandle fooBarDefinedInAbstract(); public abstract MethodHandle fooBarImplDefinedInAbstract(); public abstract MethodHandle fooBarNonDefault(); public abstract MethodHandle fooAndFooConflictDefault(); public abstract MethodHandle baseInterface(); }