Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/IKVM.Java/local/ikvm/runtime/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@
*/
package ikvm.runtime;

import cli.System.Delegate;
import cli.System.Type;
import cli.System.RuntimeTypeHandle;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import sun.misc.Unsafe;

public final class Util
Expand All @@ -48,8 +54,33 @@ private Util()

public static native Type getRuntimeTypeFromClass(Class classObject);

/**
* Creates a delegate of the given type that invokes the given method handle. The handle is adapted to the
* signature of the delegate's Invoke method, which performs any conversions required; a handle that cannot be
* adapted results in a {@link java.lang.invoke.WrongMethodTypeException}.
*/
public static native Delegate getDelegateFromMethodHandle(Type delegateType, MethodHandle methodHandle);

/**
* Creates a delegate of the given type that invokes the given method or constructor. Access is checked as it is
* for {@link MethodHandles.Lookup#unreflect}; call {@code setAccessible(true)} on the member first in order to
* create a delegate for an otherwise inaccessible one.
*/
public static Delegate getDelegateFromMethod(Type delegateType, Executable executable) throws IllegalAccessException
{
if (executable == null)
throw new NullPointerException("executable");

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle methodHandle = executable instanceof Constructor
? lookup.unreflectConstructor((Constructor)executable)
: lookup.unreflect((Method)executable);

return getDelegateFromMethodHandle(delegateType, methodHandle);
}

public static native Throwable mapException(Throwable x);

public static native Throwable unmapException(Throwable x);

public static void throwException(cli.System.Exception x)
Expand Down
40 changes: 40 additions & 0 deletions src/IKVM.Runtime/Java/Externs/ikvm/runtime/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,46 @@ public static Type getRuntimeTypeFromClass(global::java.lang.Class classObject)
return wrapper.TypeAsBaseType;
}

/// <summary>
/// Creates a delegate of the given type that invokes the given method handle. The handle is adapted to the
/// signature of the delegate's Invoke method, which performs any conversions required (boxing, primitive
/// widening, ghost wrapping, receiver binding); an incompatible handle results in a
/// <see cref="global::java.lang.invoke.WrongMethodTypeException"/>.
/// </summary>
/// <param name="delegateType"></param>
/// <param name="methodHandle"></param>
/// <returns></returns>
public static Delegate getDelegateFromMethodHandle(Type delegateType, global::java.lang.invoke.MethodHandle methodHandle)
{
#if FIRST_PASS
throw new NotImplementedException();
#else
if (delegateType == null)
throw new global::java.lang.NullPointerException("delegateType");
if (methodHandle == null)
throw new global::java.lang.NullPointerException("methodHandle");

var invoke = delegateType.BaseType == typeof(MulticastDelegate) ? delegateType.GetMethod("Invoke") : null;
if (invoke == null)
throw new global::java.lang.IllegalArgumentException(delegateType.FullName + " is not a delegate type.");

foreach (var parameter in invoke.GetParameters())
if (parameter.ParameterType.IsByRef || parameter.ParameterType.IsPointer)
throw new global::java.lang.IllegalArgumentException(delegateType.FullName + " has a by-ref or pointer parameter.");

// adapt the handle to the delegate's own signature; this is what performs the conversions, and is also
// what rejects a handle that cannot be called through this delegate
var methodType = JVM.Context.MethodHandleUtil.GetDelegateMethodType(delegateType);
methodHandle = methodHandle.asType(methodType).asFixedArity();

// the adapted handle is materialized as its canonical MH/MHV delegate, which by construction has exactly
// the signature of the requested delegate; binding its Invoke as the target closes over it
var inner = JVM.Context.MethodHandleUtil.GetDelegateForInvokeExact(methodHandle);
return Delegate.CreateDelegate(delegateType, inner, inner.GetType().GetMethod("Invoke"), false) ??
throw new global::java.lang.IllegalArgumentException("Cannot create a " + delegateType.FullName + " for a method handle of type " + methodType + ".");
#endif
}

[HideFromJava]
public static Exception mapException(Exception e)
{
Expand Down
24 changes: 18 additions & 6 deletions src/IKVM.Runtime/MethodHandleUtil.jniexport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -633,20 +633,32 @@ internal Type GetDelegateTypeForInvokeExact(global::java.lang.invoke.MethodType
return type._invokeExactDelegateType;
}

internal T GetDelegateForInvokeExact<T>(global::java.lang.invoke.MethodHandle mh)
where T : class, Delegate
/// <summary>
/// Gets the canonical delegate that invokes the given method handle exactly. The type of the returned delegate
/// is the <see cref="MH"/> or <see cref="MHV"/> instantiation that corresponds to the handle's method type, as
/// returned by <see cref="GetDelegateTypeForInvokeExact"/>.
/// </summary>
/// <param name="mh"></param>
/// <returns></returns>
internal Delegate GetDelegateForInvokeExact(global::java.lang.invoke.MethodHandle mh)
{
var type = mh.type();
if (mh._invokeExactDelegate == null)
{
type._invokeExactDynamicMethod ??= DynamicMethodBuilder.CreateInvokeExact(context, type);
mh._invokeExactDelegate = type._invokeExactDynamicMethod.CreateDelegate(GetDelegateTypeForInvokeExact(type), mh);
var del = mh._invokeExactDelegate as T;
if (del != null)
return del;
}

throw java.lang.invoke.Invokers.newWrongMethodTypeException(GetDelegateMethodType(typeof(T)), type);
return (Delegate)mh._invokeExactDelegate;
}

internal T GetDelegateForInvokeExact<T>(global::java.lang.invoke.MethodHandle mh)
where T : class, Delegate
{
if (GetDelegateForInvokeExact(mh) is T del)
return del;

throw java.lang.invoke.Invokers.newWrongMethodTypeException(GetDelegateMethodType(typeof(T)), mh.type());
}

/// <summary>
Expand Down
157 changes: 157 additions & 0 deletions src/IKVM.Tests/Java/ikvm/runtime/UtilDelegateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;

using FluentAssertions;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace IKVM.Tests.Java.ikvm.runtime
{

[TestClass]
public class UtilDelegateTests
{

static global::java.lang.Class Class(string name) => global::java.lang.Class.forName(name);

static global::java.lang.reflect.Method Method(string className, string methodName, params string[] parameterTypeNames)
{
var parameterTypes = new global::java.lang.Class[parameterTypeNames.Length];
for (int i = 0; i < parameterTypes.Length; i++)
parameterTypes[i] = Class(parameterTypeNames[i]);

return Class(className).getMethod(methodName, parameterTypes);
}

[TestMethod]
public void CanCreateDelegateForInstanceMethod()
{
var m = Method("java.util.ArrayList", "size");
var d = (Func<object, int>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object, int>), m);

var list = new global::java.util.ArrayList();
list.add("a");
list.add("b");

d(list).Should().Be(2);
}

[TestMethod]
public void CanCreateDelegateForInstanceMethodWithArgument()
{
var m = Method("java.lang.StringBuilder", "append", "java.lang.String");
var d = (Func<object, object, object>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object, object, object>), m);

var sb = new global::java.lang.StringBuilder("a");
d(sb, "b").Should().BeSameAs(sb);
sb.toString().Should().Be("ab");
}

[TestMethod]
public void CanCreateDelegateForStaticMethod()
{
var m = Method("java.lang.Integer", "parseInt", "java.lang.String");
var d = (Func<object, int>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object, int>), m);

d("42").Should().Be(42);
}

[TestMethod]
public void CanCreateDelegateForVoidMethod()
{
var m = Method("java.util.ArrayList", "clear");
var d = (Action<object>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Action<object>), m);

var list = new global::java.util.ArrayList();
list.add("a");
d(list);

list.size().Should().Be(0);
}

/// <summary>
/// java.lang.Object is a remapped type, so its methods have no single backing MethodBase; the handle has to
/// route through the instance helper.
/// </summary>
[TestMethod]
public void CanCreateDelegateForMethodOnRemappedType()
{
var m = Method("java.lang.Object", "hashCode");
var d = (Func<object, int>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object, int>), m);

var o = new object();
d(o).Should().Be(global::java.lang.System.identityHashCode(o));
}

[TestMethod]
public void CanCreateDelegateForConstructor()
{
var c = Class("java.util.ArrayList").getConstructor(new global::java.lang.Class[0]);
var d = (Func<object>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object>), c);

d().Should().BeOfType<global::java.util.ArrayList>();
}

/// <summary>
/// Access is checked as for Lookup.unreflect, so an inaccessible member is rejected unless it has been marked
/// accessible. java.lang.Runtime's constructor is private.
/// </summary>
[TestMethod]
public void ThrowsForInaccessibleMemberUntilMarkedAccessible()
{
var c = Class("java.lang.Runtime").getDeclaredConstructor(new global::java.lang.Class[0]);

var act = () => global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object>), c);
act.Should().Throw<global::java.lang.IllegalAccessException>();

c.setAccessible(true);
var d = (Func<object>)global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object>), c);
d().Should().BeOfType<global::java.lang.Runtime>();
}

[TestMethod]
public void CanCreateDelegateForBoundMethodHandle()
{
var m = Method("java.util.ArrayList", "size");
var list = new global::java.util.ArrayList();
list.add("a");

var mh = global::java.lang.invoke.MethodHandles.publicLookup().unreflect(m).bindTo(list);
var d = (Func<int>)global::ikvm.runtime.Util.getDelegateFromMethodHandle(typeof(Func<int>), mh);

d().Should().Be(1);
}

[TestMethod]
public void ThrowsForTypeThatIsNotADelegate()
{
var m = Method("java.util.ArrayList", "size");

var act = () => global::ikvm.runtime.Util.getDelegateFromMethod(typeof(string), m);
act.Should().Throw<global::java.lang.IllegalArgumentException>();
}

[TestMethod]
public void ThrowsForIncompatibleDelegateSignature()
{
var m = Method("java.lang.Integer", "parseInt", "java.lang.String");

// parseInt takes one argument; this delegate supplies three
var act = () => global::ikvm.runtime.Util.getDelegateFromMethod(typeof(Func<object, object, object, int>), m);
act.Should().Throw<global::java.lang.invoke.WrongMethodTypeException>();
}

[TestMethod]
public void ThrowsForNullArguments()
{
var m = Method("java.util.ArrayList", "size");

var nullType = () => global::ikvm.runtime.Util.getDelegateFromMethodHandle(null, global::java.lang.invoke.MethodHandles.publicLookup().unreflect(m));
nullType.Should().Throw<global::java.lang.NullPointerException>();

var nullHandle = () => global::ikvm.runtime.Util.getDelegateFromMethodHandle(typeof(Func<object, int>), null);
nullHandle.Should().Throw<global::java.lang.NullPointerException>();
}

}

}
Loading