[Lldb-commits] [lldb] [lldb] Inline expression evaluator error visualization (PR #106470)
Adrian Prantl via lldb-commits
lldb-commits at lists.llvm.org
Thu Sep 19 13:53:22 PDT 2024
https://github.com/adrian-prantl updated https://github.com/llvm/llvm-project/pull/106470
>From 8c682fc70bff5c6dff43c4601c14e5e61d11a08f Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Wed, 28 Aug 2024 10:04:33 -0700
Subject: [PATCH 1/2] [lldb] Store expression evaluator diagnostics in an
llvm::Error (NFC)
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal
for better expression evaluator error messages:
https://github.com/llvm/llvm-project/pull/80938
This patch is preparatory patch for improving the rendering of
expression evaluator diagnostics. Currently diagnostics are rendered
into a string and the command interpreter layer then textually parses
words like "error:" to (sometimes) color the output accordingly. In
order to enable user interfaces to do better with diagnostics, we need
to store them in a machine-readable fromat. This patch does this by
adding a new llvm::Error kind wrapping a DiagnosticDetail struct that
is used when the error type is eErrorTypeExpression. Multiple
diagnostics are modeled using llvm::ErrorList.
Right now the extra information is not used by the CommandInterpreter,
this will be added in a follow-up patch!
---
.../lldb/Expression/DiagnosticManager.h | 91 +++++++++++++-----
lldb/include/lldb/Utility/Status.h | 5 +-
lldb/source/Breakpoint/BreakpointLocation.cpp | 10 +-
lldb/source/Expression/DiagnosticManager.cpp | 94 +++++++++++++++----
lldb/source/Expression/ExpressionParser.cpp | 5 +-
lldb/source/Expression/UserExpression.cpp | 32 ++++---
lldb/source/Expression/UtilityFunction.cpp | 16 ++--
.../ExpressionParser/Clang/ClangDiagnostic.h | 5 +-
.../Clang/ClangExpressionParser.cpp | 69 ++++++++++----
.../Clang/ClangUserExpression.h | 2 +
.../Plugins/Platform/POSIX/PlatformPOSIX.cpp | 10 +-
.../Platform/Windows/PlatformWindows.cpp | 10 +-
lldb/source/Target/Target.cpp | 2 +-
lldb/source/Utility/Status.cpp | 8 ++
.../TestModulesCompileError.py | 2 +-
.../Expression/DiagnosticManagerTest.cpp | 22 +++--
lldb/unittests/Utility/StatusTest.cpp | 2 +-
17 files changed, 267 insertions(+), 118 deletions(-)
diff --git a/lldb/include/lldb/Expression/DiagnosticManager.h b/lldb/include/lldb/Expression/DiagnosticManager.h
index d49b7c99b114fb..1c0763eea92f05 100644
--- a/lldb/include/lldb/Expression/DiagnosticManager.h
+++ b/lldb/include/lldb/Expression/DiagnosticManager.h
@@ -12,6 +12,9 @@
#include "lldb/lldb-defines.h"
#include "lldb/lldb-types.h"
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/Status.h"
+
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
@@ -20,6 +23,52 @@
namespace lldb_private {
+/// A compiler-independent representation of a Diagnostic. Expression
+/// evaluation failures often have more than one diagnostic that a UI
+/// layer might want to render differently, for example to colorize
+/// it.
+///
+/// Running example:
+/// (lldb) expr 1+foo
+/// error: <user expression 0>:1:3: use of undeclared identifier 'foo'
+/// 1+foo
+/// ^
+struct DiagnosticDetail {
+ struct SourceLocation {
+ FileSpec file;
+ unsigned line = 0;
+ uint16_t column = 0;
+ uint16_t length = 0;
+ bool in_user_input = false;
+ };
+ /// Contains {{}, 1, 3, 3, true} in the example above.
+ std::optional<SourceLocation> source_location;
+ /// Contains eSeverityError in the example above.
+ lldb::Severity severity = lldb::eSeverityInfo;
+ /// Contains "use of undeclared identifier 'x'" in the example above.
+ std::string message;
+ /// Contains the fully rendered error message.
+ std::string rendered;
+};
+
+/// An llvm::Error used to communicate diagnostics in Status. Multiple
+/// diagnostics may be chained in an llvm::ErrorList.
+class DetailedExpressionError
+ : public llvm::ErrorInfo<DetailedExpressionError, CloneableError> {
+ DiagnosticDetail m_detail;
+
+public:
+ using llvm::ErrorInfo<DetailedExpressionError, CloneableError>::ErrorInfo;
+ DetailedExpressionError(DiagnosticDetail detail) : m_detail(detail) {}
+ std::string message() const override;
+ DiagnosticDetail GetDetail() const { return m_detail; }
+ std::error_code convertToErrorCode() const override;
+ void log(llvm::raw_ostream &OS) const override;
+ std::unique_ptr<CloneableError> Clone() const override;
+
+ static char ID;
+};
+
enum DiagnosticOrigin {
eDiagnosticOriginUnknown = 0,
eDiagnosticOriginLLDB,
@@ -49,37 +98,28 @@ class Diagnostic {
}
}
- Diagnostic(llvm::StringRef message, lldb::Severity severity,
- DiagnosticOrigin origin, uint32_t compiler_id)
- : m_message(message), m_severity(severity), m_origin(origin),
- m_compiler_id(compiler_id) {}
-
- Diagnostic(const Diagnostic &rhs)
- : m_message(rhs.m_message), m_severity(rhs.m_severity),
- m_origin(rhs.m_origin), m_compiler_id(rhs.m_compiler_id) {}
+ Diagnostic(DiagnosticOrigin origin, uint32_t compiler_id,
+ DiagnosticDetail detail)
+ : m_origin(origin), m_compiler_id(compiler_id), m_detail(detail) {}
virtual ~Diagnostic() = default;
virtual bool HasFixIts() const { return false; }
- lldb::Severity GetSeverity() const { return m_severity; }
+ lldb::Severity GetSeverity() const { return m_detail.severity; }
uint32_t GetCompilerID() const { return m_compiler_id; }
- llvm::StringRef GetMessage() const { return m_message; }
+ llvm::StringRef GetMessage() const { return m_detail.message; }
+ llvm::Error GetAsError() const;
- void AppendMessage(llvm::StringRef message,
- bool precede_with_newline = true) {
- if (precede_with_newline)
- m_message.push_back('\n');
- m_message += message;
- }
+ void AppendMessage(llvm::StringRef message, bool precede_with_newline = true);
protected:
- std::string m_message;
- lldb::Severity m_severity;
DiagnosticOrigin m_origin;
- uint32_t m_compiler_id; // Compiler-specific diagnostic ID
+ /// Compiler-specific diagnostic ID.
+ uint32_t m_compiler_id;
+ DiagnosticDetail m_detail;
};
typedef std::vector<std::unique_ptr<Diagnostic>> DiagnosticList;
@@ -102,10 +142,7 @@ class DiagnosticManager {
void AddDiagnostic(llvm::StringRef message, lldb::Severity severity,
DiagnosticOrigin origin,
- uint32_t compiler_id = LLDB_INVALID_COMPILER_ID) {
- m_diagnostics.emplace_back(
- std::make_unique<Diagnostic>(message, severity, origin, compiler_id));
- }
+ uint32_t compiler_id = LLDB_INVALID_COMPILER_ID);
void AddDiagnostic(std::unique_ptr<Diagnostic> diagnostic) {
if (diagnostic)
@@ -130,6 +167,14 @@ class DiagnosticManager {
m_diagnostics.back()->AppendMessage(str);
}
+ /// Copies the diagnostics into an llvm::Error{List}.
+ /// The first diagnostic wraps \c result.
+ llvm::Error GetAsError(lldb::ExpressionResults result) const;
+
+ /// Copies the diagnostics into an llvm::Error, the first diagnostic
+ /// being an llvm::StringError.
+ llvm::Error GetAsError(llvm::Twine msg) const;
+
// Returns a string containing errors in this format:
//
// "error: error text\n
diff --git a/lldb/include/lldb/Utility/Status.h b/lldb/include/lldb/Utility/Status.h
index 084ce4afb8cefd..cac0fad4c3b309 100644
--- a/lldb/include/lldb/Utility/Status.h
+++ b/lldb/include/lldb/Utility/Status.h
@@ -175,8 +175,11 @@ class Status {
/// Avoid using this in new code. Migrate APIs to llvm::Expected instead.
static Status FromError(llvm::Error error);
- /// FIXME: Replace this with a takeError() method.
+ /// FIXME: Replace all uses with takeError() instead.
llvm::Error ToError() const;
+
+ llvm::Error takeError() { return std::move(m_error); }
+
/// Don't call this function in new code. Instead, redesign the API
/// to use llvm::Expected instead of Status.
Status Clone() const { return Status(ToError()); }
diff --git a/lldb/source/Breakpoint/BreakpointLocation.cpp b/lldb/source/Breakpoint/BreakpointLocation.cpp
index 8d7364052a006a..fd9248eb0677c5 100644
--- a/lldb/source/Breakpoint/BreakpointLocation.cpp
+++ b/lldb/source/Breakpoint/BreakpointLocation.cpp
@@ -264,9 +264,9 @@ bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx,
if (!m_user_expression_sp->Parse(diagnostics, exe_ctx,
eExecutionPolicyOnlyWhenNeeded, true,
false)) {
- error = Status::FromErrorStringWithFormat(
- "Couldn't parse conditional expression:\n%s",
- diagnostics.GetString().c_str());
+ error = Status::FromError(
+ diagnostics.GetAsError("Couldn't parse conditional expression:"));
+
m_user_expression_sp.reset();
return true;
}
@@ -324,8 +324,8 @@ bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx,
}
} else {
ret = false;
- error = Status::FromErrorStringWithFormat(
- "Couldn't execute expression:\n%s", diagnostics.GetString().c_str());
+ error = Status::FromError(
+ diagnostics.GetAsError("Couldn't execute expression:"));
}
return ret;
diff --git a/lldb/source/Expression/DiagnosticManager.cpp b/lldb/source/Expression/DiagnosticManager.cpp
index a8330138f3d53b..99533aefb685b1 100644
--- a/lldb/source/Expression/DiagnosticManager.cpp
+++ b/lldb/source/Expression/DiagnosticManager.cpp
@@ -14,22 +14,7 @@
#include "lldb/Utility/StreamString.h"
using namespace lldb_private;
-
-void DiagnosticManager::Dump(Log *log) {
- if (!log)
- return;
-
- std::string str = GetString();
-
- // GetString() puts a separator after each diagnostic. We want to remove the
- // last '\n' because log->PutCString will add one for us.
-
- if (str.size() && str.back() == '\n') {
- str.pop_back();
- }
-
- log->PutCString(str.c_str());
-}
+char DetailedExpressionError::ID;
static const char *StringForSeverity(lldb::Severity severity) {
switch (severity) {
@@ -44,9 +29,28 @@ static const char *StringForSeverity(lldb::Severity severity) {
llvm_unreachable("switch needs another case for lldb::Severity enum");
}
+std::string DetailedExpressionError::message() const {
+ std::string str;
+ llvm::raw_string_ostream(str)
+ << StringForSeverity(m_detail.severity) << m_detail.rendered;
+ return str;
+}
+
+std::error_code DetailedExpressionError::convertToErrorCode() const {
+ return llvm::inconvertibleErrorCode();
+}
+
+void DetailedExpressionError::log(llvm::raw_ostream &OS) const {
+ OS << message();
+}
+
+std::unique_ptr<CloneableError> DetailedExpressionError::Clone() const {
+ return std::make_unique<DetailedExpressionError>(m_detail);
+}
+
std::string DiagnosticManager::GetString(char separator) {
- std::string ret;
- llvm::raw_string_ostream stream(ret);
+ std::string str;
+ llvm::raw_string_ostream stream(str);
for (const auto &diagnostic : Diagnostics()) {
llvm::StringRef severity = StringForSeverity(diagnostic->GetSeverity());
@@ -61,8 +65,50 @@ std::string DiagnosticManager::GetString(char separator) {
stream << message.drop_front(severity_pos + severity.size());
stream << separator;
}
+ return str;
+}
- return ret;
+void DiagnosticManager::Dump(Log *log) {
+ if (!log)
+ return;
+
+ std::string str = GetString();
+
+ // We want to remove the last '\n' because log->PutCString will add
+ // one for us.
+
+ if (str.size() && str.back() == '\n')
+ str.pop_back();
+
+ log->PutString(str);
+}
+
+llvm::Error Diagnostic::GetAsError() const {
+ return llvm::make_error<DetailedExpressionError>(m_detail);
+}
+
+llvm::Error
+DiagnosticManager::GetAsError(lldb::ExpressionResults result) const {
+ llvm::Error diags = Status::FromExpressionError(result, "").takeError();
+ for (const auto &diagnostic : m_diagnostics)
+ diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
+ return diags;
+}
+
+llvm::Error DiagnosticManager::GetAsError(llvm::Twine msg) const {
+ llvm::Error diags = llvm::createStringError(msg);
+ for (const auto &diagnostic : m_diagnostics)
+ diags = llvm::joinErrors(std::move(diags), diagnostic->GetAsError());
+ return diags;
+}
+
+void DiagnosticManager::AddDiagnostic(llvm::StringRef message,
+ lldb::Severity severity,
+ DiagnosticOrigin origin,
+ uint32_t compiler_id) {
+ m_diagnostics.emplace_back(std::make_unique<Diagnostic>(
+ origin, compiler_id,
+ DiagnosticDetail{{}, severity, message.str(), message.str()}));
}
size_t DiagnosticManager::Printf(lldb::Severity severity, const char *format,
@@ -85,3 +131,13 @@ void DiagnosticManager::PutString(lldb::Severity severity,
return;
AddDiagnostic(str, severity, eDiagnosticOriginLLDB);
}
+
+void Diagnostic::AppendMessage(llvm::StringRef message,
+ bool precede_with_newline) {
+ if (precede_with_newline) {
+ m_detail.message.push_back('\n');
+ m_detail.rendered.push_back('\n');
+ }
+ m_detail.message += message;
+ m_detail.rendered += message;
+}
diff --git a/lldb/source/Expression/ExpressionParser.cpp b/lldb/source/Expression/ExpressionParser.cpp
index 868556c1c58a57..26bc19874c53cc 100644
--- a/lldb/source/Expression/ExpressionParser.cpp
+++ b/lldb/source/Expression/ExpressionParser.cpp
@@ -63,9 +63,8 @@ ExpressionParser::RunStaticInitializers(IRExecutionUnitSP &execution_unit_sp,
exe_ctx, call_static_initializer, options, execution_errors);
if (results != eExpressionCompleted) {
- err = Status::FromErrorStringWithFormat(
- "couldn't run static initializer: %s",
- execution_errors.GetString().c_str());
+ err = Status::FromError(
+ execution_errors.GetAsError("couldn't run static initializer:"));
return err;
}
}
diff --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp
index 872f6304f91baa..55ba857db88db6 100644
--- a/lldb/source/Expression/UserExpression.cpp
+++ b/lldb/source/Expression/UserExpression.cpp
@@ -328,18 +328,20 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
}
if (!parse_success) {
- std::string msg;
- {
- llvm::raw_string_ostream os(msg);
- if (!diagnostic_manager.Diagnostics().empty())
- os << diagnostic_manager.GetString();
- else
- os << "expression failed to parse (no further compiler diagnostics)";
- if (target->GetEnableNotifyAboutFixIts() && fixed_expression &&
- !fixed_expression->empty())
- os << "\nfixed expression suggested:\n " << *fixed_expression;
+ if (target->GetEnableNotifyAboutFixIts() && fixed_expression &&
+ !fixed_expression->empty()) {
+ std::string fixit =
+ "fixed expression suggested:\n " + *fixed_expression;
+ diagnostic_manager.AddDiagnostic(fixit, lldb::eSeverityInfo,
+ eDiagnosticOriginLLDB);
}
- error = Status::FromExpressionError(execution_results, msg);
+ if (diagnostic_manager.Diagnostics().empty())
+ error = Status::FromExpressionError(
+ execution_results,
+ "expression failed to parse (no further compiler diagnostics)");
+ else
+ error =
+ Status::FromError(diagnostic_manager.GetAsError(execution_results));
}
}
@@ -351,7 +353,7 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
LLDB_LOG(log, "== [UserExpression::Evaluate] Expression may not run, but "
"is not constant ==");
- if (!diagnostic_manager.Diagnostics().size())
+ if (diagnostic_manager.Diagnostics().empty())
error = Status::FromExpressionError(
lldb::eExpressionSetupError,
"expression needed to run but couldn't");
@@ -380,12 +382,12 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx,
LLDB_LOG(log, "== [UserExpression::Evaluate] Execution completed "
"abnormally ==");
- if (!diagnostic_manager.Diagnostics().size())
+ if (diagnostic_manager.Diagnostics().empty())
error = Status::FromExpressionError(
execution_results, "expression failed to execute, unknown error");
else
- error = Status::FromExpressionError(execution_results,
- diagnostic_manager.GetString());
+ error = Status::FromError(
+ diagnostic_manager.GetAsError(execution_results));
} else {
if (expr_result) {
result_valobj_sp = expr_result->GetValueObject();
diff --git a/lldb/source/Expression/UtilityFunction.cpp b/lldb/source/Expression/UtilityFunction.cpp
index 55ebfb8ef342e8..e35c0774815298 100644
--- a/lldb/source/Expression/UtilityFunction.cpp
+++ b/lldb/source/Expression/UtilityFunction.cpp
@@ -83,19 +83,18 @@ FunctionCaller *UtilityFunction::MakeFunctionCaller(
m_caller_up.reset(process_sp->GetTarget().GetFunctionCallerForLanguage(
Language().AsLanguageType(), return_type, impl_code_address,
arg_value_list, name.c_str(), error));
- if (error.Fail()) {
-
+ if (error.Fail())
return nullptr;
- }
+
if (m_caller_up) {
DiagnosticManager diagnostics;
unsigned num_errors =
m_caller_up->CompileFunction(thread_to_use_sp, diagnostics);
if (num_errors) {
- error = Status::FromErrorStringWithFormat(
- "Error compiling %s caller function: \"%s\".",
- m_function_name.c_str(), diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "Error compiling " + m_function_name + " caller function:"));
+
m_caller_up.reset();
return nullptr;
}
@@ -104,9 +103,8 @@ FunctionCaller *UtilityFunction::MakeFunctionCaller(
ExecutionContext exe_ctx(process_sp);
if (!m_caller_up->WriteFunctionWrapper(exe_ctx, diagnostics)) {
- error = Status::FromErrorStringWithFormat(
- "Error inserting caller function for %s: \"%s\".",
- m_function_name.c_str(), diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "Error inserting " + m_function_name + " caller function:"));
m_caller_up.reset();
return nullptr;
}
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangDiagnostic.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangDiagnostic.h
index 21abd71cc34eeb..c473df808ee8d0 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangDiagnostic.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangDiagnostic.h
@@ -29,9 +29,8 @@ class ClangDiagnostic : public Diagnostic {
return diag->getKind() == eDiagnosticOriginClang;
}
- ClangDiagnostic(llvm::StringRef message, lldb::Severity severity,
- uint32_t compiler_id)
- : Diagnostic(message, severity, eDiagnosticOriginClang, compiler_id) {}
+ ClangDiagnostic(DiagnosticDetail detail, uint32_t compiler_id)
+ : Diagnostic(eDiagnosticOriginClang, compiler_id, detail) {}
~ClangDiagnostic() override = default;
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 2fe3c0460aa7f8..92bd554767dae2 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -26,6 +26,7 @@
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/FrontendPluginRegistry.h"
+#include "clang/Frontend/TextDiagnostic.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Lex/Preprocessor.h"
@@ -161,7 +162,8 @@ static void AddAllFixIts(ClangDiagnostic *diag, const clang::Diagnostic &Info) {
class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
public:
- ClangDiagnosticManagerAdapter(DiagnosticOptions &opts) {
+ ClangDiagnosticManagerAdapter(DiagnosticOptions &opts, StringRef filename)
+ : m_filename(filename) {
DiagnosticOptions *options = new DiagnosticOptions(opts);
options->ShowPresumedLoc = true;
options->ShowLevel = false;
@@ -212,20 +214,20 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
m_passthrough->HandleDiagnostic(DiagLevel, Info);
m_os->flush();
- lldb::Severity severity;
+ DiagnosticDetail detail;
bool make_new_diagnostic = true;
switch (DiagLevel) {
case DiagnosticsEngine::Level::Fatal:
case DiagnosticsEngine::Level::Error:
- severity = lldb::eSeverityError;
+ detail.severity = lldb::eSeverityError;
break;
case DiagnosticsEngine::Level::Warning:
- severity = lldb::eSeverityWarning;
+ detail.severity = lldb::eSeverityWarning;
break;
case DiagnosticsEngine::Level::Remark:
case DiagnosticsEngine::Level::Ignored:
- severity = lldb::eSeverityInfo;
+ detail.severity = lldb::eSeverityInfo;
break;
case DiagnosticsEngine::Level::Note:
m_manager->AppendMessageToDiagnostic(m_output);
@@ -254,14 +256,46 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
std::string stripped_output =
std::string(llvm::StringRef(m_output).trim());
- auto new_diagnostic = std::make_unique<ClangDiagnostic>(
- stripped_output, severity, Info.getID());
+ // Translate the source location.
+ if (Info.hasSourceManager()) {
+ DiagnosticDetail::SourceLocation loc;
+ clang::SourceManager &sm = Info.getSourceManager();
+ const clang::SourceLocation sloc = Info.getLocation();
+ if (sloc.isValid()) {
+ const clang::FullSourceLoc fsloc(sloc, sm);
+ clang::PresumedLoc PLoc = fsloc.getPresumedLoc(true);
+ StringRef filename =
+ PLoc.isValid() ? PLoc.getFilename() : StringRef{};
+ loc.file = FileSpec(filename);
+ loc.line = fsloc.getSpellingLineNumber();
+ loc.column = fsloc.getSpellingColumnNumber();
+ loc.in_user_input = filename == m_filename;
+
+ // Find the range of the primary location.
+ for (const auto &range : Info.getRanges()) {
+ if (range.getBegin() == sloc) {
+ // FIXME: This is probably not handling wide characters correctly.
+ unsigned end_col = sm.getSpellingColumnNumber(range.getEnd());
+ if (end_col > loc.column)
+ loc.length = end_col - loc.column;
+ break;
+ }
+ }
+ detail.source_location = loc;
+ }
+ }
+ llvm::SmallString<0> msg;
+ Info.FormatDiagnostic(msg);
+ detail.message = msg.str();
+ detail.rendered = stripped_output;
+ auto new_diagnostic =
+ std::make_unique<ClangDiagnostic>(detail, Info.getID());
// Don't store away warning fixits, since the compiler doesn't have
// enough context in an expression for the warning to be useful.
// FIXME: Should we try to filter out FixIts that apply to our generated
// code, and not the user's expression?
- if (severity == lldb::eSeverityError)
+ if (detail.severity == lldb::eSeverityError)
AddAllFixIts(new_diagnostic.get(), Info);
m_manager->AddDiagnostic(std::move(new_diagnostic));
@@ -281,6 +315,7 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
std::shared_ptr<llvm::raw_string_ostream> m_os;
/// Output string filled by m_os.
std::string m_output;
+ StringRef m_filename;
};
/// Returns true if the SDK for the specified triple supports
@@ -711,8 +746,8 @@ ClangExpressionParser::ClangExpressionParser(
// 4. Set language options.
SetupLangOpts(*m_compiler, *exe_scope, expr);
- if (auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
- clang_expr && clang_expr->DidImportCxxModules()) {
+ auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
+ if (clang_expr && clang_expr->DidImportCxxModules()) {
LLDB_LOG(log, "Adding lang options for importing C++ modules");
SetupImportStdModuleLangOpts(*m_compiler, *target_sp);
SetupModuleHeaderPaths(m_compiler.get(), m_include_directories, target_sp);
@@ -739,9 +774,9 @@ ClangExpressionParser::ClangExpressionParser(
m_compiler->getLangOpts());
// 5. Set up the diagnostic buffer for reporting errors
-
auto diag_mgr = new ClangDiagnosticManagerAdapter(
- m_compiler->getDiagnostics().getDiagnosticOptions());
+ m_compiler->getDiagnostics().getDiagnosticOptions(),
+ clang_expr ? clang_expr->GetFilename() : StringRef());
m_compiler->getDiagnostics().setClient(diag_mgr);
// 6. Set up the source management objects inside the compiler
@@ -1503,13 +1538,9 @@ lldb_private::Status ClangExpressionParser::DoPrepareForExecution(
new ClangDynamicCheckerFunctions();
DiagnosticManager install_diags;
- if (Error Err = dynamic_checkers->Install(install_diags, exe_ctx)) {
- std::string ErrMsg = "couldn't install checkers: " + toString(std::move(Err));
- if (install_diags.Diagnostics().size())
- ErrMsg = ErrMsg + "\n" + install_diags.GetString().c_str();
- err = Status(ErrMsg);
- return err;
- }
+ if (Error Err = dynamic_checkers->Install(install_diags, exe_ctx))
+ return Status::FromError(
+ install_diags.GetAsError("couldn't install checkers:"));
process->SetDynamicCheckers(dynamic_checkers);
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
index 09604feea5dec4..7c0c6a0147e2a0 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h
@@ -177,6 +177,8 @@ class ClangUserExpression : public LLVMUserExpression {
/// Returns true iff this expression is using any imported C++ modules.
bool DidImportCxxModules() const { return !m_imported_cpp_modules.empty(); }
+ llvm::StringRef GetFilename() const { return m_filename; }
+
private:
/// Populate m_in_cplusplus_method and m_in_objectivec_method based on the
/// environment.
diff --git a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
index 31315e46ca168d..5d873bf73da260 100644
--- a/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
+++ b/lldb/source/Plugins/Platform/POSIX/PlatformPOSIX.cpp
@@ -863,9 +863,8 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
func_args_addr,
arguments,
diagnostics)) {
- error = Status::FromErrorStringWithFormat(
- "dlopen error: could not write function arguments: %s",
- diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "dlopen error: could not write function arguments:"));
return LLDB_INVALID_IMAGE_TOKEN;
}
@@ -906,9 +905,8 @@ uint32_t PlatformPOSIX::DoLoadImage(lldb_private::Process *process,
ExpressionResults results = do_dlopen_function->ExecuteFunction(
exe_ctx, &func_args_addr, options, diagnostics, return_value);
if (results != eExpressionCompleted) {
- error = Status::FromErrorStringWithFormat(
- "dlopen error: failed executing dlopen wrapper function: %s",
- diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "dlopen error: failed executing dlopen wrapper function:"));
return LLDB_INVALID_IMAGE_TOKEN;
}
diff --git a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
index 7352d6f33f2174..fa39d93a91297c 100644
--- a/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
+++ b/lldb/source/Plugins/Platform/Windows/PlatformWindows.cpp
@@ -341,9 +341,8 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
diagnostics.Clear();
if (!invocation->WriteFunctionArguments(context, injected_parameters,
parameters, diagnostics)) {
- error = Status::FromErrorStringWithFormat(
- "LoadLibrary error: unable to write function parameters: %s",
- diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "LoadLibrary error: unable to write function parameters:"));
return LLDB_INVALID_IMAGE_TOKEN;
}
@@ -384,9 +383,8 @@ uint32_t PlatformWindows::DoLoadImage(Process *process,
invocation->ExecuteFunction(context, &injected_parameters, options,
diagnostics, value);
if (result != eExpressionCompleted) {
- error = Status::FromErrorStringWithFormat(
- "LoadLibrary error: failed to execute LoadLibrary helper: %s",
- diagnostics.GetString().c_str());
+ error = Status::FromError(diagnostics.GetAsError(
+ "LoadLibrary error: failed to execute LoadLibrary helper:"));
return LLDB_INVALID_IMAGE_TOKEN;
}
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index f1659aae0800db..57061ecd5bd445 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -2605,7 +2605,7 @@ Target::CreateUtilityFunction(std::string expression, std::string name,
DiagnosticManager diagnostics;
if (!utility_fn->Install(diagnostics, exe_ctx))
- return llvm::createStringError(diagnostics.GetString());
+ return diagnostics.GetAsError("Could not install utility function:");
return std::move(utility_fn);
}
diff --git a/lldb/source/Utility/Status.cpp b/lldb/source/Utility/Status.cpp
index ba08353c7b24d2..3099e54af483ff 100644
--- a/lldb/source/Utility/Status.cpp
+++ b/lldb/source/Utility/Status.cpp
@@ -224,6 +224,14 @@ const char *Status::AsCString(const char *default_error_str) const {
if (!m_string.empty() && m_string[m_string.size() - 1] == '\n')
m_string.pop_back();
+ // FIXME: Workaround for ErrorList[ExpressionError, ...].
+ if (m_error.isA<llvm::ErrorList>()) {
+ while (!m_string.empty() && m_string[0] == '\n')
+ m_string = std::string(m_string.data() + 1, m_string.size() - 1);
+ if (!m_string.empty() && m_string[m_string.size() - 1] != '\n')
+ m_string += '\n';
+ }
+
if (m_string.empty()) {
if (default_error_str)
m_string.assign(default_error_str);
diff --git a/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py b/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py
index 620b6e44fc852a..36e302be2525b5 100644
--- a/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py
+++ b/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py
@@ -21,7 +21,7 @@ def test(self):
"expr @import LLDBTestModule",
error=True,
substrs=[
- "module.h:4:1: use of undeclared identifier 'syntax_error_for_lldb_to_find'",
+ "module.h:4:1: error: use of undeclared identifier 'syntax_error_for_lldb_to_find'",
"syntax_error_for_lldb_to_find // comment that tests source printing",
"could not build module 'LLDBTestModule'",
],
diff --git a/lldb/unittests/Expression/DiagnosticManagerTest.cpp b/lldb/unittests/Expression/DiagnosticManagerTest.cpp
index 05fe7c164d6818..4608b779f79a96 100644
--- a/lldb/unittests/Expression/DiagnosticManagerTest.cpp
+++ b/lldb/unittests/Expression/DiagnosticManagerTest.cpp
@@ -19,8 +19,8 @@ class FixItDiag : public Diagnostic {
public:
FixItDiag(llvm::StringRef msg, bool has_fixits)
- : Diagnostic(msg, lldb::eSeverityError,
- DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id),
+ : Diagnostic(DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id,
+ DiagnosticDetail{{}, lldb::eSeverityError, msg.str(), {}}),
m_has_fixits(has_fixits) {}
bool HasFixIts() const override { return m_has_fixits; }
};
@@ -30,8 +30,8 @@ namespace {
class TextDiag : public Diagnostic {
public:
TextDiag(llvm::StringRef msg, lldb::Severity severity)
- : Diagnostic(msg, severity, DiagnosticOrigin::eDiagnosticOriginLLDB,
- custom_diag_id) {}
+ : Diagnostic(DiagnosticOrigin::eDiagnosticOriginLLDB, custom_diag_id,
+ DiagnosticDetail{{}, severity, msg.str(), msg.str()}) {}
};
} // namespace
@@ -42,8 +42,8 @@ TEST(DiagnosticManagerTest, AddDiagnostic) {
std::string msg = "foo bar has happened";
lldb::Severity severity = lldb::eSeverityError;
DiagnosticOrigin origin = DiagnosticOrigin::eDiagnosticOriginLLDB;
- auto diag =
- std::make_unique<Diagnostic>(msg, severity, origin, custom_diag_id);
+ auto diag = std::make_unique<Diagnostic>(
+ origin, custom_diag_id, DiagnosticDetail{{}, severity, msg, {}});
mgr.AddDiagnostic(std::move(diag));
EXPECT_EQ(1U, mgr.Diagnostics().size());
const Diagnostic *got = mgr.Diagnostics().front().get();
@@ -197,3 +197,13 @@ TEST(DiagnosticManagerTest, FixedExpression) {
mgr.SetFixedExpression("bar");
EXPECT_EQ("bar", mgr.GetFixedExpression());
}
+
+TEST(DiagnosticManagerTest, StatusConversion) {
+ DiagnosticManager mgr;
+ mgr.AddDiagnostic(std::make_unique<TextDiag>("abc", lldb::eSeverityError));
+ mgr.AddDiagnostic(std::make_unique<TextDiag>("def", lldb::eSeverityWarning));
+ Status status =
+ Status::FromError(mgr.GetAsError(lldb::eExpressionParseError));
+ EXPECT_EQ(std::string("error: abc\nwarning: def\n"),
+ std::string(status.AsCString()));
+}
diff --git a/lldb/unittests/Utility/StatusTest.cpp b/lldb/unittests/Utility/StatusTest.cpp
index e37c94ac17f2d0..48110b160be93c 100644
--- a/lldb/unittests/Utility/StatusTest.cpp
+++ b/lldb/unittests/Utility/StatusTest.cpp
@@ -55,7 +55,7 @@ TEST(StatusTest, ErrorCodeConstructor) {
llvm::Error list = llvm::joinErrors(llvm::createStringError("foo"),
llvm::createStringError("bar"));
Status foobar = Status::FromError(std::move(list));
- EXPECT_EQ(std::string("foo\nbar"), std::string(foobar.AsCString()));
+ EXPECT_EQ(std::string("foo\nbar\n"), std::string(foobar.AsCString()));
}
TEST(StatusTest, ErrorConversion) {
>From f84f26610f1bed9ed6647f09fa6f27cde3126653 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Wed, 28 Aug 2024 16:19:21 -0700
Subject: [PATCH 2/2] [lldb] Inline expression evaluator error visualization
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This patch is a reworking of Pete Lawrence's (@PortalPete) proposal
for better expression evaluator error messages:
https://github.com/llvm/llvm-project/pull/80938
Before:
```
$ lldb -o "expr a+b"
(lldb) expr a+b
error: <user expression 0>:1:1: use of undeclared identifier 'a'
a+b
^
error: <user expression 0>:1:3: use of undeclared identifier 'b'
a+b
^
```
After:
```
(lldb) expr a+b
^ ^
│ ╰─ error: use of undeclared identifier 'b'
╰─ error: use of undeclared identifier 'a'
```
This eliminates the confusing `<user expression 0>:1:3` source
location and avoids echoing the expression to the console again, which
results in a cleaner presentation that makes it easier to grasp what's
going on.
---
.../lldb/Expression/DiagnosticManager.h | 1 +
lldb/include/lldb/Interpreter/CommandObject.h | 8 +
.../Commands/CommandObjectExpression.cpp | 157 ++++++++++++++++--
.../source/Interpreter/CommandInterpreter.cpp | 4 +-
.../Clang/ClangExpressionParser.cpp | 8 +-
.../ctf/CommandObjectThreadTraceExportCTF.h | 2 +-
lldb/source/Utility/Status.cpp | 3 +-
.../diagnostics/TestExprDiagnostics.py | 50 ++++++
.../TestPersistentVariables.py | 4 +-
.../TestStaticInitializers.py | 2 +-
.../TestTemplateFunctions.py | 2 +-
.../test/API/lang/mixed/TestMixedLanguages.py | 2 +-
lldb/test/Shell/Expr/TestObjCIDCast.test | 2 +-
.../test/Shell/Expr/TestObjCInCXXContext.test | 2 +-
.../NativePDB/incomplete-tag-type.cpp | 4 +-
.../Expression/DiagnosticManagerTest.cpp | 33 ++--
lldb/unittests/Interpreter/CMakeLists.txt | 1 +
.../TestCommandObjectExpression.cpp | 34 ++++
18 files changed, 274 insertions(+), 45 deletions(-)
create mode 100644 lldb/unittests/Interpreter/TestCommandObjectExpression.cpp
diff --git a/lldb/include/lldb/Expression/DiagnosticManager.h b/lldb/include/lldb/Expression/DiagnosticManager.h
index 1c0763eea92f05..67e5082dff208e 100644
--- a/lldb/include/lldb/Expression/DiagnosticManager.h
+++ b/lldb/include/lldb/Expression/DiagnosticManager.h
@@ -39,6 +39,7 @@ struct DiagnosticDetail {
unsigned line = 0;
uint16_t column = 0;
uint16_t length = 0;
+ bool hidden = false;
bool in_user_input = false;
};
/// Contains {{}, 1, 3, 3, true} in the example above.
diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index 20c4769af90338..c5167e5e0ecb6a 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -340,6 +340,13 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
return false;
}
+ /// Set the command input as it appeared in the terminal. This
+ /// is used to have errors refer directly to the original command.
+ void SetOriginalCommandString(std::string s) { m_original_command = s; }
+
+ /// \param offset_in_command is on what column \c args_string
+ /// appears, if applicable. This enables diagnostics that refer back
+ /// to the user input.
virtual void Execute(const char *args_string,
CommandReturnObject &result) = 0;
@@ -404,6 +411,7 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
std::string m_cmd_help_short;
std::string m_cmd_help_long;
std::string m_cmd_syntax;
+ std::string m_original_command;
Flags m_flags;
std::vector<CommandArgumentEntry> m_arguments;
lldb::CommandOverrideCallback m_deprecated_command_override_callback;
diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp
index 771194638e1b65..b5c6cd143eb06b 100644
--- a/lldb/source/Commands/CommandObjectExpression.cpp
+++ b/lldb/source/Commands/CommandObjectExpression.cpp
@@ -10,6 +10,7 @@
#include "CommandObjectExpression.h"
#include "lldb/Core/Debugger.h"
+#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Expression/REPL.h"
#include "lldb/Expression/UserExpression.h"
@@ -398,6 +399,122 @@ CanBeUsedForElementCountPrinting(ValueObject &valobj) {
return Status();
}
+static llvm::raw_ostream &PrintSeverity(Stream &stream,
+ lldb::Severity severity) {
+ llvm::HighlightColor color;
+ llvm::StringRef text;
+ switch (severity) {
+ case eSeverityError:
+ color = llvm::HighlightColor::Error;
+ text = "error: ";
+ break;
+ case eSeverityWarning:
+ color = llvm::HighlightColor::Warning;
+ text = "warning: ";
+ break;
+ case eSeverityInfo:
+ color = llvm::HighlightColor::Remark;
+ text = "note: ";
+ break;
+ }
+ return llvm::WithColor(stream.AsRawOstream(), color, llvm::ColorMode::Enable)
+ << text;
+}
+
+namespace lldb_private {
+// Public for unittesting.
+void RenderDiagnosticDetails(Stream &stream,
+ std::optional<uint16_t> offset_in_command,
+ bool multiline,
+ llvm::ArrayRef<DiagnosticDetail> details) {
+ if (details.empty())
+ return;
+
+ if (!offset_in_command) {
+ for (const DiagnosticDetail &detail : details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ }
+ return;
+ }
+
+ // Print a line with caret indicator(s) below the lldb prompt + command.
+ const size_t padding = *offset_in_command;
+ stream << std::string(padding, ' ');
+
+ size_t offset = 1;
+ std::vector<DiagnosticDetail> remaining_details, other_details,
+ hidden_details;
+ for (const DiagnosticDetail &detail : details) {
+ if (multiline || !detail.source_location) {
+ other_details.push_back(detail);
+ continue;
+ }
+ if (detail.source_location->hidden) {
+ hidden_details.push_back(detail);
+ continue;
+ }
+ if (!detail.source_location->in_user_input) {
+ other_details.push_back(detail);
+ continue;
+ }
+
+ auto &loc = *detail.source_location;
+ remaining_details.push_back(detail);
+ if (offset > loc.column)
+ continue;
+ stream << std::string(loc.column - offset, ' ') << '^';
+ if (loc.length > 1)
+ stream << std::string(loc.length - 1, '~');
+ offset = loc.column + 1;
+ }
+ stream << '\n';
+
+ // Work through each detail in reverse order using the vector/stack.
+ bool did_print = false;
+ for (auto detail = remaining_details.rbegin();
+ detail != remaining_details.rend();
+ ++detail, remaining_details.pop_back()) {
+ // Get the information to print this detail and remove it from the stack.
+ // Print all the lines for all the other messages first.
+ stream << std::string(padding, ' ');
+ size_t offset = 1;
+ for (auto &remaining_detail :
+ llvm::ArrayRef(remaining_details).drop_back(1)) {
+ uint16_t column = remaining_detail.source_location->column;
+ stream << std::string(column - offset, ' ') << "│";
+ offset = column + 1;
+ }
+
+ // Print the line connecting the ^ with the error message.
+ uint16_t column = detail->source_location->column;
+ if (offset <= column)
+ stream << std::string(column - offset, ' ') << "╰─ ";
+
+ // Print a colorized string based on the message's severity type.
+ PrintSeverity(stream, detail->severity);
+
+ // Finally, print the message and start a new line.
+ stream << detail->message << '\n';
+ did_print = true;
+ }
+
+ // Print the non-located details.
+ for (const DiagnosticDetail &detail : other_details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ did_print = true;
+ }
+
+ // Print the hidden details as a last resort.
+ if (!did_print)
+ for (const DiagnosticDetail &detail : hidden_details) {
+ PrintSeverity(stream, detail.severity);
+ stream << detail.rendered << '\n';
+ }
+}
+} // namespace lldb_private
+
bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
Stream &output_stream,
Stream &error_stream,
@@ -486,19 +603,37 @@ bool CommandObjectExpression::EvaluateExpression(llvm::StringRef expr,
result.SetStatus(eReturnStatusSuccessFinishResult);
} else {
- const char *error_cstr = result_valobj_sp->GetError().AsCString();
- if (error_cstr && error_cstr[0]) {
- const size_t error_cstr_len = strlen(error_cstr);
- const bool ends_with_newline = error_cstr[error_cstr_len - 1] == '\n';
- if (strstr(error_cstr, "error:") != error_cstr)
- error_stream.PutCString("error: ");
- error_stream.Write(error_cstr, error_cstr_len);
- if (!ends_with_newline)
- error_stream.EOL();
+ // Retrieve the diagnostics.
+ std::vector<DiagnosticDetail> details;
+ llvm::consumeError(
+ llvm::handleErrors(result_valobj_sp->GetError().ToError(),
+ [&](DetailedExpressionError &error) {
+ details.push_back(error.GetDetail());
+ }));
+ // Find the position of the expression in the command.
+ std::optional<uint16_t> expr_pos;
+ size_t nchar = m_original_command.find(expr);
+ if (nchar != std::string::npos)
+ expr_pos = nchar + GetDebugger().GetPrompt().size();
+
+ if (!details.empty()) {
+ bool multiline = expr.contains('\n');
+ RenderDiagnosticDetails(error_stream, expr_pos, multiline, details);
} else {
- error_stream.PutCString("error: unknown error\n");
+ const char *error_cstr = result_valobj_sp->GetError().AsCString();
+ if (error_cstr && error_cstr[0]) {
+ const size_t error_cstr_len = strlen(error_cstr);
+ const bool ends_with_newline =
+ error_cstr[error_cstr_len - 1] == '\n';
+ if (strstr(error_cstr, "error:") != error_cstr)
+ error_stream.PutCString("error: ");
+ error_stream.Write(error_cstr, error_cstr_len);
+ if (!ends_with_newline)
+ error_stream.EOL();
+ } else {
+ error_stream.PutCString("error: unknown error\n");
+ }
}
-
result.SetStatus(eReturnStatusFailed);
}
}
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index b93f47a8a8d5ec..49681f06802443 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -1887,7 +1887,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
CommandReturnObject &result,
bool force_repeat_command) {
std::string command_string(command_line);
- std::string original_command_string(command_line);
+ std::string original_command_string(command_string);
+ std::string real_original_command_string(command_string);
Log *log = GetLog(LLDBLog::Commands);
llvm::PrettyStackTraceFormat stack_trace("HandleCommand(command = \"%s\")",
@@ -2076,6 +2077,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line,
}
ElapsedTime elapsed(execute_time);
+ cmd_obj->SetOriginalCommandString(real_original_command_string);
cmd_obj->Execute(remainder.c_str(), result);
}
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 92bd554767dae2..2aac25543a3a0f 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -215,8 +215,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
m_os->flush();
DiagnosticDetail detail;
- bool make_new_diagnostic = true;
-
switch (DiagLevel) {
case DiagnosticsEngine::Level::Fatal:
case DiagnosticsEngine::Level::Error:
@@ -230,9 +228,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
detail.severity = lldb::eSeverityInfo;
break;
case DiagnosticsEngine::Level::Note:
- m_manager->AppendMessageToDiagnostic(m_output);
- make_new_diagnostic = false;
-
// 'note:' diagnostics for errors and warnings can also contain Fix-Its.
// We add these Fix-Its to the last error diagnostic to make sure
// that we later have all Fix-Its related to an 'error' diagnostic when
@@ -250,7 +245,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
AddAllFixIts(clang_diag, Info);
break;
}
- if (make_new_diagnostic) {
// ClangDiagnostic messages are expected to have no whitespace/newlines
// around them.
std::string stripped_output =
@@ -270,6 +264,7 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
loc.line = fsloc.getSpellingLineNumber();
loc.column = fsloc.getSpellingColumnNumber();
loc.in_user_input = filename == m_filename;
+ loc.hidden = filename.starts_with("<lldb wrapper ");
// Find the range of the primary location.
for (const auto &range : Info.getRanges()) {
@@ -299,7 +294,6 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
AddAllFixIts(new_diagnostic.get(), Info);
m_manager->AddDiagnostic(std::move(new_diagnostic));
- }
}
void BeginSourceFile(const LangOptions &LO, const Preprocessor *PP) override {
diff --git a/lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.h b/lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.h
index 1a034e87cfb65b..06834edf14ea16 100644
--- a/lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.h
+++ b/lldb/source/Plugins/TraceExporter/ctf/CommandObjectThreadTraceExportCTF.h
@@ -48,7 +48,7 @@ class CommandObjectThreadTraceExportCTF : public CommandObjectParsed {
Options *GetOptions() override { return &m_options; }
protected:
- void DoExecute(Args &command, CommandReturnObject &result) override;
+ void DoExecute(Args &args, CommandReturnObject &result) override;
CommandOptions m_options;
};
diff --git a/lldb/source/Utility/Status.cpp b/lldb/source/Utility/Status.cpp
index 3099e54af483ff..e2c3029244735e 100644
--- a/lldb/source/Utility/Status.cpp
+++ b/lldb/source/Utility/Status.cpp
@@ -8,6 +8,7 @@
#include "lldb/Utility/Status.h"
+#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/VASPrintf.h"
@@ -279,8 +280,6 @@ ErrorType Status::GetType() const {
else if (error.convertToErrorCode().category() == lldb_generic_category() ||
error.convertToErrorCode() == llvm::inconvertibleErrorCode())
result = eErrorTypeGeneric;
- else
- result = eErrorTypeInvalid;
});
return result;
}
diff --git a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
index ddc1c3598480cf..f81d3852946b9a 100644
--- a/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
+++ b/lldb/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
@@ -183,3 +183,53 @@ def test_source_locations_from_objc_modules(self):
# The NSLog definition source line should be printed. Return value and
# the first argument are probably stable enough that this test can check for them.
self.assertIn("void NSLog(NSString *format", value.GetError().GetCString())
+
+ def test_command_expr_formatting(self):
+ """Test that the source and caret positions LLDB prints are correct"""
+ self.build()
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+ self, "// Break here", self.main_source_spec
+ )
+ frame = thread.GetFrameAtIndex(0)
+
+ def check(input_ref):
+ self.expect(input_ref[0], error=True, substrs=input_ref[1:])
+
+ check(
+ [
+ "expression -- a+b",
+ " ^ ^",
+ " │ ╰─ error: use of undeclared identifier 'b'",
+ " ╰─ error: use of undeclared identifier 'a'",
+ ]
+ )
+
+ check(
+ [
+ "expr -- a",
+ " ^",
+ " ╰─ error: use of undeclared identifier 'a'",
+ ]
+ )
+ check(
+ [
+ "expr -i 0 -o 0 -- a",
+ " ^",
+ " ╰─ error: use of undeclared identifier 'a'",
+ ]
+ )
+
+ self.expect(
+ "expression --top-level -- template<typename T> T FOO(T x) { return x/2;}"
+ )
+ check(
+ [
+ 'expression -- FOO("")',
+ " ^",
+ " ╰─ note: in instantiation of function template specialization 'FOO<const char *>' requested here",
+ "error: <user expression",
+ "invalid operands to binary expression",
+ ]
+ )
+ check(["expression --\na\n+\nb", "error: <user", "a", "error: <user", "b"])
diff --git a/lldb/test/API/commands/expression/persistent_variables/TestPersistentVariables.py b/lldb/test/API/commands/expression/persistent_variables/TestPersistentVariables.py
index 6a7995ff2a837e..d0d9358589a719 100644
--- a/lldb/test/API/commands/expression/persistent_variables/TestPersistentVariables.py
+++ b/lldb/test/API/commands/expression/persistent_variables/TestPersistentVariables.py
@@ -56,7 +56,7 @@ def test_persistent_variables(self):
self.expect(
"expr int $i = 123",
error=True,
- substrs=["error: redefinition of persistent variable '$i'"],
+ substrs=["redefinition of persistent variable '$i'"],
)
self.expect_expr("$i", result_type="int", result_value="5")
@@ -65,7 +65,7 @@ def test_persistent_variables(self):
self.expect(
"expr long $i = 123",
error=True,
- substrs=["error: redefinition of persistent variable '$i'"],
+ substrs=["redefinition of persistent variable '$i'"],
)
self.expect_expr("$i", result_type="int", result_value="5")
diff --git a/lldb/test/API/commands/expression/static-initializers/TestStaticInitializers.py b/lldb/test/API/commands/expression/static-initializers/TestStaticInitializers.py
index ea3aa6a4608c41..c734033bd6c028 100644
--- a/lldb/test/API/commands/expression/static-initializers/TestStaticInitializers.py
+++ b/lldb/test/API/commands/expression/static-initializers/TestStaticInitializers.py
@@ -35,7 +35,7 @@ def test_failing_init(self):
self.expect(
"expr -p -- struct Foo2 { Foo2() { do_abort(); } }; Foo2 f;",
error=True,
- substrs=["error: couldn't run static initializer:"],
+ substrs=["couldn't run static initializer:"],
)
def test_without_process(self):
diff --git a/lldb/test/API/lang/cpp/template-function/TestTemplateFunctions.py b/lldb/test/API/lang/cpp/template-function/TestTemplateFunctions.py
index 71df90e6a6d161..8d4200b1076d03 100644
--- a/lldb/test/API/lang/cpp/template-function/TestTemplateFunctions.py
+++ b/lldb/test/API/lang/cpp/template-function/TestTemplateFunctions.py
@@ -21,7 +21,7 @@ def do_test_template_function(self, add_cast):
"expr b1 <=> b2",
error=True,
substrs=[
- "warning: <user expression 0>:1:4: '<=>' is a single token in C++20; add a space to avoid a change in behavior"
+ "warning:", "'<=>' is a single token in C++20; add a space to avoid a change in behavior"
],
)
diff --git a/lldb/test/API/lang/mixed/TestMixedLanguages.py b/lldb/test/API/lang/mixed/TestMixedLanguages.py
index 8b73254cce4a93..1637d59a5edcba 100644
--- a/lldb/test/API/lang/mixed/TestMixedLanguages.py
+++ b/lldb/test/API/lang/mixed/TestMixedLanguages.py
@@ -40,7 +40,7 @@ def cleanup():
self.runCmd("run")
self.expect("thread backtrace", substrs=["`main", "lang=c"])
# Make sure evaluation of C++11 fails.
- self.expect("expr foo != nullptr", error=True, startstr="error")
+ self.expect("expr foo != nullptr", error=True, substrs=["error"])
# Run to BP at foo (in foo.cpp) and test that the language is C++.
self.runCmd("breakpoint set -n foo")
diff --git a/lldb/test/Shell/Expr/TestObjCIDCast.test b/lldb/test/Shell/Expr/TestObjCIDCast.test
index 0611171da09e2e..19ca404643c1d9 100644
--- a/lldb/test/Shell/Expr/TestObjCIDCast.test
+++ b/lldb/test/Shell/Expr/TestObjCIDCast.test
@@ -6,4 +6,4 @@
// RUN: 2>&1 | FileCheck %s
// CHECK: (lldb) expression --language objc -- *(id)0x1
-// CHECK: error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
+// CHECK: error:{{.*}}Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
diff --git a/lldb/test/Shell/Expr/TestObjCInCXXContext.test b/lldb/test/Shell/Expr/TestObjCInCXXContext.test
index 8537799bdeb674..f8cad5b58a1e53 100644
--- a/lldb/test/Shell/Expr/TestObjCInCXXContext.test
+++ b/lldb/test/Shell/Expr/TestObjCInCXXContext.test
@@ -18,4 +18,4 @@
// CHECK-NEXT: (NSString *){{.*}}= nil
// CHECK: (lldb) expression NSString
-// CHECK-NEXT: error:{{.*}} use of undeclared identifier 'NSString'
+// CHECK: error:{{.*}}use of undeclared identifier 'NSString'
diff --git a/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp b/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp
index a249057282d890..8c168286903014 100644
--- a/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp
+++ b/lldb/test/Shell/SymbolFile/NativePDB/incomplete-tag-type.cpp
@@ -13,9 +13,7 @@
// CHECK: (lldb) expression d
// CHECK: (D) $1 = {}
// CHECK: (lldb) expression static_e_ref
-// CHECK: error: {{.*}}incomplete type 'E' where a complete type is required
-// CHECK: static_e_ref
-// CHECK: ^
+// CHECK: error:{{.*}}incomplete type 'E' where a complete type is required
// Complete base class.
struct A { int x; A(); };
diff --git a/lldb/unittests/Expression/DiagnosticManagerTest.cpp b/lldb/unittests/Expression/DiagnosticManagerTest.cpp
index 4608b779f79a96..2373b8101bf4e2 100644
--- a/lldb/unittests/Expression/DiagnosticManagerTest.cpp
+++ b/lldb/unittests/Expression/DiagnosticManagerTest.cpp
@@ -72,18 +72,25 @@ TEST(DiagnosticManagerTest, HasFixits) {
EXPECT_TRUE(mgr.HasFixIts());
}
+static std::string toString(DiagnosticManager &mgr) {
+ std::string s = llvm::toString(mgr.GetAsError(""));
+ if (s.empty())
+ return s;
+ return s.substr(1) + "\n";
+}
+
TEST(DiagnosticManagerTest, GetStringNoDiags) {
DiagnosticManager mgr;
- EXPECT_EQ("", mgr.GetString());
+ EXPECT_EQ("", toString(mgr));
std::unique_ptr<Diagnostic> empty;
mgr.AddDiagnostic(std::move(empty));
- EXPECT_EQ("", mgr.GetString());
+ EXPECT_EQ("", toString(mgr));
}
TEST(DiagnosticManagerTest, GetStringBasic) {
DiagnosticManager mgr;
mgr.AddDiagnostic(std::make_unique<TextDiag>("abc", lldb::eSeverityError));
- EXPECT_EQ("error: abc\n", mgr.GetString());
+ EXPECT_EQ("error: abc\n", toString(mgr));
}
TEST(DiagnosticManagerTest, GetStringMultiline) {
@@ -91,15 +98,15 @@ TEST(DiagnosticManagerTest, GetStringMultiline) {
// Multiline diagnostics should only get one severity label.
mgr.AddDiagnostic(std::make_unique<TextDiag>("b\nc", lldb::eSeverityError));
- EXPECT_EQ("error: b\nc\n", mgr.GetString());
+ EXPECT_EQ("error: b\nc\n", toString(mgr));
}
TEST(DiagnosticManagerTest, GetStringMultipleDiags) {
DiagnosticManager mgr;
mgr.AddDiagnostic(std::make_unique<TextDiag>("abc", lldb::eSeverityError));
- EXPECT_EQ("error: abc\n", mgr.GetString());
+ EXPECT_EQ("error: abc\n", toString(mgr));
mgr.AddDiagnostic(std::make_unique<TextDiag>("def", lldb::eSeverityError));
- EXPECT_EQ("error: abc\nerror: def\n", mgr.GetString());
+ EXPECT_EQ("error: abc\nerror: def\n", toString(mgr));
}
TEST(DiagnosticManagerTest, GetStringSeverityLabels) {
@@ -110,7 +117,7 @@ TEST(DiagnosticManagerTest, GetStringSeverityLabels) {
mgr.AddDiagnostic(std::make_unique<TextDiag>("bar", lldb::eSeverityWarning));
// Remarks have no labels.
mgr.AddDiagnostic(std::make_unique<TextDiag>("baz", lldb::eSeverityInfo));
- EXPECT_EQ("error: foo\nwarning: bar\nbaz\n", mgr.GetString());
+ EXPECT_EQ("error: foo\nwarning: bar\nbaz\n", toString(mgr));
}
TEST(DiagnosticManagerTest, GetStringPreserveOrder) {
@@ -120,7 +127,7 @@ TEST(DiagnosticManagerTest, GetStringPreserveOrder) {
mgr.AddDiagnostic(std::make_unique<TextDiag>("baz", lldb::eSeverityInfo));
mgr.AddDiagnostic(std::make_unique<TextDiag>("bar", lldb::eSeverityWarning));
mgr.AddDiagnostic(std::make_unique<TextDiag>("foo", lldb::eSeverityError));
- EXPECT_EQ("baz\nwarning: bar\nerror: foo\n", mgr.GetString());
+ EXPECT_EQ("baz\nwarning: bar\nerror: foo\n", toString(mgr));
}
TEST(DiagnosticManagerTest, AppendMessageNoDiag) {
@@ -139,7 +146,7 @@ TEST(DiagnosticManagerTest, AppendMessageAttachToLastDiag) {
// This should append to 'bar' and not to 'foo'.
mgr.AppendMessageToDiagnostic("message text");
- EXPECT_EQ("error: foo\nerror: bar\nmessage text\n", mgr.GetString());
+ EXPECT_EQ("error: foo\nerror: bar\nmessage text\n", toString(mgr));
}
TEST(DiagnosticManagerTest, AppendMessageSubsequentDiags) {
@@ -150,7 +157,7 @@ TEST(DiagnosticManagerTest, AppendMessageSubsequentDiags) {
// Pushing another diag after the message should work fine.
mgr.AddDiagnostic(std::make_unique<TextDiag>("foo", lldb::eSeverityError));
- EXPECT_EQ("error: bar\nmessage text\nerror: foo\n", mgr.GetString());
+ EXPECT_EQ("error: bar\nmessage text\nerror: foo\n", toString(mgr));
}
TEST(DiagnosticManagerTest, PutString) {
@@ -159,7 +166,7 @@ TEST(DiagnosticManagerTest, PutString) {
mgr.PutString(lldb::eSeverityError, "foo");
EXPECT_EQ(1U, mgr.Diagnostics().size());
EXPECT_EQ(eDiagnosticOriginLLDB, mgr.Diagnostics().front()->getKind());
- EXPECT_EQ("error: foo\n", mgr.GetString());
+ EXPECT_EQ("error: foo\n", toString(mgr));
}
TEST(DiagnosticManagerTest, PutStringMultiple) {
@@ -169,7 +176,7 @@ TEST(DiagnosticManagerTest, PutStringMultiple) {
mgr.PutString(lldb::eSeverityError, "foo");
mgr.PutString(lldb::eSeverityError, "bar");
EXPECT_EQ(2U, mgr.Diagnostics().size());
- EXPECT_EQ("error: foo\nerror: bar\n", mgr.GetString());
+ EXPECT_EQ("error: foo\nerror: bar\n", toString(mgr));
}
TEST(DiagnosticManagerTest, PutStringSeverities) {
@@ -180,7 +187,7 @@ TEST(DiagnosticManagerTest, PutStringSeverities) {
mgr.PutString(lldb::eSeverityError, "foo");
mgr.PutString(lldb::eSeverityWarning, "bar");
EXPECT_EQ(2U, mgr.Diagnostics().size());
- EXPECT_EQ("error: foo\nwarning: bar\n", mgr.GetString());
+ EXPECT_EQ("error: foo\nwarning: bar\n", toString(mgr));
}
TEST(DiagnosticManagerTest, FixedExpression) {
diff --git a/lldb/unittests/Interpreter/CMakeLists.txt b/lldb/unittests/Interpreter/CMakeLists.txt
index 54cea995084d3d..14a7d6c5610388 100644
--- a/lldb/unittests/Interpreter/CMakeLists.txt
+++ b/lldb/unittests/Interpreter/CMakeLists.txt
@@ -1,5 +1,6 @@
add_lldb_unittest(InterpreterTests
TestCommandPaths.cpp
+ TestCommandObjectExpression.cpp
TestCompletion.cpp
TestOptionArgParser.cpp
TestOptions.cpp
diff --git a/lldb/unittests/Interpreter/TestCommandObjectExpression.cpp b/lldb/unittests/Interpreter/TestCommandObjectExpression.cpp
new file mode 100644
index 00000000000000..9fe0432fd22653
--- /dev/null
+++ b/lldb/unittests/Interpreter/TestCommandObjectExpression.cpp
@@ -0,0 +1,34 @@
+#include "lldb/Expression/DiagnosticManager.h"
+#include "lldb/Utility/StreamString.h"
+#include "gtest/gtest.h"
+
+namespace lldb_private {
+std::string RenderDiagnosticDetails(Stream &stream,
+ std::optional<uint16_t> offset_in_command,
+ bool multiline,
+ llvm::ArrayRef<DiagnosticDetail> details);
+}
+
+using namespace lldb_private;
+using namespace lldb;
+using llvm::StringRef;
+namespace {
+class ErrorDisplayTest : public ::testing::Test {};
+} // namespace
+
+static std::string Render(std::vector<DiagnosticDetail> details) {
+ StreamString stream;
+ RenderDiagnosticDetails(stream, 0, details);
+ return stream.GetData();
+}
+
+TEST_F(ErrorDisplayTest, RenderStatus) {
+ DiagnosticDetail::SourceLocation inline_loc;
+ inline_loc.in_user_input = true;
+ {
+ std::string result =
+ Render({DiagnosticDetail{inline_loc, eSeverityError, "foo", ""}});
+ ASSERT_TRUE(StringRef(result).contains("error:"));
+ ASSERT_TRUE(StringRef(result).contains("foo"));
+ }
+}
More information about the lldb-commits
mailing list