Skip to content

Commit 3fe1722

Browse files
[TrimmableTypeMap][Core B] Refactor AssemblyIndex
- Remove IJniNameProviderAttribute discovery and ReclassifyAttributes; only support hardcoded known component attributes for now - Remove JniNameProviderAttributeInfo (unused without dynamic discovery) - Convert RegisterInfo and ExportInfo to records with required init - Use .IsNullOrEmpty() extension method instead of string.IsNullOrEmpty() - Add NullableExtensions.cs for netstandard2.0 nullable-aware string checks - Throw on unknown component attribute name instead of silently creating a JniNameProviderAttributeInfo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4921d7d commit 3fe1722

File tree

2 files changed

+37
-107
lines changed

2 files changed

+37
-107
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
4+
5+
static class NullableExtensions
6+
{
7+
// The static methods in System.String are not NRT annotated in netstandard2.0,
8+
// so we need to add our own extension methods to make them nullable aware.
9+
public static bool IsNullOrEmpty ([NotNullWhen (false)] this string? str)
10+
{
11+
return string.IsNullOrEmpty (str);
12+
}
13+
14+
public static bool IsNullOrWhiteSpace ([NotNullWhen (false)] this string? str)
15+
{
16+
return string.IsNullOrWhiteSpace (str);
17+
}
18+
}

src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/AssemblyIndex.cs

Lines changed: 19 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,6 @@ sealed class AssemblyIndex : IDisposable
3434
/// </summary>
3535
public Dictionary<TypeDefinitionHandle, TypeAttributeInfo> AttributesByType { get; } = new ();
3636

37-
/// <summary>
38-
/// Type names of attributes that implement <c>Java.Interop.IJniNameProviderAttribute</c>
39-
/// in this assembly. Used to detect JNI name providers without hardcoding attribute names.
40-
/// </summary>
41-
public HashSet<string> JniNameProviderAttributes { get; } = new (StringComparer.Ordinal);
42-
43-
/// <summary>
44-
/// Merged set of all JNI name provider attribute type names across all loaded assemblies.
45-
/// Set by <see cref="JavaPeerScanner"/> after all assemblies are indexed.
46-
/// </summary>
47-
HashSet<string>? allJniNameProviderAttributes;
48-
4937
AssemblyIndex (PEReader peReader, MetadataReader reader, string assemblyName, string filePath)
5038
{
5139
this.peReader = peReader;
@@ -89,45 +77,6 @@ void Build ()
8977
}
9078
}
9179

