Skip to content
Open
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
10 changes: 10 additions & 0 deletions build-tools/automation/yaml-templates/stage-package-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,16 @@ stages:
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-CoreCLR

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: Mono.Android.NET_Tests-CoreCLRTrimmable
project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj
testResultsFiles: TestResult-Mono.Android.NET_Tests-$(XA.Build.Configuration)CoreCLRTrimmable.xml
extraBuildArgs: -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-CoreCLRTrimmable

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ static class ModelBuilder
{
const string ProxyTypeSuffix = "_Proxy";

// Workaround for https://github.com/dotnet/runtime/issues/127004
// When true, all TypeMap entries are emitted as 2-arg (unconditional) to avoid the
// trimmer bug that strips TypeMapAssociation attributes when a TypeMap attribute
// references the same type. Set to false once the runtime bug is fixed to re-enable
// 3-arg conditional entries that allow unused framework bindings to be trimmed away.
const bool ForceUnconditionalEntries = true;

static readonly HashSet<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
"java/lang/Object",
"java/lang/Class",
Expand Down Expand Up @@ -122,8 +129,15 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
model.ProxyTypes.Add (proxy);
}

model.Entries.Add (BuildEntry (peer, proxy, assemblyName, jniName));
if (proxy != null && peer.IsGenericDefinition) {
var entry = BuildEntry (peer, proxy, assemblyName, jniName);
model.Entries.Add (entry);

// Emit a TypeMapAssociation for every entry that has a proxy.
// The runtime's _proxyTypeMap (GetOrCreateProxyTypeMapping) is populated from
// TypeMapAssociationAttribute — NOT from TypeMapAttribute's 3rd arg.
// Without this, the proxy type map is empty and CreatePeer fails for
// interface types like IIterator where targetType-based lookup is needed.
if (proxy != null) {
model.Associations.Add (new TypeMapAssociationData {
SourceTypeReference = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName),
AliasProxyTypeReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", assemblyName),
Expand Down Expand Up @@ -353,7 +367,9 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr
proxyRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName);
}

bool isUnconditional = IsUnconditionalEntry (peer);
// When ForceUnconditionalEntries is true, always emit 2-arg (unconditional) TypeMap
// attributes to work around https://github.com/dotnet/runtime/issues/127004.
bool isUnconditional = ForceUnconditionalEntries || IsUnconditionalEntry (peer);
string? targetRef = null;
if (!isUnconditional) {
targetRef = AssemblyQualify (peer.ManagedTypeName, peer.AssemblyName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
attrs, MethodImplAttributes.IL,
Metadata.GetOrAddString (name),
sigBlobHandle,
bodyOffset, default);
bodyOffset, MetadataTokens.ParameterHandle (Metadata.GetRowCount (TableIndex.Param) + 1));
}

/// <summary>
Expand Down Expand Up @@ -358,7 +358,7 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
attrs, MethodImplAttributes.IL,
Metadata.GetOrAddString (name),
sigBlobHandle,
bodyOffset, default);
bodyOffset, MetadataTokens.ParameterHandle (Metadata.GetRowCount (TableIndex.Param) + 1));
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ sealed class TypeMapAssemblyEmitter
TypeReferenceHandle _iJavaPeerableRef;
TypeReferenceHandle _jniHandleOwnershipRef;
TypeReferenceHandle _jniObjectReferenceRef;
TypeReferenceHandle _jniObjectReferenceTypeRef;
TypeReferenceHandle _jniObjectReferenceOptionsRef;
TypeReferenceHandle _iAndroidCallableWrapperRef;
TypeReferenceHandle _jniEnvRef;
Expand All @@ -91,7 +92,7 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _notSupportedExceptionCtorRef;
MemberReferenceHandle _jniObjectReferenceCtorRef;
MemberReferenceHandle _jniEnvDeleteRefRef;
MemberReferenceHandle _withinNewObjectScopeRef;
MemberReferenceHandle _shouldSkipActivationRef;
MemberReferenceHandle _ucoAttrCtorRef;
BlobHandle _ucoAttrBlobHandle;
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
Expand Down Expand Up @@ -206,6 +207,8 @@ void EmitTypeReferences ()
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JNIEnv"));
_jniObjectReferenceRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReference"));
_jniObjectReferenceTypeRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReferenceType"));
_jniObjectReferenceOptionsRef = metadata.AddTypeReference (_javaInteropRef,
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReferenceOptions"));
_iAndroidCallableWrapperRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
Expand Down Expand Up @@ -279,10 +282,16 @@ void EmitMemberReferences ()
rt => rt.Void (),
p => p.AddParameter ().Type ().String ()));

