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
1 change: 1 addition & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4253](xa4253.md): Generated Java callable wrapper code changed: '{path}'
+ [XA4254](xa4254.md): Trimmable type map Java source input directory '{input}' and output directory '{output}' must be different.
+ [XA4255](xa4255.md): Generated trimmable type map Java source '{path}' was not found.
+ [XA4256](xa4256.md): Skipping Java peer type '{type}' from assembly '{assembly}' because referenced type '{referencedType}' from assembly '{referencedAssembly}' could not be resolved in '{path}'. This type will not be included in the trimmable type map.
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
Expand Down
27 changes: 27 additions & 0 deletions Documentation/docs-mobile/messages/xa4256.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: .NET for Android warning XA4256
description: XA4256 warning code
ms.date: 06/25/2026
f1_keywords:
- "XA4256"
---

# .NET for Android warning XA4256

## Example message

```
warning XA4256: Skipping Java peer type 'Google.Android.Material.Shadow.ShadowDrawableWrapper' from assembly 'Xamarin.Google.Android.Material' because referenced type 'AndroidX.AppCompat.Graphics.Drawable.DrawableWrapper' from assembly 'Xamarin.AndroidX.AppCompat.AppCompatResources' could not be resolved in '/home/user/.nuget/packages/xamarin.androidx.appcompat.appcompatresources/1.6.0/lib/net6.0-android31.0/Xamarin.AndroidX.AppCompat.AppCompatResources.dll'. This type will not be included in the trimmable type map.
```

## Issue

The trimmable type map found a Java peer type whose managed base type or implemented interface references a type that is not present in the resolved assembly set.

This can happen when NuGet packages in the Android binding graph were built against different versions of another binding package. The type may be unused by the app, but NativeAOT compiles a closed world and must resolve rooted managed types eagerly.

## Solution

If your app uses the skipped type, update the affected NuGet packages so the referenced type exists, or update to versions of the binding packages that are compatible with each other.

If your app does not use the skipped type, no action is required. The type is omitted from the trimmable type map so NativeAOT does not fail while resolving unused stale metadata.
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ public interface ITrimmableTypeMapLogger
void LogGeneratedJcwFilesInfo (int sourceCount);
void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName);
void LogManifestReferencedTypeNotFoundWarning (string javaTypeName);
void LogUnresolvableJavaPeerSkippedWarning (
string managedTypeName,
string assemblyName,
string unresolvedTypeName,
string unresolvedAssemblyName,
string unresolvedAssemblyPath);
void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ sealed class AssemblyIndex : IDisposable
/// </summary>
public Dictionary<string, HashSet<string>> ReferencedTypeNamesByAssembly { get; } = new (StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Type-forwarded or otherwise exported types declared by this assembly.
/// </summary>
public HashSet<string> ExportedTypeNames { get; } = new (StringComparer.Ordinal);

/// <summary>
/// True iff the assembly's metadata mentions
/// <c>Java.Interop.JniAddNativeMethodRegistrationAttribute</c> (as a
Expand Down Expand Up @@ -90,6 +95,11 @@ void Build ()
}
}

foreach (var exportedTypeHandle in Reader.ExportedTypes) {
var exportedType = Reader.GetExportedType (exportedTypeHandle);
ExportedTypeNames.Add (GetExportedTypeFullName (exportedType));
}

foreach (var typeHandle in Reader.TypeDefinitions) {
var typeDef = Reader.GetTypeDefinition (typeHandle);

Expand All @@ -115,6 +125,17 @@ void Build ()
RegisterInfoByType [typeHandle] = registerInfo;
}
}

string GetExportedTypeFullName (ExportedType exportedType)
{
var name = Reader.GetString (exportedType.Name);
if (exportedType.Implementation.Kind == HandleKind.ExportedType) {
var declaringType = Reader.GetExportedType ((ExportedTypeHandle) exportedType.Implementation);
return MetadataTypeNameResolver.JoinNestedTypeName (GetExportedTypeFullName (declaringType), name);
}
var ns = Reader.GetString (exportedType.Namespace);
return MetadataTypeNameResolver.JoinNamespaceAndName (ns, name);
}
}

bool TryGetTypeReferenceAssemblyName (TypeReference typeReference, [NotNullWhen (true)] out string? assemblyName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ enum HashedPackageNamingPolicy {
LowercaseCrc64,
}

readonly record struct ResolvabilityResult (bool IsResolvable, string? UnresolvedTypeName, string? UnresolvedAssemblyName);

readonly Dictionary<string, AssemblyIndex> assemblyCache = new (StringComparer.Ordinal);
readonly Dictionary<(string typeName, string assemblyName), ActivationCtorInfo> activationCtorCache = new ();
readonly Dictionary<(string AssemblyName, int TypeRow), ResolvabilityResult> resolvabilityCache = new ();
readonly HashSet<(string AssemblyName, int TypeRow)> resolvabilityVisited = new ();
readonly ITrimmableTypeMapLogger? logger;
readonly HashedPackageNamingPolicy packageNamingPolicy;
readonly HashSet<string> frameworkAssemblyNames;
Expand Down Expand Up @@ -233,9 +237,17 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
}
}