92-
/// <summary>
93-
/// Sets the merged set of JNI name provider attributes from all loaded assemblies
94-
/// and re-classifies any attributes that weren't recognized in the initial pass.
95-
/// </summary>
96-
public void ReclassifyAttributes (HashSet<string> mergedJniNameProviders)
97-
{
98-
allJniNameProviderAttributes = mergedJniNameProviders;
99-
100-
foreach (var typeHandle in Reader.TypeDefinitions) {
101-
var typeDef = Reader.GetTypeDefinition (typeHandle);
102-
AttributesByType.TryGetValue (typeHandle, out var existing);
103-
104-
// Skip types that already have component attribute info
105-
if (existing is not null) {
106-
continue;
107-
}
108-
109-
// Re-check custom attributes with the full set of known providers
110-
foreach (var caHandle in typeDef.GetCustomAttributes ()) {
111-
var ca = Reader.GetCustomAttribute (caHandle);
112-
var attrName = GetCustomAttributeName (ca, Reader);
113-
114-
if (attrName is null || attrName == "RegisterAttribute" || attrName == "ExportAttribute") {
115-
continue;
116-
}
117-
118-
if (mergedJniNameProviders.Contains (attrName) && !IsKnownComponentAttribute (attrName)) {
119-
var componentName = TryGetNameProperty (ca);
120-
if (componentName is not null) {
121-
var attrInfo = new JniNameProviderAttributeInfo (attrName) {
122-
JniName = componentName.Replace ('.', '/'),
123-
};
124-
AttributesByType [typeHandle] = attrInfo;
125-
}
126-
}
127-
}
128-
}
129-
}
130-
13180
internal static string GetFullName (TypeDefinition typeDef, MetadataReader reader)
13281
{
13382
var name = reader.GetString (typeDef.Name);
@@ -163,7 +112,7 @@ internal static string GetFullName (TypeDefinition typeDef, MetadataReader reade
163112
registerInfo = ParseRegisterAttribute (ca, customAttributeTypeProvider);
164113
} else if (attrName == "ExportAttribute") {
165114
// [Export] methods are not handled yet and supporting them will be implemented later
166-
} else if (IsJniNameProviderAttribute (attrName)) {
115+
} else if (IsKnownComponentAttribute (attrName)) {
167116
attrInfo ??= CreateTypeAttributeInfo (attrName);
168117
var componentName = TryGetNameProperty (ca);
169118
if (componentName is not null) {
@@ -179,27 +128,6 @@ internal static string GetFullName (TypeDefinition typeDef, MetadataReader reade
179128
return (registerInfo, attrInfo);
180129
}
181130

182-
/// <summary>
183-
/// Checks if an attribute type name is a known <c>IJniNameProviderAttribute</c> implementor.
184-
/// Uses the local set first (from this assembly), then falls back to the merged set
185-
/// (populated after all assemblies are loaded), then falls back to hardcoded names
186-
/// for the well-known Android component attributes.
187-
/// </summary>
188-
bool IsJniNameProviderAttribute (string attrName)
189-
{
190-
if (JniNameProviderAttributes.Contains (attrName)) {
191-
return true;
192-
}
193-
194-
if (allJniNameProviderAttributes is not null && allJniNameProviderAttributes.Contains (attrName)) {
195-
return true;
196-
}
197-
198-
// Fallback for the case where we haven't loaded the assembly defining the attribute yet.
199-
// This covers the common case where user assemblies reference Mono.Android attributes.
200-
return IsKnownComponentAttribute (attrName);
201-
}
202-
203131
static TypeAttributeInfo CreateTypeAttributeInfo (string attrName)
204132
{
205133
return attrName switch {
@@ -209,7 +137,7 @@ static TypeAttributeInfo CreateTypeAttributeInfo (string attrName)
209137
"ContentProviderAttribute" => new ContentProviderAttributeInfo (),
210138
"ApplicationAttribute" => new ApplicationAttributeInfo (),
211139
"InstrumentationAttribute" => new InstrumentationAttributeInfo (),
212-
_ => new JniNameProviderAttributeInfo (attrName),
140+
_ => throw new ArgumentException ($"Unknown component attribute: {attrName}"),
213141
};
214142
}
215143

@@ -262,14 +190,19 @@ internal static RegisterInfo ParseRegisterAttribute (CustomAttribute ca, ICustom
262190
doNotGenerateAcw = doNotGenerateAcwValue;
263191
}
264192

265-
return new RegisterInfo (jniName, signature, connector, doNotGenerateAcw);
193+
return new RegisterInfo {
194+
JniName = jniName,
195+
Signature = signature,
196+
Connector = connector,
197+
DoNotGenerateAcw = doNotGenerateAcw,
198+
};
266199
}
267200

268201
string? TryGetTypeProperty (CustomAttribute ca, string propertyName)
269202
{
270203
var value = ca.DecodeValue (customAttributeTypeProvider);
271204
var typeName = TryGetNamedStringArgument (value, propertyName);
272-
if (!string.IsNullOrEmpty (typeName)) {
205+
if (!typeName.IsNullOrEmpty ()) {
273206
return typeName;
274207
}
275208
return null;
@@ -281,12 +214,12 @@ internal static RegisterInfo ParseRegisterAttribute (CustomAttribute ca, ICustom
281214

282215
// Check named arguments first (e.g., [Activity(Name = "...")])
283216
var name = TryGetNamedStringArgument (value, "Name");
284-
if (!string.IsNullOrEmpty (name)) {
217+
if (!name.IsNullOrEmpty ()) {
285218
return name;
286219
}
287220

288221
// Fall back to first constructor argument (e.g., [CustomJniName("...")])
289-
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string ctorName && !string.IsNullOrEmpty (ctorName)) {
222+
if (value.FixedArguments.Length > 0 && value.FixedArguments [0].Value is string ctorName && !ctorName.IsNullOrEmpty ()) {
290223
return ctorName;
291224
}
292225

@@ -326,35 +259,21 @@ public void Dispose ()
326259
/// <summary>
327260
/// Parsed [Register] attribute data for a type or method.
328261
/// </summary>
329-
sealed class RegisterInfo
262+
sealed record RegisterInfo
330263
{
331-
public string JniName { get; }
332-
public string? Signature { get; }
333-
public string? Connector { get; }
334-
public bool DoNotGenerateAcw { get; }
335-
336-
public RegisterInfo (string jniName, string? signature, string? connector, bool doNotGenerateAcw)
337-
{
338-
JniName = jniName;
339-
Signature = signature;
340-
Connector = connector;
341-
DoNotGenerateAcw = doNotGenerateAcw;
342-
}
264+
public required string JniName { get; init; }
265+
public string? Signature { get; init; }
266+
public string? Connector { get; init; }
267+
public bool DoNotGenerateAcw { get; init; }
343268
}
344269

345270
/// <summary>
346271
/// Parsed [Export] attribute data for a method.
347272
/// </summary>
348-
sealed class ExportInfo
273+
sealed record ExportInfo
349274
{
350-
public IReadOnlyList<string>? ThrownNames { get; }
351-
public string? SuperArgumentsString { get; }
352-
353-
public ExportInfo (IReadOnlyList<string>? thrownNames, string? superArgumentsString)
354-
{
355-
ThrownNames = thrownNames;
356-
SuperArgumentsString = superArgumentsString;
357-
}
275+
public IReadOnlyList<string>? ThrownNames { get; init; }
276+
public string? SuperArgumentsString { get; init; }
358277
}
359278

360279
abstract class TypeAttributeInfo
@@ -403,13 +322,6 @@ public InstrumentationAttributeInfo () : base ("InstrumentationAttribute")
403322
}
404323
}
405324

406-
sealed class JniNameProviderAttributeInfo : TypeAttributeInfo
407-
{
408-
public JniNameProviderAttributeInfo (string attributeName) : base (attributeName)
409-
{
410-
}
411-
}
412-
413325
sealed class ApplicationAttributeInfo : TypeAttributeInfo
414326
{
415327
public ApplicationAttributeInfo () : base ("ApplicationAttribute")

0 commit comments

Comments
 (0)