Skip to content

Conversation

medhatiwari
Copy link
Contributor

Fix System.Object.GetCustomAttributes() returning extra internal attributes on Mono

Problem Description

On Mono runtime, System.Object.GetCustomAttributes() returns 5 attributes instead of 1 like CoreCLR, causing compatibility issues with ASP.NET Core and other frameworks.

Mono (before fix):

System.Object attributes count: 5
  - Type: System.Runtime.CompilerServices.NullableContextAttribute
  - Type: System.Runtime.InteropServices.ClassInterfaceAttribute  
  - Type: System.Runtime.InteropServices.ComVisibleAttribute
  - Type: System.Runtime.CompilerServices.TypeForwardedFromAttribute
  - Type: System.SerializableAttribute

CoreCLR (expected behavior):

System.Object attributes count: 1
  - Type: System.SerializableAttribute

Root Cause

The issue was discovered through multiple ASP.NET Core test failures in the MVC test bucket:
Test Class: ModelAttributesTest (in src/Mvc/Mvc.Core/test/ModelBinding/Metadata/ModelAttributesTest.cs)

  1. GetAttributesForParameter_NoAttributes() - Expected exactly 1 attribute for System.Object but Mono returned 5 attributes, causing Assert.Single() to fail
  2. GetAttributesForParameter_SomeAttributes() - Expected SerializableAttribute as the first type attribute but received IsReadOnlyAttribute as the first attribute
  3. GetAttributesForParameter_IncludesTypeAttributes() - Affected by extra internal attributes disrupting expected attribute collections
  4. GetAttributesForParameter_WithModelType_IncludesTypeAttributes() - Similar issues with unexpected additional framework-specific attributes
  • CoreCLR: typeof(System.Object).GetCustomAttributes() returns 1 attribute (SerializableAttribute)

  • Mono: typeof(System.Object).GetCustomAttributes() returns 5 attributes (including internal ones)

  • ASP.NET Core expectation: Tests expect CoreCLR behavior and fail on Mono

Implementation Difference

Investigation revealed that Mono's mono_custom_attrs_construct_by_type() function in custom-attrs.c was exposing internal/compiler-generated attributes that CoreCLR does not expose through reflection APIs. Whether CoreCLR actively filters these attributes or simply does not include them in metadata is implementation-specific, but the result is that Mono exposes more attributes than CoreCLR for the same types.

Regardless of the specific mechanism, the fix makes Mono behave consistently with CoreCLR by filtering out these internal attributes from reflection results, resolving compatibility issues with frameworks expecting CoreCLR behavior.

Solution

Added filtering logic to mono_custom_attrs_construct_by_type() to exclude these internal attributes from reflection results:

  • System.Runtime.CompilerServices.NullableContextAttribute
  • System.Runtime.CompilerServices.TypeForwardedFromAttribute
  • System.Runtime.CompilerServices.IsReadOnlyAttribute
  • System.Runtime.InteropServices.ClassInterfaceAttribute
  • System.Runtime.InteropServices.ComVisibleAttribute

The filtering is applied in three phases:

  1. Attribute counting (specific type filtering)
  2. Attribute counting (all types)
  3. Attribute array construction

Changes Made

  • File: src/mono/mono/metadata/custom-attrs.c
  • Function: mono_custom_attrs_construct_by_type() (around line 1817)
  • Added: should_filter_attribute_for_reflection() helper function
  • Modified: Three loops in the function to apply filtering

Reproduction Test

using System;
using System.Reflection;
using System.Linq;

class Program 
{
    public static void TestMethod(object param) { }
    
