Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -21,4 +21,6 @@ void LogUnresolvableJavaPeerSkippedWarning (
string unresolvedAssemblyName,
string unresolvedAssemblyPath);
void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName);
void LogCustomJavaObjectError (string managedTypeName);
void LogCustomJavaObjectWarning (string managedTypeName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ enum HashedPackageNamingPolicy {
readonly ITrimmableTypeMapLogger? logger;
readonly HashedPackageNamingPolicy packageNamingPolicy;
readonly HashSet<string> frameworkAssemblyNames;
readonly bool errorOnCustomJavaObject;

public JavaPeerScanner (string? packageNamingPolicy = null, ITrimmableTypeMapLogger? logger = null, HashSet<string>? frameworkAssemblyNames = null)
public JavaPeerScanner (string? packageNamingPolicy = null, ITrimmableTypeMapLogger? logger = null, HashSet<string>? frameworkAssemblyNames = null, bool errorOnCustomJavaObject = false)
{
this.packageNamingPolicy = ParsePackageNamingPolicy (packageNamingPolicy);
this.logger = logger;
this.frameworkAssemblyNames = frameworkAssemblyNames ?? new HashSet<string> (StringComparer.OrdinalIgnoreCase);
this.errorOnCustomJavaObject = errorOnCustomJavaObject;
}
Comment thread
Copilot marked this conversation as resolved.
Outdated

/// <summary>
Expand Down Expand Up @@ -338,6 +340,17 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
if (ExtendsJavaPeer (typeDef, index)) {
(jniName, compatJniName) = ComputeAutoJniNames (typeDef, index);
} else {
// A managed class that implements Android.Runtime.IJavaObject but does not
// derive from a Java peer (Java.Lang.Object / Java.Lang.Throwable) cannot be
// marshaled to Java. Mirror the legacy XAJavaTypeScanner XA4212 diagnostic,
// which the managed/llvm-ir typemap paths raise via GenerateJavaStubs.
if (IsCustomJavaObject (typeDef, index)) {
if (errorOnCustomJavaObject) {
logger?.LogCustomJavaObjectError (fullName);
} else {
logger?.LogCustomJavaObjectWarning (fullName);
}
}
continue;
}
}
Expand Down Expand Up @@ -2151,6 +2164,100 @@ public void Dispose ()

readonly Dictionary<string, bool> extendsJavaPeerCache = new (StringComparer.Ordinal);

const string IJavaObjectFullName = "Android.Runtime.IJavaObject";

readonly Dictionary<string, bool> implementsIJavaObjectCache = new (StringComparer.Ordinal);

/// <summary>
/// Determines whether a type is a "custom" Java object: a managed class that implements
/// Android.Runtime.IJavaObject but does not derive from a Java peer (Java.Lang.Object /
/// Java.Lang.Throwable). Such types cannot be marshaled and produce XA4212. Interfaces and
/// System.Exception subclasses are excluded, matching the legacy XAJavaTypeScanner.
/// </summary>
bool IsCustomJavaObject (TypeDefinition typeDef, AssemblyIndex index)
{
if ((typeDef.Attributes & TypeAttributes.Interface) != 0) {
return false;
}
if (IsSubclassOfSystemException (typeDef, index)) {
return false;
}
return ImplementsIJavaObject (typeDef, index);
}
Comment thread
Copilot marked this conversation as resolved.

/// <summary>
/// Check whether a type implements Android.Runtime.IJavaObject, directly or through an
/// interface that extends it, or via a base class. Results are cached; false-before-recurse
/// prevents cycles.
/// </summary>
bool ImplementsIJavaObject (TypeDefinition typeDef, AssemblyIndex index)
{
var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);
var key = $"{index.AssemblyName}:{fullName}";

if (implementsIJavaObjectCache.TryGetValue (key, out var cached)) {
return cached;
}

// Mark as false to prevent cycles, then compute
implementsIJavaObjectCache [key] = false;

foreach (var implHandle in typeDef.GetInterfaceImplementations ()) {
var impl = index.Reader.GetInterfaceImplementation (implHandle);
var resolved = ResolveEntityHandle (impl.Interface, index);
if (resolved is null) {
continue;
}

if (resolved.ManagedTypeName == IJavaObjectFullName) {
implementsIJavaObjectCache [key] = true;
return true;
}

// Recurse into the interface's own base interfaces
if (TryResolveType (resolved.ManagedTypeName, resolved.AssemblyName, out var ifaceHandle, out var ifaceIndex)) {
var ifaceDef = ifaceIndex.Reader.GetTypeDefinition (ifaceHandle);
if (ImplementsIJavaObject (ifaceDef, ifaceIndex)) {
implementsIJavaObjectCache [key] = true;
return true;
}
}
}

// Walk the base class chain
var baseInfo = GetBaseTypeInfo (typeDef, index);
if (baseInfo is not null &&
TryResolveType (baseInfo.ManagedTypeName, baseInfo.AssemblyName, out var baseHandle, out var baseIndex)) {
var baseDef = baseIndex.Reader.GetTypeDefinition (baseHandle);
if (ImplementsIJavaObject (baseDef, baseIndex)) {
implementsIJavaObjectCache [key] = true;
return true;
}
}

return false;
}

/// <summary>
/// Walk the base type chain to determine whether the type derives from System.Exception.
/// </summary>
bool IsSubclassOfSystemException (TypeDefinition typeDef, AssemblyIndex index)
{
var baseInfo = GetBaseTypeInfo (typeDef, index);
int guard = 0;
while (baseInfo is not null && guard++ < 256) {
if (baseInfo.ManagedTypeName == "System.Exception") {
return true;
}
if (!TryResolveType (baseInfo.ManagedTypeName, baseInfo.AssemblyName, out var baseHandle, out var baseIndex)) {
return false;
}
var baseDef = baseIndex.Reader.GetTypeDefinition (baseHandle);
baseInfo = GetBaseTypeInfo (baseDef, baseIndex);
}
return false;
}

/// <summary>
/// Check if a type extends a known Java peer (has [Register] or component attribute)
/// by walking the base type chain. Results are cached; false-before-recurse prevents cycles.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public TrimmableTypeMapResult Execute (
XDocument? manifestTemplate = null,
string? packageNamingPolicy = null,
int maxArrayRank = 0,
bool generateTypeMapAssemblies = true)
bool generateTypeMapAssemblies = true,
bool errorOnCustomJavaObject = false)
{
Comment thread
simonrozsival marked this conversation as resolved.
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
Expand All @@ -44,7 +45,7 @@ public TrimmableTypeMapResult Execute (
throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0.");
}

var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies, packageNamingPolicy, frameworkAssemblyNames);
var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies, packageNamingPolicy, frameworkAssemblyNames, errorOnCustomJavaObject);
if (allPeers.Count == 0) {
logger.LogNoJavaPeerTypesFound ();
return new TrimmableTypeMapResult ([], [], allPeers);
Expand Down Expand Up @@ -166,9 +167,10 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
(List<JavaPeerInfo> peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (
IReadOnlyList<AssemblyInput> assemblies,
string? packageNamingPolicy,
HashSet<string> frameworkAssemblyNames)
HashSet<string> frameworkAssemblyNames,
bool errorOnCustomJavaObject = false)
{
using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames);
using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames, errorOnCustomJavaObject);
var peers = scanner.Scan (assemblies);
var manifestInfo = scanner.ScanAssemblyManifestInfo ();
logger.LogJavaPeerScanInfo (assemblies.Count, peers.Count);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
ManifestPlaceholders="$(AndroidManifestPlaceholders)"
CheckedBuild="$(_AndroidCheckedBuild)"
ApplicationJavaClass="$(AndroidApplicationJavaClass)"
ErrorOnCustomJavaObject="$(AndroidErrorOnCustomJavaObject)"
GeneratedAssembliesListFile="$(_TypeMapAssembliesListFile)"
AcwMapOutputFile="$(IntermediateOutputPath)acw-map.txt"
ApplicationRegistrationOutputFile="$(IntermediateOutputPath)android/src/net/dot/android/ApplicationRegistration.java">
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,11 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
<data name="XA4213" xml:space="preserve">
<value>The type '{0}' must provide a public default constructor</value>
</data>
<data name="XA4212" xml:space="preserve">
<value>Type `{0}` implements `Android.Runtime.IJavaObject` but does not inherit `Java.Lang.Object` or `Java.Lang.Throwable`. This is not supported.</value>
<comment>The following are literal names and should not be translated: Android.Runtime.IJavaObject, Java.Lang.Object, Java.Lang.Throwable
{0} - The managed type name</comment>
</data>
<data name="XA4214" xml:space="preserve">
<value>The managed type `{0}` exists in multiple assemblies: {1}. Please refactor the managed type names in these assemblies so that they are not identical.</value>
<comment>{0} - The managed type name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public void LogUnresolvableJavaPeerSkippedWarning (
log.LogCodedWarning ("XA4257", Properties.Resources.XA4257, managedTypeName, assemblyName, unresolvedTypeName, unresolvedAssemblyName, unresolvedAssemblyPath);
public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName) =>
log.LogCodedError ("XA4251", Properties.Resources.XA4251, managedTypeName);
public void LogCustomJavaObjectError (string managedTypeName) =>
log.LogError ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}");
public void LogCustomJavaObjectWarning (string managedTypeName) =>
log.LogWarning ("{0}", $"XA4212: {string.Format (Properties.Resources.XA4212, managedTypeName)}");
}

public override string TaskPrefix => "GTT";
Expand Down Expand Up @@ -115,6 +119,13 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN
public bool GenerateTypeMapAssemblies { get; set; } = true;
public bool CleanJavaSourceOutputDirectory { get; set; }

/// <summary>
/// When true (the default, from <c>$(AndroidErrorOnCustomJavaObject)</c>), a managed class
/// that implements <c>Android.Runtime.IJavaObject</c> without deriving from a Java peer is
/// reported as the XA4212 error; otherwise it is reported as a warning.
/// </summary>
public bool ErrorOnCustomJavaObject { get; set; } = true;

[Output]
public ITaskItem [] GeneratedAssemblies { get; set; } = [];
[Output]
Expand Down Expand Up @@ -215,7 +226,8 @@ public override bool RunTask ()
manifestTemplate: manifestTemplate,
packageNamingPolicy: PackageNamingPolicy,
maxArrayRank: MaxArrayRank,
generateTypeMapAssemblies: GenerateTypeMapAssemblies);
generateTypeMapAssemblies: GenerateTypeMapAssemblies,
errorOnCustomJavaObject: ErrorOnCustomJavaObject);

if (GenerateTypeMapAssemblies) {
GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyInputs.Select (i => i.Path).ToList ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public void LogUnresolvableJavaPeerSkippedWarning (
$"'{unresolvedTypeName}' from '{unresolvedAssemblyName}' at '{unresolvedAssemblyPath}' could not be resolved.");
public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName) =>
logMessages.Add ($"XA4251: Type '{managedTypeName}' uses [JniAddNativeMethodRegistrationAttribute], which is not supported by the trimmable type map.");
public void LogCustomJavaObjectError (string managedTypeName) =>
warnings?.Add ($"XA4212: Type `{managedTypeName}` implements `Android.Runtime.IJavaObject` but does not inherit `Java.Lang.Object` or `Java.Lang.Throwable`. This is not supported.");
public void LogCustomJavaObjectWarning (string managedTypeName) =>
Comment thread
simonrozsival marked this conversation as resolved.
warnings?.Add ($"XA4212: Type `{managedTypeName}` implements `Android.Runtime.IJavaObject` but does not inherit `Java.Lang.Object` or `Java.Lang.Throwable`. This is not supported.");
}

[Fact]
Expand Down
Loading