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
Original file line number Diff line number Diff line change
Expand Up @@ -976,16 +976,20 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
});

// Open generic types can't be activated — emit a no-op UCO.
// Open generic types can't be activated because Java construction cannot provide the type arguments.
if (proxy.IsGenericDefinition) {
var noopHandle = _pe.EmitBody (uco.WrapperName,
var openGenericHandle = _pe.EmitBody (uco.WrapperName,
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
encodeSig,
encoder => {
encoder.OpCode (ILOpCode.Ret);
});
AddUnmanagedCallersOnlyAttribute (noopHandle);
return noopHandle;
(encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => {
enc.LoadString (_pe.Metadata.GetOrAddUserString ("Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined."));
enc.OpCode (ILOpCode.Newobj);
enc.Token (_notSupportedExceptionCtorRef);
enc.OpCode (ILOpCode.Throw);
}),
EncodeUcoConstructorLocals_Standard);
AddUnmanagedCallersOnlyAttribute (openGenericHandle);
return openGenericHandle;
}

MethodDefinitionHandle handle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,32 @@ private protected static List<string> GetMemberRefNames (MetadataReader reader)
.Select (m => reader.GetString (m.Name))
.ToList ();

/// <summary>
/// Returns true if the IL byte stream contains a Call (0x28) or Callvirt (0x6F) instruction
/// whose metadata token matches <paramref name="token"/>.
/// </summary>
private protected static bool ILContainsCallToken (byte[] ilBytes, int token)
{
return ILContainsOpcodeToken (ilBytes, token, (byte) ILOpCode.Call, (byte) ILOpCode.Callvirt);
}

private protected static bool ILContainsNewobjToken (byte[] ilBytes, int token)
{
return ILContainsOpcodeToken (ilBytes, token, (byte) ILOpCode.Newobj);
}

static bool ILContainsOpcodeToken (byte[] ilBytes, int token, params byte[] opcodes)
{
byte t0 = (byte)(token & 0xFF);
byte t1 = (byte)((token >> 8) & 0xFF);
byte t2 = (byte)((token >> 16) & 0xFF);
byte t3 = (byte)((token >> 24) & 0xFF);
for (int i = 0; i < ilBytes.Length - 4; i++) {
if (opcodes.Contains (ilBytes [i]) &&
ilBytes [i + 1] == t0 && ilBytes [i + 2] == t1 &&
ilBytes [i + 3] == t2 && ilBytes [i + 4] == t3)
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1173,28 +1173,61 @@ public void Generate_UcoConstructor_JiStyle_HasExceptionRegions ()
}

[Fact]
public void Generate_UcoConstructor_GenericDefinition_NoExceptionRegions ()
public void Generate_UcoConstructor_GenericDefinition_ThrowsWithMarshalMethodPattern ()
{
// Open-generic UCO constructors are no-ops and must NOT have exception regions
// (a single 'ret' is emitted with no surrounding try/catch/finally).
var peers = ScanFixtures ();
var generic = peers.First (p => p.JavaName == "my/app/GenericHolder");
Assert.True (generic.IsGenericDefinition);
// Open-generic UCO constructors throw inside the same marshal-method wrapper used
// by normal UCO constructors, so the exception is surfaced through
// JniRuntime.OnUserUnhandledException instead of crossing the JNI boundary.
var generic = MakeAcwPeer ("test/GenericHolder", "Test.GenericHolder`1", "TestAsm") with {
IsGenericDefinition = true,
};

using var stream = GenerateAssembly (new [] { generic }, "GenericUcoCtorTest");
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();

var nctorMethodHandle = FindNctorUcoMethod (reader);
Assert.False (nctorMethodHandle.IsNil, "Open-generic ACWs should emit a throwing nctor_*_uco method");

if (!nctorMethodHandle.IsNil) {
// If a nctor_*_uco method exists for the generic type, it must be a no-op (no exception regions).
var nctorMethod = reader.GetMethodDefinition (nctorMethodHandle);
var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress);
Assert.NotNull (body);
Assert.Empty (body.ExceptionRegions);
}
// Open-generic types do not get a nctor_*_uco wrapper — no UCO ctors for generics.
var nctorMethod = reader.GetMethodDefinition (nctorMethodHandle);
var body = pe.GetMethodBody (nctorMethod.RelativeVirtualAddress);
Assert.NotNull (body);

var regions = body.ExceptionRegions;
Assert.True (regions.Length >= 2,
$"Open-generic UCO constructor should have at least 2 exception regions, found {regions.Length}");
Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Catch);
Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Finally);

var typeNames = GetTypeRefNames (reader);
Assert.Contains ("NotSupportedException", typeNames);

var ilBytes = body.GetILBytes ();
Assert.NotNull (ilBytes);
var memberRefHandles = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef))
.Select (i => MetadataTokens.MemberReferenceHandle (i))
.ToList ();
var notSupportedExceptionCtorHandle = memberRefHandles.First (h => {
var member = reader.GetMemberReference (h);
if (reader.GetString (member.Name) != ".ctor" || member.Parent.Kind != HandleKind.TypeReference) {
return false;
}

var parent = reader.GetTypeReference ((TypeReferenceHandle) member.Parent);
return reader.GetString (parent.Name) == "NotSupportedException";
});
var beginHandle = memberRefHandles.First (h => reader.GetString (reader.GetMemberReference (h).Name) == "BeginMarshalMethod");
var endHandle = memberRefHandles.First (h => reader.GetString (reader.GetMemberReference (h).Name) == "EndMarshalMethod");
var exHandle = memberRefHandles.First (h => reader.GetString (reader.GetMemberReference (h).Name) == "OnUserUnhandledException");
int notSupportedExceptionCtorToken = MetadataTokens.GetToken (notSupportedExceptionCtorHandle);
int beginToken = MetadataTokens.GetToken (beginHandle);
int endToken = MetadataTokens.GetToken (endHandle);
int exToken = MetadataTokens.GetToken (exHandle);
Assert.True (ILContainsNewobjToken (ilBytes, notSupportedExceptionCtorToken), "open-generic nctor_*_uco IL should construct NotSupportedException");
Assert.True (ilBytes.Contains ((byte) ILOpCode.Throw), "open-generic nctor_*_uco IL should throw");
Assert.True (ILContainsCallToken (ilBytes, beginToken), "open-generic nctor_*_uco IL should call BeginMarshalMethod");
Assert.True (ILContainsCallToken (ilBytes, endToken), "open-generic nctor_*_uco IL should call EndMarshalMethod");
Assert.True (ILContainsCallToken (ilBytes, exToken), "open-generic nctor_*_uco IL should call OnUserUnhandledException");
Comment thread
simonrozsival marked this conversation as resolved.
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ public void InvokingNullInstanceDoesNotCrashDalvik ()
}
}

// TODO: https://github.com/dotnet/android/issues/11170 — open generic creation should throw but succeeds under trimmable typemap
[Test]
public void NewOpenGenericTypeThrows ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer)
"Java.InteropTests.JniPeerMembersTests.ReplacementTypeUsedForMethodLookup",
"Java.InteropTests.JniPeerMembersTests.ReplaceStaticMethodName",

// net.dot.jni.test.GenericHolder Java class not in APK
"Java.InteropTests.JniTypeManagerTests.CannotCreateGenericHolderFromJava",

// Open generic type handling differs from non-trimmable
"Java.InteropTests.JnienvTest.NewOpenGenericTypeThrows",

// Throwable subclass registration
"Java.InteropTests.JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered",

Expand Down