1 //===-- SourceBreakpoint.cpp ------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 #include "SourceBreakpoint.h"
14 SourceBreakpoint::SourceBreakpoint(const llvm::json::Object
&obj
)
15 : Breakpoint(obj
), logMessage(std::string(GetString(obj
, "logMessage"))),
16 line(GetUnsigned(obj
, "line", 0)), column(GetUnsigned(obj
, "column", 0)) {
19 void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path
) {
20 lldb::SBFileSpecList module_list
;
21 bp
= g_dap
.target
.BreakpointCreateByLocation(source_path
.str().c_str(), line
,
22 column
, 0, module_list
);
23 if (!logMessage
.empty())
25 Breakpoint::SetBreakpoint();
28 void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint
&request_bp
) {
29 if (logMessage
!= request_bp
.logMessage
) {
30 logMessage
= request_bp
.logMessage
;
33 BreakpointBase::UpdateBreakpoint(request_bp
);
36 lldb::SBError
SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part
,
39 logMessageParts
.emplace_back(part
, is_expr
);
41 std::string formatted
;
42 lldb::SBError error
= FormatLogText(part
, formatted
);
45 logMessageParts
.emplace_back(formatted
, is_expr
);
47 return lldb::SBError();
50 // TODO: consolidate this code with the implementation in
51 // FormatEntity::ParseInternal().
52 lldb::SBError
SourceBreakpoint::FormatLogText(llvm::StringRef text
,
53 std::string
&formatted
) {
55 while (!text
.empty()) {
56 size_t backslash_pos
= text
.find_first_of('\\');
57 if (backslash_pos
== std::string::npos
) {
58 formatted
+= text
.str();
62 formatted
+= text
.substr(0, backslash_pos
).str();
63 // Skip the characters before and including '\'.
64 text
= text
.drop_front(backslash_pos
+ 1);
68 "'\\' character was not followed by another character");
72 const char desens_char
= text
[0];
73 text
= text
.drop_front(); // Skip the desensitized char character
74 switch (desens_char
) {
76 formatted
.push_back('\a');
79 formatted
.push_back('\b');
82 formatted
.push_back('\f');
85 formatted
.push_back('\n');
88 formatted
.push_back('\r');
91 formatted
.push_back('\t');
94 formatted
.push_back('\v');
97 formatted
.push_back('\'');
100 formatted
.push_back('\\');
103 // 1 to 3 octal chars
106 error
.SetErrorString("missing octal number following '\\0'");
110 // Make a string that can hold onto the initial zero char, up to 3
111 // octal digits, and a terminating NULL.
112 char oct_str
[5] = {0, 0, 0, 0, 0};
116 i
< text
.size() && i
< 4 && (text
[i
] >= '0' && text
[i
] <= '7');
118 oct_str
[i
] = text
[i
];
121 text
= text
.drop_front(i
);
122 unsigned long octal_value
= ::strtoul(oct_str
, nullptr, 8);
123 if (octal_value
<= UINT8_MAX
) {
124 formatted
.push_back((char)octal_value
);
126 error
.SetErrorString("octal number is larger than a single byte");
134 error
.SetErrorString("missing hex number following '\\x'");
137 // hex number in the text
138 if (isxdigit(text
[0])) {
139 // Make a string that can hold onto two hex chars plus a
141 char hex_str
[3] = {0, 0, 0};
142 hex_str
[0] = text
[0];
144 text
= text
.drop_front();
146 if (!text
.empty() && isxdigit(text
[0])) {
147 hex_str
[1] = text
[0];
148 text
= text
.drop_front();
151 unsigned long hex_value
= strtoul(hex_str
, nullptr, 16);
152 if (hex_value
<= UINT8_MAX
) {
153 formatted
.push_back((char)hex_value
);
155 error
.SetErrorString("hex number is larger than a single byte");
159 formatted
.push_back(desens_char
);
165 // Just desensitize any other character by just printing what came
167 formatted
.push_back(desens_char
);
174 // logMessage will be divided into array of LogMessagePart as two kinds:
175 // 1. raw print text message, and
176 // 2. interpolated expression for evaluation which is inside matching curly
179 // The function tries to parse logMessage into a list of LogMessageParts
180 // for easy later access in BreakpointHitCallback.
181 void SourceBreakpoint::SetLogMessage() {
182 logMessageParts
.clear();
184 // Contains unmatched open curly braces indices.
185 std::vector
<int> unmatched_curly_braces
;
187 // Contains all matched curly braces in logMessage.
188 // Loop invariant: matched_curly_braces_ranges are sorted by start index in
189 // ascending order without any overlap between them.
190 std::vector
<std::pair
<int, int>> matched_curly_braces_ranges
;
193 // Part1 - parse matched_curly_braces_ranges.
194 // locating all curly braced expression ranges in logMessage.
195 // The algorithm takes care of nested and imbalanced curly braces.
196 for (size_t i
= 0; i
< logMessage
.size(); ++i
) {
197 if (logMessage
[i
] == '{') {
198 unmatched_curly_braces
.push_back(i
);
199 } else if (logMessage
[i
] == '}') {
200 if (unmatched_curly_braces
.empty())
204 int last_unmatched_index
= unmatched_curly_braces
.back();
205 unmatched_curly_braces
.pop_back();
207 // Erase any matched ranges included in the new match.
208 while (!matched_curly_braces_ranges
.empty()) {
209 assert(matched_curly_braces_ranges
.back().first
!=
210 last_unmatched_index
&&
211 "How can a curley brace be matched twice?");
212 if (matched_curly_braces_ranges
.back().first
< last_unmatched_index
)
215 // This is a nested range let's earse it.
216 assert((size_t)matched_curly_braces_ranges
.back().second
< i
);
217 matched_curly_braces_ranges
.pop_back();
221 assert(matched_curly_braces_ranges
.empty() ||
222 matched_curly_braces_ranges
.back().first
< last_unmatched_index
);
223 matched_curly_braces_ranges
.emplace_back(last_unmatched_index
, i
);
227 // Part2 - parse raw text and expresions parts.
228 // All expression ranges have been parsed in matched_curly_braces_ranges.
229 // The code below uses matched_curly_braces_ranges to divide logMessage
230 // into raw text parts and expression parts.
231 int last_raw_text_start
= 0;
232 for (const std::pair
<int, int> &curly_braces_range
:
233 matched_curly_braces_ranges
) {
234 // Raw text before open curly brace.
235 assert(curly_braces_range
.first
>= last_raw_text_start
);
236 size_t raw_text_len
= curly_braces_range
.first
- last_raw_text_start
;
237 if (raw_text_len
> 0) {
238 error
= AppendLogMessagePart(
239 llvm::StringRef(logMessage
.c_str() + last_raw_text_start
,
243 NotifyLogMessageError(error
.GetCString());
248 // Expression between curly braces.
249 assert(curly_braces_range
.second
> curly_braces_range
.first
);
250 size_t expr_len
= curly_braces_range
.second
- curly_braces_range
.first
- 1;
251 error
= AppendLogMessagePart(
252 llvm::StringRef(logMessage
.c_str() + curly_braces_range
.first
+ 1,
256 NotifyLogMessageError(error
.GetCString());
260 last_raw_text_start
= curly_braces_range
.second
+ 1;
262 // Trailing raw text after close curly brace.
263 assert(last_raw_text_start
>= 0);
264 if (logMessage
.size() > (size_t)last_raw_text_start
) {
265 error
= AppendLogMessagePart(
266 llvm::StringRef(logMessage
.c_str() + last_raw_text_start
,
267 logMessage
.size() - last_raw_text_start
),
270 NotifyLogMessageError(error
.GetCString());
275 bp
.SetCallback(BreakpointHitCallback
, this);
278 void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error
) {
279 std::string message
= "Log message has error: ";
281 g_dap
.SendOutput(OutputType::Console
, message
);
285 bool SourceBreakpoint::BreakpointHitCallback(
286 void *baton
, lldb::SBProcess
&process
, lldb::SBThread
&thread
,
287 lldb::SBBreakpointLocation
&location
) {
291 SourceBreakpoint
*bp
= (SourceBreakpoint
*)baton
;
292 lldb::SBFrame frame
= thread
.GetSelectedFrame();
295 for (const SourceBreakpoint::LogMessagePart
&messagePart
:
296 bp
->logMessageParts
) {
297 if (messagePart
.is_expr
) {
298 // Try local frame variables first before fall back to expression
300 const std::string
&expr_str
= messagePart
.text
;
301 const char *expr
= expr_str
.c_str();
302 lldb::SBValue value
=
303 frame
.GetValueForVariablePath(expr
, lldb::eDynamicDontRunTarget
);
304 if (value
.GetError().Fail())
305 value
= frame
.EvaluateExpression(expr
);
306 output
+= VariableDescription(value
).display_value
;
308 output
+= messagePart
.text
;
311 if (!output
.empty() && output
.back() != '\n')
312 output
.push_back('\n'); // Ensure log message has line break.
313 g_dap
.SendOutput(OutputType::Console
, output
.c_str());
319 } // namespace lldb_dap