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

InvalidCastException in GetUsageData when using nullable reference types #714

Open
rubberduck203 opened this issue Nov 21, 2020 · 2 comments
Open

Comments

@rubberduck203
Copy link

@rubberduck203 rubberduck203 commented Nov 21, 2020

rubberduck203/GitNStats#27

I recently enabled nullable reference types for my application.
Since then, calling my app with the --help flag has started failing with an unhandled exception.

$ dotnet run --project src/gitnstats/gitnstats.csproj -- --help
Unhandled exception. System.InvalidCastException: Unable to cast object of type 'System.Runtime.CompilerServices.NullableAttribute' to type 'CommandLine.Text.UsageAttribute'.
   at CommandLine.Core.ReflectionExtensions.<>c.<GetUsageData>b__2_3(<>f__AnonymousType1`2 <>h__TransparentIdentifier0)
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
   at CommandLine.Core.ReflectionExtensions.GetUsageData(Type type)
   at CommandLine.Text.HelpText.GetUsageFromType(Type type)
   at CommandLine.Text.HelpText.RenderUsageTextAsLines[T](ParserResult`1 parserResult, Func`2 mapperFunc)+MoveNext()
   at CSharpx.EnumerableExtensions.ToMaybe[T](IEnumerable`1 source)
   at CommandLine.Text.HelpText.AutoBuild[T](ParserResult`1 parserResult, Func`2 onError, Func`2 onExample, Boolean verbsIndex, Int32 maxDisplayWidth)
   at CommandLine.Text.HelpText.AutoBuild[T](ParserResult`1 parserResult, Func`2 onError, Int32 maxDisplayWidth)
   at CommandLine.Text.HelpText.AutoBuild[T](ParserResult`1 parserResult, Int32 maxDisplayWidth)
   at CommandLine.Parser.<>c__DisplayClass17_0`1.<DisplayHelp>b__1(IEnumerable`1 _, TextWriter writer)
   at CSharpx.MaybeExtensions.Do[T1,T2](Maybe`1 maybe, Action`2 action)
   at CommandLine.Parser.<>c__DisplayClass17_0`1.<DisplayHelp>b__0(IEnumerable`1 errors)
   at CommandLine.ParserResultExtensions.WithNotParsed[T](ParserResult`1 result, Action`1 action)
   at CommandLine.Parser.DisplayHelp[T](ParserResult`1 parserResult, TextWriter helpWriter, Int32 maxDisplayWidth)
   at CommandLine.Parser.MakeParserResult[T](ParserResult`1 parserResult, ParserSettings settings)
   at CommandLine.Parser.ParseArguments[T](IEnumerable`1 args)
   at GitNStats.Program.Main(String[] args) in /Users/rubberduck/src/GitNStats/src/gitnstats/Program.cs:line 11

The invalid cast is occurring in this block of code.

public static Maybe<Tuple<PropertyInfo, UsageAttribute>> GetUsageData(this Type type)
{
return
(from pi in type.FlattenHierarchy().SelectMany(x => x.GetTypeInfo().GetProperties())
let attrs = pi.GetCustomAttributes(true)
where attrs.OfType<UsageAttribute>().Any()
select Tuple.Create(pi, (UsageAttribute)attrs.First()))
.SingleOrDefault()
.ToMaybe();
}

I haven't figured out if it's occurring on line 44 or 45 yet, but GetUsageData seems to be assuming that it will only find a single attribute.

I'll do my best to update this with a minimal reproduction.

rubberduck203 added a commit to rubberduck203/commandline that referenced this issue Nov 21, 2020
@rubberduck203
Copy link
Author

@rubberduck203 rubberduck203 commented Nov 21, 2020

Minimal reproduction can be found here: rubberduck203@90ccab8

In the test project file, enable nullable reference types
This means the tests can not target net461, so we need a separate test project.

<nullable>enable</nullable>

The options class must have a nullable property, then we need to add custom usage examples in order to trigger the bug.

    public class ReflectionExtensionsTests
    {
        [Fact]
        public void GetUsageDataDoesNotExplodeWhenUsedWithNullableReferenceTypesUsingTheUsageAttribute()
        {
            typeof(NullableReferenceTypeOptions).GetUsageData();
        }
    }

    public class NullableReferenceTypeOptions
    {
        [Option(HelpText = "Define a string value here.")]
        public string? StringValue { get; set; }

        [Usage]
        public static IEnumerable<Example> Examples 
        {
            get 
            {
                yield return new Example("Run on current directory", new NullableReferenceTypeOptions());
            }
        }
    }
rubberduck203 added a commit to rubberduck203/commandline that referenced this issue Nov 21, 2020
@rubberduck203
Copy link
Author

@rubberduck203 rubberduck203 commented Nov 21, 2020

I submitted PR #715 to fix this.

rubberduck203 added a commit to rubberduck203/GitNStats that referenced this issue Nov 23, 2020
There's a bug in the commandline parser library that triggers when an option class has a custom usage function
and that function has more than one attribute.
When a class has nullable reference type properties, it's members are all tagged with the System.Runtime.CompilerServices.NullableAttribute attribute.
Disabling NRTs in just the Options.cs file fixes the bug while still allowing us to use NRTs elsewhere.

A bug has been submitted upstream
commandlineparser/commandline#714

As well as a pull request that fixes the issue
commandlineparser/commandline#715

The compiler directive can be removed once it's accepted and released.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant
You can’t perform that action at this time.