diff --git a/include/swift/Basic/SymbolicLinks.h b/include/swift/Basic/SymbolicLinks.h new file mode 100644 index 0000000000000..97757ab1eb81f --- /dev/null +++ b/include/swift/Basic/SymbolicLinks.h @@ -0,0 +1,37 @@ +//===--- SymbolicLinks.h ----------------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_BASIC_SYMBOLICLINKS_H +#define SWIFT_BASIC_SYMBOLICLINKS_H + +#include "swift/Basic/LLVM.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/VirtualFileSystem.h" +#include +#include + +namespace swift { + +/// Tries to resolve symbolic links in a given path, but preserves +/// substitute drives on Windows to avoid MAX_PATH issues. +/// \param InputPath The path to be resolved. +/// \param FileSystem The FileSystem for resolving the path. +/// \param Style An optional path style to honor in the return value. +/// \returns The resolved path, or the original path on failure. +std::string resolveSymbolicLinks(llvm::StringRef InputPath, + llvm::vfs::FileSystem &FileSystem, + llvm::Optional Style = llvm::None); + +} // namespace swift + +#endif // SWIFT_BASIC_BLOTSETVECTOR_H diff --git a/lib/Basic/CMakeLists.txt b/lib/Basic/CMakeLists.txt index e6bf1cb10179b..19754ba554d11 100644 --- a/lib/Basic/CMakeLists.txt +++ b/lib/Basic/CMakeLists.txt @@ -72,6 +72,7 @@ add_swift_host_library(swiftBasic STATIC StableHasher.cpp Statistic.cpp StringExtras.cpp + SymbolicLinks.cpp TargetInfo.cpp TaskQueue.cpp ThreadSafeRefCounted.cpp diff --git a/lib/Basic/SymbolicLinks.cpp b/lib/Basic/SymbolicLinks.cpp new file mode 100644 index 0000000000000..037227bbd8192 --- /dev/null +++ b/lib/Basic/SymbolicLinks.cpp @@ -0,0 +1,69 @@ +//===--- SymbolicLinks.cpp - Utility functions for resolving symlinks -----===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/Basic/SymbolicLinks.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Path.h" + +using namespace llvm; + +static std::error_code resolveSymbolicLinks( + StringRef InputPath, + llvm::vfs::FileSystem &FileSystem, + SmallVectorImpl &Result) { + + if (auto ErrorCode = FileSystem.getRealPath(InputPath, Result)) { + return ErrorCode; + } + + if (!is_style_windows(llvm::sys::path::Style::native)) { + return std::error_code(); + } + + // For Windows paths, make sure we didn't resolve across drives. + SmallString<128> AbsPathBuf = InputPath; + if (auto ErrorCode = FileSystem.makeAbsolute(AbsPathBuf)) { + // We can't guarantee that the real path preserves the drive + return ErrorCode; + } + + if (llvm::sys::path::root_name(StringRef(Result.data(), Result.size())) == + llvm::sys::path::root_name(AbsPathBuf)) { + // Success, the real path preserves the drive + return std::error_code(); + } + + // Fallback to using the absolute path. + // Simplifying /../ is semantically valid on Windows even in the + // presence of symbolic links. + llvm::sys::path::remove_dots(AbsPathBuf, /*remove_dot_dot=*/ true); + Result.assign(AbsPathBuf); + return std::error_code(); +} + +std::string swift::resolveSymbolicLinks( + StringRef InputPath, + llvm::vfs::FileSystem &FileSystem, + llvm::Optional Style) { + + llvm::SmallString<128> OutputPathBuf; + if (::resolveSymbolicLinks(InputPath, FileSystem, OutputPathBuf)) { + // Error, fallback on input path + OutputPathBuf = InputPath; + } + + if (Style) { + llvm::sys::path::native(OutputPathBuf, *Style); + } + + return std::string(OutputPathBuf); +} diff --git a/lib/IDETool/CompilerInvocation.cpp b/lib/IDETool/CompilerInvocation.cpp index f2027c6a123a9..a08d0a2699798 100644 --- a/lib/IDETool/CompilerInvocation.cpp +++ b/lib/IDETool/CompilerInvocation.cpp @@ -12,6 +12,7 @@ #include "swift/IDETool/CompilerInvocation.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Driver/FrontendUtil.h" #include "swift/Frontend/Frontend.h" #include "clang/AST/DeclObjC.h" @@ -78,24 +79,22 @@ static std::string adjustClangTriple(StringRef TripleStr) { } static FrontendInputsAndOutputs resolveSymbolicLinksInInputs( - FrontendInputsAndOutputs &inputsAndOutputs, StringRef UnresolvedPrimaryFile, + FrontendInputsAndOutputs &inputsAndOutputs, + StringRef UnresolvedPrimaryFile, llvm::IntrusiveRefCntPtr FileSystem, std::string &Error) { assert(FileSystem); - llvm::SmallString<128> PrimaryFile; - if (auto err = FileSystem->getRealPath(UnresolvedPrimaryFile, PrimaryFile)) - PrimaryFile = UnresolvedPrimaryFile; + std::string PrimaryFile = resolveSymbolicLinks( + UnresolvedPrimaryFile, *FileSystem); unsigned primaryCount = 0; // FIXME: The frontend should be dealing with symlinks, maybe similar to // clang's FileManager ? FrontendInputsAndOutputs replacementInputsAndOutputs; for (const InputFile &input : inputsAndOutputs.getAllInputs()) { - llvm::SmallString<128> newFilename; - if (auto err = FileSystem->getRealPath(input.getFileName(), newFilename)) - newFilename = input.getFileName(); - llvm::sys::path::native(newFilename); + std::string newFilename = resolveSymbolicLinks(input.getFileName(), + *FileSystem, llvm::sys::path::Style::native); bool newIsPrimary = input.isPrimary() || (!PrimaryFile.empty() && PrimaryFile == newFilename); if (newIsPrimary) { @@ -104,7 +103,7 @@ static FrontendInputsAndOutputs resolveSymbolicLinksInInputs( assert(primaryCount < 2 && "cannot handle multiple primaries"); replacementInputsAndOutputs.addInput( - InputFile(newFilename.str(), newIsPrimary, input.getBuffer())); + InputFile(newFilename, newIsPrimary, input.getBuffer())); } if (PrimaryFile.empty() || primaryCount == 1) { diff --git a/test/SourceKit/CursorInfo/cursor_symlink.swift b/test/SourceKit/CursorInfo/cursor_symlink.swift index 0275e30f296c9..8184c0a514e09 100644 --- a/test/SourceKit/CursorInfo/cursor_symlink.swift +++ b/test/SourceKit/CursorInfo/cursor_symlink.swift @@ -4,5 +4,8 @@ // RUN: %sourcekitd-test -req=cursor -pos=1:5 %t.dir/linked.swift -- %t.dir/real.swift | %FileCheck %s // RUN: %sourcekitd-test -req=cursor -pos=1:5 %t.dir/real.swift -- %t.dir/linked.swift | %FileCheck %s +// We can't resolve symlinks on a substituted drive without risking MAX_PATH issues +// REQUIRES: !windows_substituted_drive + // CHECK: source.lang.swift.decl.var.global (1:5-1:8) // CHECK: foo diff --git a/test/SourceKit/Sema/sema_symlink.swift b/test/SourceKit/Sema/sema_symlink.swift index 5a3847840cc2c..3c2d53a4c6949 100644 --- a/test/SourceKit/Sema/sema_symlink.swift +++ b/test/SourceKit/Sema/sema_symlink.swift @@ -5,3 +5,6 @@ // RUN: %diff -u %s.response %t.link.response // RUN: %sourcekitd-test -req=sema %t.dir/real.swift -- %t.dir/linked.swift | %sed_clean > %t.real.response // RUN: %diff -u %s.response %t.real.response + +// We can't resolve symlinks on a substituted drive without risking MAX_PATH issues +// REQUIRES: !windows_substituted_drive diff --git a/test/SourceKit/lit.local.cfg b/test/SourceKit/lit.local.cfg index a65d6563ac716..37f97c3114218 100644 --- a/test/SourceKit/lit.local.cfg +++ b/test/SourceKit/lit.local.cfg @@ -1,6 +1,26 @@ import os +import platform import shlex +# NOTE: this mirrors the kIsWindows from the parent lit.cfg +kIsWindows = platform.system() == 'Windows' +if kIsWindows: + # Detect if we are on a substituted drive + # We can't rely on os.path.realpath because older Python versions implement it as abspath + # So get the list of substitute drives from subst.exe + import subprocess + subst_path = os.path.join(os.environ["SystemRoot"], "system32", "subst.exe") + subst_stdout = subprocess.run([subst_path], capture_output=True).stdout + subst_lines_bytes = subst_stdout.splitlines() + + def is_on_subst_drive(path): + # Encoding doesn't matter, the drive is one letter and a colon + drive_bytes = os.path.splitdrive(path)[0].encode("utf-8") + return any(line_bytes.startswith(current_drive_bytes) for line_bytes in subst_lines_bytes) + + current_drive_bytes = os.path.splitdrive(config.test_exec_root)[0].encode("utf-8") + if is_on_subst_drive(config.test_source_root) or is_on_subst_drive(config.test_exec_root): + config.available_features.add('windows_substituted_drive') if 'sourcekit' not in config.available_features: config.unsupported = True diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index 4471ca0901149..5bf5facd3eaba 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -13,6 +13,7 @@ import os import platform import sys +import lit.util config.cmake = "@CMAKE_COMMAND@" config.llvm_src_root = "@LLVM_MAIN_SRC_DIR@" @@ -172,6 +173,6 @@ if '@SWIFT_SWIFT_PARSER@' == 'TRUE': # Let the main config do the real work. if config.test_exec_root is None: - config.test_exec_root = os.path.dirname(os.path.realpath(__file__)) + config.test_exec_root = os.path.dirname(lit.util.abs_path_preserve_drive(__file__)) lit_config.load_config( config, os.path.join(config.swift_src_root, "test", "lit.cfg")) diff --git a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp index d35078262add7..43cea0c3b1265 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp @@ -22,6 +22,7 @@ #include "swift/AST/PluginLoader.h" #include "swift/Basic/Cache.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Driver/FrontendUtil.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" @@ -843,8 +844,8 @@ FileContent SwiftASTManager::Implementation::getFileContent( StringRef UnresolvedPath, bool IsPrimary, llvm::IntrusiveRefCntPtr FileSystem, std::string &Error) const { - std::string FilePath = SwiftLangSupport::resolvePathSymlinks(UnresolvedPath); - if (auto EditorDoc = EditorDocs->findByPath(FilePath, /*IsRealpath=*/true)) + std::string FilePath = resolveSymbolicLinks(UnresolvedPath, *FileSystem); + if (auto EditorDoc = EditorDocs->findByPath(FilePath)) return getFileContentFromSnap(EditorDoc->getLatestSnapshot(), IsPrimary, FilePath); @@ -863,7 +864,7 @@ BufferStamp SwiftASTManager::Implementation::getBufferStamp( assert(FileSystem); if (CheckEditorDocs) { - if (auto EditorDoc = EditorDocs->findByPath(FilePath)) { + if (auto EditorDoc = EditorDocs->findByPath(FilePath, FileSystem.get())) { return EditorDoc->getLatestSnapshot()->getStamp(); } } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp b/tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp index b447ae8b5dad2..49ed6d29e5419 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp @@ -30,6 +30,7 @@ #include "swift/AST/DiagnosticsSIL.h" #include "swift/Basic/Compiler.h" #include "swift/Basic/SourceManager.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Demangling/ManglingUtils.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" @@ -303,12 +304,13 @@ SwiftEditorDocumentFileMap::getByUnresolvedName(StringRef FilePath) { } SwiftEditorDocumentRef -SwiftEditorDocumentFileMap::findByPath(StringRef FilePath, bool IsRealpath) { +SwiftEditorDocumentFileMap::findByPath(StringRef FilePath, + llvm::vfs::FileSystem *FileSystem) { SwiftEditorDocumentRef EditorDoc; std::string Scratch; - if (!IsRealpath) { - Scratch = SwiftLangSupport::resolvePathSymlinks(FilePath); + if (FileSystem) { + Scratch = resolveSymbolicLinks(FilePath, *FileSystem); FilePath = Scratch; } Queue.dispatchSync([&]{ @@ -325,12 +327,12 @@ SwiftEditorDocumentFileMap::findByPath(StringRef FilePath, bool IsRealpath) { } bool SwiftEditorDocumentFileMap::getOrUpdate( - StringRef FilePath, SwiftLangSupport &LangSupport, - SwiftEditorDocumentRef &EditorDoc) { + StringRef FilePath, llvm::vfs::FileSystem &FileSystem, + SwiftLangSupport &LangSupport, SwiftEditorDocumentRef &EditorDoc) { bool found = false; - std::string ResolvedPath = SwiftLangSupport::resolvePathSymlinks(FilePath); + std::string ResolvedPath = resolveSymbolicLinks(FilePath, FileSystem); Queue.dispatchBarrierSync([&]{ DocInfo &Doc = Docs[FilePath]; if (!Doc.DocRef) { @@ -2395,7 +2397,7 @@ void SwiftLangSupport::editorOpen(StringRef Name, llvm::MemoryBuffer *Buf, Snapshot = EditorDoc->initializeText( Buf, Args, Consumer.needsSemanticInfo(), fileSystem); EditorDoc->resetSyntaxInfo(Snapshot, *this); - if (EditorDocuments->getOrUpdate(Name, *this, EditorDoc)) { + if (EditorDocuments->getOrUpdate(Name, *fileSystem, *this, EditorDoc)) { // Document already exists, re-initialize it. This should only happen // if we get OPEN request while the previous document is not closed. LOG_WARN_FUNC("Document already exists in editorOpen(..): " << Name); diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 1ac6a33cab616..dc5b04a43d497 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -23,6 +23,7 @@ #include "swift/AST/ParameterList.h" #include "swift/AST/SILOptions.h" #include "swift/AST/USRGeneration.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Config.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/IDE/CodeCompletionCache.h" @@ -972,13 +973,6 @@ void SwiftLangSupport::printMemberDeclDescription(const swift::ValueDecl *VD, } } -std::string SwiftLangSupport::resolvePathSymlinks(StringRef FilePath) { - std::string InputPath = FilePath.str(); - llvm::SmallString<256> output; - if (llvm::sys::fs::real_path(InputPath, output)) - return InputPath; - return std::string(output.str()); -} void SwiftLangSupport::getStatistics(StatisticsReceiver receiver) { std::vector stats = { @@ -1040,10 +1034,8 @@ void SwiftLangSupport::performWithParamsToCompletionLikeOperation( // Resolve symlinks for the input file; we resolve them for the input files // in the arguments as well. // FIXME: We need the Swift equivalent of Clang's FileEntry. - llvm::SmallString<128> bufferIdentifier; - if (auto err = FileSystem->getRealPath( - UnresolvedInputFile->getBufferIdentifier(), bufferIdentifier)) - bufferIdentifier = UnresolvedInputFile->getBufferIdentifier(); + std::string bufferIdentifier = resolveSymbolicLinks( + UnresolvedInputFile->getBufferIdentifier(), *FileSystem); // Create a buffer for code completion. This contains '\0' at 'Offset' // position of 'UnresolvedInputFile' buffer. diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index b13578f4dfc32..9d9fa23aee8d5 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -143,15 +143,19 @@ class SwiftEditorDocumentFileMap { public: bool getOrUpdate(StringRef FilePath, + llvm::vfs::FileSystem &FileSystem, SwiftLangSupport &LangSupport, SwiftEditorDocumentRef &EditorDoc); + /// Looks up the document only by the path name that was given initially. SwiftEditorDocumentRef getByUnresolvedName(StringRef FilePath); - /// Looks up the document by resolving symlinks in the paths. - /// If \p IsRealpath is \c true, then \p FilePath must already be - /// canonicalized to a realpath. - SwiftEditorDocumentRef findByPath(StringRef FilePath, - bool IsRealpath = false); + + /// Looks up the document, resolving symlinks in paths if a filesystem + /// is provided, and otherwise assumes prior canonicalization. + SwiftEditorDocumentRef + findByPath(StringRef FilePath, + llvm::vfs::FileSystem *FileSystem = nullptr); + SwiftEditorDocumentRef remove(StringRef FilePath); }; @@ -516,10 +520,6 @@ class SwiftLangSupport : public LangSupport { printMemberDeclDescription(const swift::ValueDecl *VD, swift::Type baseTy, bool usePlaceholder, llvm::raw_ostream &OS); - /// Tries to resolve the path to the real file-system path. If it fails it - /// returns the original path; - static std::string resolvePathSymlinks(StringRef FilePath); - /// The result returned from \c performWithParamsToCompletionLikeOperation. struct CompletionLikeOperationParams { swift::CompilerInvocation &Invocation; diff --git a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp index bf66ab085c97e..1cf1313aa1234 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp @@ -27,6 +27,7 @@ #include "swift/AST/NameLookup.h" #include "swift/AST/SwiftNameTranslation.h" #include "swift/Basic/SourceManager.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Frontend/Frontend.h" #include "swift/Frontend/PrintingDiagnosticConsumer.h" #include "swift/IDE/CodeCompletion.h" @@ -640,8 +641,7 @@ mapOffsetToNewerSnapshot(unsigned Offset, static void mapLocToLatestSnapshot( SwiftLangSupport &Lang, LocationInfo &Location, ArrayRef PreviousASTSnaps) { - auto EditorDoc = Lang.getEditorDocuments()->findByPath(Location.Filename, - /*IsRealpath=*/true); + auto EditorDoc = Lang.getEditorDocuments()->findByPath(Location.Filename); if (!EditorDoc) return; @@ -1495,8 +1495,7 @@ class CursorRangeInfoConsumer : public SwiftASTConsumer { // blocked waiting on the AST to be fully typechecked. ImmutableTextSnapshotRef InputSnap; - if (auto EditorDoc = Lang.getEditorDocuments()->findByPath( - PrimaryFilePath, /*IsRealpath=*/true)) + if (auto EditorDoc = Lang.getEditorDocuments()->findByPath(PrimaryFilePath)) InputSnap = EditorDoc->getLatestSnapshot(); if (!InputSnap) return false; @@ -1555,8 +1554,8 @@ static SourceFile *retrieveInputFile(StringRef inputBufferName, // done that) if (haveRealPath) return nullptr; - std::string realPath = - SwiftLangSupport::resolvePathSymlinks(inputBufferName); + std::string realPath = resolveSymbolicLinks(inputBufferName, + CI.getFileSystem()); return retrieveInputFile(realPath, CI, /*haveRealPath=*/true); } @@ -2119,8 +2118,8 @@ void SwiftLangSupport::getCursorInfo( std::shared_ptr InputBuffer; if (InputBufferName.empty() && Length == 0) { std::string InputFileError; - llvm::SmallString<128> RealInputFilePath; - fileSystem->getRealPath(PrimaryFilePath, RealInputFilePath); + std::string RealInputFilePath = resolveSymbolicLinks(PrimaryFilePath, + *fileSystem); InputBuffer = std::shared_ptr(getASTManager()->getMemoryBuffer( RealInputFilePath, fileSystem, InputFileError)); diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index e5f7aa5c9fba5..3e7f0aa28e2eb 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -9,6 +9,7 @@ add_sourcekit_executable(sourcekitd-test ) target_link_libraries(sourcekitd-test PRIVATE SourceKitSupport + swiftBasic clangRewrite clangLex clangBasic) diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index 03259f6b810fe..2ea67d9849f97 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -14,6 +14,7 @@ #include "SourceKit/Support/Concurrency.h" #include "TestOptions.h" +#include "swift/Basic/SymbolicLinks.h" #include "swift/Demangling/ManglingMacros.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" @@ -2081,10 +2082,8 @@ static void printCursorInfo(sourcekitd_variant_t Info, StringRef FilenameIn, return ResponseSymbolInfo::read(Entry); }); - std::string Filename = FilenameIn.str(); - llvm::SmallString<256> output; - if (!llvm::sys::fs::real_path(Filename, output)) - Filename = std::string(output.str()); + std::string Filename = swift::resolveSymbolicLinks(FilenameIn.str(), + *llvm::vfs::getRealFileSystem()); SymbolInfo.print(OS, Filename, VFSFiles); OS << "ACTIONS BEGIN\n";