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"
10 #include "BreakpointBase.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"
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())
39 Breakpoint::SetBreakpoint();
42 void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint
&request_bp
) {
43 if (logMessage
!= request_bp
.logMessage
) {
44 logMessage
= request_bp
.logMessage
;
47 BreakpointBase::UpdateBreakpoint(request_bp
);
50 lldb::SBError
SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part
,
53 logMessageParts
.emplace_back(part
, is_expr
);
55 std::string formatted
;
56 lldb::SBError error
= FormatLogText(part
, formatted
);
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
) {
69 while (!text
.empty()) {
70 size_t backslash_pos
= text
.find_first_of('\\');
71 if (backslash_pos
== std::string::npos
) {
72 formatted
+= text
.str();
76 formatted
+= text
.substr(0, backslash_pos
).str();
77 // Skip the characters before and including '\'.
78 text
= text
.drop_front(backslash_pos
+ 1);
82 "'\\' character was not followed by another character");
86 const char desens_char
= text
[0];
87 text
= text
.drop_front(); // Skip the desensitized char character
88 switch (desens_char
) {
90 formatted
.push_back('\a');
93 formatted
.push_back('\b');
96 formatted
.push_back('\f');
99 formatted
.push_back('\n');
102 formatted
.push_back('\r');
105 formatted
.push_back('\t');
108 formatted
.push_back('\v');
111 formatted
.push_back('\'');
114 formatted
.push_back('\\');
117 // 1 to 3 octal chars
120 error
.SetErrorString("missing octal number following '\\0'");
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};
130 i
< text
.size() && i
< 4 && (text
[i
] >= '0' && text
[i
] <= '7');
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
);
140 error
.SetErrorString("octal number is larger than a single byte");
148 error
.SetErrorString("missing hex number following '\\x'");
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
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
);
169 error
.SetErrorString("hex number is larger than a single byte");
173 formatted
.push_back(desens_char
);
179 // Just desensitize any other character by just printing what came
181 formatted
.push_back(desens_char
);
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
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
;
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())
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
)
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();
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
,
257 NotifyLogMessageError(error
.GetCString());
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,
270 NotifyLogMessageError(error
.GetCString());
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
),
284 NotifyLogMessageError(error
.GetCString());
289 bp
.SetCallback(BreakpointHitCallback
, this);
292 void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error
) {
293 std::string message
= "Log message has error: ";
295 dap
.SendOutput(OutputType::Console
, message
);
299 bool SourceBreakpoint::BreakpointHitCallback(
300 void *baton
, lldb::SBProcess
&process
, lldb::SBThread
&thread
,
301 lldb::SBBreakpointLocation
&location
) {
305 SourceBreakpoint
*bp
= (SourceBreakpoint
*)baton
;
306 lldb::SBFrame frame
= thread
.GetSelectedFrame();
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
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
);
321 VariableDescription(value
, bp
->dap
.enable_auto_variable_summaries
)
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());
335 } // namespace lldb_dap