Description
Description
TypeDesciptor.AddAttributes is a static registration API and each time it's called it adds a new AttributeProvider for the instance or type specified. If this provider is not removed it will remain for the lifetime of the AppDomain/Process. If an application calls this API multiple times for the same instance/type - as may be the case if the call to AddAttributes is in an instance type/method - the providers will accumulate. That accumulation will cause issues wherever CustomTypeDescriptor walks the provider chain.
For example:
These methods recursively walk the _parent chain on the stack - which will eventually lead to StackOverflowException when that chain is too long.
Reproduction Steps
using System.ComponentModel;
for (int i = 0; i < 30000; i++)
TypeDescriptor.AddAttributes(typeof(int), new TypeConverterAttribute(typeof(MyCoverter)));
// any of the following will overflow
TypeDescriptor.GetAttributes(typeof(int));
TypeDescriptor.GetConverter(typeof(int));
TypeDescriptor.GetProperties(typeof(int));
TypeDescriptor.GetEvents(typeof(int));
public class MyCoverter : TypeConverter
{ }
Expected behavior
The behavior is expected, but it could be better.
Seems like we could capture how many times we recurse and throw a better exception before hitting stack overflow.
We could also consider an analyzer to catch this problem. If someone calls TypeDescriptor.AddProvider without doing anything with the return value - raise a diagnostic. The user might decide to suppress that if they could garuntee the code would only ever run once.
Actual behavior
Stack overflow.
at System.ComponentModel.TypeDescriptor+AttributeProvider.GetTypeDescriptor(System.Type, System.Object)
at System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetAttributes()
at System.ComponentModel.TypeDescriptor+AttributeProvider+AttributeTypeDescriptor.GetAttributes()
at System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetAttributes()
at System.ComponentModel.TypeDescriptor+AttributeProvider+AttributeTypeDescriptor.GetAttributes()
at System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetAttributes()
at System.ComponentModel.TypeDescriptor+AttributeProvider+AttributeTypeDescriptor.GetAttributes()
...
Depending on which method is called it may also overflow at any of the following:
System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetAttributes()
System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetConverter()
System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetProperties()
System.ComponentModel.TypeDescriptor+DefaultTypeDescriptor.GetEvents()
Regression?
No, behavior exists in .NETFramework as well.
Known Workarounds
Make sure you remove any provider added with AddAttributes, either in a finally block if it's only needed for the method, or in the Dispose method if it's needed for the lifetime of the type. Alternatively you can add attributes with a singleton like the following:
// register
MyConverterRegistration<int>.EnsureRegistered();
public static class MyConverterRegistration<T>
{
static MyConverterRegistration() =>
TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(typeof(MyCoverter)));
public static void EnsureRegistered() { /* intentionally empty -- call to register coverter */}
}
Configuration
No response
Other information
No response