// JniObjectReference..ctor(IntPtr handle, JniObjectReferenceType type)
// Note: The C# constructor has a default parameter (type = Invalid), but in IL there is only
// the 2-parameter overload. We must emit both parameters explicitly.
_jniObjectReferenceCtorRef = _pe.AddMemberRef (_jniObjectReferenceRef, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1,
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2,
rt => rt.Void (),
p => p.AddParameter ().Type ().IntPtr ()));
p => {
p.AddParameter ().Type ().IntPtr ();
p.AddParameter ().Type ().Type (_jniObjectReferenceTypeRef, true);
}));

// JNIEnv.DeleteRef(IntPtr, JniHandleOwnership) — static, internal
// Used by JI-style activation to clean up the original handle after constructing the peer.
Expand All @@ -295,11 +304,11 @@ void EmitMemberReferences ()
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
}));

// JniEnvironment.get_WithinNewObjectScope() -> bool (static property)
_withinNewObjectScopeRef = _pe.AddMemberRef (_jniEnvironmentRef, "get_WithinNewObjectScope",
sig => sig.MethodSignature ().Parameters (0,
// JavaPeerProxy.ShouldSkipActivation(IntPtr) -> bool (static method)
_shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "ShouldSkipActivation",
sig => sig.MethodSignature ().Parameters (1,
rt => rt.Type ().Boolean (),
p => { }));
p => { p.AddParameter ().Type ().IntPtr (); }));

