Skip to content

Commit

Permalink
[clang] Fix ASTUnit working directory handling
Browse files Browse the repository at this point in the history
Fix a couple of issues with the handling of the current working directory in ASTUnit:

- Use `createPhysicalFileSystem` instead of `getRealFileSystem` to avoid affecting the process' current working directory, and set it at the top of `ASTUnit::LoadFromCommandLine` such that the driver used for argument parsing and the ASTUnit share the same VFS. This ensures that '-working-directory' correctly sets the VFS working directory in addition to the FileManager working directory.
- Ensure we preserve the FileSystemOptions set on the FileManager when re-creating it (as `ASTUnit::Reparse` will clear the currently set FileManager).

rdar://110697657

Reviewed By: bnbarham, benlangmuir

Differential Revision: https://reviews.llvm.org/D154134
  • Loading branch information
hamishknight authored and bnbarham committed Jun 30, 2023
1 parent aa964f1 commit 62e4c22
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 3 deletions.
10 changes: 7 additions & 3 deletions clang/lib/Frontend/ASTUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,7 @@ bool ASTUnit::Parse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
// Create the compiler instance to use for building the AST.
std::unique_ptr<CompilerInstance> Clang(
new CompilerInstance(std::move(PCHContainerOps)));
Clang->setInvocation(CCInvocation);

