CommandLineUtils icon indicating copy to clipboard operation
CommandLineUtils copied to clipboard

.NET 8: CommandLineApplication.Execute with unknown command throws `System.InvalidOperationException: Enumeration already finished`

Open craigktreasure opened this issue 1 year ago • 7 comments

Describe the bug

Using McMaster.Extensions.CommandLineUtils 4.1.0.

Upgrading my application from .NET 7 to .NET 8 and my tests and application started failing with an unexpected error when parsing unexpected commands.

Unhandled exception. System.InvalidOperationException: Enumeration already finished.
   at System.SZGenericArrayEnumerator`1.get_Current()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.CommandArgumentEnumerator.get_Current()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.CommandArgumentEnumerator.MoveNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessCommandOrParameter(CommandOrParameterArgument arg)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.Process()
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Parse(String[] args)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync(String[] args, CancellationToken cancellationToken)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
   at Program.<Main>$(String[] args) in C:\Users\craig\Desktop\MyApp\MyApp\Program.cs:line 10

To Reproduce

Create a simple sample app with the following:

MyApp.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net8.0;net7.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.0" />
  </ItemGroup>

</Project>

Program.cs

using McMaster.Extensions.CommandLineUtils;

CommandLineApplication app = new();
app.HelpOption();
app.OnExecute(() =>
{
    app.ShowHelp();
    return 1;
});
return app.Execute(args);

Now run the app with an invalid command for each of the target frameworks:

~\Desktop\MyApp\MyApp
> dotnet run -f net7.0 -- invalid
Specify --help for a list of available options and commands.
Unhandled exception. McMaster.Extensions.CommandLineUtils.UnrecognizedCommandParsingException: Unrecognized command or argument 'invalid'
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessUnexpectedArg(String argTypeName, String argValue)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessCommandOrParameter(CommandOrParameterArgument arg)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.Process()
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Parse(String[] args)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync(String[] args, CancellationToken cancellationToken)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
   at Program.<Main>$(String[] args) in C:\Users\craig\Desktop\MyApp\MyApp\Program.cs:line 10
~\Desktop\MyApp\MyApp
> dotnet run -f net8.0 -- invalid
Unhandled exception. System.InvalidOperationException: Enumeration already finished.
   at System.SZGenericArrayEnumerator`1.get_Current()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.CommandArgumentEnumerator.get_Current()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.CommandArgumentEnumerator.MoveNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessCommandOrParameter(CommandOrParameterArgument arg)
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.ProcessNext()
   at McMaster.Extensions.CommandLineUtils.CommandLineProcessor.Process()
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Parse(String[] args)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.ExecuteAsync(String[] args, CancellationToken cancellationToken)
   at McMaster.Extensions.CommandLineUtils.CommandLineApplication.Execute(String[] args)
   at Program.<Main>$(String[] args) in C:\Users\craig\Desktop\MyApp\MyApp\Program.cs:line 10

Expected behavior

The result from the net7.0 invocation is what I would expect. It correctly indicates that the parameter wasn't recognized.

Additional context

I'm also seeing the issue with valid commands, but I haven't nailed down a minimal repro for that. I have a feeling the fix for the unexpected commands will either fix the same issue or shine light on a deeper issue.

Runtime information:

> dotnet --list-sdks
6.0.417 [C:\Program Files\dotnet\sdk]
7.0.404 [C:\Program Files\dotnet\sdk]
8.0.100 [C:\Program Files\dotnet\sdk]

> dotnet --list-runtimes
Microsoft.AspNetCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

craigktreasure avatar Nov 30 '23 18:11 craigktreasure

There has been a change in the undefined behaviour for calling enumerator.Current before MoveNext has been called in net8: https://github.com/dotnet/runtime/issues/94256

sallerga avatar Dec 04 '23 22:12 sallerga

Is there a workaround for this? It basically makes the library unusable on .NET 8... @natemcmaster are you aware of this?

thomaslevesque avatar Dec 31 '23 20:12 thomaslevesque

The problem is here: https://github.com/natemcmaster/CommandLineUtils/blob/e0b6a2cde6bbda2004e113aa2499548a2e51f39e/src/CommandLineUtils/Internal/CommandLineProcessor.cs#L534-L539

MoveNext calls Current, assuming it will be null if the enumeration hasn't started. However, the behavior for calling Current after MoveNext() returned false is undefined; it used to return the last value, but now it throws.

thomaslevesque avatar Dec 31 '23 20:12 thomaslevesque

@thomaslevesque Correct. It's not usable on .NET 8. There is no workaround. I just spent a few days porting an app to System.CommandLine.

If you take a look above, there was also a fix submitted by @sallerga (#542).

craigktreasure avatar Jan 01 '24 02:01 craigktreasure

If you take a look above, there was also a fix submitted by @sallerga (#542).

Oh, I missed it. Thanks, I'll take a look at it.

thomaslevesque avatar Jan 01 '24 03:01 thomaslevesque

We also just ran into this issue with pipelines failing after upgrading to .NET 8. Googling the error led me here. Just for my information. Do you by chance have a rough estimate when this will be available as nuget? 😊

Thank you for your great work!

prodigy avatar Jan 10 '24 09:01 prodigy

Do you by chance have a rough estimate when this will be available as nuget?

Given that @natemcmaster has never replied to this issue, he's probably unavailable for a prolonged period of time. I wouldn't expect a new release soon.

I tried to switch to System.CommandLine, but it was hell. Since my use case was pretty simple, I eventually just wrote a bespoke parser.

thomaslevesque avatar Jan 10 '24 14:01 thomaslevesque

I've got a simple workaround that's working for my case and I'm able to replicate issue consistently with existing commands. Executing a command with a following empty argument would consistently throw the Enumeration already finished exception.

tool "" --help -> Enumeration already finished

args.Where(a => !string.IsNullOrEmpty(a)).ToArray()

connorstorer-kbxcom avatar Jan 29 '24 23:01 connorstorer-kbxcom

Fixed in https://github.com/natemcmaster/CommandLineUtils/pull/542 by @sallerga. Thanks for the fix!

This will be released as version 4.1.1

natemcmaster avatar Feb 19 '24 04:02 natemcmaster