    static void Main()
    {
        Console.WriteLine("=== MONO ATTRIBUTE BUG BASELINE TEST ===");
        Console.WriteLine($"Runtime: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
        Console.WriteLine($"Platform: {System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier}");
        Console.WriteLine();
        
        var method = typeof(Program).GetMethod("TestMethod");
        var parameter = method.GetParameters()[0];
        
        // Get parameter attributes (should be 0)
        var paramAttributes = parameter.GetCustomAttributes().ToArray();
        Console.WriteLine($"Parameter attributes count: {paramAttributes.Length}");
        foreach (var attr in paramAttributes)
            Console.WriteLine($"  - Parameter: {attr.GetType().FullName}");
        
        Console.WriteLine();
        
        // Get System.Object type attributes  
        var paramType = parameter.ParameterType;
        var typeAttributes = paramType.GetCustomAttributes().ToArray();
        Console.WriteLine($"System.Object attributes count: {typeAttributes.Length}");
        foreach (var attr in typeAttributes)
            Console.WriteLine($"  - Type: {attr.GetType().FullName}");
        
        Console.WriteLine();
        Console.WriteLine("EXPECTED RESULTS:");
        Console.WriteLine("  - Parameter attributes: 0");  
        Console.WriteLine("  - System.Object attributes: 1 (SerializableAttribute)");
        Console.WriteLine();
        
        if (typeAttributes.Length == 1 && typeAttributes[0].GetType().Name == "SerializableAttribute")
        {
            Console.WriteLine(" CoreCLR behavior!");
        }
        else if (typeAttributes.Length == 5)
        {
            Console.WriteLine(" Mono bug (5 attributes)");
        }
        else
        {
            Console.WriteLine($"Unexpected: {typeAttributes.Length} attributes");
        }
    }
}

Before fix (Mono): 5 attributes
After fix (Mono): 1 attribute
CoreCLR: 1 attribute

Testing

  • Reproduction test passes
  • ASP.NET Core GetAttributesForParameter_NoAttributes(), GetAttributesForParameter_SomeAttributes(), GetAttributesForParameter_IncludesTypeAttributes(), GetAttributesForParameter_WithModelType_IncludesTypeAttributes() tests pass
  • No regressions observed in runtime functionality
  • Maintains compatibility with CoreCLR behavior

Related Issues

This addresses compatibility issues between Mono and CoreCLR reflection APIs that were causing ASP.NET Core test failures, particularly around custom attribute enumeration.

cc: @uweigand @giritrivedi @saitama951

@Copilot Copilot AI review requested due to automatic review settings July 20, 2025 18:54
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Jul 20, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jul 20, 2025
Copilot

This comment was marked as outdated.

@jkotas
Copy link
Member

jkotas commented Jul 20, 2025

Notice that typeof(System.Object).GetCustomAttributes(false) returns the full list of attributes on CoreCLR, but typeof(System.Object).GetCustomAttributes(true) returns only the serialiable attribute.

I would say that it is a bug in CoreCLR that it only returns the serialiable attribute for System.Object. The problem is here. We should rather fix the bug in CoreCLR instead of trying to introduce it in Mono.

In any case, this PR needs a test.

@jkotas jkotas added area-System.Reflection and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Jul 20, 2025
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection
See info in area-owners.md if you want to be subscribed.

@medhatiwari
Copy link
Contributor Author

medhatiwari commented Jul 22, 2025

Thanks @jkotas! You're right about the logical inconsistency in CoreCLR's behavior.

If we proceed with fixing CoreCLR, it might lead to changes in ASP.NET Core test cases and other areas.

I was looking into src/mono/System.Private.CoreLib/src/System/Reflection/CustomAttribute.cs and we can modify the internal static object[] GetCustomAttributes function to introduce the same behavior as CoreCLR - adding special handling for System.Object with inherit=true to return only pseudo attributes.

I've tried this approach and it works, correctly returning 5 attributes for inherit=false and 1 for inherit=true.

Would you prefer we proceed with the Mono fix, or tackle the CoreCLR inconsistency instead?

@jkotas
Copy link
Member

jkotas commented Jul 22, 2025

I would prefer to fix the buggy CoreCLR behavior.

@jkotas
Copy link
Member

jkotas commented Jul 22, 2025

it might lead to changes in ASP.NET Core test cases and other areas.

The number of attributes on System.Object is internal implementation detail. If the ASP.NET test depends on the exact number of attributes on System.Object, it is a fragile test. I am not worried about the test requiring changes to reflect the correct reflection behavior.

Could you please submit a PR to fix the ASP.NET test together with the fix here?

@medhatiwari medhatiwari requested a review from jkotas July 22, 2025 20:46
@medhatiwari
Copy link
Contributor Author

I would prefer to fix the buggy CoreCLR behavior.

ok thanks, we will go forward with it

@medhatiwari medhatiwari requested a review from Copilot July 22, 2025 22:38
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes an incompatibility between Mono and CoreCLR runtimes where System.Object.GetCustomAttributes() returns different numbers of attributes, causing ASP.NET Core test failures. The fix ensures both runtimes return consistent results by filtering internal compiler-generated attributes in Mono and correcting inheritance logic in CoreCLR.

  • Adds test coverage for System.Object custom attribute inheritance consistency
  • Modifies CoreCLR's inheritance traversal to include System.Object in the loop
  • Addresses compatibility issues that were causing ASP.NET Core MVC test failures

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
CustomAttributeTests.cs Adds test to verify consistent attribute counts between inherit=true/false
RuntimeCustomAttributeData.cs Fixes inheritance traversal logic to include System.Object in the loop

@medhatiwari medhatiwari requested a review from jkotas July 22, 2025 23:11
Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@jkotas
Copy link
Member

jkotas commented Jul 23, 2025

Could you please submit a PR with the ASP.NET test fixes before this gets merged?

@medhatiwari
Copy link
Contributor Author

medhatiwari commented Jul 23, 2025

Could you please submit a PR with the ASP.NET test fixes before this gets merged?

Hi @jkotas

I have raised PR

but I see that CoreCLR has a broader inconsistency problem that affects not just System.Object, but also primitive types like int, string, double, etc.

the program

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var intType = typeof(int);
        
        var inheritFalse = intType.GetCustomAttributes(inherit: false);
        var inheritTrue = intType.GetCustomAttributes(inherit: true);
        
        Console.WriteLine($"inherit=false ({inheritFalse.Length} attributes):");
        for (int i = 0; i < inheritFalse.Length; i++)
            Console.WriteLine($"  [{i}] {inheritFalse[i].GetType().Name}");
        
        Console.WriteLine($"inherit=true  ({inheritTrue.Length} attributes):");
        for (int i = 0; i < inheritTrue.Length; i++)
            Console.WriteLine($"  [{i}] {inheritTrue[i].GetType().Name}");
    }
}

output on coreclr
inherit=false (3 attributes):
[0] IsReadOnlyAttribute
[1] TypeForwardedFromAttribute
[2] SerializableAttribute

inherit=true (3 attributes):
[0] SerializableAttribute
[1] IsReadOnlyAttribute
[2] TypeForwardedFromAttribute

output for mono
inherit=false (3 attributes):
[0] IsReadOnlyAttribute
[1] TypeForwardedFromAttribute
[2] SerializableAttribute
inherit=true (3 attributes):
[0] IsReadOnlyAttribute
[1] TypeForwardedFromAttribute
[2] SerializableAttribute

This inconsistency is causing ASP.NET Core test failures on Mono:
GetAttributesForParameter_SomeAttributes - Expects SerializableAttribute first but gets IsReadOnlyAttribute

@jkotas
Copy link
Member

jkotas commented Jul 23, 2025

The order of attributes is not significant. For example, from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes#223-attribute-specification: "The order in which attributes are specified in such a list, and the order in which sections attached to the same program entity are arranged, is not significant. "

If the test depends on attributes being returned in a specific order, it should be fixed to not do that.

@medhatiwari
Copy link
Contributor Author

The order of attributes is not significant. For example, from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes#223-attribute-specification: "The order in which attributes are specified in such a list, and the order in which sections attached to the same program entity are arranged, is not significant. "

If the test depends on attributes being returned in a specific order, it should be fixed to not do that.

yeah, in this case we better fix the testcase

@jkotas
Copy link
Member

jkotas commented Jul 23, 2025

/ba-g timeouts

@jkotas jkotas merged commit f4f3be6 into dotnet:main Jul 23, 2025
120 of 127 checks passed
@jkotas
Copy link
Member

jkotas commented Jul 23, 2025

Thanks!

@github-actions github-actions bot locked and limited conversation to collaborators Aug 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Reflection community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants