undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
403
test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
Normal file
403
test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, 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.
|
||||
*/
|
||||
|
||||
import java.lang.classfile.ClassHierarchyResolver;
|
||||
import java.lang.classfile.ClassFile;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.constant.ClassDesc;
|
||||
import java.lang.constant.MethodTypeDesc;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandleProxies;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.WrongMethodTypeException;
|
||||
import java.lang.reflect.AccessFlag;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.ToLongFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import static java.lang.invoke.MethodHandleProxies.*;
|
||||
import static java.lang.invoke.MethodType.genericMethodType;
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
import static java.lang.classfile.ClassFile.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 6983726 8206955 8269351 8350549
|
||||
* @summary Basic sanity tests for MethodHandleProxies
|
||||
* @build BasicTest Client
|
||||
* @run junit BasicTest
|
||||
*/
|
||||
public class BasicTest {
|
||||
|
||||
@Test
|
||||
public void testUsual() throws Throwable {
|
||||
AtomicInteger ai = new AtomicInteger(5);
|
||||
var mh = MethodHandles.lookup().findVirtual(AtomicInteger.class, "getAndIncrement", methodType(int.class));
|
||||
IntSupplier is = asInterfaceInstance(IntSupplier.class, mh.bindTo(ai));
|
||||
assertEquals(5, is.getAsInt());
|
||||
assertEquals(6, is.getAsInt());
|
||||
assertEquals(7, is.getAsInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Established null behaviors of MHP API.
|
||||
*/
|
||||
@Test
|
||||
public void testNulls() {
|
||||
assertThrows(NullPointerException.class, () ->
|
||||
asInterfaceInstance(null, MethodHandles.zero(void.class)),
|
||||
"asInterfaceInstance - intfc");
|
||||
assertThrows(NullPointerException.class, () ->
|
||||
asInterfaceInstance(Runnable.class, null),
|
||||
"asInterfaceInstance - target");
|
||||
|
||||
assertFalse(isWrapperInstance(null), "isWrapperInstance");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceTarget(null),
|
||||
"wrapperInstanceTarget");
|
||||
assertThrows(IllegalArgumentException.class, () -> wrapperInstanceType(null),
|
||||
"wrapperInstanceType");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrapperInstance() throws Throwable {
|
||||
var mh = MethodHandles.publicLookup()
|
||||
.findVirtual(Integer.class, "compareTo", methodType(int.class, Integer.class));
|
||||
@SuppressWarnings("unchecked")
|
||||
Comparator<Integer> proxy = (Comparator<Integer>) asInterfaceInstance(Comparator.class, mh);
|
||||
|
||||
assertTrue(isWrapperInstance(proxy));
|
||||
assertSame(mh, wrapperInstanceTarget(proxy));
|
||||
assertSame(Comparator.class, wrapperInstanceType(proxy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests undeclared exceptions and declared exceptions in proxies.
|
||||
*/
|
||||
@Test
|
||||
public void testThrowables() {
|
||||
// don't wrap
|
||||
assertThrows(Error.class, throwing(Error.class, new Error())::close,
|
||||
"Errors should be propagated");
|
||||
assertThrows(RuntimeException.class, throwing(RuntimeException.class, new RuntimeException())::close,
|
||||
"RuntimeException should be propagated");
|
||||
assertThrows(IOException.class, throwing(IOException.class, new IOException())::close,
|
||||
"Declared IOException should be propagated");
|
||||
// wrap
|
||||
assertThrows(UndeclaredThrowableException.class, throwing(IllegalAccessException.class,
|
||||
new IllegalAccessException())::close,
|
||||
"Undeclared IllegalAccessException should be wrapped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that invalid interfaces are rejected.
|
||||
*/
|
||||
@Test
|
||||
public void testRejects() {
|
||||
var mh = MethodHandles.constant(String.class, "42");
|
||||
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(PackagePrivate.class, mh),
|
||||
"non-public interface");
|
||||
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(loadHidden(), mh),
|
||||
"hidden interface");
|
||||
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(MultiAbstractMethods.class, mh),
|
||||
"multiple abstract method names");
|
||||
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(NoAbstractMethods.class, mh),
|
||||
"no abstract method");
|
||||
assertThrows(IllegalArgumentException.class, () -> asInterfaceInstance(Sealed.class, mh),
|
||||
"sealed interface");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that non-sealed interfaces can be implemented.
|
||||
*/
|
||||
@Test
|
||||
public void testNonSealed() {
|
||||
MethodHandle target = MethodHandles.constant(String.class, "Non-Sealed");
|
||||
NonSealed proxy = asInterfaceInstance(NonSealed.class, target);
|
||||
assertEquals(proxy.m(), "Non-Sealed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Proxy correctly proxies potential bridge abstract methods.
|
||||
*/
|
||||
@Test
|
||||
public void testMultiSameName() throws Throwable {
|
||||
var baseAndChild = loadBaseAndChild();
|
||||
var baseClass = baseAndChild.get(0);
|
||||
var childClass = baseAndChild.get(1);
|
||||
checkMethods(childClass.getMethods());
|
||||
checkMethods(childClass.getDeclaredMethods());
|
||||
|
||||
var lookup = MethodHandles.lookup();
|
||||
var baseValueMh = lookup.findVirtual(baseClass, "value", genericMethodType(0))
|
||||
.asType(genericMethodType(1));
|
||||
var childIntegerValueMh = lookup.findVirtual(childClass, "value", methodType(Integer.class))
|
||||
.asType(methodType(Integer.class, Object.class));
|
||||
var childIntValueMh = lookup.findVirtual(childClass, "value", methodType(int.class))
|
||||
.asType(methodType(int.class, Object.class));
|
||||
|
||||
Object child = asInterfaceInstance(childClass, MethodHandles.constant(Integer.class, 7));
|
||||
|
||||
assertEquals(7, (Object) baseValueMh.invokeExact(child));
|
||||
assertEquals(7, (Integer) childIntegerValueMh.invokeExact(child));
|
||||
assertEquals(7, (int) childIntValueMh.invokeExact(child));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default methods can be used.
|
||||
*/
|
||||
@Test
|
||||
public void testDefaultMethods() {
|
||||
MethodHandle target = MethodHandles.constant(String.class, "F");
|
||||
C proxy = asInterfaceInstance(C.class, target);
|
||||
|
||||
assertEquals(proxy.f(), "F");
|
||||
assertEquals(proxy.a(), "A");
|
||||
assertEquals(proxy.b(), "B");
|
||||
assertEquals(proxy.c(), "C");
|
||||
assertEquals(proxy.concat(), "ABC");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that correct implementation of default methods are called,
|
||||
* and correct abstract methods are implemented.
|
||||
*/
|
||||
@Test
|
||||
public void testOverrides() {
|
||||
MethodHandle target = MethodHandles.constant(String.class, "concat");
|
||||
D proxy = asInterfaceInstance(D.class, target);
|
||||
|
||||
assertEquals(proxy.a(), "OA");
|
||||
assertEquals(proxy.b(), "OB");
|
||||
assertEquals(proxy.c(), "OC");
|
||||
assertEquals(proxy.f(), "OF");
|
||||
assertEquals(proxy.concat(), "concat");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests primitive type conversions in proxies.
|
||||
*/
|
||||
@Test
|
||||
public void testPrimitiveConversion() throws Throwable {
|
||||
var mh = MethodHandles.lookup().findStatic(BasicTest.class, "mul",
|
||||
methodType(long.class, int.class));
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Integer, Long> func = (Function<Integer, Long>) asInterfaceInstance(Function.class, mh);
|
||||
assertEquals(32423432L * 32423432L, func.apply(32423432));
|
||||
@SuppressWarnings("unchecked")
|
||||
ToLongFunction<Integer> func1 = (ToLongFunction<Integer>) asInterfaceInstance(ToLongFunction.class, mh);
|
||||
assertEquals(32423432L * 32423432L, func1.applyAsLong(32423432));
|
||||
@SuppressWarnings("unchecked")
|
||||
IntFunction<Long> func2 = (IntFunction<Long>) asInterfaceInstance(IntFunction.class, mh);
|
||||
assertEquals(32423432L * 32423432L, func2.apply(32423432));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests common type conversions in proxies.
|
||||
*/
|
||||
@Test
|
||||
public void testBasicConversion() {
|
||||
var mh = MethodHandles.constant(String.class, "42");
|
||||
asInterfaceInstance(Client.class, mh).exec(); // return value dropped, runs fine
|
||||
|
||||
var nullMh = MethodHandles.zero(String.class);
|
||||
var badIterable = asInterfaceInstance(Iterable.class, nullMh);
|
||||
assertNull(badIterable.iterator()); // null is convertible
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests incompatible type conversions in proxy construction.
|
||||
*/
|
||||
@Test
|
||||
public void testWrongConversion() {
|
||||
var mh = MethodHandles.constant(String.class, "42");
|
||||
assertThrows(WrongMethodTypeException.class, () -> asInterfaceInstance(IntSupplier.class, mh),
|
||||
"cannot convert String return to int under any circumstance");
|
||||
|
||||
var proxy = asInterfaceInstance(Iterable.class, mh);
|
||||
assertThrows(ClassCastException.class, proxy::iterator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies {@code isWrapperInstance} works under race and is thread safe
|
||||
* like {@code Class} objects are.
|
||||
*/
|
||||
@Test
|
||||
public void testRacyWrapperCheck() {
|
||||
MethodHandle noop = MethodHandles.zero(void.class);
|
||||
var lookup = MethodHandles.lookup();
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Stream.generate(() -> {
|
||||
String name = "MHPRaceIface" + counter.getAndIncrement();
|
||||
var bytes = ClassFile.of().build(ClassDesc.of(name), clb ->
|
||||
clb.withFlags(ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE)
|
||||
.withMethod("sam", MTD_void, ACC_PUBLIC | ACC_ABSTRACT, _ -> {}));
|
||||
try {
|
||||
return lookup.defineClass(bytes);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).parallel()
|
||||
.map(cl -> MethodHandleProxies.asInterfaceInstance(cl, noop))
|
||||
.limit(100)
|
||||
.forEach(inst -> assertTrue(MethodHandleProxies.isWrapperInstance(inst),
|
||||
() -> Objects.toIdentityString(inst) + " should pass wrapper test"));
|
||||
}
|
||||
|
||||
private static <T extends Throwable> Closeable throwing(Class<T> clz, T value) {
|
||||
return asInterfaceInstance(Closeable.class, MethodHandles.throwException(void.class, clz).bindTo(value));
|
||||
}
|
||||
|
||||
private static long mul(int i) {
|
||||
return (long) i * i;
|
||||
}
|
||||
|
||||
void checkMethods(Method[] methods) {
|
||||
assertTrue(methods.length > 1, () -> "Should have more than 1 declared methods, found only " + Arrays.toString(methods));
|
||||
for (Method method : methods) {
|
||||
assertTrue(method.accessFlags().contains(AccessFlag.ABSTRACT), () -> method + " is not abstract");
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> loadHidden() {
|
||||
try (var is = BasicTest.class.getResourceAsStream("Client.class")) {
|
||||
var bytes = Objects.requireNonNull(is).readAllBytes();
|
||||
var lookup = MethodHandles.lookup();
|
||||
return lookup.defineHiddenClass(bytes, true).lookupClass();
|
||||
} catch (Throwable ex) {
|
||||
return fail("Hidden interface loading failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Base: Object value();
|
||||
// Child: Integer value(); int value();
|
||||
private List<Class<?>> loadBaseAndChild() throws IllegalAccessException {
|
||||
ClassDesc baseCd = ClassDesc.of("BasicTest$Base");
|
||||
ClassDesc childCd = ClassDesc.of("BasicTest$Child");
|
||||
var objMtd = MethodTypeDesc.of(CD_Object);
|
||||
var integerMtd = MethodTypeDesc.of(CD_Integer);
|
||||
var intMtd = MethodTypeDesc.of(CD_int);
|
||||
var classfile = ClassFile.of(ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver.defaultResolver().orElse(
|
||||
ClassHierarchyResolver.of(List.of(baseCd, childCd), Map.ofEntries(Map.entry(baseCd, CD_Object),
|
||||
Map.entry(childCd, CD_Object))))));
|
||||
|
||||
var baseBytes = classfile.build(baseCd, clb -> {
|
||||
clb.withSuperclass(CD_Object);
|
||||
clb.withFlags(ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT);
|
||||
clb.withMethod("value", objMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||
});
|
||||
|
||||
var lookup = MethodHandles.lookup();
|
||||
var base = lookup.ensureInitialized(lookup.defineClass(baseBytes));
|
||||
|
||||
var childBytes = classfile.build(childCd, clb -> {
|
||||
clb.withSuperclass(CD_Object);
|
||||
clb.withInterfaceSymbols(baseCd);
|
||||
clb.withFlags(ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT);
|
||||
clb.withMethod("value", integerMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||
clb.withMethod("value", intMtd, ACC_PUBLIC | ACC_ABSTRACT, mb -> {});
|
||||
});
|
||||
|
||||
var child = lookup.ensureInitialized(lookup.defineClass(childBytes));
|
||||
return List.of(base, child);
|
||||
}
|
||||
|
||||
public interface MultiAbstractMethods {
|
||||
String a();
|
||||
String b();
|
||||
}
|
||||
|
||||
public interface NoAbstractMethods {
|
||||
String toString();
|
||||
}
|
||||
|
||||
interface PackagePrivate {
|
||||
Object value();
|
||||
}
|
||||
|
||||
public interface A {
|
||||
default String a() {
|
||||
return "A";
|
||||
}
|
||||
}
|
||||
|
||||
public interface B {
|
||||
default String b() {
|
||||
return "B";
|
||||
}
|
||||
}
|
||||
|
||||
public interface C extends A, B {
|
||||
String f();
|
||||
|
||||
default String c() {
|
||||
return "C";
|
||||
}
|
||||
|
||||
default String concat() {
|
||||
return a() + b() + c();
|
||||
}
|
||||
}
|
||||
|
||||
public interface D extends C {
|
||||
String concat();
|
||||
|
||||
default String f() {
|
||||
return "OF";
|
||||
}
|
||||
|
||||
default String a() {
|
||||
return "OA";
|
||||
}
|
||||
|
||||
default String b() {
|
||||
return "OB";
|
||||
}
|
||||
|
||||
default String c() {
|
||||
return "OC";
|
||||
}
|
||||
}
|
||||
|
||||
public sealed interface Sealed permits NonSealed {
|
||||
String m();
|
||||
}
|
||||
|
||||
public non-sealed interface NonSealed extends Sealed {
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue