cmd/toolstash: check -linkobj files to support early export

cmd/go now passes a -linkobj flag with an additional archive
to support early export. The -o file now contains export
data while the -linkobj file contains the object code.

This CL makes sure 'toolstash -cmp' knows to compare the -linkobj
file.

Prior to this CL, 'go build -toolexec "toolstash -cmp" -a std' would
miss changes in the -linkobj file, which I verified by making
a temporary SSA change.

After this CL, 'toolstash -cmp' is again able to catch a change
like that. I also checked it still reports changes to the -o
export files by making a temporary inliner change.

Note this relies on cmd/go CL 842645, which avoids 'toolstash -cmp'
failing with a 'exportfd: broken pipe' error.

Updates golang/go#15734

Change-Id: I14e5e37d9f92f4337a1bd659f82ae1788fe45695
Reviewed-on: https://go-review.googlesource.com/c/tools/+/842665
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: PJ Malloy <pjmalloy@google.com>
diff --git a/cmd/toolstash/main.go b/cmd/toolstash/main.go
index 3a92c00..795b13c 100644
--- a/cmd/toolstash/main.go
+++ b/cmd/toolstash/main.go
@@ -132,6 +132,7 @@
 	"os/exec"
 	"path/filepath"
 	"runtime"
+	"slices"
 	"strings"
 	"time"
 )
@@ -267,9 +268,12 @@
 		cmd[0] = filepath.Join(toolDir, tool)
 	}
 
-	outfile, ok := cmpRun(false, cmd)
+	outfile, linkobj, ok := cmpRun(false, cmd)
 	if ok {
 		os.Remove(outfile + ".stash")
+		if linkobj != "" {
+			os.Remove(linkobj + ".stash")
+		}
 		return
 	}
 
@@ -291,7 +295,7 @@
 			}
 		}
 		cmdN := injectflags(cmd, nil, useDashN)
-		_, ok := cmpRun(false, cmdN)
+		_, _, ok := cmpRun(false, cmdN)
 		if !ok {
 			if useDashN {
 				log.Printf("compiler output differs, with optimizers disabled (-N)")
@@ -319,8 +323,10 @@
 	}
 
 	cmdS := injectflags(cmd, []string{extra}, false)
-	outfile, _ = cmpRun(true, cmdS)
+	outfile, _, _ = cmpRun(true, cmdS)
 
+	// The outfile name is always the key used for log comparison,
+	// even if linkobj also exists.
 	fmt.Fprintf(os.Stderr, "\n%s\n", compareLogs(outfile))
 	os.Exit(2)
 }
@@ -335,7 +341,7 @@
 	return x
 }
 
-func cmpRun(keepLog bool, cmd []string) (outfile string, match bool) {
+func cmpRun(keepLog bool, cmd []string) (outfile, linkobj string, match bool) {
 	cmdStash := make([]string, len(cmd))
 	copy(cmdStash, cmd)
 	cmdStash[0] = toolStash
@@ -351,6 +357,11 @@
 			break
 		}
 	}
+	// Also handle -linkobj if present.
+	if i := slices.Index(cmdStash, "-linkobj"); i >= 0 {
+		linkobj = cmdStash[i+1]
+		cmdStash[i+1] += ".stash"
+	}
 
 	if outfile == "" {
 		log.Fatalf("cannot determine output file for command: %s", strings.Join(cmd, " "))
@@ -383,8 +394,11 @@
 		}
 		log.Fatal(err)
 	}
-
-	return outfile, sameObject(outfile, outfile+".stash")
+	match = sameObject(outfile, outfile+".stash")
+	if match && linkobj != "" {
+		match = sameObject(linkobj, linkobj+".stash")
+	}
+	return outfile, linkobj, match
 }
 
 func sameObject(file1, file2 string) bool {