Skip to content
Closed
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
37 changes: 37 additions & 0 deletions include/swift/Basic/SymbolicLinks.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <optional>

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<llvm::sys::path::Style> Style = llvm::None);

} // namespace swift

#endif // SWIFT_BASIC_BLOTSETVECTOR_H
1 change: 1 addition & 0 deletions lib/Basic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ add_swift_host_library(swiftBasic STATIC
StableHasher.cpp
Statistic.cpp
StringExtras.cpp
SymbolicLinks.cpp
TargetInfo.cpp
TaskQueue.cpp
ThreadSafeRefCounted.cpp
Expand Down
69 changes: 69 additions & 0 deletions lib/Basic/SymbolicLinks.cpp
Original file line number Diff line number Diff line change
@@ -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<char> &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<llvm::sys::path::Style> 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);
}
17 changes: 8 additions & 9 deletions lib/IDETool/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -78,24 +79,22 @@ static std::string adjustClangTriple(StringRef TripleStr) {
}

static FrontendInputsAndOutputs resolveSymbolicLinksInInputs(
FrontendInputsAndOutputs &inputsAndOutputs, StringRef UnresolvedPrimaryFile,
FrontendInputsAndOutputs &inputsAndOutputs,
StringRef UnresolvedPrimaryFile,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> 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) {
Expand All @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions test/SourceKit/CursorInfo/cursor_symlink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions test/SourceKit/Sema/sema_symlink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions test/SourceKit/lit.local.cfg
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 2 additions & 1 deletion test/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -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@"
Expand Down Expand Up @@ -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"))
7 changes: 4 additions & 3 deletions tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -843,8 +844,8 @@ FileContent SwiftASTManager::Implementation::getFileContent(
StringRef UnresolvedPath, bool IsPrimary,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> 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);

Expand All @@ -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();
}
}
Expand Down
16 changes: 9 additions & 7 deletions tools/SourceKit/lib/SwiftLang/SwiftEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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([&]{
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 3 additions & 11 deletions tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<Statistic *> stats = {
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 9 additions & 9 deletions tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down Expand Up @@ -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;
Expand Down
Loading