299 changes: 1 addition & 298 deletions lldb/tools/lldb-dap/BreakpointBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<std::pair<int, int>> 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<int, int> &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) {
Expand All @@ -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() {
Expand Down
33 changes: 6 additions & 27 deletions lldb/tools/lldb-dap/BreakpointBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <string>
Expand All @@ -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<LogMessagePart> 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
Expand Down
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 2 additions & 10 deletions lldb/tools/lldb-dap/FunctionBreakpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions lldb/tools/lldb-dap/FunctionBreakpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 3 additions & 43 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<llvm::StringRef> request_path,
std::optional<uint32_t> request_line,
std::optional<uint32_t> 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);
Expand Down Expand Up @@ -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<llvm::StringRef> request_path,
std::optional<uint32_t> request_line) {
breakpoints.emplace_back(CreateBreakpoint(bp, request_path, request_line));
Expand Down
5 changes: 3 additions & 2 deletions lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<llvm::StringRef> request_path = std::nullopt,
std::optional<uint32_t> request_line = std::nullopt);

Expand Down Expand Up @@ -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<llvm::StringRef> request_path = std::nullopt,
std::optional<uint32_t> request_line = std::nullopt,
std::optional<uint32_t> request_column = std::nullopt);
Expand Down
304 changes: 295 additions & 9 deletions lldb/tools/lldb-dap/SourceBreakpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> 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<std::pair<int, int>> 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<int, int> &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
30 changes: 27 additions & 3 deletions lldb/tools/lldb-dap/SourceBreakpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LogMessagePart> 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,
Expand Down
17 changes: 9 additions & 8 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions llvm/utils/gn/secondary/lldb/tools/lldb-dap/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down