[NFC][Coroutines] Use structured binding with llvm::enumerate in CoroSplit (#116879)
[llvm-project.git] / lldb / tools / lldb-dap / SourceBreakpoint.cpp
blob418e205312c9f8b9a2ae164bb865199402673b49
1 //===-- SourceBreakpoint.cpp ------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
9 #include "SourceBreakpoint.h"
10 #include "BreakpointBase.h"
11 #include "DAP.h"
12 #include "JSONUtils.h"
13 #include "lldb/API/SBBreakpoint.h"
14 #include "lldb/API/SBFileSpecList.h"
15 #include "lldb/API/SBFrame.h"
16 #include "lldb/API/SBTarget.h"
17 #include "lldb/API/SBThread.h"
18 #include "lldb/API/SBValue.h"
19 #include "lldb/lldb-enumerations.h"
20 #include <cassert>
21 #include <cctype>
22 #include <cstdlib>
23 #include <utility>
25 namespace lldb_dap {
27 SourceBreakpoint::SourceBreakpoint(DAP &dap, const llvm::json::Object &obj)
28 : Breakpoint(dap, obj),
29 logMessage(std::string(GetString(obj, "logMessage"))),
30 line(GetUnsigned(obj, "line", 0)), column(GetUnsigned(obj, "column", 0)) {
33 void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) {
34 lldb::SBFileSpecList module_list;
35 bp = dap.target.BreakpointCreateByLocation(source_path.str().c_str(), line,
36 column, 0, module_list);
37 if (!logMessage.empty())
38 SetLogMessage();
39 Breakpoint::SetBreakpoint();
42 void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) {
43 if (logMessage != request_bp.logMessage) {
44 logMessage = request_bp.logMessage;
45 SetLogMessage();
47 BreakpointBase::UpdateBreakpoint(request_bp);
50 lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part,
51 bool is_expr) {
52 if (is_expr) {
53 logMessageParts.emplace_back(part, is_expr);
54 } else {
55 std::string formatted;
56 lldb::SBError error = FormatLogText(part, formatted);
57 if (error.Fail())
58 return error;
59 logMessageParts.emplace_back(formatted, is_expr);
61 return lldb::SBError();
64 // TODO: consolidate this code with the implementation in
65 // FormatEntity::ParseInternal().
66 lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text,
67 std::string &formatted) {
68 lldb::SBError error;
69 while (!text.empty()) {
70 size_t backslash_pos = text.find_first_of('\\');
71 if (backslash_pos == std::string::npos) {
72 formatted += text.str();
73 return error;
76 formatted += text.substr(0, backslash_pos).str();
77 // Skip the characters before and including '\'.
78 text = text.drop_front(backslash_pos + 1);
80 if (text.empty()) {
81 error.SetErrorString(
82 "'\\' character was not followed by another character");
83 return error;
86 const char desens_char = text[0];
87 text = text.drop_front(); // Skip the desensitized char character
88 switch (desens_char) {
89 case 'a':
90 formatted.push_back('\a');
91 break;
92 case 'b':
93 formatted.push_back('\b');
94 break;
95 case 'f':
96 formatted.push_back('\f');
97 break;
98 case 'n':
99 formatted.push_back('\n');
100 break;
101 case 'r':
102 formatted.push_back('\r');
103 break;
104 case 't':
105 formatted.push_back('\t');
106 break;
107 case 'v':
108 formatted.push_back('\v');
109 break;
110 case '\'':
111 formatted.push_back('\'');
112 break;
113 case '\\':
114 formatted.push_back('\\');
115 break;
116 case '0':
117 // 1 to 3 octal chars
119 if (text.empty()) {
120 error.SetErrorString("missing octal number following '\\0'");
121 return error;
124 // Make a string that can hold onto the initial zero char, up to 3
125 // octal digits, and a terminating NULL.
126 char oct_str[5] = {0, 0, 0, 0, 0};
128 size_t i;
129 for (i = 0;
130 i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7');
131 ++i) {
132 oct_str[i] = text[i];
135 text = text.drop_front(i);
136 unsigned long octal_value = ::strtoul(oct_str, nullptr, 8);
137 if (octal_value <= UINT8_MAX) {
138 formatted.push_back((char)octal_value);
139 } else {
140 error.SetErrorString("octal number is larger than a single byte");
141 return error;
144 break;
146 case 'x': {
147 if (text.empty()) {
148 error.SetErrorString("missing hex number following '\\x'");
149 return error;
151 // hex number in the text
152 if (std::isxdigit(text[0])) {
153 // Make a string that can hold onto two hex chars plus a
154 // NULL terminator
155 char hex_str[3] = {0, 0, 0};
156 hex_str[0] = text[0];
158 text = text.drop_front();
160 if (!text.empty() && std::isxdigit(text[0])) {
161 hex_str[1] = text[0];
162 text = text.drop_front();
165 unsigned long hex_value = strtoul(hex_str, nullptr, 16);
166 if (hex_value <= UINT8_MAX) {
167 formatted.push_back((char)hex_value);
168 } else {
169 error.SetErrorString("hex number is larger than a single byte");
170 return error;
172 } else {
173 formatted.push_back(desens_char);
175 break;
178 default:
179 // Just desensitize any other character by just printing what came
180 // after the '\'
181 formatted.push_back(desens_char);
182 break;
185 return error;
188 // logMessage will be divided into array of LogMessagePart as two kinds:
189 // 1. raw print text message, and
190 // 2. interpolated expression for evaluation which is inside matching curly
191 // braces.
193 // The function tries to parse logMessage into a list of LogMessageParts
194 // for easy later access in BreakpointHitCallback.
195 void SourceBreakpoint::SetLogMessage() {
196 logMessageParts.clear();
198 // Contains unmatched open curly braces indices.
199 std::vector<int> unmatched_curly_braces;
201 // Contains all matched curly braces in logMessage.
202 // Loop invariant: matched_curly_braces_ranges are sorted by start index in
203 // ascending order without any overlap between them.
204 std::vector<std::pair<int, int>> matched_curly_braces_ranges;
206 lldb::SBError error;
207 // Part1 - parse matched_curly_braces_ranges.
208 // locating all curly braced expression ranges in logMessage.
209 // The algorithm takes care of nested and imbalanced curly braces.
210 for (size_t i = 0; i < logMessage.size(); ++i) {
211 if (logMessage[i] == '{') {
212 unmatched_curly_braces.push_back(i);
213 } else if (logMessage[i] == '}') {
214 if (unmatched_curly_braces.empty())
215 // Nothing to match.
216 continue;
218 int last_unmatched_index = unmatched_curly_braces.back();
219 unmatched_curly_braces.pop_back();
221 // Erase any matched ranges included in the new match.
222 while (!matched_curly_braces_ranges.empty()) {
223 assert(matched_curly_braces_ranges.back().first !=
224 last_unmatched_index &&
225 "How can a curley brace be matched twice?");
226 if (matched_curly_braces_ranges.back().first < last_unmatched_index)
227 break;
229 // This is a nested range let's earse it.
230 assert((size_t)matched_curly_braces_ranges.back().second < i);
231 matched_curly_braces_ranges.pop_back();
234 // Assert invariant.
235 assert(matched_curly_braces_ranges.empty() ||
236 matched_curly_braces_ranges.back().first < last_unmatched_index);
237 matched_curly_braces_ranges.emplace_back(last_unmatched_index, i);
241 // Part2 - parse raw text and expresions parts.
242 // All expression ranges have been parsed in matched_curly_braces_ranges.
243 // The code below uses matched_curly_braces_ranges to divide logMessage
244 // into raw text parts and expression parts.
245 int last_raw_text_start = 0;
246 for (const std::pair<int, int> &curly_braces_range :
247 matched_curly_braces_ranges) {
248 // Raw text before open curly brace.
249 assert(curly_braces_range.first >= last_raw_text_start);
250 size_t raw_text_len = curly_braces_range.first - last_raw_text_start;
251 if (raw_text_len > 0) {
252 error = AppendLogMessagePart(
253 llvm::StringRef(logMessage.c_str() + last_raw_text_start,
254 raw_text_len),
255 /*is_expr=*/false);
256 if (error.Fail()) {
257 NotifyLogMessageError(error.GetCString());
258 return;
262 // Expression between curly braces.
263 assert(curly_braces_range.second > curly_braces_range.first);
264 size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1;
265 error = AppendLogMessagePart(
266 llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1,
267 expr_len),
268 /*is_expr=*/true);
269 if (error.Fail()) {
270 NotifyLogMessageError(error.GetCString());
271 return;
274 last_raw_text_start = curly_braces_range.second + 1;
276 // Trailing raw text after close curly brace.
277 assert(last_raw_text_start >= 0);
278 if (logMessage.size() > (size_t)last_raw_text_start) {
279 error = AppendLogMessagePart(
280 llvm::StringRef(logMessage.c_str() + last_raw_text_start,
281 logMessage.size() - last_raw_text_start),
282 /*is_expr=*/false);
283 if (error.Fail()) {
284 NotifyLogMessageError(error.GetCString());
285 return;
289 bp.SetCallback(BreakpointHitCallback, this);
292 void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {
293 std::string message = "Log message has error: ";
294 message += error;
295 dap.SendOutput(OutputType::Console, message);
298 /*static*/
299 bool SourceBreakpoint::BreakpointHitCallback(
300 void *baton, lldb::SBProcess &process, lldb::SBThread &thread,
301 lldb::SBBreakpointLocation &location) {
302 if (!baton)
303 return true;
305 SourceBreakpoint *bp = (SourceBreakpoint *)baton;
306 lldb::SBFrame frame = thread.GetSelectedFrame();
308 std::string output;
309 for (const SourceBreakpoint::LogMessagePart &messagePart :
310 bp->logMessageParts) {
311 if (messagePart.is_expr) {
312 // Try local frame variables first before fall back to expression
313 // evaluation
314 const std::string &expr_str = messagePart.text;
315 const char *expr = expr_str.c_str();
316 lldb::SBValue value =
317 frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
318 if (value.GetError().Fail())
319 value = frame.EvaluateExpression(expr);
320 output +=
321 VariableDescription(value, bp->dap.enable_auto_variable_summaries)
322 .display_value;
323 } else {
324 output += messagePart.text;
327 if (!output.empty() && output.back() != '\n')
328 output.push_back('\n'); // Ensure log message has line break.
329 bp->dap.SendOutput(OutputType::Console, output.c_str());
331 // Do not stop.
332 return false;
335 } // namespace lldb_dap