// Clean up on error, disengage it if the function returns successfully.
auto CleanOnError = llvm::make_scope_exit([&]() {
Expand All @@ -1171,7 +1172,6 @@ bool ASTUnit::Parse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
llvm::CrashRecoveryContextCleanupRegistrar<CompilerInstance>
CICleanup(Clang.get());

Clang->setInvocation(CCInvocation);
OriginalSourceFile =
std::string(Clang->getFrontendOpts().Inputs[0].getFile());

Expand Down Expand Up @@ -1754,6 +1754,12 @@ ASTUnit *ASTUnit::LoadFromCommandLine(
IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
assert(Diags.get() && "no DiagnosticsEngine was provided");

// If no VFS was provided, create one that tracks the physical file system.
// If '-working-directory' was passed as an argument, 'createInvocation' will
// set this as the current working directory of the VFS.
if (!VFS)
VFS = llvm::vfs::createPhysicalFileSystem();

SmallVector<StoredDiagnostic, 4> StoredDiagnostics;

std::shared_ptr<CompilerInvocation> CI;
Expand Down Expand Up @@ -1799,8 +1805,6 @@ ASTUnit *ASTUnit::LoadFromCommandLine(
ConfigureDiags(Diags, *AST, CaptureDiagnostics);
AST->Diagnostics = Diags;
AST->FileSystemOpts = CI->getFileSystemOpts();
if (!VFS)
VFS = llvm::vfs::getRealFileSystem();
VFS = createVFSFromCompilerInvocation(*CI, *Diags, VFS);
AST->FileMgr = new FileManager(AST->FileSystemOpts, VFS);
AST->StorePreamblesInMemory = StorePreamblesInMemory;
Expand Down
32 changes: 32 additions & 0 deletions clang/unittests/Frontend/ASTUnitTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,36 @@ TEST_F(ASTUnitTest, LoadFromCommandLineEarlyError) {
ASSERT_NE(ErrUnit->stored_diag_size(), 0U);
}

TEST_F(ASTUnitTest, LoadFromCommandLineWorkingDirectory) {
EXPECT_FALSE(
llvm::sys::fs::createTemporaryFile("bar", "c", FD, InputFileName));
auto Input = std::make_unique<ToolOutputFile>(InputFileName, FD);
Input->os() << "";

SmallString<128> WorkingDir;
ASSERT_FALSE(sys::fs::createUniqueDirectory("foo", WorkingDir));
const char *Args[] = {"clang", "-working-directory", WorkingDir.c_str(),
InputFileName.c_str()};

auto Diags = CompilerInstance::createDiagnostics(new DiagnosticOptions());
auto PCHContainerOps = std::make_shared<PCHContainerOperations>();
std::unique_ptr<clang::ASTUnit> ErrUnit;

auto *AST = ASTUnit::LoadFromCommandLine(
&Args[0], &Args[4], PCHContainerOps, Diags, "", false, "", false,
CaptureDiagsKind::All, std::nullopt, true, 0, TU_Complete, false, false,
false, SkipFunctionBodiesScope::None, false, true, false, false,
std::nullopt, &ErrUnit, nullptr);

ASSERT_NE(AST, nullptr);
ASSERT_FALSE(Diags->hasErrorOccurred());

// Make sure '-working-directory' sets both the FileSystemOpts and underlying
// VFS working directory.
const auto &FM = AST->getFileManager();
const auto &VFS = FM.getVirtualFileSystem();
ASSERT_EQ(*VFS.getCurrentWorkingDirectory(), WorkingDir.str());
ASSERT_EQ(FM.getFileSystemOpts().WorkingDir, WorkingDir.str());
}

} // anonymous namespace
1 change: 1 addition & 0 deletions clang/unittests/Frontend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_clang_unittest(FrontendTests
CodeGenActionTest.cpp
ParsedSourceLocationTest.cpp
PCHPreambleTest.cpp
ReparseWorkingDirTest.cpp
OutputStreamTest.cpp
TextDiagnosticTest.cpp
UtilsTest.cpp
Expand Down
118 changes: 118 additions & 0 deletions clang/unittests/Frontend/ReparseWorkingDirTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//====-- unittests/Frontend/ReparseWorkingDirTest.cpp - FrontendAction tests =//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace clang;

namespace {
class ReparseWorkingDirTest : public ::testing::Test {
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> VFS;
std::shared_ptr<PCHContainerOperations> PCHContainerOpts;

public:
void SetUp() override { VFS = new vfs::InMemoryFileSystem(); }
void TearDown() override {}

void setWorkingDirectory(StringRef Path) {
VFS->setCurrentWorkingDirectory(Path);
}

void AddFile(const std::string &Filename, const std::string &Contents) {
::time_t now;
::time(&now);
VFS->addFile(Filename, now,
MemoryBuffer::getMemBufferCopy(Contents, Filename));
}

std::unique_ptr<ASTUnit> ParseAST(StringRef EntryFile) {
PCHContainerOpts = std::make_shared<PCHContainerOperations>();
auto CI = std::make_shared<CompilerInvocation>();
CI->getFrontendOpts().Inputs.push_back(FrontendInputFile(
EntryFile, FrontendOptions::getInputKindForExtension(
llvm::sys::path::extension(EntryFile).substr(1))));

CI->getHeaderSearchOpts().AddPath("headers",
frontend::IncludeDirGroup::Quoted,
/*isFramework*/ false,
/*IgnoreSysRoot*/ false);

CI->getFileSystemOpts().WorkingDir = *VFS->getCurrentWorkingDirectory();
CI->getTargetOpts().Triple = "i386-unknown-linux-gnu";

IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
CompilerInstance::createDiagnostics(new DiagnosticOptions,
new DiagnosticConsumer));

FileManager *FileMgr = new FileManager(CI->getFileSystemOpts(), VFS);

std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation(
CI, PCHContainerOpts, Diags, FileMgr, false, CaptureDiagsKind::None,
/*PrecompilePreambleAfterNParses=*/1);
return AST;
}

bool ReparseAST(const std::unique_ptr<ASTUnit> &AST) {
bool reparseFailed =
AST->Reparse(PCHContainerOpts, /*RemappedFiles*/ {}, VFS);
return !reparseFailed;
}
};

TEST_F(ReparseWorkingDirTest, ReparseWorkingDir) {
// Setup the working directory path.
SmallString<16> WorkingDir;
#ifdef _WIN32
WorkingDir = "C:\\";
#else
WorkingDir = "/";
#endif
llvm::sys::path::append(WorkingDir, "root");
setWorkingDirectory(WorkingDir);

SmallString<32> Header;
llvm::sys::path::append(Header, WorkingDir, "headers", "header.h");

SmallString<32> MainName;
llvm::sys::path::append(MainName, WorkingDir, "main.cpp");

AddFile(MainName.str().str(), R"cpp(
#include "header.h"
int main() { return foo(); }
)cpp");
AddFile(Header.str().str(), R"h(
static int foo() { return 0; }
)h");

// Parse the main file, ensuring we can include the header.
std::unique_ptr<ASTUnit> AST(ParseAST(MainName.str()));
ASSERT_TRUE(AST.get());
ASSERT_FALSE(AST->getDiagnostics().hasErrorOccurred());

// Reparse and check that the working directory was preserved.
ASSERT_TRUE(ReparseAST(AST));

const auto &FM = AST->getFileManager();
const auto &FS = FM.getVirtualFileSystem();
ASSERT_EQ(FM.getFileSystemOpts().WorkingDir, WorkingDir);
ASSERT_EQ(*FS.getCurrentWorkingDirectory(), WorkingDir);
}

} // end anonymous namespace

0 comments on commit 62e4c22

Please sign in to comment.