Skip to content

Commit

Permalink
Merge pull request #1101 from VirrageS/v1-backport-fix
Browse files Browse the repository at this point in the history
[v1] (backport) Prevent panic on flagSet access from custom BashComplete
  • Loading branch information
coilysiren committed Mar 31, 2020
2 parents c354cec + 949a6ea commit 053ba9d
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 7 deletions.
4 changes: 2 additions & 2 deletions app.go
Expand Up @@ -207,7 +207,7 @@ func (a *App) Run(arguments []string) (err error) {
return err
}

err = parseIter(set, a, arguments[1:])
err = parseIter(set, a, arguments[1:], shellComplete)
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
Expand Down Expand Up @@ -330,7 +330,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
return err
}

err = parseIter(set, a, ctx.Args().Tail())
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)

Expand Down
6 changes: 3 additions & 3 deletions command.go
Expand Up @@ -114,7 +114,7 @@ func (c Command) Run(ctx *Context) (err error) {
c.UseShortOptionHandling = true
}

set, err := c.parseFlags(ctx.Args().Tail())
set, err := c.parseFlags(ctx.Args().Tail(), ctx.shellComplete)

context := NewContext(ctx.App, set, ctx)
context.Command = c
Expand Down Expand Up @@ -179,7 +179,7 @@ func (c Command) Run(ctx *Context) (err error) {
return err
}

func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
if c.SkipFlagParsing {
set, err := c.newFlagSet()
if err != nil {
Expand All @@ -198,7 +198,7 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
return nil, err
}

err = parseIter(set, c, args)
err = parseIter(set, c, args, shellComplete)
if err != nil {
return nil, err
}
Expand Down
47 changes: 47 additions & 0 deletions command_test.go
@@ -1,6 +1,7 @@
package cli

import (
"bytes"
"errors"
"flag"
"fmt"
Expand Down Expand Up @@ -371,3 +372,49 @@ func TestCommandSkipFlagParsing(t *testing.T) {
expect(t, args, c.expectedArgs)
}
}

func TestCommand_Run_CustomShellCompleteAcceptsMalformedFlags(t *testing.T) {
cases := []struct {
testArgs []string
expectedOut string
}{
{testArgs: []string{"--undefined"}, expectedOut: "found 0 args | flag 'number' set: false"},
{testArgs: []string{"--number"}, expectedOut: "found 0 args | flag 'number' set: false"},
{testArgs: []string{"--number", "fourty-two"}, expectedOut: "found 0 args | flag 'number' set: false"},
{testArgs: []string{"--number", "42"}, expectedOut: "found 0 args | flag 'number' set: true"},
{testArgs: []string{"--number", "42", "newArg"}, expectedOut: "found 1 args | flag 'number' set: true"},
{testArgs: []string{"--undefined", "--number", "42", "newArg"}, expectedOut: "found 1 args | flag 'number' set: true"},
}

for _, c := range cases {
var outputBuffer bytes.Buffer
app := &App{
Writer: &outputBuffer,
EnableBashCompletion: true,
Commands: []Command{
{
Name: "bar",
Usage: "this is for testing",
Flags: []Flag{
&IntFlag{
Name: "number",
Usage: "A number to parse",
},
},
BashComplete: func(c *Context) {
fmt.Fprintf(c.App.Writer, "found %d args | flag 'number' set: %t", c.NArg(), c.IsSet("number"))
},
},
},
}

osArgs := []string{"foo", "bar"}
osArgs = append(osArgs, c.testArgs...)
osArgs = append(osArgs, "--generate-bash-completion")

err := app.Run(osArgs)
stdout := outputBuffer.String()
expect(t, err, nil)
expect(t, stdout, c.expectedOut)
}
}
9 changes: 7 additions & 2 deletions parse.go
Expand Up @@ -11,13 +11,18 @@ type iterativeParser interface {
}

// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
// iteratively catch parsing errors. This way we achieve LR parsing without
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string) error {
// Pass `shellComplete` to continue parsing options on failure during shell
// completion when, the user-supplied options may be incomplete.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
for {
err := set.Parse(args)
if !ip.useShortOptionHandling() || err == nil {
if shellComplete {
return nil
}
return err
}

Expand Down

0 comments on commit 053ba9d

Please sign in to comment.