diff --git a/lldb/tools/lldb-dap/Breakpoint.cpp b/lldb/tools/lldb-dap/Breakpoint.cpp new file mode 100644 index 00000000000000..0c33d4b114d760 --- /dev/null +++ b/lldb/tools/lldb-dap/Breakpoint.cpp @@ -0,0 +1,76 @@ +//===-- Breakpoint.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Breakpoint.h" +#include "DAP.h" +#include "JSONUtils.h" +#include "llvm/ADT/StringExtras.h" + +using namespace lldb_dap; + +void Breakpoint::SetCondition() { bp.SetCondition(condition.c_str()); } + +void Breakpoint::SetHitCondition() { + uint64_t hitCount = 0; + if (llvm::to_integer(hitCondition, hitCount)) + bp.SetIgnoreCount(hitCount - 1); +} + +void Breakpoint::CreateJsonObject(llvm::json::Object &object) { + // Each breakpoint location is treated as a separate breakpoint for VS code. + // They don't have the notion of a single breakpoint with multiple locations. + if (!bp.IsValid()) + return; + object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); + object.try_emplace("id", bp.GetID()); + // VS Code DAP doesn't currently allow one breakpoint to have multiple + // locations so we just report the first one. If we report all locations + // then the IDE starts showing the wrong line numbers and locations for + // other source file and line breakpoints in the same file. + + // Below we search for the first resolved location in a breakpoint and report + // this as the breakpoint location since it will have a complete location + // that is at least loaded in the current process. + lldb::SBBreakpointLocation bp_loc; + const auto num_locs = bp.GetNumLocations(); + for (size_t i = 0; i < num_locs; ++i) { + bp_loc = bp.GetLocationAtIndex(i); + if (bp_loc.IsResolved()) + break; + } + // If not locations are resolved, use the first location. + if (!bp_loc.IsResolved()) + bp_loc = bp.GetLocationAtIndex(0); + auto bp_addr = bp_loc.GetAddress(); + + if (bp_addr.IsValid()) { + std::string formatted_addr = + "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_dap.target)); + object.try_emplace("instructionReference", formatted_addr); + auto line_entry = bp_addr.GetLineEntry(); + const auto line = line_entry.GetLine(); + if (line != UINT32_MAX) + object.try_emplace("line", line); + const auto column = line_entry.GetColumn(); + if (column != 0) + object.try_emplace("column", column); + object.try_emplace("source", CreateSource(line_entry)); + } +} + +bool Breakpoint::MatchesName(const char *name) { return bp.MatchesName(name); } + +void Breakpoint::SetBreakpoint() { + // See comments in BreakpointBase::GetBreakpointLabel() for details of why + // we add a label to our breakpoints. + bp.AddName(GetBreakpointLabel()); + if (!condition.empty()) + SetCondition(); + if (!hitCondition.empty()) + SetHitCondition(); +} diff --git a/lldb/tools/lldb-dap/Breakpoint.h b/lldb/tools/lldb-dap/Breakpoint.h new file mode 100644 index 00000000000000..47a9d9c59ae2b7 --- /dev/null +++ b/lldb/tools/lldb-dap/Breakpoint.h @@ -0,0 +1,33 @@ +//===-- Breakpoint.h --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_BREAKPOINT_H +#define LLDB_TOOLS_LLDB_DAP_BREAKPOINT_H + +#include "BreakpointBase.h" + +namespace lldb_dap { + +struct Breakpoint : public BreakpointBase { + // The LLDB breakpoint associated wit this source breakpoint + lldb::SBBreakpoint bp; + + Breakpoint() = default; + Breakpoint(const llvm::json::Object &obj) : BreakpointBase(obj){}; + Breakpoint(lldb::SBBreakpoint bp) : bp(bp) {} + + void SetCondition() override; + void SetHitCondition() override; + void CreateJsonObject(llvm::json::Object &object) override; + + bool MatchesName(const char *name); + void SetBreakpoint(); +}; +} // namespace lldb_dap + +#endif diff --git a/lldb/tools/lldb-dap/BreakpointBase.cpp b/lldb/tools/lldb-dap/BreakpointBase.cpp index fb4b27fbe315fc..519729f5519ffc 100644 --- a/lldb/tools/lldb-dap/BreakpointBase.cpp +++ b/lldb/tools/lldb-dap/BreakpointBase.cpp @@ -8,306 +8,13 @@ #include "BreakpointBase.h" #include "DAP.h" -#include "JSONUtils.h" #include "llvm/ADT/StringExtras.h" using namespace lldb_dap; BreakpointBase::BreakpointBase(const llvm::json::Object &obj) : condition(std::string(GetString(obj, "condition"))), - hitCondition(std::string(GetString(obj, "hitCondition"))), - logMessage(std::string(GetString(obj, "logMessage"))) {} - -void BreakpointBase::SetCondition() { bp.SetCondition(condition.c_str()); } - -void BreakpointBase::SetHitCondition() { - uint64_t hitCount = 0; - if (llvm::to_integer(hitCondition, hitCount)) - bp.SetIgnoreCount(hitCount - 1); -} - -lldb::SBError BreakpointBase::AppendLogMessagePart(llvm::StringRef part, - bool is_expr) { - if (is_expr) { - logMessageParts.emplace_back(part, is_expr); - } else { - std::string formatted; - lldb::SBError error = FormatLogText(part, formatted); - if (error.Fail()) - return error; - logMessageParts.emplace_back(formatted, is_expr); - } - return lldb::SBError(); -} - -// TODO: consolidate this code with the implementation in -// FormatEntity::ParseInternal(). -lldb::SBError BreakpointBase::FormatLogText(llvm::StringRef text, - std::string &formatted) { - lldb::SBError error; - while (!text.empty()) { - size_t backslash_pos = text.find_first_of('\\'); - if (backslash_pos == std::string::npos) { - formatted += text.str(); - return error; - } - - formatted += text.substr(0, backslash_pos).str(); - // Skip the characters before and including '\'. - text = text.drop_front(backslash_pos + 1); - - if (text.empty()) { - error.SetErrorString( - "'\\' character was not followed by another character"); - return error; - } - - const char desens_char = text[0]; - text = text.drop_front(); // Skip the desensitized char character - switch (desens_char) { - case 'a': - formatted.push_back('\a'); - break; - case 'b': - formatted.push_back('\b'); - break; - case 'f': - formatted.push_back('\f'); - break; - case 'n': - formatted.push_back('\n'); - break; - case 'r': - formatted.push_back('\r'); - break; - case 't': - formatted.push_back('\t'); - break; - case 'v': - formatted.push_back('\v'); - break; - case '\'': - formatted.push_back('\''); - break; - case '\\': - formatted.push_back('\\'); - break; - case '0': - // 1 to 3 octal chars - { - if (text.empty()) { - error.SetErrorString("missing octal number following '\\0'"); - return error; - } - - // Make a string that can hold onto the initial zero char, up to 3 - // octal digits, and a terminating NULL. - char oct_str[5] = {0, 0, 0, 0, 0}; - - size_t i; - for (i = 0; - i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7'); - ++i) { - oct_str[i] = text[i]; - } - - text = text.drop_front(i); - unsigned long octal_value = ::strtoul(oct_str, nullptr, 8); - if (octal_value <= UINT8_MAX) { - formatted.push_back((char)octal_value); - } else { - error.SetErrorString("octal number is larger than a single byte"); - return error; - } - } - break; - - case 'x': { - if (text.empty()) { - error.SetErrorString("missing hex number following '\\x'"); - return error; - } - // hex number in the text - if (isxdigit(text[0])) { - // Make a string that can hold onto two hex chars plus a - // NULL terminator - char hex_str[3] = {0, 0, 0}; - hex_str[0] = text[0]; - - text = text.drop_front(); - - if (!text.empty() && isxdigit(text[0])) { - hex_str[1] = text[0]; - text = text.drop_front(); - } - - unsigned long hex_value = strtoul(hex_str, nullptr, 16); - if (hex_value <= UINT8_MAX) { - formatted.push_back((char)hex_value); - } else { - error.SetErrorString("hex number is larger than a single byte"); - return error; - } - } else { - formatted.push_back(desens_char); - } - break; - } - - default: - // Just desensitize any other character by just printing what came - // after the '\' - formatted.push_back(desens_char); - break; - } - } - return error; -} - -// logMessage will be divided into array of LogMessagePart as two kinds: -// 1. raw print text message, and -// 2. interpolated expression for evaluation which is inside matching curly -// braces. -// -// The function tries to parse logMessage into a list of LogMessageParts -// for easy later access in BreakpointHitCallback. -void BreakpointBase::SetLogMessage() { - logMessageParts.clear(); - - // Contains unmatched open curly braces indices. - std::vector unmatched_curly_braces; - - // Contains all matched curly braces in logMessage. - // Loop invariant: matched_curly_braces_ranges are sorted by start index in - // ascending order without any overlap between them. - std::vector> matched_curly_braces_ranges; - - lldb::SBError error; - // Part1 - parse matched_curly_braces_ranges. - // locating all curly braced expression ranges in logMessage. - // The algorithm takes care of nested and imbalanced curly braces. - for (size_t i = 0; i < logMessage.size(); ++i) { - if (logMessage[i] == '{') { - unmatched_curly_braces.push_back(i); - } else if (logMessage[i] == '}') { - if (unmatched_curly_braces.empty()) - // Nothing to match. - continue; - - int last_unmatched_index = unmatched_curly_braces.back(); - unmatched_curly_braces.pop_back(); - - // Erase any matched ranges included in the new match. - while (!matched_curly_braces_ranges.empty()) { - assert(matched_curly_braces_ranges.back().first != - last_unmatched_index && - "How can a curley brace be matched twice?"); - if (matched_curly_braces_ranges.back().first < last_unmatched_index) - break; - - // This is a nested range let's earse it. - assert((size_t)matched_curly_braces_ranges.back().second < i); - matched_curly_braces_ranges.pop_back(); - } - - // Assert invariant. - assert(matched_curly_braces_ranges.empty() || - matched_curly_braces_ranges.back().first < last_unmatched_index); - matched_curly_braces_ranges.emplace_back(last_unmatched_index, i); - } - } - - // Part2 - parse raw text and expresions parts. - // All expression ranges have been parsed in matched_curly_braces_ranges. - // The code below uses matched_curly_braces_ranges to divide logMessage - // into raw text parts and expression parts. - int last_raw_text_start = 0; - for (const std::pair &curly_braces_range : - matched_curly_braces_ranges) { - // Raw text before open curly brace. - assert(curly_braces_range.first >= last_raw_text_start); - size_t raw_text_len = curly_braces_range.first - last_raw_text_start; - if (raw_text_len > 0) { - error = AppendLogMessagePart( - llvm::StringRef(logMessage.c_str() + last_raw_text_start, - raw_text_len), - /*is_expr=*/false); - if (error.Fail()) { - NotifyLogMessageError(error.GetCString()); - return; - } - } - - // Expression between curly braces. - assert(curly_braces_range.second > curly_braces_range.first); - size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1; - error = AppendLogMessagePart( - llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1, - expr_len), - /*is_expr=*/true); - if (error.Fail()) { - NotifyLogMessageError(error.GetCString()); - return; - } - - last_raw_text_start = curly_braces_range.second + 1; - } - // Trailing raw text after close curly brace. - assert(last_raw_text_start >= 0); - if (logMessage.size() > (size_t)last_raw_text_start) { - error = AppendLogMessagePart( - llvm::StringRef(logMessage.c_str() + last_raw_text_start, - logMessage.size() - last_raw_text_start), - /*is_expr=*/false); - if (error.Fail()) { - NotifyLogMessageError(error.GetCString()); - return; - } - } - - bp.SetCallback(BreakpointBase::BreakpointHitCallback, this); -} - -void BreakpointBase::NotifyLogMessageError(llvm::StringRef error) { - std::string message = "Log message has error: "; - message += error; - g_dap.SendOutput(OutputType::Console, message); -} - -/*static*/ -bool BreakpointBase::BreakpointHitCallback( - void *baton, lldb::SBProcess &process, lldb::SBThread &thread, - lldb::SBBreakpointLocation &location) { - if (!baton) - return true; - - BreakpointBase *bp = (BreakpointBase *)baton; - lldb::SBFrame frame = thread.GetSelectedFrame(); - - std::string output; - for (const BreakpointBase::LogMessagePart &messagePart : - bp->logMessageParts) { - if (messagePart.is_expr) { - // Try local frame variables first before fall back to expression - // evaluation - const std::string &expr_str = messagePart.text; - const char *expr = expr_str.c_str(); - lldb::SBValue value = - frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget); - if (value.GetError().Fail()) - value = frame.EvaluateExpression(expr); - output += VariableDescription(value).display_value; - } else { - output += messagePart.text; - } - } - if (!output.empty() && output.back() != '\n') - output.push_back('\n'); // Ensure log message has line break. - g_dap.SendOutput(OutputType::Console, output.c_str()); - - // Do not stop. - return false; -} + hitCondition(std::string(GetString(obj, "hitCondition"))) {} void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) { if (condition != request_bp.condition) { @@ -318,10 +25,6 @@ void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) { hitCondition = request_bp.hitCondition; SetHitCondition(); } - if (logMessage != request_bp.logMessage) { - logMessage = request_bp.logMessage; - SetLogMessage(); - } } const char *BreakpointBase::GetBreakpointLabel() { diff --git a/lldb/tools/lldb-dap/BreakpointBase.h b/lldb/tools/lldb-dap/BreakpointBase.h index 41787f78610215..5a04bb201615fc 100644 --- a/lldb/tools/lldb-dap/BreakpointBase.h +++ b/lldb/tools/lldb-dap/BreakpointBase.h @@ -9,7 +9,6 @@ #ifndef LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H #define LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H -#include "JSONUtils.h" #include "lldb/API/SBBreakpoint.h" #include "llvm/Support/JSON.h" #include @@ -18,44 +17,24 @@ namespace lldb_dap { struct BreakpointBase { - // logMessage part can be either a raw text or an expression. - struct LogMessagePart { - LogMessagePart(llvm::StringRef text, bool is_expr) - : text(text), is_expr(is_expr) {} - std::string text; - bool is_expr; - }; + // An optional expression for conditional breakpoints. std::string condition; // An optional expression that controls how many hits of the breakpoint are // ignored. The backend is expected to interpret the expression as needed std::string hitCondition; - // If this attribute exists and is non-empty, the backend must not 'break' - // (stop) but log the message instead. Expressions within {} are - // interpolated. - std::string logMessage; - std::vector logMessageParts; - // The LLDB breakpoint associated wit this source breakpoint - lldb::SBBreakpoint bp; BreakpointBase() = default; BreakpointBase(const llvm::json::Object &obj); + virtual ~BreakpointBase() = default; - void SetCondition(); - void SetHitCondition(); - void SetLogMessage(); - void UpdateBreakpoint(const BreakpointBase &request_bp); + virtual void SetCondition() = 0; + virtual void SetHitCondition() = 0; + virtual void CreateJsonObject(llvm::json::Object &object) = 0; - // Format \param text and return formatted text in \param formatted. - // \return any formatting failures. - lldb::SBError FormatLogText(llvm::StringRef text, std::string &formatted); - lldb::SBError AppendLogMessagePart(llvm::StringRef part, bool is_expr); - void NotifyLogMessageError(llvm::StringRef error); + void UpdateBreakpoint(const BreakpointBase &request_bp); static const char *GetBreakpointLabel(); - static bool BreakpointHitCallback(void *baton, lldb::SBProcess &process, - lldb::SBThread &thread, - lldb::SBBreakpointLocation &location); }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index 554567eb3b0e23..f8c0e4ecf36c2f 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -24,6 +24,7 @@ tablegen(LLVM Options.inc -gen-opt-parser-defs) add_public_tablegen_target(LLDBDAPOptionsTableGen) add_lldb_tool(lldb-dap lldb-dap.cpp + Breakpoint.cpp BreakpointBase.cpp ExceptionBreakpoint.cpp FifoFiles.cpp diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp index d4bdb976500ecd..21743bf908706d 100644 --- a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp +++ b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp @@ -12,21 +12,13 @@ namespace lldb_dap { FunctionBreakpoint::FunctionBreakpoint(const llvm::json::Object &obj) - : BreakpointBase(obj), functionName(std::string(GetString(obj, "name"))) {} + : Breakpoint(obj), functionName(std::string(GetString(obj, "name"))) {} void FunctionBreakpoint::SetBreakpoint() { if (functionName.empty()) return; bp = g_dap.target.BreakpointCreateByName(functionName.c_str()); - // See comments in BreakpointBase::GetBreakpointLabel() for details of why - // we add a label to our breakpoints. - bp.AddName(GetBreakpointLabel()); - if (!condition.empty()) - SetCondition(); - if (!hitCondition.empty()) - SetHitCondition(); - if (!logMessage.empty()) - SetLogMessage(); + Breakpoint::SetBreakpoint(); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.h b/lldb/tools/lldb-dap/FunctionBreakpoint.h index fc23e94e128763..b15ff1931a6b22 100644 --- a/lldb/tools/lldb-dap/FunctionBreakpoint.h +++ b/lldb/tools/lldb-dap/FunctionBreakpoint.h @@ -9,11 +9,11 @@ #ifndef LLDB_TOOLS_LLDB_DAP_FUNCTIONBREAKPOINT_H #define LLDB_TOOLS_LLDB_DAP_FUNCTIONBREAKPOINT_H -#include "BreakpointBase.h" +#include "Breakpoint.h" namespace lldb_dap { -struct FunctionBreakpoint : public BreakpointBase { +struct FunctionBreakpoint : public Breakpoint { std::string functionName; FunctionBreakpoint() = default; diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index a8b438d9d6df39..878449a91aa66a 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -364,54 +364,14 @@ llvm::json::Value CreateScope(const llvm::StringRef name, // }, // "required": [ "verified" ] // } -llvm::json::Value CreateBreakpoint(lldb::SBBreakpoint &bp, +llvm::json::Value CreateBreakpoint(BreakpointBase *bp, std::optional request_path, std::optional request_line, std::optional request_column) { - // Each breakpoint location is treated as a separate breakpoint for VS code. - // They don't have the notion of a single breakpoint with multiple locations. llvm::json::Object object; - if (!bp.IsValid()) - return llvm::json::Value(std::move(object)); - - object.try_emplace("verified", bp.GetNumResolvedLocations() > 0); - object.try_emplace("id", bp.GetID()); - // VS Code DAP doesn't currently allow one breakpoint to have multiple - // locations so we just report the first one. If we report all locations - // then the IDE starts showing the wrong line numbers and locations for - // other source file and line breakpoints in the same file. - - // Below we search for the first resolved location in a breakpoint and report - // this as the breakpoint location since it will have a complete location - // that is at least loaded in the current process. - lldb::SBBreakpointLocation bp_loc; - const auto num_locs = bp.GetNumLocations(); - for (size_t i = 0; i < num_locs; ++i) { - bp_loc = bp.GetLocationAtIndex(i); - if (bp_loc.IsResolved()) - break; - } - // If not locations are resolved, use the first location. - if (!bp_loc.IsResolved()) - bp_loc = bp.GetLocationAtIndex(0); - auto bp_addr = bp_loc.GetAddress(); - if (request_path) object.try_emplace("source", CreateSource(*request_path)); - - if (bp_addr.IsValid()) { - std::string formatted_addr = - "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_dap.target)); - object.try_emplace("instructionReference", formatted_addr); - auto line_entry = bp_addr.GetLineEntry(); - const auto line = line_entry.GetLine(); - if (line != UINT32_MAX) - object.try_emplace("line", line); - const auto column = line_entry.GetColumn(); - if (column != 0) - object.try_emplace("column", column); - object.try_emplace("source", CreateSource(line_entry)); - } + bp->CreateJsonObject(object); // We try to add request_line as a fallback if (request_line) object.try_emplace("line", *request_line); @@ -506,7 +466,7 @@ llvm::json::Value CreateModule(lldb::SBModule &module) { return llvm::json::Value(std::move(object)); } -void AppendBreakpoint(lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints, +void AppendBreakpoint(BreakpointBase *bp, llvm::json::Array &breakpoints, std::optional request_path, std::optional request_line) { breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line)); diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 62338548890c0c..1515f5ba2e5f4d 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -9,6 +9,7 @@ #ifndef LLDB_TOOLS_LLDB_DAP_JSONUTILS_H #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H +#include "BreakpointBase.h" #include "DAPForward.h" #include "lldb/API/SBModule.h" #include "llvm/ADT/StringRef.h" @@ -191,7 +192,7 @@ void FillResponse(const llvm::json::Object &request, /// provided by the setBreakpoints request are returned to the IDE as a /// fallback. void AppendBreakpoint( - lldb::SBBreakpoint &bp, llvm::json::Array &breakpoints, + BreakpointBase *bp, llvm::json::Array &breakpoints, std::optional request_path = std::nullopt, std::optional request_line = std::nullopt); @@ -223,7 +224,7 @@ void AppendBreakpoint( /// A "Breakpoint" JSON object with that follows the formal JSON /// definition outlined by Microsoft. llvm::json::Value -CreateBreakpoint(lldb::SBBreakpoint &bp, +CreateBreakpoint(BreakpointBase *bp, std::optional request_path = std::nullopt, std::optional request_line = std::nullopt, std::optional request_column = std::nullopt); diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp index 3bd83c0a6874de..f5dd1346cb9e54 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp +++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp @@ -12,22 +12,308 @@ namespace lldb_dap { SourceBreakpoint::SourceBreakpoint(const llvm::json::Object &obj) - : BreakpointBase(obj), line(GetUnsigned(obj, "line", 0)), - column(GetUnsigned(obj, "column", 0)) {} + : Breakpoint(obj), logMessage(std::string(GetString(obj, "logMessage"))), + line(GetUnsigned(obj, "line", 0)), column(GetUnsigned(obj, "column", 0)) { +} void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { lldb::SBFileSpecList module_list; bp = g_dap.target.BreakpointCreateByLocation(source_path.str().c_str(), line, column, 0, module_list); - // See comments in BreakpointBase::GetBreakpointLabel() for details of why - // we add a label to our breakpoints. - bp.AddName(GetBreakpointLabel()); - if (!condition.empty()) - SetCondition(); - if (!hitCondition.empty()) - SetHitCondition(); if (!logMessage.empty()) SetLogMessage(); + Breakpoint::SetBreakpoint(); +} + +void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) { + if (logMessage != request_bp.logMessage) { + logMessage = request_bp.logMessage; + SetLogMessage(); + } + BreakpointBase::UpdateBreakpoint(request_bp); +} + +lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part, + bool is_expr) { + if (is_expr) { + logMessageParts.emplace_back(part, is_expr); + } else { + std::string formatted; + lldb::SBError error = FormatLogText(part, formatted); + if (error.Fail()) + return error; + logMessageParts.emplace_back(formatted, is_expr); + } + return lldb::SBError(); +} + +// TODO: consolidate this code with the implementation in +// FormatEntity::ParseInternal(). +lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text, + std::string &formatted) { + lldb::SBError error; + while (!text.empty()) { + size_t backslash_pos = text.find_first_of('\\'); + if (backslash_pos == std::string::npos) { + formatted += text.str(); + return error; + } + + formatted += text.substr(0, backslash_pos).str(); + // Skip the characters before and including '\'. + text = text.drop_front(backslash_pos + 1); + + if (text.empty()) { + error.SetErrorString( + "'\\' character was not followed by another character"); + return error; + } + + const char desens_char = text[0]; + text = text.drop_front(); // Skip the desensitized char character + switch (desens_char) { + case 'a': + formatted.push_back('\a'); + break; + case 'b': + formatted.push_back('\b'); + break; + case 'f': + formatted.push_back('\f'); + break; + case 'n': + formatted.push_back('\n'); + break; + case 'r': + formatted.push_back('\r'); + break; + case 't': + formatted.push_back('\t'); + break; + case 'v': + formatted.push_back('\v'); + break; + case '\'': + formatted.push_back('\''); + break; + case '\\': + formatted.push_back('\\'); + break; + case '0': + // 1 to 3 octal chars + { + if (text.empty()) { + error.SetErrorString("missing octal number following '\\0'"); + return error; + } + + // Make a string that can hold onto the initial zero char, up to 3 + // octal digits, and a terminating NULL. + char oct_str[5] = {0, 0, 0, 0, 0}; + + size_t i; + for (i = 0; + i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7'); + ++i) { + oct_str[i] = text[i]; + } + + text = text.drop_front(i); + unsigned long octal_value = ::strtoul(oct_str, nullptr, 8); + if (octal_value <= UINT8_MAX) { + formatted.push_back((char)octal_value); + } else { + error.SetErrorString("octal number is larger than a single byte"); + return error; + } + } + break; + + case 'x': { + if (text.empty()) { + error.SetErrorString("missing hex number following '\\x'"); + return error; + } + // hex number in the text + if (isxdigit(text[0])) { + // Make a string that can hold onto two hex chars plus a + // NULL terminator + char hex_str[3] = {0, 0, 0}; + hex_str[0] = text[0]; + + text = text.drop_front(); + + if (!text.empty() && isxdigit(text[0])) { + hex_str[1] = text[0]; + text = text.drop_front(); + } + + unsigned long hex_value = strtoul(hex_str, nullptr, 16); + if (hex_value <= UINT8_MAX) { + formatted.push_back((char)hex_value); + } else { + error.SetErrorString("hex number is larger than a single byte"); + return error; + } + } else { + formatted.push_back(desens_char); + } + break; + } + + default: + // Just desensitize any other character by just printing what came + // after the '\' + formatted.push_back(desens_char); + break; + } + } + return error; +} + +// logMessage will be divided into array of LogMessagePart as two kinds: +// 1. raw print text message, and +// 2. interpolated expression for evaluation which is inside matching curly +// braces. +// +// The function tries to parse logMessage into a list of LogMessageParts +// for easy later access in BreakpointHitCallback. +void SourceBreakpoint::SetLogMessage() { + logMessageParts.clear(); + + // Contains unmatched open curly braces indices. + std::vector unmatched_curly_braces; + + // Contains all matched curly braces in logMessage. + // Loop invariant: matched_curly_braces_ranges are sorted by start index in + // ascending order without any overlap between them. + std::vector> matched_curly_braces_ranges; + + lldb::SBError error; + // Part1 - parse matched_curly_braces_ranges. + // locating all curly braced expression ranges in logMessage. + // The algorithm takes care of nested and imbalanced curly braces. + for (size_t i = 0; i < logMessage.size(); ++i) { + if (logMessage[i] == '{') { + unmatched_curly_braces.push_back(i); + } else if (logMessage[i] == '}') { + if (unmatched_curly_braces.empty()) + // Nothing to match. + continue; + + int last_unmatched_index = unmatched_curly_braces.back(); + unmatched_curly_braces.pop_back(); + + // Erase any matched ranges included in the new match. + while (!matched_curly_braces_ranges.empty()) { + assert(matched_curly_braces_ranges.back().first != + last_unmatched_index && + "How can a curley brace be matched twice?"); + if (matched_curly_braces_ranges.back().first < last_unmatched_index) + break; + + // This is a nested range let's earse it. + assert((size_t)matched_curly_braces_ranges.back().second < i); + matched_curly_braces_ranges.pop_back(); + } + + // Assert invariant. + assert(matched_curly_braces_ranges.empty() || + matched_curly_braces_ranges.back().first < last_unmatched_index); + matched_curly_braces_ranges.emplace_back(last_unmatched_index, i); + } + } + + // Part2 - parse raw text and expresions parts. + // All expression ranges have been parsed in matched_curly_braces_ranges. + // The code below uses matched_curly_braces_ranges to divide logMessage + // into raw text parts and expression parts. + int last_raw_text_start = 0; + for (const std::pair &curly_braces_range : + matched_curly_braces_ranges) { + // Raw text before open curly brace. + assert(curly_braces_range.first >= last_raw_text_start); + size_t raw_text_len = curly_braces_range.first - last_raw_text_start; + if (raw_text_len > 0) { + error = AppendLogMessagePart( + llvm::StringRef(logMessage.c_str() + last_raw_text_start, + raw_text_len), + /*is_expr=*/false); + if (error.Fail()) { + NotifyLogMessageError(error.GetCString()); + return; + } + } + + // Expression between curly braces. + assert(curly_braces_range.second > curly_braces_range.first); + size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1; + error = AppendLogMessagePart( + llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1, + expr_len), + /*is_expr=*/true); + if (error.Fail()) { + NotifyLogMessageError(error.GetCString()); + return; + } + + last_raw_text_start = curly_braces_range.second + 1; + } + // Trailing raw text after close curly brace. + assert(last_raw_text_start >= 0); + if (logMessage.size() > (size_t)last_raw_text_start) { + error = AppendLogMessagePart( + llvm::StringRef(logMessage.c_str() + last_raw_text_start, + logMessage.size() - last_raw_text_start), + /*is_expr=*/false); + if (error.Fail()) { + NotifyLogMessageError(error.GetCString()); + return; + } + } + + bp.SetCallback(BreakpointHitCallback, this); +} + +void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) { + std::string message = "Log message has error: "; + message += error; + g_dap.SendOutput(OutputType::Console, message); +} + +/*static*/ +bool SourceBreakpoint::BreakpointHitCallback( + void *baton, lldb::SBProcess &process, lldb::SBThread &thread, + lldb::SBBreakpointLocation &location) { + if (!baton) + return true; + + SourceBreakpoint *bp = (SourceBreakpoint *)baton; + lldb::SBFrame frame = thread.GetSelectedFrame(); + + std::string output; + for (const SourceBreakpoint::LogMessagePart &messagePart : + bp->logMessageParts) { + if (messagePart.is_expr) { + // Try local frame variables first before fall back to expression + // evaluation + const std::string &expr_str = messagePart.text; + const char *expr = expr_str.c_str(); + lldb::SBValue value = + frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget); + if (value.GetError().Fail()) + value = frame.EvaluateExpression(expr); + output += VariableDescription(value).display_value; + } else { + output += messagePart.text; + } + } + if (!output.empty() && output.back() != '\n') + output.push_back('\n'); // Ensure log message has line break. + g_dap.SendOutput(OutputType::Console, output.c_str()); + + // Do not stop. + return false; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.h b/lldb/tools/lldb-dap/SourceBreakpoint.h index f4b54a44fc6875..aa3fbe6d0f96d2 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.h +++ b/lldb/tools/lldb-dap/SourceBreakpoint.h @@ -9,21 +9,45 @@ #ifndef LLDB_TOOLS_LLDB_DAP_SOURCEBREAKPOINT_H #define LLDB_TOOLS_LLDB_DAP_SOURCEBREAKPOINT_H -#include "BreakpointBase.h" +#include "Breakpoint.h" #include "llvm/ADT/StringRef.h" namespace lldb_dap { -struct SourceBreakpoint : public BreakpointBase { +struct SourceBreakpoint : public Breakpoint { + // logMessage part can be either a raw text or an expression. + struct LogMessagePart { + LogMessagePart(llvm::StringRef text, bool is_expr) + : text(text), is_expr(is_expr) {} + std::string text; + bool is_expr; + }; + // If this attribute exists and is non-empty, the backend must not 'break' + // (stop) but log the message instead. Expressions within {} are + // interpolated. + std::string logMessage; + std::vector logMessageParts; uint32_t line; ///< The source line of the breakpoint or logpoint uint32_t column; ///< An optional source column of the breakpoint - SourceBreakpoint() : BreakpointBase(), line(0), column(0) {} + SourceBreakpoint() : Breakpoint(), line(0), column(0) {} SourceBreakpoint(const llvm::json::Object &obj); // Set this breakpoint in LLDB as a new breakpoint void SetBreakpoint(const llvm::StringRef source_path); + void UpdateBreakpoint(const SourceBreakpoint &request_bp); + + void SetLogMessage(); + // Format \param text and return formatted text in \param formatted. + // \return any formatting failures. + lldb::SBError FormatLogText(llvm::StringRef text, std::string &formatted); + lldb::SBError AppendLogMessagePart(llvm::StringRef part, bool is_expr); + void NotifyLogMessageError(llvm::StringRef error); + + static bool BreakpointHitCallback(void *baton, lldb::SBProcess &process, + lldb::SBThread &thread, + lldb::SBBreakpointLocation &location); }; inline bool operator<(const SourceBreakpoint &lhs, diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 01494dcc7da00f..67022347e6d624 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -525,7 +525,8 @@ void EventThreadFunction() { if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - auto bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event); + auto bp = + Breakpoint(lldb::SBBreakpoint::GetBreakpointFromEvent(event)); // If the breakpoint was originated from the IDE, it will have the // BreakpointBase::GetBreakpointLabel() label attached. Regardless // of wether the locations were added or removed, the breakpoint @@ -541,7 +542,7 @@ void EventThreadFunction() { // mapped. Note that CreateBreakpoint doesn't apply source mapping. // Besides, the current implementation of VSCode ignores the // "source" element of breakpoint events. - llvm::json::Value source_bp = CreateBreakpoint(bp); + llvm::json::Value source_bp = CreateBreakpoint(&bp); source_bp.getAsObject()->erase("source"); body.try_emplace("breakpoint", source_bp); @@ -2345,7 +2346,7 @@ void request_setBreakpoints(const llvm::json::Object &request) { existing_source_bps->second.find(src_bp.line); if (existing_bp != existing_source_bps->second.end()) { existing_bp->second.UpdateBreakpoint(src_bp); - AppendBreakpoint(existing_bp->second.bp, response_breakpoints, path, + AppendBreakpoint(&existing_bp->second, response_breakpoints, path, src_bp.line); continue; } @@ -2354,7 +2355,7 @@ void request_setBreakpoints(const llvm::json::Object &request) { g_dap.source_breakpoints[path][src_bp.line] = src_bp; SourceBreakpoint &new_bp = g_dap.source_breakpoints[path][src_bp.line]; new_bp.SetBreakpoint(path.data()); - AppendBreakpoint(new_bp.bp, response_breakpoints, path, new_bp.line); + AppendBreakpoint(&new_bp, response_breakpoints, path, new_bp.line); } } } @@ -2567,7 +2568,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) { // handled it here and we don't need to set a new breakpoint below. request_bps.erase(request_pos); // Add this breakpoint info to the response - AppendBreakpoint(pair.second.bp, response_breakpoints); + AppendBreakpoint(&pair.second, response_breakpoints); } } // Remove any breakpoints that are no longer in our list @@ -2581,7 +2582,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) { g_dap.function_breakpoints[pair.first()] = std::move(pair.second); FunctionBreakpoint &new_bp = g_dap.function_breakpoints[pair.first()]; new_bp.SetBreakpoint(); - AppendBreakpoint(new_bp.bp, response_breakpoints); + AppendBreakpoint(&new_bp, response_breakpoints); } llvm::json::Object body; @@ -3582,8 +3583,8 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) { FillResponse(request, response); llvm::json::Array response_breakpoints; for (uint32_t i = 0; g_dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) { - auto bp = g_dap.target.GetBreakpointAtIndex(i); - AppendBreakpoint(bp, response_breakpoints); + auto bp = Breakpoint(g_dap.target.GetBreakpointAtIndex(i)); + AppendBreakpoint(&bp, response_breakpoints); } llvm::json::Object body; body.try_emplace("breakpoints", std::move(response_breakpoints)); diff --git a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn index d8292df8c0e74f..98c2068f6da291 100644 --- a/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn +++ b/llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn @@ -38,6 +38,7 @@ executable("lldb-dap") { # FIXME: rpath/install_name stuff on macOS for framework on macOS sources = [ + "Breakpoint.cpp", "BreakpointBase.cpp", "DAP.cpp", "ExceptionBreakpoint.cpp",