cmd/govulncheck: return 0 exit code for -json mode

Rationale is that the outcome of -json mode is a json data and its
successful creation should indicate success of the whole command. This
is in line with tools/analysis/singlechecker (see #53509).

Change-Id: I22edad675cba58fccdb4b59223a07b83558a0ac8
Reviewed-on: https://go-review.googlesource.com/c/vuln/+/432236
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/cmd/govulncheck/doc.go b/cmd/govulncheck/doc.go
index 7ad3792..dc43c59 100644
--- a/cmd/govulncheck/doc.go
+++ b/cmd/govulncheck/doc.go
@@ -53,7 +53,8 @@
 functions. Its output omits call stacks, which require source code analysis.
 
 Govulncheck exits successfully (exit code 0) if there are no vulnerabilities,
-and exits unsuccessfully if there are.
+and exits unsuccessfully if there are. It also exits successfully if -json flag
+is provided, regardless of the number of detected vulnerabilities.
 
 # Flags
 
@@ -63,7 +64,8 @@
 when run on source. It has no effect when run on a binary.
 
 The -json flag causes govulncheck to print its output as a JSON object
-corresponding to the type [golang.org/x/vuln/vulncheck.Result].
+corresponding to the type [golang.org/x/vuln/vulncheck.Result]. The exit code
+of govulncheck is 0 when this flag is provided.
 
 The -tags flag accepts a comma-separated list of build tags to control which
 files should be included in loaded packages for source analysis.
diff --git a/cmd/govulncheck/main.go b/cmd/govulncheck/main.go
index da8b8aa..8bf044d 100644
--- a/cmd/govulncheck/main.go
+++ b/cmd/govulncheck/main.go
@@ -123,15 +123,19 @@
 	}
 
 	if *jsonFlag {
+		// Following golang.org/x/tools/go/analysis/singlechecker,
+		// return 0 exit code in -json mode.
 		writeJSON(r)
-	} else {
-		// set of top-level packages, used to find representative symbols
-		ci := govulncheck.GetCallInfo(r, pkgs)
-		writeText(r, ci, unaffected)
+		os.Exit(0)
 	}
-	exitCode := 0
+
+	// set of top-level packages, used to find representative symbols
+	ci := govulncheck.GetCallInfo(r, pkgs)
+	writeText(r, ci, unaffected)
+
 	// Following golang.org/x/tools/go/analysis/singlechecker,
 	// fail with 3 if there are findings (in this case, vulns).
+	exitCode := 0
 	if len(r.Vulns) > 0 {
 		exitCode = 3
 	}
diff --git a/cmd/govulncheck/testdata/json-binary.ct b/cmd/govulncheck/testdata/json-binary.ct
index df8e4d7..dfe929f 100644
--- a/cmd/govulncheck/testdata/json-binary.ct
+++ b/cmd/govulncheck/testdata/json-binary.ct
@@ -20,7 +20,7 @@
 	]
 }
 
-$ govulncheck -json ${vuln_binary} --> FAIL 3
+$ govulncheck -json ${vuln_binary}
 {
 	"Calls": null,
 	"Imports": null,
diff --git a/cmd/govulncheck/testdata/json.ct b/cmd/govulncheck/testdata/json.ct
index 18dce39..9e75f84 100644
--- a/cmd/govulncheck/testdata/json.ct
+++ b/cmd/govulncheck/testdata/json.ct
@@ -41,7 +41,7 @@
 }
 
 $ cdmodule vuln
-$ govulncheck -json . --> FAIL 3
+$ govulncheck -json .
 {
 	"Calls": {
 		"Functions": {