Adding more supported operators to DIL breaks tests in `DWIMPrint` and `lldb-dap`, which shouldn't be simply adjusted for new DIL capabilities. They act as a check for the boundaries of what subset of expressions `DWIMPrint` and `lldb-dap` expect to be evaluated when using `GetValueForVariableExpressionPath` function. With this patch, the caller can now pick a mode that limits the expressions DIL can evaluate, which ensures the expected preexisting behavior. More operators can now be safely added to DIL, which can still be evaluated by DIL when using `frame var` command or the API call with Full mode selected (or not specified at all). DIL will only attempt evaluating expressions that contain operations allowed by a selected mode: - Simple: identifiers, operators: '.' - Legacy: identifiers, integers, operators: '.', '->', '*', '&', '[]' - Full: everything supported by DIL
420 lines
14 KiB
C++
420 lines
14 KiB
C++
//===-- SourceBreakpoint.cpp ------------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "SourceBreakpoint.h"
|
|
#include "BreakpointBase.h"
|
|
#include "DAP.h"
|
|
#include "JSONUtils.h"
|
|
#include "lldb/API/SBBreakpoint.h"
|
|
#include "lldb/API/SBFileSpec.h"
|
|
#include "lldb/API/SBFileSpecList.h"
|
|
#include "lldb/API/SBFrame.h"
|
|
#include "lldb/API/SBInstruction.h"
|
|
#include "lldb/API/SBMutex.h"
|
|
#include "lldb/API/SBSymbol.h"
|
|
#include "lldb/API/SBTarget.h"
|
|
#include "lldb/API/SBThread.h"
|
|
#include "lldb/API/SBValue.h"
|
|
#include "lldb/lldb-enumerations.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <mutex>
|
|
#include <utility>
|
|
|
|
namespace lldb_dap {
|
|
|
|
SourceBreakpoint::SourceBreakpoint(DAP &dap,
|
|
const protocol::SourceBreakpoint &breakpoint)
|
|
: Breakpoint(dap, breakpoint.condition, breakpoint.hitCondition),
|
|
m_log_message(breakpoint.logMessage.value_or("")),
|
|
m_line(breakpoint.line),
|
|
m_column(breakpoint.column.value_or(LLDB_INVALID_COLUMN_NUMBER)) {}
|
|
|
|
llvm::Error SourceBreakpoint::SetBreakpoint(const protocol::Source &source) {
|
|
lldb::SBMutex lock = m_dap.GetAPIMutex();
|
|
std::lock_guard<lldb::SBMutex> guard(lock);
|
|
|
|
if (m_line == 0)
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Invalid line number.");
|
|
|
|
if (source.sourceReference) {
|
|
// Breakpoint set by assembly source.
|
|
if (source.adapterData && source.adapterData->persistenceData) {
|
|
// Prefer use the adapter persitence data, because this could be a
|
|
// breakpoint from a previous session where the `sourceReference` is not
|
|
// valid anymore.
|
|
if (llvm::Error error = CreateAssemblyBreakpointWithPersistenceData(
|
|
*source.adapterData->persistenceData))
|
|
return error;
|
|
} else {
|
|
if (llvm::Error error = CreateAssemblyBreakpointWithSourceReference(
|
|
*source.sourceReference))
|
|
return error;
|
|
}
|
|
} else {
|
|
CreatePathBreakpoint(source);
|
|
}
|
|
|
|
if (!m_log_message.empty())
|
|
SetLogMessage();
|
|
Breakpoint::SetBreakpoint();
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) {
|
|
if (m_log_message != request_bp.m_log_message) {
|
|
m_log_message = request_bp.m_log_message;
|
|
SetLogMessage();
|
|
}
|
|
BreakpointBase::UpdateBreakpoint(request_bp);
|
|
}
|
|
|
|
void SourceBreakpoint::CreatePathBreakpoint(const protocol::Source &source) {
|
|
const auto source_path = source.path.value_or("");
|
|
lldb::SBFileSpecList module_list;
|
|
m_bp = m_dap.target.BreakpointCreateByLocation(source_path.c_str(), m_line,
|
|
m_column, 0, module_list);
|
|
}
|
|
|
|
llvm::Error SourceBreakpoint::CreateAssemblyBreakpointWithSourceReference(
|
|
int64_t source_reference) {
|
|
std::optional<lldb::addr_t> raw_addr =
|
|
m_dap.GetSourceReferenceAddress(source_reference);
|
|
if (!raw_addr)
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Invalid sourceReference.");
|
|
|
|
lldb::SBAddress source_address(*raw_addr, m_dap.target);
|
|
if (!source_address.IsValid())
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Invalid sourceReference.");
|
|
|
|
lldb::SBSymbol symbol = source_address.GetSymbol();
|
|
if (!symbol.IsValid()) {
|
|
// FIXME: Support assembly breakpoints without a valid symbol.
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Breakpoints in assembly without a valid "
|
|
"symbol are not supported yet.");
|
|
}
|
|
|
|
lldb::SBInstructionList inst_list =
|
|
m_dap.target.ReadInstructions(symbol.GetStartAddress(), m_line);
|
|
if (inst_list.GetSize() < m_line)
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"Invalid instruction list size.");
|
|
|
|
lldb::SBAddress address =
|
|
inst_list.GetInstructionAtIndex(m_line - 1).GetAddress();
|
|
|
|
m_bp = m_dap.target.BreakpointCreateBySBAddress(address);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error SourceBreakpoint::CreateAssemblyBreakpointWithPersistenceData(
|
|
const protocol::PersistenceData &persistence_data) {
|
|
lldb::SBFileSpec file_spec(persistence_data.module_path.c_str());
|
|
lldb::SBFileSpecList comp_unit_list;
|
|
lldb::SBFileSpecList file_spec_list;
|
|
file_spec_list.Append(file_spec);
|
|
m_bp = m_dap.target.BreakpointCreateByName(
|
|
persistence_data.symbol_name.c_str(), lldb::eFunctionNameTypeFull,
|
|
lldb::eLanguageTypeUnknown, m_line - 1, true, file_spec_list,
|
|
comp_unit_list);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part,
|
|
bool is_expr) {
|
|
if (is_expr) {
|
|
m_log_message_parts.emplace_back(part, is_expr);
|
|
} else {
|
|
std::string formatted;
|
|
lldb::SBError error = FormatLogText(part, formatted);
|
|
if (error.Fail())
|
|
return error;
|
|
m_log_message_parts.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 (std::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() && std::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() {
|
|
m_log_message_parts.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 < m_log_message.size(); ++i) {
|
|
if (m_log_message[i] == '{') {
|
|
unmatched_curly_braces.push_back(i);
|
|
} else if (m_log_message[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(m_log_message.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(m_log_message.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 (m_log_message.size() > (size_t)last_raw_text_start) {
|
|
error = AppendLogMessagePart(
|
|
llvm::StringRef(m_log_message.c_str() + last_raw_text_start,
|
|
m_log_message.size() - last_raw_text_start),
|
|
/*is_expr=*/false);
|
|
if (error.Fail()) {
|
|
NotifyLogMessageError(error.GetCString());
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_bp.SetCallback(BreakpointHitCallback, this);
|
|
}
|
|
|
|
void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {
|
|
std::string message = "Log message has error: ";
|
|
message += error;
|
|
m_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->m_log_message_parts) {
|
|
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, lldb::eDILModeLegacy);
|
|
if (value.GetError().Fail())
|
|
value = frame.EvaluateExpression(expr);
|
|
output += VariableDescription(
|
|
value, bp->m_dap.configuration.enableAutoVariableSummaries)
|
|
.display_value;
|
|
} else {
|
|
output += messagePart.text;
|
|
}
|
|
}
|
|
if (!output.empty() && output.back() != '\n')
|
|
output.push_back('\n'); // Ensure log message has line break.
|
|
bp->m_dap.SendOutput(OutputType::Console, output.c_str());
|
|
|
|
// Do not stop.
|
|
return false;
|
|
}
|
|
|
|
} // namespace lldb_dap
|