Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/extractor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
"//go/extractor/dbscheme",
"//go/extractor/diagnostics",
"//go/extractor/srcarchive",
"//go/extractor/subst",
"//go/extractor/toolchain",
"//go/extractor/trap",
"//go/extractor/util",
Expand Down
5 changes: 3 additions & 2 deletions go/extractor/extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/github/codeql-go/extractor/dbscheme"
"github.com/github/codeql-go/extractor/diagnostics"
"github.com/github/codeql-go/extractor/srcarchive"
"github.com/github/codeql-go/extractor/subst"
"github.com/github/codeql-go/extractor/toolchain"
"github.com/github/codeql-go/extractor/trap"
"github.com/github/codeql-go/extractor/util"
Expand Down Expand Up @@ -764,9 +765,9 @@ func normalizedPath(ast *ast.File, fset *token.FileSet) string {
file := fset.File(ast.Package).Name()
path, err := filepath.EvalSymlinks(file)
if err != nil {
return file
path = file
}
return path
return subst.ResolvePath(path)
}

// extractFile extracts AST information for the given file
Expand Down
12 changes: 12 additions & 0 deletions go/extractor/subst/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions go/extractor/subst/subst.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package subst

// ResolvePath resolves subst'd drive letters in a full path.
// If the path starts with a subst'd drive letter, replaces it with the backing path.
// Otherwise returns the path unchanged.
func ResolvePath(path string) string {
return resolvePath(path, ResolveDrive)
}

func resolvePath(path string, resolveDrive func(string) 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 := resolveDrive(path[:3])
if resolved == "" {
return path
}
return resolved + path[2:]
}
6 changes: 6 additions & 0 deletions go/extractor/subst/subst_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build !windows

package subst

// ResolveDrive is a no-op on non-Windows platforms.
func ResolveDrive(driveRoot string) string { return "" }
67 changes: 67 additions & 0 deletions go/extractor/subst/subst_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//go:build windows

package subst

import (
"os"
"path/filepath"
"syscall"
"unsafe"
)

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_u8")
if err != nil {
return
}
f, _ := d.FindProc("resolve_subst_free_u8")
dll = d
procResolve = p
procFree = f
available = true
}

// ResolveDrive resolves a subst'd drive root (e.g. "X:\") to its backing path.
// Returns "" if the drive is not subst'd or on error.
func ResolveDrive(driveRoot string) string {
if !available {
return ""
}
driveBytes := append([]byte(driveRoot), 0)
ret, _, _ := procResolve.Call(uintptr(unsafe.Pointer(&driveBytes[0])))
if ret == 0 {
return ""
}
result := goString((*byte)(unsafe.Pointer(ret)))
if procFree != nil {
procFree.Call(ret)
}
return result
}

func goString(p *byte) string {
if p == nil {
return ""
}
var n int
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
ptr = unsafe.Add(ptr, 1)
}
return string(unsafe.Slice(p, n))
}
Original file line number Diff line number Diff line change
Expand Up @@ -1242,12 +1242,13 @@ public static String relativePathLink (File f, File base)
public static File tryMakeCanonical (File f)
{
try {
return f.getCanonicalFile();
f = f.getCanonicalFile();
}
catch (IOException ignored) {
Exceptions.ignore(ignored, "Can't log error: Could be too verbose.");
return new File(simplifyPath(f));
f = new File(simplifyPath(f));
}
return SubstResolver.resolve(f);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.semmle.util.files;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
* Resolves Windows {@code subst}ed drive letters to their underlying paths. On non-Windows
* platforms, or when the native library failed to load, {@link #resolve(File)} is a no-op that
* returns its argument unchanged.
*/
public class SubstResolver {
private static final boolean available;

static {
boolean loaded = false;
if (File.separatorChar == '\\') {
String dist = System.getenv("CODEQL_DIST");
if (dist != null && !dist.isEmpty()) {
try {
Path library = Paths.get(dist).resolve("tools").resolve("win64")
.resolve("canonicalize.dll").toAbsolutePath();
System.load(library.toString());
loaded = true;
} catch (RuntimeException | UnsatisfiedLinkError ignored) {
}
}
}
available = loaded;
}

private SubstResolver() {}

/**
* Given a drive root like {@code "X:\\"}, returns the path that drive is
* {@code subst}ed to, or {@code null} if the letter isn't a subst mapping.
*/
private static native String nativeResolveSubst(String driveRoot);

/**
* If {@code f} is an absolute path starting with a {@code subst}ed drive letter, return an
* equivalent path with the drive letter replaced by its target. Otherwise return {@code f}
* unchanged.
*/
public static File resolve(File f) {
if (!available) {
return f;
}
String path = f.getPath();
if (path.length() < 3 || path.charAt(1) != ':') {
return f;
}
char sep = path.charAt(2);
if (sep != '\\' && sep != '/') {
return f;
}
if (!isDriveLetter(path.charAt(0))) {
return f;
}

String resolved = nativeResolveSubst(path.substring(0, 3));
if (resolved == null) {
return f;
}

return new File(resolved + path.substring(2));
}

private static boolean isDriveLetter(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

public static boolean isAvailable() {
return available;
}
}
Loading