Skip to content

Conversation

@uloamaka
Copy link
Contributor

@uloamaka uloamaka commented Oct 9, 2025

This PR resolves #1523,
Packages using flag.ExitOnError or flag.Parse() can exit on invalid flags or --help, which violates the principle of reusability the deep-exit rule enforces.

This PR enhances the deep-exit rule to detect indirect program termination caused by the Go flag package.

Specifically, it adds checks for:

  • flag.NewFlagSet created with flag.ExitOnError
  • Calls to flag.Parse() outside of main() or init()

These usages can terminate programs unexpectedly, reducing reusability of packages that expose such functions.

Updates:

  • testdata/deep_exit.go
  • I added tests

@alexandear alexandear changed the title feat: extend deep-exit to detect exit-triggering flag usage deep-exit: detect exit-triggering flag usage Oct 10, 2025
@uloamaka
Copy link
Contributor Author

@alexandear Can you please help review this PR.

@alexandear alexandear requested a review from chavacava October 14, 2025 10:26
flag.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage)
flag.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage)
flag.Parse()
flag.Parse() //revive:disable-line:deep-exit
Copy link
Collaborator

@chavacava chavacava Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact we disable the rule in our own code makes me think on the pertinence of checking calls to flag.Parse outside the main function.

Copy link
Contributor

@ccoVeille ccoVeille Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because the right way to do is to use either this

func main() {

	// (...)
	flag.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage)
	flag.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage)
	flag.Parse()

	// (...)
}

or this

func main() {
	err := run()
	// (...)
}

func run() error {
	fl := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	fl.BoolVar(&setExitStatus, "set_exit_status", false, exitStatusUsage)
	fl.IntVar(&maxOpenFiles, "max_open_files", 0, maxOpenFilesUsage)
	err := fl.Parse(os.Args[1:])
	if err != nil {
		return err
	}
	// (...)

	return nil
}

You can take a look at the code I wrote for gh-reaction

But maybe, the error message for flag.Parse should be way clearer, about the fact there is a need to use flag.ContinueOnError

Please note also, that the same code, using flag.NewFlagSet(os.Args[0], flag.ExitOnError) or flag.NewFlagSet(os.Args[0], flag.PanicOnError) should be reported as deep-exit, but it would be a next move, and somehow a niche maybe

EDIT: it's not a niche :P https://github.com/search?q=language%3Ago%20%2Fflag%5C.NewFlagSet.*flag%5C.(Exit%7CPanic)OnError%2F%20-is%3Afork%20-is%3Aarchived&type=code

@chavacava chavacava merged commit 1bc57ac into mgechev:master Oct 23, 2025
10 checks passed
@chavacava
Copy link
Collaborator

Thank you @uloamaka for the PR and @alexandear @ccoVeille for the feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deep-exit: add Go standard library flag.Parse call to default exclusions

4 participants