Skip to content

os/exec: calling Cmd.Start after setting Cmd.Path manually to absolute path without ".exe" no longer implicitly adds ".exe" in Go 1.22 #66586

@dmitshur

Description

@dmitshur

https://go.dev/doc/go1.22#os/exec includes:

On Windows, Command and Cmd.Start no longer call LookPath if the path to the executable is already absolute and has an executable file extension.

I don't quite understand what criteria is used for determining whether a path has an executable file extension. Which extensions are included? Is the PATHEXT environment variable involved?


That said, I've narrowed down a following behavior change between Go 1.21 and 1.22 that I'm not sure if it's working as intended, so reporting it for investigation.

Consider the output of the following Go program on a Windows machine that has an executable file at the path "C:\Program Files\Go\bin\gofmt.exe", in a roughly default environment (i.e., PATHEXT is not modified):

package main

import (
	"fmt"
	"os"
	"os/exec"
	"strings"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

func main() {
	if _, err := os.Stat(`C:\Program Files\Go\bin\gofmt.exe`); err != nil {
		fmt.Println("returning early; if gofmt.exe doesn't exist the rest of the output will be misleading")
		return
	} else if !strings.Contains(os.Getenv("PATHEXT"), ".EXE") {
		fmt.Println("returning early; if .exe isn't included in PATHEXT the rest of the output will be misleading")
		return
	}

	cmdViaCommand := exec.Command(`C:\Program Files\Go\bin\gofmt`)
	cmdManualPath := &exec.Cmd{
		Path: `C:\Program Files\Go\bin\gofmt`,
		Args: []string{`C:\Program Files\Go\bin\gofmt`},
	}
	diff := cmp.Diff(cmdViaCommand, cmdManualPath, cmpopts.IgnoreUnexported(exec.Cmd{}))
	if diff == "" {
		diff = "(no diff)\n"
	}
	fmt.Printf("diff (-cmdViaCommand +cmdManualPath):\n%s\n", diff)

	err := cmdManualPath.Run()
	fmt.Println("err:", err)
}

When running it using Go 1.21.8, the output is:

$ go run .
diff (-cmdViaCommand +cmdManualPath):
(no diff)

err: <nil>

But when running it with Go 1.22.1:

$ go run .
diff (-cmdViaCommand +cmdManualPath):
  &exec.Cmd{
-       Path: `C:\Program Files\Go\bin\gofmt.exe`,
+       Path: `C:\Program Files\Go\bin\gofmt`,
        Args: {`C:\Program Files\Go\bin\gofmt`},
        Env:  nil,
        ... // 8 ignored and 11 identical fields
  }

err: fork/exec C:\Program Files\Go\bin\gofmt: The system cannot find the file specified.

Not having to manually add ".exe" to the path and instead relying on the PATHEXT mechanism is very convenient when writing multi-platform Go programs, since it permits there not to be special cases for one of the GOOS values.

In that context, it seems there's no change in behavior when using exec.Command to create a *exec.Cmd and then calling Cmd.Start on it. But when creating it manually, Go 1.21 would use PATHEXT compensate for Cmd.Path missing a ".exe" suffix, whereas Go 1.22 doesn't. I can't quite tell from os/exec documentation (or the seemingly relevant release note) if this is a bug fix or a bug.

CC @golang/windows.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions