Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@ class TestDbgInfoContentVector(TestBase):

mydir = TestBase.compute_mydir(__file__)

# FIXME: This should work on more setups, so remove these
# skipIf's in the future.
@add_test_categories(["libc++"])
@skipIf(compiler=no_match("clang"))
@skipIf(oslist=no_match(["linux"]))
@skipIf(debug_info=no_match(["dwarf"]))
def test(self):
self.build()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
USE_LIBCPP := 1
CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ class TestVectorOfVectors(TestBase):

mydir = TestBase.compute_mydir(__file__)

# FIXME: This should work on more setups, so remove these
# skipIf's in the future.
@add_test_categories(["libc++"])
@skipIf(compiler=no_match("clang"))
@skipIf(oslist=no_match(["linux"]))
@skipIf(debug_info=no_match(["dwarf"]))
def test(self):
self.build()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
USE_LIBCPP := 1
CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ class TestDbgInfoContentWeakPtr(TestBase):

mydir = TestBase.compute_mydir(__file__)

# FIXME: This should work on more setups, so remove these
# skipIf's in the future.
@add_test_categories(["libc++"])
@skipIf(compiler=no_match("clang"))
@skipIf(oslist=no_match(["linux"]))
@skipIf(debug_info=no_match(["dwarf"]))
def test(self):
self.build()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
USE_LIBCPP := 1
CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ class TestSharedPtr(TestBase):

mydir = TestBase.compute_mydir(__file__)

# FIXME: This should work on more setups, so remove these
# skipIf's in the future.
@add_test_categories(["libc++"])
@skipIf(compiler=no_match("clang"))
@skipIf(oslist=no_match(["linux"]))
@skipIf(debug_info=no_match(["dwarf"]))
def test(self):
self.build()

Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_lldb_library(lldbPluginExpressionParserClang PLUGIN
ClangPersistentVariables.cpp
ClangUserExpression.cpp
ClangUtilityFunction.cpp
CppModuleConfiguration.cpp
IRForTarget.cpp
IRDynamicChecks.cpp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,38 +241,23 @@ static void SetupModuleHeaderPaths(CompilerInstance *compiler,
search_opts.ModuleCachePath = module_cache.str();
LLDB_LOG(log, "Using module cache path: {0}", module_cache.c_str());

FileSpec clang_resource_dir = GetClangResourceDir();
std::string resource_dir = clang_resource_dir.GetPath();
if (FileSystem::Instance().IsDirectory(resource_dir)) {
search_opts.ResourceDir = resource_dir;
std::string resource_include = resource_dir + "/include";
search_opts.AddPath(resource_include, frontend::System, false, true);

LLDB_LOG(log, "Added resource include dir: {0}", resource_include);
}
search_opts.ResourceDir = GetClangResourceDir().GetPath();

search_opts.ImplicitModuleMaps = true;

std::vector<std::string> system_include_directories =
target_sp->GetPlatform()->GetSystemIncludeDirectories(
lldb::eLanguageTypeC_plus_plus);

for (const std::string &include_dir : system_include_directories) {
search_opts.AddPath(include_dir, frontend::System, false, true);

LLDB_LOG(log, "Added system include dir: {0}", include_dir);
}
}

//===----------------------------------------------------------------------===//
// Implementation of ClangExpressionParser
//===----------------------------------------------------------------------===//

ClangExpressionParser::ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
bool generate_debug_info, std::vector<std::string> include_directories, std::string filename)
ClangExpressionParser::ClangExpressionParser(
ExecutionContextScope *exe_scope, Expression &expr,
bool generate_debug_info, std::vector<std::string> include_directories,
std::string filename)
: ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
m_pp_callbacks(nullptr),
m_include_directories(std::move(include_directories)), m_filename(std::move(filename)) {
m_include_directories(std::move(include_directories)),
m_filename(std::move(filename)) {
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));

// We can't compile expressions without a target. So if the exe_scope is
Expand Down
81 changes: 50 additions & 31 deletions lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "ClangExpressionParser.h"
#include "ClangModulesDeclVendor.h"
#include "ClangPersistentVariables.h"
#include "CppModuleConfiguration.h"

