Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid Attribute.GetCustomAttributes() returning null for open generic type #65237

Merged
merged 12 commits into from
Mar 4, 2022

Conversation

madelson
Copy link
Contributor

Fix #64335

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Feb 12, 2022
@ghost
Copy link

ghost commented Feb 12, 2022

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

Issue Details

Fix #64335

Author: madelson
Assignees: -
Labels:

area-System.Reflection

Milestone: -

Previously, this test asserted that GetCustomAttributes on an open generic
type would return null. Now we return empty instead.
@jkotas
Copy link
Member

jkotas commented Feb 15, 2022

Could you please also fix it for NativeAOT here: https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Attribute.CoreRT.cs#L149

@madelson
Copy link
Contributor Author

madelson commented Feb 16, 2022

Could you please also fix it for NativeAOT here: https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Attribute.CoreRT.cs#L149

@jkotas is your proposal to return Array.Empty<Attribute>() in that case instead of null? Do we still want to use the exception-based control flow?

Also, do you know if there is test coverage that should be updated for this?

@jkotas
Copy link
Member

jkotas commented Feb 16, 2022

is your proposal to return Array.Empty() in that case instead of null?

I think it needs to return Attribute array of the right size.

Do we still want to use the exception-based control flow?

It would be nice to use non-exception based control flow, same as CoreCLR.

Also, do you know if there is test coverage that should be updated for this?

We are working to get the existing tests enabled for NativeAOT. No need to add special test coverage.

Enable open generic attribute type requests for parameters,
break out larger tests into separate cases.
public static void GetCustomAttributesBehaviorMemberInfoReturnsNull()
{
FieldInfo field = new CustomFieldInfo(null!);
if (PlatformDetection.IsMonoRuntime)
Copy link
Member

Choose a reason for hiding this comment

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

Should we fix Mono to return null in this corner case as well to avoid this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jkotas I'll defer to your decision but I'm not sure it makes sense. Looking through the code it seems like null vs throw in both CoreCLR and Mono are going to be inconsistent depending on the member type and other factors (e. g. whether inherit is true or false). It would take quite a bit more test coverage and probably quite a few changes to get to a consistent state across all the different call flows.

Another option would just be to stop testing this case, since it is so niche so perhaps undefined behavior is fine.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Another option would just be to stop testing this case, since it is so niche so perhaps undefined behavior is fine.

I agree. We either care about this corner case and the behavior should be consistent across runtimes; or we do not care about this corner case and we should not be testing it at all.

return (object[])Array.CreateInstance(elementType, elementCount);
return (object[])Array.CreateInstance(caType, elementCount);

T[] CreateArray<T>() => elementCount == 0 ? Array.Empty<T>() : new T[elementCount];
Copy link
Member

@jkotas jkotas Feb 16, 2022

Choose a reason for hiding this comment

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

(Non-static) local methods with captured arguments are inefficient. The compiler will create display type to pass the captured locals around that comes with a lot of ceremony.

I think I would inline it. The local method is not worth it. Also, I think it may be worth it to check for Attribute and use the faster non-reflection based path for it. Something like:

bool useAttributeArray = false;
bool useObjectArray = false;

if (caType == typeof(Attribute))
{
    useAttributeArray = true;
}
else if (caType.IsValueType)
{
    useObjectArray = true;
}
else if (caType.ContainsGenericParameters)
{
    if (caType.IsSubclassOf(typeof(Attribute)))
    {
        useAttributeArray = true;
    }
    else
    {
        useObjectArray = true;
    } 
}

if (useAttributeArray) (elementCount != 0) ? new Attribute[elementCount] : Array.Empty<Attribute>();
if (useObjectArray) (elementCount != 0) ? new object[elementCount] : Array.Empty<object>();
return (elementCount != 0) ? (object[])Array.CreateInstance(caType, elementCount) : caType.GetEmptyArray();

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.

LGTM otherwise. Thank you!

@jkotas
Copy link
Member

jkotas commented Feb 16, 2022

Looks like Mono is crashing on the newly added tests even though they are disabled:

#13 0x0000007f8d2cfbdc in mono_assertion_message_unreachable (file=0x64 <error: Cannot access memory at address 0x64>, line=6) at /__w/1/s/src/mono/mono/eglib/goutput.c:234
#14 0x0000007f8d115120 in custom_attr_class_name_from_method_token (image=0x5589355e20, method_token=167772165, nspace=0x7fc25c2d58, class_name=0x7fc25c2d50, assembly_token=<optimized out>) at /__w/1/s/src/mono/mono/metadata/custom-attrs.c:2441

if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));

if (MdToken.IsNullToken(m_tkParamDef))
return CustomAttribute.CreateAttributeArrayHelper(attributeRuntimeType, 0);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was required to prevent a failure in Microsoft.CSharp.RuntimeBinder.Tests.AccessTests.PublicMethod().

I feel a bit weird exposing the CreateAttributeArrayHelper method as internal, but this is really the exact logic we want here. It makes this branch consistent with other GetCustomAttribute methods which all allow the consumer to cast the array to the requested attribute type.

Moving this after the argument check presumably has some slight perf cost but ultimately feels more correct (and unless we change CreateAttributeArrayHelper to take Type instead of RuntimeType we need the check to happen first regardless).

Copy link
Member

Choose a reason for hiding this comment

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

Ok. Do we need a test to directly verify the behavior of ParamerInfo.GetCustomAttributes for this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added!

@madelson
Copy link
Contributor Author

Looks like Mono is crashing on the newly added tests even though they are disabled:

@jkotas @buyaa-n From some experimentation, it seems that this is caused by adding the generic assembly attribute. Any thoughts on how best to address this? I tried putting the assembly/module generic attributes behind #if !MONO but that did not seem to work. Another option would be to move the module/attribute tests to https://github.com/dotnet/runtime/pull/65237/files#diff-d55f466b22e54fbf3b96b94f21d22c390ae92ff4047896b0e87c6af28e4dcc6f which I believe is CoreCLR-only.

@jkotas
Copy link
Member

jkotas commented Feb 19, 2022

I do not think that it makes sense to keep pilling workarounds for Mono bugs.

@SamMonoRT @lambdageek Could you please take a look and suggest fix for the Mono crash that the CI is hitting?

@madelson
Copy link
Contributor Author

@jkotas @buyaa-n I'm a bit lost interpreting the current crop of errors. Some of them seem to be in cpp files while others are canceled/timed out. Any advice on where to look?

@jkotas
Copy link
Member

jkotas commented Feb 26, 2022

I see some infrastructure related failures (the logs that talk about deadlettered queues). I also see number of failures related to the change in the PR:

[23:03:07] info: Expected: typeof(System.InvalidCastException)
[23:03:07] info: Actual:   typeof(System.ArrayTypeMismatchException): Source array type cannot be assigned to destination array type.
[23:03:07] info: ---- System.ArrayTypeMismatchException : Source array type cannot be assigned to destination array type.
[23:03:07] info:    at System.Array.CopySlow(Array , Int32 , Array , Int32 , Int32 , Boolean )
[23:03:07] info:    at System.Tests.AttributeGetCustomAttributes.<>c__DisplayClass13_0.<GetCustomAttributesBehaviorMemberInfoReturnsStringArray>b__0()
[23:03:07] info: ----- Inner Stack Trace -----
[23:03:07] info:    at System.Array.CopySlow(Array , Int32 , Array , Int32 , Int32 , Boolean )
[23:03:07] info:    at System.Tests.AttributeGetCustomAttributes.<>c__DisplayClass13_0.<GetCustomAttributesBehaviorMemberInfoReturnsStringArray>b__0()
[23:03:14] info: Finished:    System.Runtime.Tests.dll

or

    reflection/GenericAttribute/GenericAttributeTests/GenericAttributeTests.sh [FAIL]
      Unhandled exception. System.Exception: Error in line: 161
         at Program.AssertAny(IEnumerable`1 source, Func`2 condition, Int32 count, Int32 line)
         at Program.Main(String[] args)

@@ -283,27 +283,6 @@ private static void GenericAttributesTestHelper<TGenericParameter>(Func<Type, At
Assert.Equal(1, closedGenericAttributes.Length);
Assert.Equal(typeof(GenericAttribute<TGenericParameter>[]), closedGenericAttributes.GetType());
}

[Fact]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just a test, or are you actually removing tests because they fail on Mono?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on the discussion here it seemed like these weren't worth it given the failures on Mono. They're all tests I added in earlier commits, not existing tests.

@madelson
Copy link
Contributor Author

Many of the remaining errors from the checks are like this one

Example trace:

Fatal error. Internal CLR error. (0x80131506)
   at Tests.System.Net.MultiArrayBufferTests.AddSeveralBytesRepeatedlyAndConsumeSeveralBytesRepeatedly_UsingSliceWithLength_Success()
   at System.RuntimeMethodHandle.InvokeMethod(System.Object, System.Span`1<System.Object> ByRef, System.Signature, Boolean, Boolean)
   at System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
   at System.Reflection.MethodBase.Invoke(System.Object, System.Object[])

Any thoughts on where to start looking?

@jkotas
Copy link
Member

jkotas commented Feb 28, 2022

If the failure is on Windows 7 x86, it is most likely #65890 (ie not related to your changes).

@buyaa-n
Copy link
Member

buyaa-n commented Mar 1, 2022

I do not think that it makes sense to keep pilling workarounds for Mono bugs.

@SamMonoRT @lambdageek Could you please take a look and suggest fix for the Mono crash that the CI is hitting?

It seems this is the only blocker for this PR, @jkotas should we open an issue for this and unblock the PR? Seems @madelson commented out the tests that causing mono failure:

// Module module = programTypeInfo.Module;
// AssertAny(CustomAttributeExtensions.GetCustomAttributes(module), a => a is SingleAttribute<long>);
// Assert(CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<long>)).GetEnumerator().MoveNext());
// Assert(CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<long>)).GetEnumerator().MoveNext());
// Assert(!CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<>)).GetEnumerator().MoveNext());
// Assert(!CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<>)).GetEnumerator().MoveNext());

@jkotas
Copy link
Member

jkotas commented Mar 1, 2022

this is the only blocker for this PR, @jkotas should we open an issue for this and unblock the PR?

Fine with me.

// Assert(CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<long>)).GetEnumerator().MoveNext());
// Assert(CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<long>)).GetEnumerator().MoveNext());
// Assert(!CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<>)).GetEnumerator().MoveNext());
// Assert(!CustomAttributeExtensions.GetCustomAttributes(module, typeof(SingleAttribute<>)).GetEnumerator().MoveNext());
Copy link
Member

@buyaa-n buyaa-n Mar 1, 2022

Choose a reason for hiding this comment

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

Should we include these tests within the above mulitilne comment? Seems related to dotnet/msbuild#6734

CC @davidwrighton

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure if it was related and wanted to ask about that. It's possible that these tests could be brought back.

Do you know what the preferred approach is for iterating on this particular test locally? [https://github.com/dotnet/runtime/blob/main/docs/workflow/testing/coreclr/testing.md#build-individual-test](this page) suggests that I should run

dotnet.cmd build c:\dev\runtime\src\tests\reflection\GenericAttribute\GenericAttributeMetadata.ilproj -c Release

but that just builds a dll and doesn't seem to actually invoke this main method.

Copy link
Member

@buyaa-n buyaa-n Mar 2, 2022

Choose a reason for hiding this comment

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

Sorry idk how to run them locally, @davidwrighton @jkotas might help with that, probably it is not ready to be brought back

Copy link
Member

Choose a reason for hiding this comment

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

@buyaa-n
Copy link
Member

buyaa-n commented Mar 1, 2022

this is the only blocker for this PR, @jkotas should we open an issue for this and unblock the PR?

Fine with me.

Thanks, looks there is an open issue for enabling similar tests #56880

Copy link
Member

@buyaa-n buyaa-n left a comment

Choose a reason for hiding this comment

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

Thanks for working on this @madelson!

@buyaa-n
Copy link
Member

buyaa-n commented Mar 4, 2022

An issue created for mono failure, CI failure looks related to this issue and unrelated to this change => merging.

@buyaa-n buyaa-n merged commit 2029495 into dotnet:main Mar 4, 2022
jkotas added a commit that referenced this pull request Mar 11, 2022
stephentoub pushed a commit that referenced this pull request Mar 11, 2022
madelson added a commit to madelson/runtime that referenced this pull request Mar 13, 2022
buyaa-n pushed a commit that referenced this pull request Mar 14, 2022
… type

* Revert "Revert "Avoid Attribute.GetCustomAttributes() returning null for open generic type (#65237)" (#66508)"

This reverts commit f99ba2e.

* Make DynamicMethod.GetCustomAttributes() return well-typed arrays.

This change makes DynamicMethod.GetCustomAttributes() compatible with
Attribute.GetCustomAttributes().
radekdoulik pushed a commit to radekdoulik/runtime that referenced this pull request Mar 30, 2022
… type

* Revert "Revert "Avoid Attribute.GetCustomAttributes() returning null for open generic type (dotnet#65237)" (dotnet#66508)"

This reverts commit f99ba2e.

* Make DynamicMethod.GetCustomAttributes() return well-typed arrays.

This change makes DynamicMethod.GetCustomAttributes() compatible with
Attribute.GetCustomAttributes().
@ghost ghost locked as resolved and limited conversation to collaborators Apr 3, 2022
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.

Generic attributes: Attribute.GetCustomAttributes returns null when passed an unbound generic type
5 participants