if (!IsResolvableJavaPeerType (typeHandle, index, out var unresolvedTypeName, out var unresolvedAssemblyName)) {
var unresolvedAssemblyPath = assemblyCache.TryGetValue (unresolvedAssemblyName, out var unresolvedAssemblyIndex)
? unresolvedAssemblyIndex.AssemblyPath
: "";
logger?.LogUnresolvableJavaPeerSkippedWarning (fullName, index.AssemblyName, unresolvedTypeName, unresolvedAssemblyName, unresolvedAssemblyPath);
continue;
}

var isGenericDefinition = typeDef.GetGenericParameters ().Count > 0;
var isInterface = (typeDef.Attributes & TypeAttributes.Interface) != 0;
var isAbstract = (typeDef.Attributes & TypeAttributes.Abstract) != 0;
var isGenericDefinition = typeDef.GetGenericParameters ().Count > 0;

var isUnconditional = attrInfo is not null;
var cannotRegisterInStaticConstructor = attrInfo is ApplicationAttributeInfo or InstrumentationAttributeInfo;
Expand Down Expand Up @@ -300,6 +312,242 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
}
}

bool IsResolvableJavaPeerType (
TypeDefinitionHandle typeDefHandle,
AssemblyIndex index,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
// The base/interface graph of valid managed metadata is acyclic, so the
// per-call visited set only guards against generic self-references (e.g.
// class Foo : Bar<Foo>). It is reused across peers and cleared per call to
// avoid an allocation for every candidate on large peer graphs. Keying it
// (and the cache) by type-definition row avoids building full type names on
// the hot path and on cache hits.
resolvabilityVisited.Clear ();
return IsResolvableTypeDefinition (typeDefHandle, index, resolvabilityVisited, out unresolvedTypeName, out unresolvedAssemblyName);
}

bool IsResolvableTypeDefinition (
TypeDefinitionHandle typeDefHandle,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
var cacheKey = (index.AssemblyName, MetadataTokens.GetRowNumber (typeDefHandle));

if (resolvabilityCache.TryGetValue (cacheKey, out var cached)) {
unresolvedTypeName = cached.UnresolvedTypeName;
unresolvedAssemblyName = cached.UnresolvedAssemblyName;
return cached.IsResolvable;
}

if (!visited.Add (cacheKey)) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}

var typeDef = index.Reader.GetTypeDefinition (typeDefHandle);