#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
Expand Down Expand Up @@ -433,48 +434,59 @@ static bool SupportsCxxModuleImport(lldb::LanguageType language) {
}
}

std::vector<std::string>
ClangUserExpression::GetModulesToImport(ExecutionContext &exe_ctx) {
/// Utility method that puts a message into the expression log and
/// returns an invalid module configuration.
static CppModuleConfiguration LogConfigError(const std::string &msg) {
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
LLDB_LOG(log, "[C++ module config] {0}", msg);
return CppModuleConfiguration();
}

if (!SupportsCxxModuleImport(Language()))
return {};
CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
ExecutionContext &exe_ctx) {
// Don't do anything if this is not a C++ module configuration.
if (!SupportsCxxModuleImport(language))
return LogConfigError("Language doesn't support C++ modules");

Target *target = exe_ctx.GetTargetPtr();
if (!target || !target->GetEnableImportStdModule())
return {};
if (!target)
return LogConfigError("No target");

if (!target->GetEnableImportStdModule())
return LogConfigError("Importing std module not enabled in settings");

StackFrame *frame = exe_ctx.GetFramePtr();
if (!frame)
return {};
return LogConfigError("No frame");

Block *block = frame->GetFrameBlock();
if (!block)
return {};
return LogConfigError("No block");

SymbolContext sc;
block->CalculateSymbolContext(&sc);
if (!sc.comp_unit)
return {};

if (log) {
for (const SourceModule &m : sc.comp_unit->GetImportedModules()) {
LLDB_LOG(log, "Found module in compile unit: {0:$[.]} - include dir: {1}",
llvm::make_range(m.path.begin(), m.path.end()), m.search_path);
return LogConfigError("Couldn't calculate symbol context");

// Build a list of files we need to analyze to build the configuration.
FileSpecList files;
for (const FileSpec &f : sc.comp_unit->GetSupportFiles())
files.AppendIfUnique(f);
// We also need to look at external modules in the case of -gmodules as they
// contain the support files for libc++ and the C library.
sc.comp_unit->ForEachExternalModule([&files](lldb::ModuleSP module) {
for (std::size_t i = 0; i < module->GetNumCompileUnits(); ++i) {
const FileSpecList &support_files =
module->GetCompileUnitAtIndex(i)->GetSupportFiles();
for (const FileSpec &f : support_files) {
files.AppendIfUnique(f);
}
}
}

for (const SourceModule &m : sc.comp_unit->GetImportedModules())
m_include_directories.emplace_back(m.search_path.GetCString());

// Check if we imported 'std' or any of its submodules.
// We currently don't support importing any other modules in the expression
// parser.
for (const SourceModule &m : sc.comp_unit->GetImportedModules())
if (!m.path.empty() && m.path.front() == "std")
return {"std"};

return {};
});
// Try to create a configuration from the files. If there is no valid
// configuration possible with the files, this just returns an invalid
// configuration.
return CppModuleConfiguration(files);
}

bool ClangUserExpression::PrepareForParsing(
Expand Down Expand Up @@ -502,14 +514,21 @@ bool ClangUserExpression::PrepareForParsing(

SetupDeclVendor(exe_ctx, m_target);

std::vector<std::string> used_modules = GetModulesToImport(exe_ctx);
m_imported_cpp_modules = !used_modules.empty();
CppModuleConfiguration module_config = GetModuleConfig(m_language, exe_ctx);
llvm::ArrayRef<std::string> imported_modules =
module_config.GetImportedModules();
m_imported_cpp_modules = !imported_modules.empty();
m_include_directories = module_config.GetIncludeDirs();

LLDB_LOG(log, "List of imported modules in expression: {0}",
llvm::make_range(used_modules.begin(), used_modules.end()));
llvm::make_range(imported_modules.begin(), imported_modules.end()));
LLDB_LOG(log, "List of include directories gathered for modules: {0}",
llvm::make_range(m_include_directories.begin(),
m_include_directories.end()));

UpdateLanguageForExpr();
CreateSourceCode(diagnostic_manager, exe_ctx, used_modules, for_completion);
CreateSourceCode(diagnostic_manager, exe_ctx, imported_modules,
for_completion);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ class ClangUserExpression : public LLVMUserExpression {
lldb::addr_t struct_address,
DiagnosticManager &diagnostic_manager) override;

std::vector<std::string> GetModulesToImport(ExecutionContext &exe_ctx);
void CreateSourceCode(DiagnosticManager &diagnostic_manager,
ExecutionContext &exe_ctx,
std::vector<std::string> modules_to_import,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//===-- CppModuleConfiguration.cpp ----------------------------------------===//
//
// 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 "CppModuleConfiguration.h"

#include "ClangHost.h"
#include "lldb/Host/FileSystem.h"

using namespace lldb_private;

bool CppModuleConfiguration::SetOncePath::TrySet(llvm::StringRef path) {
// Setting for the first time always works.
if (m_first) {
m_path = path.str();
m_valid = true;
m_first = false;
return true;
}
// Changing the path to the same value is fine.
if (m_path == path)
return true;

// Changing the path after it was already set is not allowed.
m_valid = false;
return false;
}

bool CppModuleConfiguration::analyzeFile(const FileSpec &f) {
llvm::SmallString<256> dir_buffer = f.GetDirectory().GetStringRef();
// Convert to posix style so that we can use '/'.
llvm::sys::path::native(dir_buffer, llvm::sys::path::Style::posix);
llvm::StringRef posix_dir(dir_buffer);

// Check for /c++/vX/ that is used by libc++.
static llvm::Regex libcpp_regex(R"regex(/c[+][+]/v[0-9]/)regex");
if (libcpp_regex.match(f.GetPath())) {
// Strip away libc++'s /experimental directory if there is one.
posix_dir.consume_back("/experimental");
return m_std_inc.TrySet(posix_dir);
}

// Check for /usr/include. On Linux this might be /usr/include/bits, so
// we should remove that '/bits' suffix to get the actual include directory.
if (posix_dir.endswith("/usr/include/bits"))
posix_dir.consume_back("/bits");
if (posix_dir.endswith("/usr/include"))
return m_c_inc.TrySet(posix_dir);

// File wasn't interesting, continue analyzing.
return true;
}

bool CppModuleConfiguration::hasValidConfig() {
// We all these include directories to have a valid usable configuration.
return m_c_inc.Valid() && m_std_inc.Valid();
}

CppModuleConfiguration::CppModuleConfiguration(
const FileSpecList &support_files) {
// Analyze all files we were given to build the configuration.
bool error = !std::all_of(support_files.begin(), support_files.end(),
std::bind(&CppModuleConfiguration::analyzeFile,
this, std::placeholders::_1));
// If we have a valid configuration at this point, set the
// include directories and module list that should be used.
if (!error && hasValidConfig()) {
// Calculate the resource directory for LLDB.
llvm::SmallString<256> resource_dir;
llvm::sys::path::append(resource_dir, GetClangResourceDir().GetPath(),
"include");
m_resource_inc = resource_dir.str();

// This order matches the way Clang orders these directories.
m_include_dirs = {m_std_inc.Get(), m_resource_inc, m_c_inc.Get()};
m_imported_modules = {"std"};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//===-- CppModuleConfiguration.h --------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_CppModuleConfiguration_h_
#define liblldb_CppModuleConfiguration_h_

#include <lldb/Core/FileSpecList.h>
#include <llvm/Support/Regex.h>

namespace lldb_private {

/// A Clang configuration when importing C++ modules.
///
/// Includes a list of include paths that should be used when importing
/// and a list of modules that can be imported. Currently only used when
/// importing the 'std' module and its dependencies.
class CppModuleConfiguration {
/// Utility class for a path that can only be set once.
class SetOncePath {
std::string m_path;
bool m_valid = false;
/// True iff this path hasn't been set yet.
bool m_first = true;

public:
/// Try setting the path. Returns true if the path was set and false if
/// the path was already set.
LLVM_NODISCARD bool TrySet(llvm::StringRef path);
/// Return the path if there is one.
std::string Get() const {
assert(m_valid && "Called Get() on an invalid SetOncePath?");
return m_path;
}
/// Returns true iff this path was set exactly once so far.
bool Valid() const { return m_valid; }
};

/// If valid, the include path used for the std module.
SetOncePath m_std_inc;
/// If valid, the include path to the C library (e.g. /usr/include).
SetOncePath m_c_inc;
/// The Clang resource include path for this configuration.
std::string m_resource_inc;

std::vector<std::string> m_include_dirs;
std::vector<std::string> m_imported_modules;

/// Analyze a given source file to build the current configuration.
/// Returns false iff there was a fatal error that makes analyzing any
/// further files pointless as the configuration is now invalid.
bool analyzeFile(const FileSpec &f);

public:
/// Creates a configuraiton by analyzing the given list of used source files.
///
/// Currently only looks at the used paths and doesn't actually access the
/// files on the disk.
explicit CppModuleConfiguration(const FileSpecList &support_files);
/// Creates an empty and invalid configuration.
CppModuleConfiguration() {}

/// Returns true iff this is a valid configuration that can be used to
/// load and compile modules.
bool hasValidConfig();

/// Returns a list of include directories that should be used when using this
/// configuration (e.g. {"/usr/include", "/usr/include/c++/v1"}).
llvm::ArrayRef<std::string> GetIncludeDirs() const { return m_include_dirs; }

/// Returns a list of (top level) modules that should be imported when using
/// this configuration (e.g. {"std"}).
llvm::ArrayRef<std::string> GetImportedModules() const {
return m_imported_modules;
}
};

} // namespace lldb_private

#endif
19 changes: 0 additions & 19 deletions lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,25 +260,6 @@ bool PlatformLinux::CanDebugProcess() {
}
}

std::vector<std::string>
PlatformLinux::GetSystemIncludeDirectories(lldb::LanguageType lang) {
std::string sys_root = GetSDKRootDirectory().AsCString("");
switch (lang) {
case lldb::eLanguageTypeC:
case lldb::eLanguageTypeC89:
case lldb::eLanguageTypeC99:
case lldb::eLanguageTypeC11:
case lldb::eLanguageTypeC_plus_plus:
case lldb::eLanguageTypeC_plus_plus_03:
case lldb::eLanguageTypeC_plus_plus_11:
case lldb::eLanguageTypeC_plus_plus_14:
case lldb::eLanguageTypeObjC_plus_plus:
return {sys_root + "/usr/include/"};
default:
return {};
}
}

// For local debugging, Linux will override the debug logic to use llgs-launch
// rather than lldb-launch, llgs-attach. This differs from current lldb-
// launch, debugserver-attach approach on MacOSX.
Expand Down
3 changes: 0 additions & 3 deletions lldb/source/Plugins/Platform/Linux/PlatformLinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ class PlatformLinux : public PlatformPOSIX {

bool CanDebugProcess() override;

std::vector<std::string>
GetSystemIncludeDirectories(lldb::LanguageType lang) override;

lldb::ProcessSP DebugProcess(ProcessLaunchInfo &launch_info,
Debugger &debugger, Target *target,
Status &error) override;
Expand Down
12 changes: 12 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,18 @@ size_t SymbolFileDWARF::ParseFunctions(CompileUnit &comp_unit) {
return functions_added;
}

void SymbolFileDWARF::ForEachExternalModule(
CompileUnit &comp_unit, llvm::function_ref<void(ModuleSP)> f) {
UpdateExternalModuleListIfNeeded();

for (auto &p : m_external_type_modules) {
ModuleSP module = p.second;
f(module);
for (std::size_t i = 0; i < module->GetNumCompileUnits(); ++i)
module->GetCompileUnitAtIndex(i)->ForEachExternalModule(f);
}
}

bool SymbolFileDWARF::ParseSupportFiles(CompileUnit &comp_unit,
FileSpecList &support_files) {
if (!comp_unit.GetLineTable())
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ class SymbolFileDWARF : public lldb_private::SymbolFile,

bool ParseDebugMacros(lldb_private::CompileUnit &comp_unit) override;

void
ForEachExternalModule(lldb_private::CompileUnit &comp_unit,
llvm::function_ref<void(lldb::ModuleSP)> f) override;

bool ParseSupportFiles(lldb_private::CompileUnit &comp_unit,
lldb_private::FileSpecList &support_files) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,14 @@ bool SymbolFileDWARFDebugMap::ParseDebugMacros(CompileUnit &comp_unit) {
return false;
}

void SymbolFileDWARFDebugMap::ForEachExternalModule(
CompileUnit &comp_unit, llvm::function_ref<void(ModuleSP)> f) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
SymbolFileDWARF *oso_dwarf = GetSymbolFile(comp_unit);
if (oso_dwarf)
oso_dwarf->ForEachExternalModule(comp_unit, f);
}

bool SymbolFileDWARFDebugMap::ParseSupportFiles(CompileUnit &comp_unit,
FileSpecList &support_files) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFile {

bool ParseDebugMacros(lldb_private::CompileUnit &comp_unit) override;

void
ForEachExternalModule(lldb_private::CompileUnit &comp_unit,
llvm::function_ref<void(lldb::ModuleSP)> f) override;

bool ParseSupportFiles(lldb_private::CompileUnit &comp_unit,
lldb_private::FileSpecList &support_files) override;

Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Symbol/CompileUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,11 @@ const std::vector<SourceModule> &CompileUnit::GetImportedModules() {
return m_imported_modules;
}

void CompileUnit::ForEachExternalModule(llvm::function_ref<void(ModuleSP)> f) {
if (SymbolFile *symfile = GetModule()->GetSymbolFile())
symfile->ForEachExternalModule(*this, f);
}

const FileSpecList &CompileUnit::GetSupportFiles() {
if (m_support_files.GetSize() == 0) {
if (m_flags.IsClear(flagsParsedSupportFiles)) {
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Expression/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_lldb_unittest(ExpressionTests
ClangParserTest.cpp
DiagnosticManagerTest.cpp
DWARFExpressionTest.cpp
CppModuleConfigurationTest.cpp

LINK_LIBS
lldbCore
Expand Down
168 changes: 168 additions & 0 deletions lldb/unittests/Expression/CppModuleConfigurationTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//===-- CppModuleConfigurationTest.cpp ---------------------------*- C++-*-===//
//
// 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 "Plugins/ExpressionParser/Clang/CppModuleConfiguration.h"
#include "Plugins/ExpressionParser/Clang/ClangHost.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

using namespace lldb_private;

namespace {
struct CppModuleConfigurationTest : public testing::Test {
static void SetUpTestCase() {
// Getting the resource directory uses those subsystems, so we should
// initialize them.
FileSystem::Initialize();
HostInfo::Initialize();
}
static void TearDownTestCase() {
HostInfo::Terminate();
FileSystem::Terminate();
}
};
} // namespace

/// Returns the Clang resource include directory.
static std::string ResourceInc() {
llvm::SmallString<256> resource_dir;
llvm::sys::path::append(resource_dir, GetClangResourceDir().GetPath(),
"include");
return resource_dir.str().str();
}

/// Utility function turningn a list of paths into a FileSpecList.
static FileSpecList makeFiles(llvm::ArrayRef<std::string> paths) {
FileSpecList result;
for (const std::string &path : paths)
result.Append(FileSpec(path));
return result;
}

TEST_F(CppModuleConfigurationTest, Linux) {
// Test the average Linux configuration.
std::string libcpp = "/usr/include/c++/v1";
std::string usr = "/usr/include";
CppModuleConfiguration config(
makeFiles({usr + "/bits/types.h", libcpp + "/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre(libcpp, ResourceInc(), usr));
}

TEST_F(CppModuleConfigurationTest, Sysroot) {
// Test that having a sysroot for the whole system works fine.
std::string libcpp = "/home/user/sysroot/usr/include/c++/v1";
std::string usr = "/home/user/sysroot/usr/include";
CppModuleConfiguration config(
makeFiles({usr + "/bits/types.h", libcpp + "/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre(libcpp, ResourceInc(), usr));
}

TEST_F(CppModuleConfigurationTest, LinuxLocalLibCpp) {
// Test that a locally build libc++ is detected.
std::string libcpp = "/home/user/llvm-build/include/c++/v1";
std::string usr = "/usr/include";
CppModuleConfiguration config(
makeFiles({usr + "/bits/types.h", libcpp + "/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre(libcpp, ResourceInc(), usr));
}

TEST_F(CppModuleConfigurationTest, UnrelatedLibrary) {
// Test that having an unrelated library in /usr/include doesn't break.
std::string libcpp = "/home/user/llvm-build/include/c++/v1";
std::string usr = "/usr/include";
CppModuleConfiguration config(makeFiles(
{usr + "/bits/types.h", libcpp + "/vector", usr + "/boost/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre(libcpp, ResourceInc(), usr));
}

TEST_F(CppModuleConfigurationTest, Xcode) {
// Test detection of libc++ coming from Xcode with generic platform names.
std::string p = "/Applications/Xcode.app/Contents/Developer/";
std::string libcpp = p + "Toolchains/B.xctoolchain/usr/include/c++/v1";
std::string usr =
p + "Platforms/A.platform/Developer/SDKs/OSVers.sdk/usr/include";
CppModuleConfiguration config(
makeFiles({libcpp + "/unordered_map", usr + "/stdio.h"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre(libcpp, ResourceInc(), usr));
}

TEST_F(CppModuleConfigurationTest, LibCppV2) {
// Test that a "v2" of libc++ is still correctly detected.
CppModuleConfiguration config(
makeFiles({"/usr/include/bits/types.h", "/usr/include/c++/v2/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre("/usr/include/c++/v2", ResourceInc(),
"/usr/include"));
}

TEST_F(CppModuleConfigurationTest, UnknownLibCppFile) {
// Test that having some unknown file in the libc++ path doesn't break
// anything.
CppModuleConfiguration config(makeFiles(
{"/usr/include/bits/types.h", "/usr/include/c++/v1/non_existing_file"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
EXPECT_THAT(config.GetIncludeDirs(),
testing::ElementsAre("/usr/include/c++/v1", ResourceInc(),
"/usr/include"));
}

TEST_F(CppModuleConfigurationTest, MissingUsrInclude) {
// Test that we don't load 'std' if we can't find the C standard library.
CppModuleConfiguration config(makeFiles({"/usr/include/c++/v1/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
}

TEST_F(CppModuleConfigurationTest, MissingLibCpp) {
// Test that we don't load 'std' if we don't have a libc++.
CppModuleConfiguration config(makeFiles({"/usr/include/bits/types.h"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
}

TEST_F(CppModuleConfigurationTest, IgnoreLibStdCpp) {
// Test that we don't do anything bad when we encounter libstdc++ paths.
CppModuleConfiguration config(makeFiles(
{"/usr/include/bits/types.h", "/usr/include/c++/8.0.1/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
}

TEST_F(CppModuleConfigurationTest, AmbiguousCLib) {
// Test that we don't do anything when we are not sure where the
// right C standard library is.
CppModuleConfiguration config(
makeFiles({"/usr/include/bits/types.h", "/usr/include/c++/v1/vector",
"/sysroot/usr/include/bits/types.h"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
}

TEST_F(CppModuleConfigurationTest, AmbiguousLibCpp) {
// Test that we don't do anything when we are not sure where the
// right libc++ is.
CppModuleConfiguration config(
makeFiles({"/usr/include/bits/types.h", "/usr/include/c++/v1/vector",
"/usr/include/c++/v2/vector"}));
EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
}