diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -65,6 +65,12 @@ eLoadDependentsNo, }; +enum ImportStdModule { + eImportStdModuleFalse, + eImportStdModuleFallback, + eImportStdModuleTrue, +}; + class TargetExperimentalProperties : public Properties { public: TargetExperimentalProperties(); @@ -135,7 +141,7 @@ bool GetEnableAutoImportClangModules() const; - bool GetEnableImportStdModule() const; + ImportStdModule GetImportStdModule() const; bool GetEnableAutoApplyFixIts() const; diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h @@ -168,12 +168,23 @@ lldb::ExpressionVariableSP GetResultAfterDematerialization(ExecutionContextScope *exe_scope) override; - bool DidImportCxxModules() const { return m_imported_cpp_modules; } + /// Returns true iff this expression is using any imported C++ modules. + bool DidImportCxxModules() const { return !m_imported_cpp_modules.empty(); } private: /// Populate m_in_cplusplus_method and m_in_objectivec_method based on the /// environment. + /// Contains the actual parsing implementation. + /// The parameter have the same meaning as in ClangUserExpression::Parse. + /// \see ClangUserExpression::Parse + bool TryParse(DiagnosticManager &diagnostic_manager, + ExecutionContextScope *exe_scope, ExecutionContext &exe_ctx, + lldb_private::ExecutionPolicy execution_policy, bool keep_result_in_memory, + bool generate_debug_info); + + void SetupCppModuleImports(ExecutionContext &exe_ctx); + void ScanContext(ExecutionContext &exe_ctx, lldb_private::Status &err) override; @@ -219,6 +230,8 @@ ResultDelegate m_result_delegate; ClangPersistentVariables *m_clang_state; std::unique_ptr m_source_code; + /// The parser instance we used to parse the expression. + std::unique_ptr m_parser; /// File name used for the expression. std::string m_filename; @@ -226,8 +239,9 @@ /// See the comment to `UserExpression::Evaluate` for details. ValueObject *m_ctx_obj; - /// True iff this expression explicitly imported C++ modules. - bool m_imported_cpp_modules = false; + /// A list of module names that should be imported when parsing. + /// \see CppModuleConfiguration::GetImportedModules + std::vector m_imported_cpp_modules; /// True if the expression parser should enforce the presence of a valid class /// pointer in order to generate the expression as a method. diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -417,7 +417,6 @@ DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx, std::vector modules_to_import, bool for_completion) { - m_filename = m_clang_state->GetNextExprFileName(); std::string prefix = m_expr_prefix; if (m_options.GetExecutionPolicy() == eExecutionPolicyTopLevel) { @@ -477,9 +476,6 @@ 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 LogConfigError("No frame"); @@ -529,8 +525,6 @@ bool ClangUserExpression::PrepareForParsing( DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx, bool for_completion) { - Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); - InstallContext(exe_ctx); if (!SetupPersistentState(diagnostic_manager, exe_ctx)) @@ -551,50 +545,20 @@ SetupDeclVendor(exe_ctx, m_target, diagnostic_manager); - CppModuleConfiguration module_config = GetModuleConfig(m_language, exe_ctx); - llvm::ArrayRef imported_modules = - module_config.GetImportedModules(); - m_imported_cpp_modules = !imported_modules.empty(); - m_include_directories = module_config.GetIncludeDirs(); + m_filename = m_clang_state->GetNextExprFileName(); - LLDB_LOG(log, "List of imported modules in expression: {0}", - 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())); + if (m_target->GetImportStdModule() == eImportStdModuleTrue) + SetupCppModuleImports(exe_ctx); - CreateSourceCode(diagnostic_manager, exe_ctx, imported_modules, + CreateSourceCode(diagnostic_manager, exe_ctx, m_imported_cpp_modules, for_completion); return true; } -bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager, - ExecutionContext &exe_ctx, - lldb_private::ExecutionPolicy execution_policy, - bool keep_result_in_memory, - bool generate_debug_info) { - Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); - - if (!PrepareForParsing(diagnostic_manager, exe_ctx, /*for_completion*/ false)) - return false; - - LLDB_LOGF(log, "Parsing the following code:\n%s", m_transformed_text.c_str()); - - //////////////////////////////////// - // Set up the target and compiler - // - - Target *target = exe_ctx.GetTargetPtr(); - - if (!target) { - diagnostic_manager.PutString(eDiagnosticSeverityError, "invalid target"); - return false; - } - - ////////////////////////// - // Parse the expression - // - +bool ClangUserExpression::TryParse( + DiagnosticManager &diagnostic_manager, ExecutionContextScope *exe_scope, + ExecutionContext &exe_ctx, lldb_private::ExecutionPolicy execution_policy, + bool keep_result_in_memory, bool generate_debug_info) { m_materializer_up = std::make_unique(); ResetDeclMap(exe_ctx, m_result_delegate, keep_result_in_memory); @@ -612,26 +576,16 @@ DeclMap()->SetLookupsEnabled(true); } - Process *process = exe_ctx.GetProcessPtr(); - ExecutionContextScope *exe_scope = process; - - if (!exe_scope) - exe_scope = exe_ctx.GetTargetPtr(); - - // We use a shared pointer here so we can use the original parser - if it - // succeeds or the rewrite parser we might make if it fails. But the - // parser_sp will never be empty. - - ClangExpressionParser parser(exe_scope, *this, generate_debug_info, - m_include_directories, m_filename); + m_parser = std::make_unique( + exe_scope, *this, generate_debug_info, m_include_directories, m_filename); - unsigned num_errors = parser.Parse(diagnostic_manager); + unsigned num_errors = m_parser->Parse(diagnostic_manager); // Check here for FixItHints. If there are any try to apply the fixits and // set the fixed text in m_fixed_text before returning an error. if (num_errors) { if (diagnostic_manager.HasFixIts()) { - if (parser.RewriteExpression(diagnostic_manager)) { + if (m_parser->RewriteExpression(diagnostic_manager)) { size_t fixed_start; size_t fixed_end; m_fixed_text = diagnostic_manager.GetFixedExpression(); @@ -652,7 +606,7 @@ // { - Status jit_error = parser.PrepareForExecution( + Status jit_error = m_parser->PrepareForExecution( m_jit_start_addr, m_jit_end_addr, m_execution_unit_sp, exe_ctx, m_can_interpret, execution_policy); @@ -666,10 +620,91 @@ return false; } } + return true; +} + +void ClangUserExpression::SetupCppModuleImports(ExecutionContext &exe_ctx) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); + + CppModuleConfiguration module_config = GetModuleConfig(m_language, exe_ctx); + m_imported_cpp_modules = module_config.GetImportedModules(); + m_include_directories = module_config.GetIncludeDirs(); + + LLDB_LOG(log, "List of imported modules in expression: {0}", + llvm::make_range(m_imported_cpp_modules.begin(), + m_imported_cpp_modules.end())); + LLDB_LOG(log, "List of include directories gathered for modules: {0}", + llvm::make_range(m_include_directories.begin(), + m_include_directories.end())); +} + +static bool shouldRetryWithCppModule(Target &target, ExecutionPolicy exe_policy) { + // Top-level expression don't yet support importing C++ modules. + if (exe_policy == ExecutionPolicy::eExecutionPolicyTopLevel) + return false; + return target.GetImportStdModule() == eImportStdModuleFallback; +} + +bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager, + ExecutionContext &exe_ctx, + lldb_private::ExecutionPolicy execution_policy, + bool keep_result_in_memory, + bool generate_debug_info) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); + + if (!PrepareForParsing(diagnostic_manager, exe_ctx, /*for_completion*/ false)) + return false; + + LLDB_LOGF(log, "Parsing the following code:\n%s", m_transformed_text.c_str()); + + //////////////////////////////////// + // Set up the target and compiler + // + + Target *target = exe_ctx.GetTargetPtr(); + + if (!target) { + diagnostic_manager.PutString(eDiagnosticSeverityError, "invalid target"); + return false; + } + + ////////////////////////// + // Parse the expression + // + + Process *process = exe_ctx.GetProcessPtr(); + ExecutionContextScope *exe_scope = process; + + if (!exe_scope) + exe_scope = exe_ctx.GetTargetPtr(); + + bool parse_success = TryParse(diagnostic_manager, exe_scope, exe_ctx, + execution_policy, keep_result_in_memory, + generate_debug_info); + // If the expression failed to parse, check if retrying parsing with a loaded + // C++ module is possible. + if (!parse_success && shouldRetryWithCppModule(*target, execution_policy)) { + // Load the loaded C++ modules. + SetupCppModuleImports(exe_ctx); + // If we did load any modules, then retry parsing. + if (!m_imported_cpp_modules.empty()) { + // The module imports are injected into the source code wrapper, + // so recreate those. + CreateSourceCode(diagnostic_manager, exe_ctx, m_imported_cpp_modules, + /*for_completion*/ false); + // Clear the error diagnostics from the previous parse attempt. + diagnostic_manager.Clear(); + parse_success = TryParse(diagnostic_manager, exe_scope, exe_ctx, + execution_policy, keep_result_in_memory, + generate_debug_info); + } + } + if (!parse_success) + return false; if (exe_ctx.GetProcessPtr() && execution_policy == eExecutionPolicyTopLevel) { Status static_init_error = - parser.RunStaticInitializers(m_execution_unit_sp, exe_ctx); + m_parser->RunStaticInitializers(m_execution_unit_sp, exe_ctx); if (!static_init_error.Success()) { const char *error_cstr = static_init_error.AsCString(); diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3516,6 +3516,28 @@ }, }; +static constexpr OptionEnumValueElement g_import_std_module_value_types[] = { + { + eImportStdModuleFalse, + "false", + "Never import the 'std' C++ module in the expression parser.", + }, + { + eImportStdModuleFallback, + "fallback", + "Retry evaluating expressions with an imported 'std' C++ module if they" + " failed to parse without the module. This allows evaluating more " + "complex expressions involving C++ standard library types." + }, + { + eImportStdModuleTrue, + "true", + "Always import the 'std' C++ module. This allows evaluating more " + "complex expressions involving C++ standard library types. This feature" + " is experimental." + }, +}; + static constexpr OptionEnumValueElement g_hex_immediate_style_values[] = { { Disassembler::eHexStyleC, @@ -3969,10 +3991,10 @@ nullptr, idx, g_target_properties[idx].default_uint_value != 0); } -bool TargetProperties::GetEnableImportStdModule() const { +ImportStdModule TargetProperties::GetImportStdModule() const { const uint32_t idx = ePropertyImportStdModule; - return m_collection_sp->GetPropertyAtIndexAsBoolean( - nullptr, idx, g_target_properties[idx].default_uint_value != 0); + return (ImportStdModule)m_collection_sp->GetPropertyAtIndexAsEnumeration( + nullptr, idx, g_target_properties[idx].default_uint_value); } bool TargetProperties::GetEnableAutoApplyFixIts() const { diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td --- a/lldb/source/Target/TargetProperties.td +++ b/lldb/source/Target/TargetProperties.td @@ -49,9 +49,11 @@ def AutoImportClangModules: Property<"auto-import-clang-modules", "Boolean">, DefaultTrue, Desc<"Automatically load Clang modules referred to by the program.">; - def ImportStdModule: Property<"import-std-module", "Boolean">, - DefaultFalse, - Desc<"Import the C++ std module to improve debugging STL containers.">; + def ImportStdModule: Property<"import-std-module", "Enum">, + DefaultEnumValue<"eImportStdModuleFalse">, + EnumValues<"OptionEnumValues(g_import_std_module_value_types)">, + Desc<"Import the 'std' C++ module to improve expression parsing involving " + " C++ standard library types.">; def AutoApplyFixIts: Property<"auto-apply-fixits", "Boolean">, DefaultTrue, Desc<"Automatically apply fix-it hints to expressions.">; diff --git a/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/Makefile b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/Makefile @@ -0,0 +1,3 @@ +USE_LIBCPP := 1 +CXX_SOURCES := main.cpp +include Makefile.rules diff --git a/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py @@ -0,0 +1,76 @@ +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @add_test_categories(["libc++"]) + @skipIf(compiler=no_match("clang")) + def test(self): + self.build() + + lldbutil.run_to_source_breakpoint(self, + "// Set break point at this line.", + lldb.SBFileSpec("main.cpp")) + + # Test printing the vector before enabling any C++ module setting. + self.expect_expr("a", result_type="std::vector >") + + # Set loading the import-std-module to 'fallback' which loads the module + # and retries when an expression fails to parse. + self.runCmd("settings set target.import-std-module fallback") + + # Printing the vector still works. This should return the same type + # as before as this shouldn't use a C++ module type (the C++ module type + # is hiding the second template parameter as it's equal to the default + # argument which the C++ module has type info for). + self.expect_expr("a", result_type="std::vector >") + + # This expression can only parse with a C++ module. LLDB should + # automatically fall back to import the C++ module to get this working. + self.expect_expr("std::max(0U, a.size())", result_value="3") + + + # The 'a' and 'local' part can be parsed without loading a C++ module and will + # load type/runtime information. The 'std::max...' part will fail to + # parse without a C++ module. Make sure we reset all the relevant parts of + # the C++ parser so that we don't end up with for example a second + # definition of 'local' when retrying. + self.expect_expr("a; local; std::max(0U, a.size())", result_value="3") + + + # Try to declare top-level declarations that require a C++ module to parse. + # Top-level expressions don't support importing the C++ module (yet), so + # this should still fail as before. + self.expect("expr --top-level -- int i = std::max(1, 2);", error=True, + substrs=["no member named 'max' in namespace 'std'"]) + + # Check that diagnostics from the first parse attempt don't show up + # in the C++ module parse attempt. In the expression below, we first + # fail to parse 'std::max'. Then we retry with a loaded C++ module + # and succeed to parse the 'std::max' part. However, the + # trailing 'unknown_identifier' will fail to parse even with the + # loaded module. The 'std::max' diagnostic from the first attempt + # however should not be shown to the user. + self.expect("expr std::max(1, 2); unknown_identifier", error=True, + matching=False, + substrs=["no member named 'max'"]) + # The proper diagnostic however should be shown on the retry. + self.expect("expr std::max(1, 2); unknown_identifier", error=True, + substrs=["use of undeclared identifier 'unknown_identifier'"]) + + # Turn on the 'import-std-module' setting and make sure we import the + # C++ module. + self.runCmd("settings set target.import-std-module true") + # This is still expected to work. + self.expect_expr("std::max(0U, a.size())", result_value="3") + + # Turn of the 'import-std-module' setting and make sure we don't load + # the module (which should prevent parsing the expression involving + # 'std::max'). + self.runCmd("settings set target.import-std-module false") + self.expect("expr std::max(1, 2);", error=True, + substrs=["no member named 'max' in namespace 'std'"]) diff --git a/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/main.cpp b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/expression/import-std-module/retry-with-std-module/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char **argv) { + std::vector a = {3, 1, 2}; + int local = 3; + return 0; // Set break point at this line. +}