// JniNativeMethod..ctor(byte*, byte*, IntPtr)
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
Expand Down Expand Up @@ -691,9 +700,10 @@ void EmitCreateInstanceViaJavaInteropNewobj (EntityHandle typeRef)
EmitCreateInstanceBodyWithLocals (
EncodeJniObjectReferenceAndObjectLocals,
encoder => {
// var jniRef = new JniObjectReference(handle);
// var jniRef = new JniObjectReference(handle, JniObjectReferenceType.Invalid);
encoder.LoadLocalAddress (0);
encoder.OpCode (ILOpCode.Ldarg_1); // handle
encoder.LoadConstantI4 (0); // JniObjectReferenceType.Invalid
encoder.Call (_jniObjectReferenceCtorRef);

// var result = new TargetType(ref jniRef, JniObjectReferenceOptions.Copy);
Expand Down Expand Up @@ -738,9 +748,10 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act
// dup obj (one copy for the call, one for the return)
encoder.OpCode (ILOpCode.Dup);

// var jniRef = new JniObjectReference(handle);
// var jniRef = new JniObjectReference(handle, JniObjectReferenceType.Invalid);
encoder.LoadLocalAddress (0);
encoder.OpCode (ILOpCode.Ldarg_1); // handle
encoder.LoadConstantI4 (0); // JniObjectReferenceType.Invalid
encoder.Call (_jniObjectReferenceCtorRef);

// obj.BaseCtor(ref jniRef, JniObjectReferenceOptions.Copy);
Expand Down Expand Up @@ -932,6 +943,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy

enc.LoadLocalAddress (3); // jniRef
enc.LoadArgument (1); // self
enc.LoadConstantI4 (0); // JniObjectReferenceType.Invalid
enc.Call (_jniObjectReferenceCtorRef);

if (activationCtor.IsOnLeafType) {
Expand Down Expand Up @@ -990,7 +1002,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy
/// <code>
/// if (!JniEnvironment.BeginMarshalMethod(jnienv, out envp, out runtime)) return;
/// try {
/// if (!JniEnvironment.WithinNewObjectScope) { [emitActivation] }
/// if (!JavaPeerProxy.ShouldSkipActivation(self)) { [emitActivation] }
/// } catch (Exception e) {
/// runtime?.OnUserUnhandledException(ref envp, e);
/// } finally {
Expand All @@ -1016,9 +1028,10 @@ void EmitUcoConstructorBodyWithMarshal (InstructionEncoder encoder, ControlFlowB
encoder.Call (_beginMarshalMethodRef);
encoder.Branch (ILOpCode.Brfalse, afterAll);

// TRY — check WithinNewObjectScope, then run activation code.
// TRY — check ShouldSkipActivation, then run activation code.
encoder.MarkLabel (tryStart);
encoder.Call (_withinNewObjectScopeRef);
encoder.LoadArgument (1); // self (IntPtr)
encoder.Call (_shouldSkipActivationRef);
encoder.Branch (ILOpCode.Brtrue, skipLabel);

emitActivation (encoder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ void Build ()
if (attrName == "RegisterAttribute") {
registerInfo = ParseRegisterAttribute (ca);
registerInfo = registerInfo with { JniName = registerInfo.JniName.Replace ('.', '/') };
} else if (attrName == "JniTypeSignatureAttribute") {
registerInfo = ParseJniTypeSignatureAttribute (ca);
} else if (attrName == "ExportAttribute") {
// [Export] is a method-level attribute; it is parsed at scan time by JavaPeerScanner
} else if (IsKnownComponentAttribute (attrName)) {
Expand Down Expand Up @@ -218,6 +220,28 @@ internal RegisterInfo ParseRegisterAttribute (CustomAttribute ca)
return ParseRegisterInfo (DecodeAttribute (ca));
}

internal RegisterInfo ParseJniTypeSignatureAttribute (CustomAttribute ca)
{
var value = DecodeAttribute (ca);

string jniName = "";
bool doNotGenerateAcw = false;

if (value.FixedArguments.Length > 0) {
jniName = (string?)value.FixedArguments [0].Value ?? "";
}

if (TryGetNamedArgument<bool> (value, "GenerateJavaPeer", out var generateJavaPeer)) {
doNotGenerateAcw = !generateJavaPeer;
}

return new RegisterInfo {
JniName = jniName.Replace ('.', '/'),
DoNotGenerateAcw = doNotGenerateAcw,
IsFromJniTypeSignature = true,
};
}

internal CustomAttributeValue<string> DecodeAttribute (CustomAttribute ca)
{
return ca.DecodeValue (customAttributeTypeProvider);
Expand Down Expand Up @@ -504,6 +528,7 @@ sealed record RegisterInfo
public string? Signature { get; init; }
public string? Connector { get; init; }
public bool DoNotGenerateAcw { get; init; }
public bool IsFromJniTypeSignature { get; init; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ public sealed record JavaPeerInfo
/// </summary>
public bool DoNotGenerateAcw { get; init; }

/// <summary>
/// True when the type was discovered via <c>[JniTypeSignatureAttribute]</c>
/// rather than <c>[RegisterAttribute]</c>. Used to resolve cross-assembly
/// alias ownership: <c>[Register]</c> types take precedence.
/// </summary>
public bool IsFromJniTypeSignature { get; init; }

/// <summary>
/// Types with component attributes ([Activity], [Service], etc.),
/// custom views from layout XML, or manifest-declared components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,15 @@ public List<JavaPeerInfo> Scan (IReadOnlyList<(string Name, PEReader Reader)> as
assemblyCache [index.AssemblyName] = index;
}

var resultsByManagedName = new Dictionary<string, JavaPeerInfo> (StringComparer.Ordinal);
// Key by (managedTypeName, assemblyName) to avoid collisions when two assemblies
// define a type with the same managed name (e.g. Java.Lang.Throwable in both
// Java.Interop and Mono.Android).
var resultsByQualifiedName = new Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> ();
foreach (var index in assemblyCache.Values) {
ScanAssembly (index, resultsByManagedName);
ScanAssembly (index, resultsByQualifiedName);
}
ForceUnconditionalCrossReferences (resultsByManagedName, assemblyCache);
return new List<JavaPeerInfo> (resultsByManagedName.Values);
ForceUnconditionalCrossReferences (resultsByQualifiedName, assemblyCache);
return new List<JavaPeerInfo> (resultsByQualifiedName.Values);
}

/// <summary>
Expand All @@ -112,19 +115,19 @@ internal AssemblyManifestInfo ScanAssemblyManifestInfo ()
/// [Application(ManageSpaceActivity = typeof(X))] must be unconditional,
/// because the manifest will reference them even if nothing else does.
/// </summary>
static void ForceUnconditionalCrossReferences (Dictionary<string, JavaPeerInfo> resultsByManagedName, Dictionary<string, AssemblyIndex> assemblyCache)
static void ForceUnconditionalCrossReferences (Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results, Dictionary<string, AssemblyIndex> assemblyCache)
{
foreach (var index in assemblyCache.Values) {
foreach (var attrInfo in index.AttributesByType.Values) {
if (attrInfo is ApplicationAttributeInfo applicationAttributeInfo) {
ForceUnconditionalIfPresent (resultsByManagedName, applicationAttributeInfo.BackupAgent);
ForceUnconditionalIfPresent (resultsByManagedName, applicationAttributeInfo.ManageSpaceActivity);
ForceUnconditionalIfPresent (results, applicationAttributeInfo.BackupAgent);
ForceUnconditionalIfPresent (results, applicationAttributeInfo.ManageSpaceActivity);
}
}
}
}

static void ForceUnconditionalIfPresent (Dictionary<string, JavaPeerInfo> resultsByManagedName, string? managedTypeName)
static void ForceUnconditionalIfPresent (Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results, string? managedTypeName)
{
if (managedTypeName is null) {
return;
Expand All @@ -135,26 +138,27 @@ static void ForceUnconditionalIfPresent (Dictionary<string, JavaPeerInfo> result
return;
}

// Try exact match first (handles both plain and assembly-qualified names)
if (resultsByManagedName.TryGetValue (managedTypeName, out var peer)) {
resultsByManagedName [managedTypeName] = peer with { IsUnconditional = true };
return;
}

// TryGetTypeProperty may return assembly-qualified names like "Ns.Type, Assembly, ..."
// Strip to just the type name for lookup
var commaIndex = managedTypeName.IndexOf (',');
if (commaIndex <= 0) {
if (commaIndex > 0) {
managedTypeName = managedTypeName.Substring (0, commaIndex).Trim ();
}

if (managedTypeName.Length == 0) {
return;
}

var typeName = managedTypeName.Substring (0, commaIndex).Trim ();
if (typeName.Length > 0 && resultsByManagedName.TryGetValue (typeName, out peer)) {
resultsByManagedName [typeName] = peer with { IsUnconditional = true };
// Search by managed type name across all assemblies (BackupAgent/ManageSpaceActivity
// attribute values are not assembly-qualified).
foreach (var key in results.Keys) {
if (string.Equals (key.ManagedName, managedTypeName, StringComparison.Ordinal)) {
results [key] = results [key] with { IsUnconditional = true };
}
}
}

void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results)
void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results)
{
foreach (var typeHandle in index.Reader.TypeDefinitions) {
var typeDef = index.Reader.GetTypeDefinition (typeHandle);
Expand Down Expand Up @@ -237,6 +241,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results
IsInterface = isInterface,
IsAbstract = isAbstract,
DoNotGenerateAcw = doNotGenerateAcw,
IsFromJniTypeSignature = registerInfo?.IsFromJniTypeSignature ?? false,
IsUnconditional = isUnconditional,
CannotRegisterInStaticConstructor = cannotRegisterInStaticConstructor,
MarshalMethods = marshalMethods,
Expand All @@ -248,7 +253,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results
ComponentAttribute = ToComponentInfo (attrInfo),
};

results [fullName] = peer;
results [(fullName, index.AssemblyName)] = peer;
}
}

Expand Down Expand Up @@ -901,7 +906,7 @@ static string GetJavaAccess (MethodAttributes access)
};
}

string? ResolveBaseJavaName (TypeDefinition typeDef, AssemblyIndex index, Dictionary<string, JavaPeerInfo> results)
string? ResolveBaseJavaName (TypeDefinition typeDef, AssemblyIndex index, Dictionary<(string ManagedName, string AssemblyName), JavaPeerInfo> results)
{
if (!TryResolveBaseType (typeDef, index, out var baseTypeDef, out _, out var baseIndex, out var baseTypeName, out _)) {
return null;
Expand All @@ -914,7 +919,7 @@ static string GetJavaAccess (MethodAttributes access)
}

// Fall back to already-scanned results (component-attributed or CRC64-computed peers)
if (results.TryGetValue (baseTypeName, out var basePeer)) {
if (results.TryGetValue ((baseTypeName, baseIndex.AssemblyName), out var basePeer)) {
return basePeer.JavaName;
}

Expand Down
Loading