From 75ee4afec9dbab835e21f0ad3394899ff58030c1 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 7 Jul 2026 10:45:29 +0200 Subject: [PATCH] Kotlin: Resolve `subst`ed drives on Windows --- .../java/com/semmle/util/files/FileUtil.java | 7 +- .../com/semmle/util/files/SubstResolver.java | 77 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 java/kotlin-extractor/src/main/java/com/semmle/util/files/SubstResolver.java diff --git a/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java b/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java index 79ce2d8d8d3..19bdc786c5e 100644 --- a/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java +++ b/java/kotlin-extractor/src/main/java/com/semmle/util/files/FileUtil.java @@ -1237,13 +1237,14 @@ public class FileUtil /** * Try to convert a file into a canonical file. Handles the possible IO exception by just making - * the path absolute. + * the path absolute. Also resolves subst drives on Windows. */ public static File tryMakeCanonical (File f) { try { - return f.getCanonicalFile(); - } + // getCanonicalFile does not canonicalize subst drives on Windows, so do this separately. This + // is a no-op on non-Windows platforms. + return SubstResolver.resolve(f.getCanonicalFile()); } catch (IOException ignored) { Exceptions.ignore(ignored, "Can't log error: Could be too verbose."); return new File(simplifyPath(f)); diff --git a/java/kotlin-extractor/src/main/java/com/semmle/util/files/SubstResolver.java b/java/kotlin-extractor/src/main/java/com/semmle/util/files/SubstResolver.java new file mode 100644 index 00000000000..3d93c45234d --- /dev/null +++ b/java/kotlin-extractor/src/main/java/com/semmle/util/files/SubstResolver.java @@ -0,0 +1,77 @@ +package com.semmle.util.files; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Resolves Windows {@code subst}ed drive letters to their underlying paths. On non-Windows + * platforms, or when the native library failed to load, resolving will be a no-op. + */ +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:\\"} (or {@code "X:/"}), returns the path that drive is + * {@code subst}ed to, or {@code null} if the drive root was not subst drive or if an internal + * error occurred. + */ + 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; + } + + // Append the remainder of the original path. The native side strips away + // any trailing separator. + return new File(resolved + path.substring(2)); + } + + private static boolean isDriveLetter(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } +}