Skip to content

Commit 6fcb857

Browse files
committed
[lldb][import-std-module] Prefer the non-module diagnostics when in fallback mode
The `fallback` setting for import-std-module is supposed to allow running expression that require an imported C++ module without causing any regressions for users (neither in terms of functionality nor performance). This is done by first trying to normally parse/evaluate an expression and when an error occurred during this first attempt, we retry with the loaded 'std' module. When we run into a system with a 'std' module that for some reason doesn't build or otherwise causes parse errors, then this currently means that the second parse attempt will overwrite the error diagnostics of the first parse attempt. Given that the module build errors are outside of the scope of what the user can influence, it makes more sense to show the errors from the first parse attempt that are only concerned with the actual user input. Reviewed By: aprantl Differential Revision: https://reviews.llvm.org/D110696
1 parent c788bea commit 6fcb857

File tree

9 files changed

+111
-14
lines changed

9 files changed

+111
-14
lines changed

lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,15 +685,22 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
685685
SetupCppModuleImports(exe_ctx);
686686
// If we did load any modules, then retry parsing.
687687
if (!m_imported_cpp_modules.empty()) {
688+
// Create a dedicated diagnostic manager for the second parse attempt.
689+
// These diagnostics are only returned to the caller if using the fallback
690+
// actually succeeded in getting the expression to parse. This prevents
691+
// that module-specific issues regress diagnostic quality with the
692+
// fallback mode.
693+
DiagnosticManager retry_manager;
688694
// The module imports are injected into the source code wrapper,
689695
// so recreate those.
690-
CreateSourceCode(diagnostic_manager, exe_ctx, m_imported_cpp_modules,
696+
CreateSourceCode(retry_manager, exe_ctx, m_imported_cpp_modules,
691697
/*for_completion*/ false);
692-
// Clear the error diagnostics from the previous parse attempt.
693-
diagnostic_manager.Clear();
694-
parse_success = TryParse(diagnostic_manager, exe_scope, exe_ctx,
698+
parse_success = TryParse(retry_manager, exe_scope, exe_ctx,
695699
execution_policy, keep_result_in_memory,
696700
generate_debug_info);
701+
// Return the parse diagnostics if we were successful.
702+
if (parse_success)
703+
diagnostic_manager = std::move(retry_manager);
697704
}
698705
}
699706
if (!parse_success)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# We don't have any standard include directories, so we can't
2+
# parse the test_common.h header we usually inject as it includes
3+
# system headers.
4+
NO_TEST_COMMON_H := 1
5+
6+
CXXFLAGS_EXTRAS = -I $(SRCDIR)/root/usr/include/c++/v1/ -I $(SRCDIR)/root/usr/include/ -nostdinc -nostdinc++ -DBUILDING_OUTSIDE_LLDB=1
7+
CXX_SOURCES := main.cpp
8+
9+
include Makefile.rules
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Tests that the import-std-module=fallback setting is only showing the error
3+
diagnostics from the first parse attempt which isn't using a module.
4+
This is supposed to prevent that a broken libc++ module renders failing
5+
expressions useless as the original failing errors are suppressed by the
6+
module build errors.
7+
"""
8+
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
from lldbsuite.test import lldbutil
12+
import os
13+
14+
15+
class TestCase(TestBase):
16+
17+
mydir = TestBase.compute_mydir(__file__)
18+
19+
# We only emulate a fake libc++ in this test and don't use the real libc++,
20+
# but we still add the libc++ category so that this test is only run in
21+
# test configurations where libc++ is actually supposed to be tested.
22+
@add_test_categories(["libc++"])
23+
@skipIfRemote
24+
@skipIf(compiler=no_match("clang"))
25+
def test(self):
26+
self.build()
27+
28+
sysroot = os.path.join(os.getcwd(), "root")
29+
30+
# Set the sysroot this test is using to provide a custom libc++.
31+
self.runCmd("platform select --sysroot '" + sysroot + "' host",
32+
CURRENT_EXECUTABLE_SET)
33+
34+
lldbutil.run_to_source_breakpoint(self,
35+
"// Set break point at this line.",
36+
lldb.SBFileSpec("main.cpp"))
37+
38+
# The expected error message when the fake libc++ module in this test
39+
# fails to build from within LLDB (as it contains invalid code).
40+
module_build_error_msg = "unknown type name 'random_token_to_fail_the_build'"
41+
42+
# First force the std module to be imported. This should show the
43+
# module build error to the user.
44+
self.runCmd("settings set target.import-std-module true")
45+
self.expect("expr (size_t)v.size()",
46+
substrs=[module_build_error_msg],
47+
error=True)
48+
49+
# In the fallback mode the module build error should not be shown.
50+
self.runCmd("settings set target.import-std-module fallback")
51+
fallback_expr = "expr v ; error_to_trigger_fallback_mode"
52+
# First check for the actual expression error that should be displayed
53+
# and is useful for the user.
54+
self.expect(fallback_expr,
55+
substrs=["use of undeclared identifier 'error_to_trigger_fallback_mode'"],
56+
error=True)
57+
# Test that the module build error is not displayed.
58+
self.expect(fallback_expr,
59+
substrs=[module_build_error_msg],
60+
matching=False,
61+
error=True)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <algorithm>
2+
3+
int main(int argc, char **argv) {
4+
// Makes sure we have the mock libc headers in the debug information.
5+
libc_struct s;
6+
std::vector<int> v;
7+
return 0; // Set break point at this line.
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This is only defined when building, but LLDB is missing this flag when loading the standard
2+
// library module so the actual contents of the module are missing.
3+
#ifdef BUILDING_OUTSIDE_LLDB
4+
5+
#include "stdio.h"
6+
7+
namespace std {
8+
inline namespace __1 {
9+
// Pretend to be a std::vector template we need to instantiate
10+
// in LLDB.
11+
template<typename T>
12+
struct vector { T i; int size() { return 2; } };
13+
}
14+
}
15+
#else
16+
// Break the build when parsing from within LLDB.
17+
random_token_to_fail_the_build
18+
#endif // BUILDING_OUTSIDE_LLDB
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module std {
2+
module "algorithm" { header "algorithm" export * }
3+
}

lldb/test/API/commands/expression/import-std-module/module-build-errors/root/usr/include/c++/v1/vector

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
struct libc_struct {};

lldb/test/API/commands/expression/import-std-module/retry-with-std-module/TestRetryWithStdModule.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,6 @@ def test(self):
4848
self.expect("expr --top-level -- int i = std::max(1, 2);", error=True,
4949
substrs=["no member named 'max' in namespace 'std'"])
5050

51-
# Check that diagnostics from the first parse attempt don't show up
52-
# in the C++ module parse attempt. In the expression below, we first
53-
# fail to parse 'std::max'. Then we retry with a loaded C++ module
54-
# and succeed to parse the 'std::max' part. However, the
55-
# trailing 'unknown_identifier' will fail to parse even with the
56-
# loaded module. The 'std::max' diagnostic from the first attempt
57-
# however should not be shown to the user.
58-
self.expect("expr std::max(1, 2); unknown_identifier", error=True,
59-
matching=False,
60-
substrs=["no member named 'max'"])
6151
# The proper diagnostic however should be shown on the retry.
6252
self.expect("expr std::max(1, 2); unknown_identifier", error=True,
6353
substrs=["use of undeclared identifier 'unknown_identifier'"])

0 commit comments

Comments
 (0)