/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/tools/lldb-vscode/BreakpointBase.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- BreakpointBase.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 | | //===----------------------------------------------------------------------===// |
8 | | |
9 | | #include "BreakpointBase.h" |
10 | | #include "VSCode.h" |
11 | | #include "llvm/ADT/StringExtras.h" |
12 | | |
13 | | using namespace lldb_vscode; |
14 | | |
15 | | BreakpointBase::BreakpointBase(const llvm::json::Object &obj) |
16 | 1 | : condition(std::string(GetString(obj, "condition"))), |
17 | 1 | hitCondition(std::string(GetString(obj, "hitCondition"))), |
18 | 1 | logMessage(std::string(GetString(obj, "logMessage"))) {} |
19 | | |
20 | 0 | void BreakpointBase::SetCondition() { bp.SetCondition(condition.c_str()); } |
21 | | |
22 | 0 | void BreakpointBase::SetHitCondition() { |
23 | 0 | uint64_t hitCount = 0; |
24 | 0 | if (llvm::to_integer(hitCondition, hitCount)) |
25 | 0 | bp.SetIgnoreCount(hitCount - 1); |
26 | 0 | } |
27 | | |
28 | | lldb::SBError BreakpointBase::AppendLogMessagePart(llvm::StringRef part, |
29 | 0 | bool is_expr) { |
30 | 0 | if (is_expr) { |
31 | 0 | logMessageParts.emplace_back(part, is_expr); |
32 | 0 | } else { |
33 | 0 | std::string formatted; |
34 | 0 | lldb::SBError error = FormatLogText(part, formatted); |
35 | 0 | if (error.Fail()) |
36 | 0 | return error; |
37 | 0 | logMessageParts.emplace_back(formatted, is_expr); |
38 | 0 | } |
39 | 0 | return lldb::SBError(); |
40 | 0 | } |
41 | | |
42 | | // TODO: consolidate this code with the implementation in |
43 | | // FormatEntity::ParseInternal(). |
44 | | lldb::SBError BreakpointBase::FormatLogText(llvm::StringRef text, |
45 | 0 | std::string &formatted) { |
46 | 0 | lldb::SBError error; |
47 | 0 | while (!text.empty()) { |
48 | 0 | size_t backslash_pos = text.find_first_of('\\'); |
49 | 0 | if (backslash_pos == std::string::npos) { |
50 | 0 | formatted += text.str(); |
51 | 0 | return error; |
52 | 0 | } |
53 | | |
54 | 0 | formatted += text.substr(0, backslash_pos).str(); |
55 | | // Skip the characters before and including '\'. |
56 | 0 | text = text.drop_front(backslash_pos + 1); |
57 | |
|
58 | 0 | if (text.empty()) { |
59 | 0 | error.SetErrorString( |
60 | 0 | "'\\' character was not followed by another character"); |
61 | 0 | return error; |
62 | 0 | } |
63 | | |
64 | 0 | const char desens_char = text[0]; |
65 | 0 | text = text.drop_front(); // Skip the desensitized char character |
66 | 0 | switch (desens_char) { |
67 | 0 | case 'a': |
68 | 0 | formatted.push_back('\a'); |
69 | 0 | break; |
70 | 0 | case 'b': |
71 | 0 | formatted.push_back('\b'); |
72 | 0 | break; |
73 | 0 | case 'f': |
74 | 0 | formatted.push_back('\f'); |
75 | 0 | break; |
76 | 0 | case 'n': |
77 | 0 | formatted.push_back('\n'); |
78 | 0 | break; |
79 | 0 | case 'r': |
80 | 0 | formatted.push_back('\r'); |
81 | 0 | break; |
82 | 0 | case 't': |
83 | 0 | formatted.push_back('\t'); |
84 | 0 | break; |
85 | 0 | case 'v': |
86 | 0 | formatted.push_back('\v'); |
87 | 0 | break; |
88 | 0 | case '\'': |
89 | 0 | formatted.push_back('\''); |
90 | 0 | break; |
91 | 0 | case '\\': |
92 | 0 | formatted.push_back('\\'); |
93 | 0 | break; |
94 | 0 | case '0': |
95 | | // 1 to 3 octal chars |
96 | 0 | { |
97 | 0 | if (text.empty()) { |
98 | 0 | error.SetErrorString("missing octal number following '\\0'"); |
99 | 0 | return error; |
100 | 0 | } |
101 | | |
102 | | // Make a string that can hold onto the initial zero char, up to 3 |
103 | | // octal digits, and a terminating NULL. |
104 | 0 | char oct_str[5] = {0, 0, 0, 0, 0}; |
105 | |
|
106 | 0 | size_t i; |
107 | 0 | for (i = 0; |
108 | 0 | i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7'); |
109 | 0 | ++i) { |
110 | 0 | oct_str[i] = text[i]; |
111 | 0 | } |
112 | |
|
113 | 0 | text = text.drop_front(i); |
114 | 0 | unsigned long octal_value = ::strtoul(oct_str, nullptr, 8); |
115 | 0 | if (octal_value <= UINT8_MAX) { |
116 | 0 | formatted.push_back((char)octal_value); |
117 | 0 | } else { |
118 | 0 | error.SetErrorString("octal number is larger than a single byte"); |
119 | 0 | return error; |
120 | 0 | } |
121 | 0 | } |
122 | 0 | break; |
123 | | |
124 | 0 | case 'x': { |
125 | 0 | if (text.empty()) { |
126 | 0 | error.SetErrorString("missing hex number following '\\x'"); |
127 | 0 | return error; |
128 | 0 | } |
129 | | // hex number in the text |
130 | 0 | if (isxdigit(text[0])) { |
131 | | // Make a string that can hold onto two hex chars plus a |
132 | | // NULL terminator |
133 | 0 | char hex_str[3] = {0, 0, 0}; |
134 | 0 | hex_str[0] = text[0]; |
135 | |
|
136 | 0 | text = text.drop_front(); |
137 | |
|
138 | 0 | if (!text.empty() && isxdigit(text[0])) { |
139 | 0 | hex_str[1] = text[0]; |
140 | 0 | text = text.drop_front(); |
141 | 0 | } |
142 | |
|
143 | 0 | unsigned long hex_value = strtoul(hex_str, nullptr, 16); |
144 | 0 | if (hex_value <= UINT8_MAX) { |
145 | 0 | formatted.push_back((char)hex_value); |
146 | 0 | } else { |
147 | 0 | error.SetErrorString("hex number is larger than a single byte"); |
148 | 0 | return error; |
149 | 0 | } |
150 | 0 | } else { |
151 | 0 | formatted.push_back(desens_char); |
152 | 0 | } |
153 | 0 | break; |
154 | 0 | } |
155 | | |
156 | 0 | default: |
157 | | // Just desensitize any other character by just printing what came |
158 | | // after the '\' |
159 | 0 | formatted.push_back(desens_char); |
160 | 0 | break; |
161 | 0 | } |
162 | 0 | } |
163 | 0 | return error; |
164 | 0 | } |
165 | | |
166 | | // logMessage will be divided into array of LogMessagePart as two kinds: |
167 | | // 1. raw print text message, and |
168 | | // 2. interpolated expression for evaluation which is inside matching curly |
169 | | // braces. |
170 | | // |
171 | | // The function tries to parse logMessage into a list of LogMessageParts |
172 | | // for easy later access in BreakpointHitCallback. |
173 | 0 | void BreakpointBase::SetLogMessage() { |
174 | 0 | logMessageParts.clear(); |
175 | | |
176 | | // Contains unmatched open curly braces indices. |
177 | 0 | std::vector<int> unmatched_curly_braces; |
178 | | |
179 | | // Contains all matched curly braces in logMessage. |
180 | | // Loop invariant: matched_curly_braces_ranges are sorted by start index in |
181 | | // ascending order without any overlap between them. |
182 | 0 | std::vector<std::pair<int, int>> matched_curly_braces_ranges; |
183 | |
|
184 | 0 | lldb::SBError error; |
185 | | // Part1 - parse matched_curly_braces_ranges. |
186 | | // locating all curly braced expression ranges in logMessage. |
187 | | // The algorithm takes care of nested and imbalanced curly braces. |
188 | 0 | for (size_t i = 0; i < logMessage.size(); ++i) { |
189 | 0 | if (logMessage[i] == '{') { |
190 | 0 | unmatched_curly_braces.push_back(i); |
191 | 0 | } else if (logMessage[i] == '}') { |
192 | 0 | if (unmatched_curly_braces.empty()) |
193 | | // Nothing to match. |
194 | 0 | continue; |
195 | | |
196 | 0 | int last_unmatched_index = unmatched_curly_braces.back(); |
197 | 0 | unmatched_curly_braces.pop_back(); |
198 | | |
199 | | // Erase any matched ranges included in the new match. |
200 | 0 | while (!matched_curly_braces_ranges.empty()) { |
201 | 0 | assert(matched_curly_braces_ranges.back().first != |
202 | 0 | last_unmatched_index && |
203 | 0 | "How can a curley brace be matched twice?"); |
204 | 0 | if (matched_curly_braces_ranges.back().first < last_unmatched_index) |
205 | 0 | break; |
206 | | |
207 | | // This is a nested range let's earse it. |
208 | 0 | assert((size_t)matched_curly_braces_ranges.back().second < i); |
209 | 0 | matched_curly_braces_ranges.pop_back(); |
210 | 0 | } |
211 | | |
212 | | // Assert invariant. |
213 | 0 | assert(matched_curly_braces_ranges.empty() || |
214 | 0 | matched_curly_braces_ranges.back().first < last_unmatched_index); |
215 | 0 | matched_curly_braces_ranges.emplace_back(last_unmatched_index, i); |
216 | 0 | } |
217 | 0 | } |
218 | | |
219 | | // Part2 - parse raw text and expresions parts. |
220 | | // All expression ranges have been parsed in matched_curly_braces_ranges. |
221 | | // The code below uses matched_curly_braces_ranges to divide logMessage |
222 | | // into raw text parts and expression parts. |
223 | 0 | int last_raw_text_start = 0; |
224 | 0 | for (const std::pair<int, int> &curly_braces_range : |
225 | 0 | matched_curly_braces_ranges) { |
226 | | // Raw text before open curly brace. |
227 | 0 | assert(curly_braces_range.first >= last_raw_text_start); |
228 | 0 | size_t raw_text_len = curly_braces_range.first - last_raw_text_start; |
229 | 0 | if (raw_text_len > 0) { |
230 | 0 | error = AppendLogMessagePart( |
231 | 0 | llvm::StringRef(logMessage.c_str() + last_raw_text_start, |
232 | 0 | raw_text_len), |
233 | 0 | /*is_expr=*/false); |
234 | 0 | if (error.Fail()) { |
235 | 0 | NotifyLogMessageError(error.GetCString()); |
236 | 0 | return; |
237 | 0 | } |
238 | 0 | } |
239 | | |
240 | | // Expression between curly braces. |
241 | 0 | assert(curly_braces_range.second > curly_braces_range.first); |
242 | 0 | size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1; |
243 | 0 | error = AppendLogMessagePart( |
244 | 0 | llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1, |
245 | 0 | expr_len), |
246 | 0 | /*is_expr=*/true); |
247 | 0 | if (error.Fail()) { |
248 | 0 | NotifyLogMessageError(error.GetCString()); |
249 | 0 | return; |
250 | 0 | } |
251 | | |
252 | 0 | last_raw_text_start = curly_braces_range.second + 1; |
253 | 0 | } |
254 | | // Trailing raw text after close curly brace. |
255 | 0 | assert(last_raw_text_start >= 0); |
256 | 0 | if (logMessage.size() > (size_t)last_raw_text_start) { |
257 | 0 | error = AppendLogMessagePart( |
258 | 0 | llvm::StringRef(logMessage.c_str() + last_raw_text_start, |
259 | 0 | logMessage.size() - last_raw_text_start), |
260 | 0 | /*is_expr=*/false); |
261 | 0 | if (error.Fail()) { |
262 | 0 | NotifyLogMessageError(error.GetCString()); |
263 | 0 | return; |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | 0 | bp.SetCallback(BreakpointBase::BreakpointHitCallback, this); |
268 | 0 | } |
269 | | |
270 | 0 | void BreakpointBase::NotifyLogMessageError(llvm::StringRef error) { |
271 | 0 | std::string message = "Log message has error: "; |
272 | 0 | message += error; |
273 | 0 | g_vsc.SendOutput(OutputType::Console, message); |
274 | 0 | } |
275 | | |
276 | | /*static*/ |
277 | | bool BreakpointBase::BreakpointHitCallback( |
278 | | void *baton, lldb::SBProcess &process, lldb::SBThread &thread, |
279 | 0 | lldb::SBBreakpointLocation &location) { |
280 | 0 | if (!baton) |
281 | 0 | return true; |
282 | | |
283 | 0 | BreakpointBase *bp = (BreakpointBase *)baton; |
284 | 0 | lldb::SBFrame frame = thread.GetSelectedFrame(); |
285 | |
|
286 | 0 | std::string output; |
287 | 0 | for (const BreakpointBase::LogMessagePart &messagePart : |
288 | 0 | bp->logMessageParts) { |
289 | 0 | if (messagePart.is_expr) { |
290 | | // Try local frame variables first before fall back to expression |
291 | | // evaluation |
292 | 0 | const std::string &expr_str = messagePart.text; |
293 | 0 | const char *expr = expr_str.c_str(); |
294 | 0 | lldb::SBValue value = |
295 | 0 | frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget); |
296 | 0 | if (value.GetError().Fail()) |
297 | 0 | value = frame.EvaluateExpression(expr); |
298 | 0 | const char *expr_val = value.GetValue(); |
299 | 0 | if (expr_val) |
300 | 0 | output += expr_val; |
301 | 0 | } else { |
302 | 0 | output += messagePart.text; |
303 | 0 | } |
304 | 0 | } |
305 | 0 | if (!output.empty() && output.back() != '\n') |
306 | 0 | output.push_back('\n'); // Ensure log message has line break. |
307 | 0 | g_vsc.SendOutput(OutputType::Console, output.c_str()); |
308 | | |
309 | | // Do not stop. |
310 | 0 | return false; |
311 | 0 | } |
312 | | |
313 | 0 | void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) { |
314 | 0 | if (condition != request_bp.condition) { |
315 | 0 | condition = request_bp.condition; |
316 | 0 | SetCondition(); |
317 | 0 | } |
318 | 0 | if (hitCondition != request_bp.hitCondition) { |
319 | 0 | hitCondition = request_bp.hitCondition; |
320 | 0 | SetHitCondition(); |
321 | 0 | } |
322 | 0 | if (logMessage != request_bp.logMessage) { |
323 | 0 | logMessage = request_bp.logMessage; |
324 | 0 | SetLogMessage(); |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | 1 | const char *BreakpointBase::GetBreakpointLabel() { |
329 | | // Breakpoints in LLDB can have names added to them which are kind of like |
330 | | // labels or categories. All breakpoints that are set through the IDE UI get |
331 | | // sent through the various VS code DAP set*Breakpoint packets, and these |
332 | | // breakpoints will be labeled with this name so if breakpoint update events |
333 | | // come in for breakpoints that the IDE doesn't know about, like if a |
334 | | // breakpoint is set manually using the debugger console, we won't report any |
335 | | // updates on them and confused the IDE. This function gets called by all of |
336 | | // the breakpoint classes after they set breakpoints to mark a breakpoint as |
337 | | // a UI breakpoint. We can later check a lldb::SBBreakpoint object that comes |
338 | | // in via LLDB breakpoint changed events and check the breakpoint by calling |
339 | | // "bool lldb::SBBreakpoint::MatchesName(const char *)" to check if a |
340 | | // breakpoint in one of the UI breakpoints that we should report changes for. |
341 | 1 | return "vscode"; |
342 | 1 | } |