if (!IsResolvableTypeHandle (typeDef.BaseType, index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
resolvabilityCache [cacheKey] = new (false, unresolvedTypeName, unresolvedAssemblyName);
return false;
}

foreach (var interfaceHandle in typeDef.GetInterfaceImplementations ()) {
Comment thread
simonrozsival marked this conversation as resolved.
var interfaceImplementation = index.Reader.GetInterfaceImplementation (interfaceHandle);
if (!IsResolvableTypeHandle (interfaceImplementation.Interface, index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
resolvabilityCache [cacheKey] = new (false, unresolvedTypeName, unresolvedAssemblyName);
return false;
}
}

// Generic-definition peers are rooted in the type map (the emitter ldtokens
// the open-generic target), so a constraint referencing a stale type would
// also fail to resolve at NativeAOT time. Walk constraints like base types.
foreach (var genericParameterHandle in typeDef.GetGenericParameters ()) {
var genericParameter = index.Reader.GetGenericParameter (genericParameterHandle);
foreach (var constraintHandle in genericParameter.GetConstraints ()) {
var constraint = index.Reader.GetGenericParameterConstraint (constraintHandle);
if (!IsResolvableTypeHandle (constraint.Type, index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
resolvabilityCache [cacheKey] = new (false, unresolvedTypeName, unresolvedAssemblyName);
return false;
}
}
}

unresolvedTypeName = null;
unresolvedAssemblyName = null;
resolvabilityCache [cacheKey] = new (true, null, null);
return true;
}

bool IsResolvableTypeHandle (
EntityHandle handle,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
if (handle.IsNil) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}

switch (handle.Kind) {
case HandleKind.TypeDefinition:
return IsResolvableTypeDefinition ((TypeDefinitionHandle) handle, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
case HandleKind.TypeReference:
return IsResolvableTypeReference ((TypeReferenceHandle) handle, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
case HandleKind.TypeSpecification:
return IsResolvableTypeSpecification ((TypeSpecificationHandle) handle, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
default:
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}
}

bool IsResolvableTypeReference (
TypeReferenceHandle handle,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
var typeRef = MetadataTypeNameResolver.GetTypeRefFromReference (index.Reader, handle, index.AssemblyName, rawTypeKind: 0);
var typeName = typeRef.ManagedTypeName;
var assemblyName = typeRef.AssemblyName;
if (!assemblyCache.TryGetValue (assemblyName, out var resolvedIndex)) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}

if (resolvedIndex.TypesByFullName.TryGetValue (typeName, out var typeHandle)) {
return IsResolvableTypeDefinition (typeHandle, resolvedIndex, visited, out unresolvedTypeName, out unresolvedAssemblyName);
}

if (resolvedIndex.ExportedTypeNames.Contains (typeName)) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}

unresolvedTypeName = typeName;
unresolvedAssemblyName = assemblyName;
return false;
}

bool IsResolvableTypeSpecification (
TypeSpecificationHandle handle,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
var reader = index.Reader.GetBlobReader (index.Reader.GetTypeSpecification (handle).Signature);
return IsResolvableSignatureType (ref reader, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
}

bool IsResolvableSignatureType (
ref BlobReader reader,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
if (reader.RemainingBytes == 0) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}

var rawTypeCode = reader.ReadByte ();
if ((SignatureTypeKind) rawTypeCode is SignatureTypeKind.ValueType or SignatureTypeKind.Class) {
return IsResolvableTypeDefOrRefEncodedHandle (reader.ReadCompressedInteger (), index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
}

var typeCode = (SignatureTypeCode) rawTypeCode;
switch (typeCode) {
case SignatureTypeCode.GenericTypeParameter:
case SignatureTypeCode.GenericMethodParameter:
reader.ReadCompressedInteger ();
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
case SignatureTypeCode.SZArray:
return IsResolvableSignatureType (ref reader, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
case SignatureTypeCode.Array:
if (!IsResolvableSignatureType (ref reader, index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
return false;
}
SkipArrayShape (ref reader);
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
case SignatureTypeCode.GenericTypeInstance:
if ((SignatureTypeKind) reader.ReadByte () is not (SignatureTypeKind.ValueType or SignatureTypeKind.Class)) {
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}
if (!IsResolvableTypeDefOrRefEncodedHandle (reader.ReadCompressedInteger (), index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
return false;
}
int genericArgumentCount = reader.ReadCompressedInteger ();
for (int i = 0; i < genericArgumentCount; i++) {
if (!IsResolvableSignatureType (ref reader, index, visited, out unresolvedTypeName, out unresolvedAssemblyName)) {
return false;
}
}
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
default:
// Pointers, byrefs, custom modifiers, and any encoding we don't
// specifically decode default to resolvable so we never wrongly skip
// a Java peer over metadata shapes this scanner doesn't model.
unresolvedTypeName = null;
unresolvedAssemblyName = null;
return true;
}
}

bool IsResolvableTypeDefOrRefEncodedHandle (
int encodedHandle,
AssemblyIndex index,
HashSet<(string AssemblyName, int TypeRow)> visited,
[NotNullWhen (false)] out string? unresolvedTypeName,
[NotNullWhen (false)] out string? unresolvedAssemblyName)
{
int tag = encodedHandle & 0x3;
int row = encodedHandle >> 2;
EntityHandle handle = tag switch {
0 => MetadataTokens.TypeDefinitionHandle (row),
1 => MetadataTokens.TypeReferenceHandle (row),
2 => MetadataTokens.TypeSpecificationHandle (row),
_ => default,
};
return IsResolvableTypeHandle (handle, index, visited, out unresolvedTypeName, out unresolvedAssemblyName);
}

static void SkipArrayShape (ref BlobReader reader)
{
reader.ReadCompressedInteger ();
int sizes = reader.ReadCompressedInteger ();
for (int i = 0; i < sizes; i++) {
reader.ReadCompressedInteger ();
}
int lowerBounds = reader.ReadCompressedInteger ();
for (int i = 0; i < lowerBounds; i++) {
reader.ReadCompressedSignedInteger ();
}
}

(List<MarshalMethodInfo>, List<JavaFieldInfo>) CollectMarshalMethods (TypeDefinition typeDef, AssemblyIndex index, bool detectBaseOverrides)
{
var methods = new List<MarshalMethodInfo> ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []);
}

(List<JavaPeerInfo> peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<AssemblyInput> assemblies, string? packageNamingPolicy, HashSet<string> frameworkAssemblyNames)
(List<JavaPeerInfo> peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (
IReadOnlyList<AssemblyInput> assemblies,
string? packageNamingPolicy,
HashSet<string> frameworkAssemblyNames)
{
using var scanner = new JavaPeerScanner (packageNamingPolicy, logger, frameworkAssemblyNames);
var peers = scanner.Scan (assemblies);
Expand Down

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

Loading
Loading