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 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "SourceBreakpoint.h" |
10 | #include "DAP.h" |
11 | |
12 | namespace lldb_dap { |
13 | |
14 | SourceBreakpoint::SourceBreakpoint(const llvm::json::Object &obj) |
15 | : Breakpoint(obj), logMessage(std::string(GetString(obj, key: "logMessage" ))), |
16 | line(GetUnsigned(obj, key: "line" , fail_value: 0)), column(GetUnsigned(obj, key: "column" , fail_value: 0)) { |
17 | } |
18 | |
19 | void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { |
20 | lldb::SBFileSpecList module_list; |
21 | bp = g_dap.target.BreakpointCreateByLocation(file_spec: source_path.str().c_str(), line, |
22 | column, offset: 0, module_list); |
23 | if (!logMessage.empty()) |
24 | SetLogMessage(); |
25 | Breakpoint::SetBreakpoint(); |
26 | } |
27 | |
28 | void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) { |
29 | if (logMessage != request_bp.logMessage) { |
30 | logMessage = request_bp.logMessage; |
31 | SetLogMessage(); |
32 | } |
33 | BreakpointBase::UpdateBreakpoint(request_bp); |
34 | } |
35 | |
36 | lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part, |
37 | bool is_expr) { |
38 | if (is_expr) { |
39 | logMessageParts.emplace_back(args&: part, args&: is_expr); |
40 | } else { |
41 | std::string formatted; |
42 | lldb::SBError error = FormatLogText(text: part, formatted); |
43 | if (error.Fail()) |
44 | return error; |
45 | logMessageParts.emplace_back(args&: formatted, args&: is_expr); |
46 | } |
47 | return lldb::SBError(); |
48 | } |
49 | |
50 | // TODO: consolidate this code with the implementation in |
51 | // FormatEntity::ParseInternal(). |
52 | lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text, |
53 | std::string &formatted) { |
54 | lldb::SBError error; |
55 | while (!text.empty()) { |
56 | size_t backslash_pos = text.find_first_of(C: '\\'); |
57 | if (backslash_pos == std::string::npos) { |
58 | formatted += text.str(); |
59 | return error; |
60 | } |
61 | |
62 | formatted += text.substr(Start: 0, N: backslash_pos).str(); |
63 | // Skip the characters before and including '\'. |
64 | text = text.drop_front(N: backslash_pos + 1); |
65 | |
66 | if (text.empty()) { |
67 | error.SetErrorString( |
68 | "'\\' character was not followed by another character" ); |
69 | return error; |
70 | } |
71 | |
72 | const char desens_char = text[0]; |
73 | text = text.drop_front(); // Skip the desensitized char character |
74 | switch (desens_char) { |
75 | case 'a': |
76 | formatted.push_back(c: '\a'); |
77 | break; |
78 | case 'b': |
79 | formatted.push_back(c: '\b'); |
80 | break; |
81 | case 'f': |
82 | formatted.push_back(c: '\f'); |
83 | break; |
84 | case 'n': |
85 | formatted.push_back(c: '\n'); |
86 | break; |
87 | case 'r': |
88 | formatted.push_back(c: '\r'); |
89 | break; |
90 | case 't': |
91 | formatted.push_back(c: '\t'); |
92 | break; |
93 | case 'v': |
94 | formatted.push_back(c: '\v'); |
95 | break; |
96 | case '\'': |
97 | formatted.push_back(c: '\''); |
98 | break; |
99 | case '\\': |
100 | formatted.push_back(c: '\\'); |
101 | break; |
102 | case '0': |
103 | // 1 to 3 octal chars |
104 | { |
105 | if (text.empty()) { |
106 | error.SetErrorString("missing octal number following '\\0'" ); |
107 | return error; |
108 | } |
109 | |
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}; |
113 | |
114 | size_t i; |
115 | for (i = 0; |
116 | i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7'); |
117 | ++i) { |
118 | oct_str[i] = text[i]; |
119 | } |
120 | |
121 | text = text.drop_front(N: i); |
122 | unsigned long octal_value = ::strtoul(nptr: oct_str, endptr: nullptr, base: 8); |
123 | if (octal_value <= UINT8_MAX) { |
124 | formatted.push_back(c: (char)octal_value); |
125 | } else { |
126 | error.SetErrorString("octal number is larger than a single byte" ); |
127 | return error; |
128 | } |
129 | } |
130 | break; |
131 | |
132 | case 'x': { |
133 | if (text.empty()) { |
134 | error.SetErrorString("missing hex number following '\\x'" ); |
135 | return error; |
136 | } |
137 | // hex number in the text |
138 | if (isxdigit(text[0])) { |
139 | // Make a string that can hold onto two hex chars plus a |
140 | // NULL terminator |
141 | char hex_str[3] = {0, 0, 0}; |
142 | hex_str[0] = text[0]; |
143 | |
144 | text = text.drop_front(); |
145 | |
146 | if (!text.empty() && isxdigit(text[0])) { |
147 | hex_str[1] = text[0]; |
148 | text = text.drop_front(); |
149 | } |
150 | |
151 | unsigned long hex_value = strtoul(nptr: hex_str, endptr: nullptr, base: 16); |
152 | if (hex_value <= UINT8_MAX) { |
153 | formatted.push_back(c: (char)hex_value); |
154 | } else { |
155 | error.SetErrorString("hex number is larger than a single byte" ); |
156 | return error; |
157 | } |
158 | } else { |
159 | formatted.push_back(c: desens_char); |
160 | } |
161 | break; |
162 | } |
163 | |
164 | default: |
165 | // Just desensitize any other character by just printing what came |
166 | // after the '\' |
167 | formatted.push_back(c: desens_char); |
168 | break; |
169 | } |
170 | } |
171 | return error; |
172 | } |
173 | |
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 |
177 | // braces. |
178 | // |
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(); |
183 | |
184 | // Contains unmatched open curly braces indices. |
185 | std::vector<int> unmatched_curly_braces; |
186 | |
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; |
191 | |
192 | lldb::SBError error; |
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(x: i); |
199 | } else if (logMessage[i] == '}') { |
200 | if (unmatched_curly_braces.empty()) |
201 | // Nothing to match. |
202 | continue; |
203 | |
204 | int last_unmatched_index = unmatched_curly_braces.back(); |
205 | unmatched_curly_braces.pop_back(); |
206 | |
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) |
213 | break; |
214 | |
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(); |
218 | } |
219 | |
220 | // Assert invariant. |
221 | assert(matched_curly_braces_ranges.empty() || |
222 | matched_curly_braces_ranges.back().first < last_unmatched_index); |
223 | matched_curly_braces_ranges.emplace_back(args&: last_unmatched_index, args&: i); |
224 | } |
225 | } |
226 | |
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 | part: llvm::StringRef(logMessage.c_str() + last_raw_text_start, |
240 | raw_text_len), |
241 | /*is_expr=*/false); |
242 | if (error.Fail()) { |
243 | NotifyLogMessageError(error: error.GetCString()); |
244 | return; |
245 | } |
246 | } |
247 | |
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 | part: llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1, |
253 | expr_len), |
254 | /*is_expr=*/true); |
255 | if (error.Fail()) { |
256 | NotifyLogMessageError(error: error.GetCString()); |
257 | return; |
258 | } |
259 | |
260 | last_raw_text_start = curly_braces_range.second + 1; |
261 | } |
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 | part: llvm::StringRef(logMessage.c_str() + last_raw_text_start, |
267 | logMessage.size() - last_raw_text_start), |
268 | /*is_expr=*/false); |
269 | if (error.Fail()) { |
270 | NotifyLogMessageError(error: error.GetCString()); |
271 | return; |
272 | } |
273 | } |
274 | |
275 | bp.SetCallback(callback: BreakpointHitCallback, baton: this); |
276 | } |
277 | |
278 | void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) { |
279 | std::string message = "Log message has error: " ; |
280 | message += error; |
281 | g_dap.SendOutput(o: OutputType::Console, output: message); |
282 | } |
283 | |
284 | /*static*/ |
285 | bool SourceBreakpoint::BreakpointHitCallback( |
286 | void *baton, lldb::SBProcess &process, lldb::SBThread &thread, |
287 | lldb::SBBreakpointLocation &location) { |
288 | if (!baton) |
289 | return true; |
290 | |
291 | SourceBreakpoint *bp = (SourceBreakpoint *)baton; |
292 | lldb::SBFrame frame = thread.GetSelectedFrame(); |
293 | |
294 | std::string output; |
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 |
299 | // evaluation |
300 | const std::string &expr_str = messagePart.text; |
301 | const char *expr = expr_str.c_str(); |
302 | lldb::SBValue value = |
303 | frame.GetValueForVariablePath(var_expr_cstr: expr, use_dynamic: lldb::eDynamicDontRunTarget); |
304 | if (value.GetError().Fail()) |
305 | value = frame.EvaluateExpression(expr); |
306 | output += VariableDescription(value).display_value; |
307 | } else { |
308 | output += messagePart.text; |
309 | } |
310 | } |
311 | if (!output.empty() && output.back() != '\n') |
312 | output.push_back(c: '\n'); // Ensure log message has line break. |
313 | g_dap.SendOutput(o: OutputType::Console, output: output.c_str()); |
314 | |
315 | // Do not stop. |
316 | return false; |
317 | } |
318 | |
319 | } // namespace lldb_dap |
320 | |