From e06ce00d6068872d8f962cd53df8cf8c510c4517 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 7 Jul 2026 14:20:55 +0200 Subject: [PATCH] Go: Resolve `subst`ed drives on Windows --- MODULE.bazel | 2 +- go/extractor/extractor.go | 2 +- go/extractor/go.mod | 5 +- go/extractor/go.sum | 2 + go/extractor/util/BUILD.bazel | 11 +++- go/extractor/util/subst_other.go | 6 +++ go/extractor/util/subst_windows.go | 86 ++++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 go/extractor/util/subst_other.go create mode 100644 go/extractor/util/subst_windows.go diff --git a/MODULE.bazel b/MODULE.bazel index 8dd50d33c3f..a5140fbd27c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -280,7 +280,7 @@ go_sdk.download(version = "1.26.5") go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//go/extractor:go.mod") -use_repo(go_deps, "com_github_stretchr_testify", "org_golang_x_mod", "org_golang_x_tools") +use_repo(go_deps, "com_github_stretchr_testify", "org_golang_x_mod", "org_golang_x_sys", "org_golang_x_tools") ripunzip_archive = use_repo_rule("//misc/ripunzip:ripunzip.bzl", "ripunzip_archive") diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index 8c217837614..4efa1daac56 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -770,7 +770,7 @@ func normalizedPath(ast *ast.File, fset *token.FileSet) string { if err != nil { return file } - return path + return util.ResolvePath(path) } // extractFile extracts AST information for the given file diff --git a/go/extractor/go.mod b/go/extractor/go.mod index b45815abbc9..c92b654c8c6 100644 --- a/go/extractor/go.mod +++ b/go/extractor/go.mod @@ -13,7 +13,10 @@ require ( golang.org/x/tools v0.48.0 ) -require github.com/stretchr/testify v1.11.1 +require ( + github.com/stretchr/testify v1.11.1 + golang.org/x/sys v0.47.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go/extractor/go.sum b/go/extractor/go.sum index 76fbec137b4..b57649cbb35 100644 --- a/go/extractor/go.sum +++ b/go/extractor/go.sum @@ -10,6 +10,8 @@ golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/go/extractor/util/BUILD.bazel b/go/extractor/util/BUILD.bazel index ccebf5ebd86..6d6c6c7a993 100644 --- a/go/extractor/util/BUILD.bazel +++ b/go/extractor/util/BUILD.bazel @@ -9,11 +9,20 @@ go_library( "logging.go", "overlays.go", "semver.go", + "subst_other.go", + "subst_windows.go", "util.go", ], importpath = "github.com/github/codeql-go/extractor/util", visibility = ["//visibility:public"], - deps = ["@org_golang_x_mod//semver"], + deps = [ + "@org_golang_x_mod//semver", + ] + select({ + "@rules_go//go/platform:windows": [ + "@org_golang_x_sys//windows", + ], + "//conditions:default": [], + }), ) go_test( diff --git a/go/extractor/util/subst_other.go b/go/extractor/util/subst_other.go new file mode 100644 index 00000000000..b32420a7746 --- /dev/null +++ b/go/extractor/util/subst_other.go @@ -0,0 +1,6 @@ +//go:build !windows + +package util + +// ResolvePath is a no-op on non-Windows platforms. +func ResolvePath(path string) string { return path } diff --git a/go/extractor/util/subst_windows.go b/go/extractor/util/subst_windows.go new file mode 100644 index 00000000000..b31ee08dc22 --- /dev/null +++ b/go/extractor/util/subst_windows.go @@ -0,0 +1,86 @@ +//go:build windows + +package util + +import ( + "os" + "path/filepath" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + dll *syscall.DLL + procResolve *syscall.Proc + procFree *syscall.Proc + available bool +) + +func init() { + dist := os.Getenv("CODEQL_DIST") + if dist == "" { + return + } + dllPath := filepath.Join(dist, "tools", "win64", "canonicalize.dll") + d, err := syscall.LoadDLL(dllPath) + if err != nil { + return + } + p, err := d.FindProc("resolve_subst") + if err != nil { + return + } + f, _ := d.FindProc("resolve_subst_free") + dll = d + procResolve = p + procFree = f + available = true +} + +// If "path" is an absolute path starting with a "subst"ed drive letter, return an +// equivalent path with the drive letter replaced by its target. Otherwise return +// "path" unchanged. +func ResolvePath(path string) string { + if len(path) < 3 { + return path + } + if path[1] != ':' { + return path + } + if path[2] != '\\' && path[2] != '/' { + return path + } + c := path[0] + if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { + return path + } + + resolved, ok := resolveDrive(path[:3]) + if !ok { + return path + } + return resolved + path[2:] +} + +// Given a drive root like "X:\" (or "X:/"), returns the path that drive is +// "subst"ed to. Returns false if the drive is not "subst"ed or an error occurred. +func resolveDrive(driveRoot string) (string, bool) { + if !available { + return "", false + } + driveBytes, err := windows.ByteSliceFromString(driveRoot) + if err != nil { + return "", false + } + ret, _, _ := procResolve.Call(uintptr(unsafe.Pointer(&driveBytes[0]))) + if ret == 0 { + return "", false + } + result := windows.BytePtrToString((*byte)(unsafe.Pointer(ret))) + if procFree != nil { + procFree.Call(ret) + } + return result, true +}