Add MethodHandles.{dropReturn,tableSwitch} from OpenJDK 17.0.2-ga
Bug: N/A
Test: atest CtsLibcoreOjTestCases:test.java.lang.invoke
Change-Id: I52ed2d781448faf53a101a5827f523a3e5c0d23c
(cherry picked from commit a2d3555f8feb3ba77c6e357a68b0f5dcc96001e9)
Merged-In: I52ed2d781448faf53a101a5827f523a3e5c0d23c
diff --git a/api/current.txt b/api/current.txt
index fb751ea..db95a21 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -4357,6 +4357,7 @@
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, Class<?>...);
method public static java.lang.invoke.MethodHandle dropArgumentsToMatch(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>, int);
+ method public static java.lang.invoke.MethodHandle dropReturn(java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle empty(java.lang.invoke.MethodType);
method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
@@ -4376,6 +4377,7 @@
method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
method public static <T extends java.lang.reflect.Member> T reflectAs(Class<T>, java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle spreadInvoker(java.lang.invoke.MethodType, int);
+ method public static java.lang.invoke.MethodHandle tableSwitch(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle...);
method public static java.lang.invoke.MethodHandle throwException(Class<?>, Class<? extends java.lang.Throwable>);
method public static java.lang.invoke.MethodHandle tryFinally(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
method public static java.lang.invoke.MethodHandle varHandleExactInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType);
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
index d5d4448..9f68d6c 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
@@ -3296,6 +3296,31 @@
}
/**
+ * Drop the return value of the target handle (if any).
+ * The returned method handle will have a {@code void} return type.
+ *
+ * @param target the method handle to adapt
+ * @return a possibly adapted method handle
+ * @throws NullPointerException if {@code target} is null
+ * @since 16
+ */
+ public static MethodHandle dropReturn(MethodHandle target) {
+ Objects.requireNonNull(target);
+ MethodType oldType = target.type();
+ Class<?> oldReturnType = oldType.returnType();
+ if (oldReturnType == void.class)
+ return target;
+
+ MethodType newType = oldType.changeReturnType(void.class);
+ // Android-changed: no support for BoundMethodHandle or LambdaForm.
+ // BoundMethodHandle result = target.rebind();
+ // LambdaForm lform = result.editor().filterReturnForm(V_TYPE, true);
+ // result = result.copyWith(newType, lform);
+ // return result;
+ return target.asType(newType);
+ }
+
+ /**
* Adapts a target method handle by pre-processing
* one or more of its arguments, each with its own unary filter function,
* and then calling the target with each pre-processed argument
@@ -5450,6 +5475,96 @@
}
}
+ /**
+ * Creates a table switch method handle, which can be used to switch over a set of target
+ * method handles, based on a given target index, called selector.
+ * <p>
+ * For a selector value of {@code n}, where {@code n} falls in the range {@code [0, N)},
+ * and where {@code N} is the number of target method handles, the table switch method
+ * handle will invoke the n-th target method handle from the list of target method handles.
+ * <p>
+ * For a selector value that does not fall in the range {@code [0, N)}, the table switch
+ * method handle will invoke the given fallback method handle.
+ * <p>
+ * All method handles passed to this method must have the same type, with the additional
+ * requirement that the leading parameter be of type {@code int}. The leading parameter
+ * represents the selector.
+ * <p>
+ * Any trailing parameters present in the type will appear on the returned table switch
+ * method handle as well. Any arguments assigned to these parameters will be forwarded,
+ * together with the selector value, to the selected method handle when invoking it.
+ *
+ * @apiNote Example:
+ * The cases each drop the {@code selector} value they are given, and take an additional
+ * {@code String} argument, which is concatenated (using {@link String#concat(String)})
+ * to a specific constant label string for each case:
+ * <blockquote><pre>{@code
+ * MethodHandles.Lookup lookup = MethodHandles.lookup();
+ * MethodHandle caseMh = lookup.findVirtual(String.class, "concat",
+ * MethodType.methodType(String.class, String.class));
+ * caseMh = MethodHandles.dropArguments(caseMh, 0, int.class);
+ *
+ * MethodHandle caseDefault = MethodHandles.insertArguments(caseMh, 1, "default: ");
+ * MethodHandle case0 = MethodHandles.insertArguments(caseMh, 1, "case 0: ");
+ * MethodHandle case1 = MethodHandles.insertArguments(caseMh, 1, "case 1: ");
+ *
+ * MethodHandle mhSwitch = MethodHandles.tableSwitch(
+ * caseDefault,
+ * case0,
+ * case1
+ * );
+ *
+ * assertEquals("default: data", (String) mhSwitch.invokeExact(-1, "data"));
+ * assertEquals("case 0: data", (String) mhSwitch.invokeExact(0, "data"));
+ * assertEquals("case 1: data", (String) mhSwitch.invokeExact(1, "data"));
+ * assertEquals("default: data", (String) mhSwitch.invokeExact(2, "data"));
+ * }</pre></blockquote>
+ *
+ * @param fallback the fallback method handle that is called when the selector is not
+ * within the range {@code [0, N)}.
+ * @param targets array of target method handles.
+ * @return the table switch method handle.
+ * @throws NullPointerException if {@code fallback}, the {@code targets} array, or any
+ * any of the elements of the {@code targets} array are
+ * {@code null}.
+ * @throws IllegalArgumentException if the {@code targets} array is empty, if the leading
+ * parameter of the fallback handle or any of the target
+ * handles is not {@code int}, or if the types of
+ * the fallback handle and all of target handles are
+ * not the same.
+ */
+ public static MethodHandle tableSwitch(MethodHandle fallback, MethodHandle... targets) {
+ Objects.requireNonNull(fallback);
+ Objects.requireNonNull(targets);
+ targets = targets.clone();
+ MethodType type = tableSwitchChecks(fallback, targets);
+ // Android-changed: use a Transformer for the implementation.
+ // return MethodHandleImpl.makeTableSwitch(type, fallback, targets);
+ return new Transformers.TableSwitch(type, fallback, targets);
+ }
+
+ private static MethodType tableSwitchChecks(MethodHandle defaultCase, MethodHandle[] caseActions) {
+ if (caseActions.length == 0)
+ throw new IllegalArgumentException("Not enough cases: " + Arrays.toString(caseActions));
+
+ MethodType expectedType = defaultCase.type();
+
+ if (!(expectedType.parameterCount() >= 1) || expectedType.parameterType(0) != int.class)
+ throw new IllegalArgumentException(
+ "Case actions must have int as leading parameter: " + Arrays.toString(caseActions));
+
+ for (MethodHandle mh : caseActions) {
+ Objects.requireNonNull(mh);
+ // Android-changed: MethodType's not interned.
+ // if (mh.type() != expectedType)
+ if (!mh.type().equals(expectedType))
+ throw new IllegalArgumentException(
+ "Case actions must have the same type: " + Arrays.toString(caseActions));
+ }
+
+ return expectedType;
+ }
+
// BEGIN Android-added: Code from OpenJDK's MethodHandleImpl.
/**
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 590b9a7..5183cf1 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -3110,4 +3110,34 @@
finiFrame.copyReturnValueTo(callerFrame);
}
}
+
+ /** Implements {@code MethodHandles.tableSwitch}. */
+ static class TableSwitch extends Transformer {
+ private final MethodHandle fallback;
+ private final MethodHandle[] targets;
+
+ TableSwitch(MethodType type, MethodHandle fallback, MethodHandle[] targets) {
+ super(type);
+ this.fallback = fallback;
+ this.targets = targets;
+ }
+
+ @Override
+ public void transform(EmulatedStackFrame callerFrame) throws Throwable {
+ final MethodHandle selected = selectMethodHandle(callerFrame);
+ invokeExactFromTransform(selected, callerFrame);
+ }
+
+ private MethodHandle selectMethodHandle(EmulatedStackFrame callerFrame) {
+ StackFrameReader reader = new StackFrameReader();
+ reader.attach(callerFrame);
+ final int index = reader.nextInt();
+
+ if (index >= 0 && index < targets.length) {
+ return targets[index];
+ } else {
+ return fallback;
+ }
+ }
+ }
}
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java b/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java
new file mode 100644
index 0000000..f0b83e9
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, 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.
+ */
+
+// Android-added: package declaration.
+package test.java.lang.invoke.MethodHandles;
+
+/*
+ * @test
+ * @bug 8255398
+ * @run testng TestDropReturn
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+
+import static java.lang.invoke.MethodType.methodType;
+import static org.testng.Assert.assertEquals;
+
+public class TestDropReturn {
+
+ @Test(dataProvider = "dropReturnCases")
+ public void testDropReturn(Class<?> cls, Object testValue) throws Throwable {
+ MethodHandle mh = MethodHandles.identity(cls);
+ assertEquals(mh.type(), methodType(cls, cls));
+ Object x = mh.invoke(testValue);
+ assertEquals(x, testValue);
+
+ mh = MethodHandles.dropReturn(mh);
+ assertEquals(mh.type(), methodType(void.class, cls));
+ mh.invoke(testValue); // should at least work
+ }
+
+ @DataProvider
+ public static Object[][] dropReturnCases() {
+ return new Object[][]{
+ { boolean.class, true },
+ { byte.class, (byte) 10 },
+ { char.class, 'x' },
+ { short.class, (short) 10 },
+ { int.class, 10 },
+ { long.class, 10L },
+ { float.class, 10F },
+ { double.class, 10D },
+ { Object.class, new Object() },
+ { String.class, "ABCD" },
+ };
+ }
+}
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java b/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java
new file mode 100644
index 0000000..ad13c86
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+// Android-added: package declaration.
+package test.java.lang.invoke.MethodHandles;
+
+/*
+ * @test
+ * @run testng/othervm -Xverify:all TestTableSwitch
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestTableSwitch {
+
+ static final MethodHandle MH_IntConsumer_accept;
+ static final MethodHandle MH_check;
+
+ static {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MH_IntConsumer_accept = lookup.findVirtual(IntConsumer.class, "accept",
+ MethodType.methodType(void.class, int.class));
+ MH_check = lookup.findStatic(TestTableSwitch.class, "check",
+ MethodType.methodType(void.class, List.class, Object[].class));
+ } catch (ReflectiveOperationException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ public static MethodHandle simpleTestCase(String value) {
+ return simpleTestCase(String.class, value);
+ }
+
+ public static MethodHandle simpleTestCase(Class<?> type, Object value) {
+ return MethodHandles.dropArguments(MethodHandles.constant(type, value), 0, int.class);
+ }
+
+ public static Object testValue(Class<?> type) {
+ if (type == String.class) {
+ return "X";
+ } else if (type == byte.class) {
+ return (byte) 42;
+ } else if (type == short.class) {
+ return (short) 84;
+ } else if (type == char.class) {
+ return 'Y';
+ } else if (type == int.class) {
+ return 168;
+ } else if (type == long.class) {
+ return 336L;
+ } else if (type == float.class) {
+ return 42F;
+ } else if (type == double.class) {
+ return 84D;
+ } else if (type == boolean.class) {
+ return true;
+ }
+ return null;
+ }
+
+ static final Class<?>[] TEST_TYPES = {
+ Object.class,
+ String.class,
+ byte.class,
+ short.class,
+ char.class,
+ int.class,
+ long.class,
+ float.class,
+ double.class,
+ boolean.class
+ };
+
+ public static Object[] testArguments(int caseNum, List<Object> testValues) {
+ Object[] args = new Object[testValues.size() + 1];
+ args[0] = caseNum;
+ int insertPos = 1;
+ for (Object testValue : testValues) {
+ args[insertPos++] = testValue;
+ }
+ return args;
+ }
+
+ @DataProvider
+ public static Object[][] nonVoidCases() {
+ List<Object[]> tests = new ArrayList<>();
+
+ for (Class<?> returnType : TEST_TYPES) {
+ for (int numCases = 1; numCases < 5; numCases++) {
+ tests.add(new Object[] { returnType, numCases, List.of() });
+ tests.add(new Object[] { returnType, numCases, List.of(TEST_TYPES) });
+ }
+ }
+
+ return tests.toArray(Object[][]::new);
+ }
+
+ private static void check(List<Object> testValues, Object[] collectedValues) {
+ assertEquals(collectedValues, testValues.toArray());
+ }
+
+ @Test(dataProvider = "nonVoidCases")
+ public void testNonVoidHandles(Class<?> type, int numCases, List<Class<?>> additionalTypes) throws Throwable {
+ MethodHandle collector = MH_check;
+ List<Object> testArguments = new ArrayList<>();
+ collector = MethodHandles.insertArguments(collector, 0, testArguments);
+ collector = collector.asCollector(Object[].class, additionalTypes.size());
+
+ Object defaultReturnValue = testValue(type);
+ MethodHandle defaultCase = simpleTestCase(type, defaultReturnValue);
+ defaultCase = MethodHandles.collectArguments(defaultCase, 1, collector);
+ Object[] returnValues = new Object[numCases];
+ MethodHandle[] cases = new MethodHandle[numCases];
+ for (int i = 0; i < cases.length; i++) {
+ Object returnValue = testValue(type);
+ returnValues[i] = returnValue;
+ MethodHandle theCase = simpleTestCase(type, returnValue);
+ theCase = MethodHandles.collectArguments(theCase, 1, collector);
+ cases[i] = theCase;
+ }
+
+ MethodHandle mhSwitch = MethodHandles.tableSwitch(
+ defaultCase,
+ cases
+ );
+
+ for (Class<?> additionalType : additionalTypes) {
+ testArguments.add(testValue(additionalType));
+ }
+
+ assertEquals(mhSwitch.invokeWithArguments(testArguments(-1, testArguments)), defaultReturnValue);
+
+ for (int i = 0; i < numCases; i++) {
+ assertEquals(mhSwitch.invokeWithArguments(testArguments(i, testArguments)), returnValues[i]);
+ }
+
+ assertEquals(mhSwitch.invokeWithArguments(testArguments(numCases, testArguments)), defaultReturnValue);
+ }
+
+ @Test
+ public void testVoidHandles() throws Throwable {
+ IntFunction<MethodHandle> makeTestCase = expectedIndex -> {
+ IntConsumer test = actualIndex -> assertEquals(actualIndex, expectedIndex);
+ return MH_IntConsumer_accept.bindTo(test);
+ };
+
+ MethodHandle mhSwitch = MethodHandles.tableSwitch(
+ /* default: */ makeTestCase.apply(-1),
+ /* case 0: */ makeTestCase.apply(0),
+ /* case 1: */ makeTestCase.apply(1),
+ /* case 2: */ makeTestCase.apply(2)
+ );
+
+ mhSwitch.invokeExact((int) -1);
+ mhSwitch.invokeExact((int) 0);
+ mhSwitch.invokeExact((int) 1);
+ mhSwitch.invokeExact((int) 2);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullDefaultHandle() {
+ MethodHandles.tableSwitch(null, simpleTestCase("test"));
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullCases() {
+ MethodHandle[] cases = null;
+ MethodHandles.tableSwitch(simpleTestCase("default"), cases);
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullCase() {
+ MethodHandles.tableSwitch(simpleTestCase("default"), simpleTestCase("case"), null);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = ".*Not enough cases.*")
+ public void testNotEnoughCases() {
+ MethodHandles.tableSwitch(simpleTestCase("default"));
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = ".*Case actions must have int as leading parameter.*")
+ public void testNotEnoughParameters() {
+ MethodHandle empty = MethodHandles.empty(MethodType.methodType(void.class));
+ MethodHandles.tableSwitch(empty, empty, empty);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = ".*Case actions must have int as leading parameter.*")
+ public void testNoLeadingIntParameter() {
+ MethodHandle empty = MethodHandles.empty(MethodType.methodType(void.class, double.class));
+ MethodHandles.tableSwitch(empty, empty, empty);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = ".*Case actions must have the same type.*")
+ public void testWrongCaseType() {
+ // doesn't return a String
+ MethodHandle wrongType = MethodHandles.empty(MethodType.methodType(void.class, int.class));
+ MethodHandles.tableSwitch(simpleTestCase("default"), simpleTestCase("case"), wrongType);
+ }
+
+}