rvesse / airline

Java annotation-based framework for parsing Git like command line structures with deep extensibility
https://rvesse.github.io/airline/
Apache License 2.0
128 stars 20 forks source link

Command parsing stuck in infinite loop when command group has sub-groups #55

Closed harshdes closed 7 years ago

harshdes commented 7 years ago

I have a CLI that has a server group and a cache sub-group.

When running any command from the server group (not 'cache' sub-group), for example the ServerAdd command, the parsing goes into an infinite loop.

Following is the gist of the CLI building code.

            CliBuilder<Runnable> builder = Cli.<Runnable>builder("cli")
                    .withDescription("cli description")
                    .withDefaultCommand(Help.class)
                    .withCommands(Help.class);

            builder.withGroup("server")
            .withDefaultCommand(Help.class)
            .withDescription("server operations")
            .withCommands(ServerAdd.class, ServerRemove.class, ServerList.class);

            // Server - cache subgroup
            builder.withGroup("server")
            .withSubGroup("cache")
            .withDefaultCommand(Help.class)
            .withDescription("cache operations")
            .withCommands(ServerCacheCreate.class, ServerCacheRemove.class);

After debugging more into the airline code, I found the infinite loop is occuring in AbstractCommandParser.parseGroup(PeekingIterator<String> tokens, ParseState<T> state) in the following lines

                // Possibly may have sub-groups specified
                while (tokens.hasNext() && state.getGroup().getSubGroups().size() > 0) {
                    //@formatter:off
                    findGroupPredicate = state.getParserConfiguration().allowsAbbreviatedCommands() 
                                         ? new AbbreviatedGroupFinder(tokens.peek(), state.getGroup().getSubGroups()) 
                                         : new GroupFinder(tokens.peek());
                    //@formatter:on
                    group = CollectionUtils.find(state.getGroup().getSubGroups(), findGroupPredicate);
                    if (group != null) {
                        tokens.next();
                        state = state.withGroup(group).pushContext(Context.GROUP);
                        state = parseOptions(tokens, state, state.getGroup().getOptions());
                    }
                }

Below is what's happening:

  1. Since the server group has sub-groups, the code goes into the while loop.
  2. findGroupPredicate is populated for the ServerAdd command ("add")
  3. CollectionUtils.find returns null for "add" since there is no sub-group by that name. Remember, that the only sub-group present is cache.
  4. Since group is null, tokens never move to the next iterator and while loop continues with steps 1-4 forever

Let me know if you need more information. I'm happy to share my user code for this bug.

rvesse commented 7 years ago

Thanks for the clear report, I will try and get a test case and fix committed for this today

I was planning to make a release for a little while yet but I would be able to push out a snapshot build with the fix for you to test/use if that is okay?

rvesse commented 7 years ago

Fix will probably be tomorrow as have run out of time today

rvesse commented 7 years ago

A fix and test cases for this based on your report has been created.

The snapshot builds will be available from oss.sonatype.org shortly - see http://central.sonatype.org/pages/ossrh-guide.html for the necessary repository settings

rvesse commented 7 years ago

Actually realised code was at a point where it could be released so have gone ahead and cut a 2.2.0 release. It may take a few hours before the artefacts are available on Maven Central.

harshdes commented 7 years ago

I've verified and the fix works. Thanks @rvesse !