DebugMacros *GetDebugMacros();
+ /// Apply a lambda to each external lldb::Module referenced by this
+ /// compilation unit. Recursively also descends into the referenced external
+ /// modules of any encountered compilation unit.
+ ///
+ /// \param[in] lambda
+ /// The lambda that should be applied to every module.
+ void ForEachExternalModule(llvm::function_ref<void(lldb::ModuleSP)> f);
+
/// Get the compile unit's support file list.
///
/// The support file list is used by the line table, and any objects that
virtual size_t ParseFunctions(CompileUnit &comp_unit) = 0;
virtual bool ParseLineTable(CompileUnit &comp_unit) = 0;
virtual bool ParseDebugMacros(CompileUnit &comp_unit) = 0;
+ virtual void
+ ForEachExternalModule(CompileUnit &comp_unit,
+ llvm::function_ref<void(lldb::ModuleSP)> f) {}
virtual bool ParseSupportFiles(CompileUnit &comp_unit,
FileSpecList &support_files) = 0;
virtual size_t ParseTypes(CompileUnit &comp_unit) = 0;
virtual bool SetRemoteWorkingDirectory(const FileSpec &working_dir);
- /// Retrieve the system include directories on this platform for the
- /// given language.
- ///
- /// \param[in] lang
- /// The language for which the include directories should be queried.
- ///
- /// \param[out] directories
- /// The include directories for this system.
- virtual std::vector<std::string>
- GetSystemIncludeDirectories(lldb::LanguageType lang) {
- return {};
- }
-
virtual UserIDResolver &GetUserIDResolver() = 0;
/// Locate a file for a platform.
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
self.expect("expr char char_a = 'b'; char char_b = 'a'; std::swap(char_a, char_b); char_a",
substrs=["(char) $3 = 'a'"])
- # 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_non_cpp_language(self):
self.build()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
# system headers.
NO_TEST_COMMON_H := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXXFLAGS += -I $(SRCDIR)/root/usr/include/c++/include/ -I $(SRCDIR)/root/usr/include/ -nostdinc -nostdinc++ -nostdlib++
CXX_SOURCES := main.cpp
include Makefile.rules
mydir = TestBase.compute_mydir(__file__)
- # FIXME: This should work on more setups, so remove these
- # skipIf's in the future.
@skipIf(compiler=no_match("clang"))
- @skipIf(oslist=no_match(["linux"]))
- @skipIf(debug_info=no_match(["dwarf"]))
def test(self):
self.build()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
USE_LIBCPP := 1
-CXXFLAGS += $(MANDATORY_CXXMODULE_BUILD_CFLAGS)
CXX_SOURCES := main.cpp
include Makefile.rules
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()
ClangPersistentVariables.cpp
ClangUserExpression.cpp
ClangUtilityFunction.cpp
+ CppModuleConfiguration.cpp
IRForTarget.cpp
IRDynamicChecks.cpp
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
#include "ClangExpressionParser.h"
#include "ClangModulesDeclVendor.h"
#include "ClangPersistentVariables.h"
+#include "CppModuleConfiguration.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
}
}
-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(
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;
}
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,
--- /dev/null
+//===-- 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"};
+ }
+}
--- /dev/null
+//===-- 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
}
}
-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.
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;
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())
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;
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());
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;
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)) {
ClangParserTest.cpp
DiagnosticManagerTest.cpp
DWARFExpressionTest.cpp
+ CppModuleConfigurationTest.cpp
LINK_LIBS
lldbCore
--- /dev/null
+//===-- 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());
+}