Index: lldb/include/lldb/Target/Target.h =================================================================== --- lldb/include/lldb/Target/Target.h +++ lldb/include/lldb/Target/Target.h @@ -137,6 +137,8 @@ bool GetEnableImportStdModule() const; + bool GetRetryWithStdModule() const; + bool GetEnableAutoApplyFixIts() const; uint64_t GetNumberOfRetriesWithFixits() const; Index: lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h +++ 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. Index: lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ 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->GetEnableImportStdModule()) + 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. + m_parser = std::make_unique( + exe_scope, *this, generate_debug_info, m_include_directories, m_filename); - ClangExpressionParser parser(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,92 @@ 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) { + if (target.GetEnableImportStdModule()) + return false; + if (exe_policy == ExecutionPolicy::eExecutionPolicyTopLevel) + return false; + return target.GetRetryWithStdModule(); +} + +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(); Index: lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.h =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.h +++ lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.h @@ -74,7 +74,7 @@ /// Returns a list of (top level) modules that should be imported when using /// this configuration (e.g. {"std"}). - llvm::ArrayRef GetImportedModules() const { + std::vector GetImportedModules() const { return m_imported_modules; } }; Index: lldb/source/Target/Target.cpp =================================================================== --- lldb/source/Target/Target.cpp +++ lldb/source/Target/Target.cpp @@ -3975,6 +3975,12 @@ nullptr, idx, g_target_properties[idx].default_uint_value != 0); } +bool TargetProperties::GetRetryWithStdModule() const { + const uint32_t idx = ePropertyRetryWithStdModule; + return m_collection_sp->GetPropertyAtIndexAsBoolean( + nullptr, idx, g_target_properties[idx].default_uint_value != 0); +} + bool TargetProperties::GetEnableAutoApplyFixIts() const { const uint32_t idx = ePropertyAutoApplyFixIts; return m_collection_sp->GetPropertyAtIndexAsBoolean( Index: lldb/source/Target/TargetProperties.td =================================================================== --- lldb/source/Target/TargetProperties.td +++ lldb/source/Target/TargetProperties.td @@ -52,6 +52,9 @@ def ImportStdModule: Property<"import-std-module", "Boolean">, DefaultFalse, Desc<"Import the C++ std module to improve debugging STL containers.">; + def RetryWithStdModule: Property<"retry-with-std-module", "Boolean">, + DefaultFalse, + Desc<"When an expression fails to parse, import the C++ std module and retry parsing the expression">; def AutoApplyFixIts: Property<"auto-apply-fixits", "Boolean">, DefaultTrue, Desc<"Automatically apply fix-it hints to expressions.">; Index: lldb/test/API/commands/expression/import-std-module/retry-with-std-module/Makefile =================================================================== --- /dev/null +++ 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 Index: lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py =================================================================== --- /dev/null +++ lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py @@ -0,0 +1,67 @@ +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 >") + + self.runCmd("settings set target.retry-with-std-module true") + + # 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") Index: lldb/test/API/commands/expression/import-std-module/retry-with-std-module/main.cpp =================================================================== --- /dev/null +++ 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. +}