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 "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/SBInstruction.h" |
17 | #include "lldb/API/SBMutex.h" |
18 | #include "lldb/API/SBSymbol.h" |
19 | #include "lldb/API/SBTarget.h" |
20 | #include "lldb/API/SBThread.h" |
21 | #include "lldb/API/SBValue.h" |
22 | #include "lldb/lldb-enumerations.h" |
23 | #include "llvm/Support/Error.h" |
24 | #include <cassert> |
25 | #include <cctype> |
26 | #include <cstdlib> |
27 | #include <mutex> |
28 | #include <utility> |
29 | |
30 | namespace lldb_dap { |
31 | |
32 | SourceBreakpoint::SourceBreakpoint(DAP &dap, |
33 | const protocol::SourceBreakpoint &breakpoint) |
34 | : Breakpoint(dap, breakpoint.condition, breakpoint.hitCondition), |
35 | m_log_message(breakpoint.logMessage.value_or(u: "" )), |
36 | m_line(breakpoint.line), |
37 | m_column(breakpoint.column.value_or(LLDB_INVALID_COLUMN_NUMBER)) {} |
38 | |
39 | llvm::Error SourceBreakpoint::SetBreakpoint(const protocol::Source &source) { |
40 | lldb::SBMutex lock = m_dap.GetAPIMutex(); |
41 | std::lock_guard<lldb::SBMutex> guard(lock); |
42 | |
43 | if (m_line == 0) |
44 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
45 | S: "Invalid line number." ); |
46 | |
47 | if (source.sourceReference) { |
48 | // Breakpoint set by assembly source. |
49 | lldb::SBAddress source_address(*source.sourceReference, m_dap.target); |
50 | if (!source_address.IsValid()) |
51 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
52 | S: "Invalid sourceReference." ); |
53 | |
54 | lldb::SBSymbol symbol = source_address.GetSymbol(); |
55 | if (!symbol.IsValid()) { |
56 | // FIXME: Support assembly breakpoints without a valid symbol. |
57 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
58 | S: "Breakpoints in assembly without a valid " |
59 | "symbol are not supported yet." ); |
60 | } |
61 | |
62 | lldb::SBInstructionList inst_list = |
63 | m_dap.target.ReadInstructions(base_addr: symbol.GetStartAddress(), count: m_line); |
64 | if (inst_list.GetSize() < m_line) |
65 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
66 | S: "Invalid instruction list size." ); |
67 | |
68 | lldb::SBAddress address = |
69 | inst_list.GetInstructionAtIndex(idx: m_line - 1).GetAddress(); |
70 | |
71 | m_bp = m_dap.target.BreakpointCreateBySBAddress(address); |
72 | } else { |
73 | // Breakpoint set by a regular source file. |
74 | const auto source_path = source.path.value_or(u: "" ); |
75 | lldb::SBFileSpecList module_list; |
76 | m_bp = m_dap.target.BreakpointCreateByLocation(file_spec: source_path.c_str(), line: m_line, |
77 | column: m_column, offset: 0, module_list); |
78 | } |
79 | |
80 | if (!m_log_message.empty()) |
81 | SetLogMessage(); |
82 | Breakpoint::SetBreakpoint(); |
83 | return llvm::Error::success(); |
84 | } |
85 | |
86 | void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) { |
87 | if (m_log_message != request_bp.m_log_message) { |
88 | m_log_message = request_bp.m_log_message; |
89 | SetLogMessage(); |
90 | } |
91 | BreakpointBase::UpdateBreakpoint(request_bp); |
92 | } |
93 | |
94 | lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part, |
95 | bool is_expr) { |
96 | if (is_expr) { |
97 | m_log_message_parts.emplace_back(args&: part, args&: is_expr); |
98 | } else { |
99 | std::string formatted; |
100 | lldb::SBError error = FormatLogText(text: part, formatted); |
101 | if (error.Fail()) |
102 | return error; |
103 | m_log_message_parts.emplace_back(args&: formatted, args&: is_expr); |
104 | } |
105 | return lldb::SBError(); |
106 | } |
107 | |
108 | // TODO: consolidate this code with the implementation in |
109 | // FormatEntity::ParseInternal(). |
110 | lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text, |
111 | std::string &formatted) { |
112 | lldb::SBError error; |
113 | while (!text.empty()) { |
114 | size_t backslash_pos = text.find_first_of(C: '\\'); |
115 | if (backslash_pos == std::string::npos) { |
116 | formatted += text.str(); |
117 | return error; |
118 | } |
119 | |
120 | formatted += text.substr(Start: 0, N: backslash_pos).str(); |
121 | // Skip the characters before and including '\'. |
122 | text = text.drop_front(N: backslash_pos + 1); |
123 | |
124 | if (text.empty()) { |
125 | error.SetErrorString( |
126 | "'\\' character was not followed by another character" ); |
127 | return error; |
128 | } |
129 | |
130 | const char desens_char = text[0]; |
131 | text = text.drop_front(); // Skip the desensitized char character |
132 | switch (desens_char) { |
133 | case 'a': |
134 | formatted.push_back(c: '\a'); |
135 | break; |
136 | case 'b': |
137 | formatted.push_back(c: '\b'); |
138 | break; |
139 | case 'f': |
140 | formatted.push_back(c: '\f'); |
141 | break; |
142 | case 'n': |
143 | formatted.push_back(c: '\n'); |
144 | break; |
145 | case 'r': |
146 | formatted.push_back(c: '\r'); |
147 | break; |
148 | case 't': |
149 | formatted.push_back(c: '\t'); |
150 | break; |
151 | case 'v': |
152 | formatted.push_back(c: '\v'); |
153 | break; |
154 | case '\'': |
155 | formatted.push_back(c: '\''); |
156 | break; |
157 | case '\\': |
158 | formatted.push_back(c: '\\'); |
159 | break; |
160 | case '0': |
161 | // 1 to 3 octal chars |
162 | { |
163 | if (text.empty()) { |
164 | error.SetErrorString("missing octal number following '\\0'" ); |
165 | return error; |
166 | } |
167 | |
168 | // Make a string that can hold onto the initial zero char, up to 3 |
169 | // octal digits, and a terminating NULL. |
170 | char oct_str[5] = {0, 0, 0, 0, 0}; |
171 | |
172 | size_t i; |
173 | for (i = 0; |
174 | i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7'); |
175 | ++i) { |
176 | oct_str[i] = text[i]; |
177 | } |
178 | |
179 | text = text.drop_front(N: i); |
180 | unsigned long octal_value = ::strtoul(nptr: oct_str, endptr: nullptr, base: 8); |
181 | if (octal_value <= UINT8_MAX) { |
182 | formatted.push_back(c: (char)octal_value); |
183 | } else { |
184 | error.SetErrorString("octal number is larger than a single byte" ); |
185 | return error; |
186 | } |
187 | } |
188 | break; |
189 | |
190 | case 'x': { |
191 | if (text.empty()) { |
192 | error.SetErrorString("missing hex number following '\\x'" ); |
193 | return error; |
194 | } |
195 | // hex number in the text |
196 | if (std::isxdigit(text[0])) { |
197 | // Make a string that can hold onto two hex chars plus a |
198 | // NULL terminator |
199 | char hex_str[3] = {0, 0, 0}; |
200 | hex_str[0] = text[0]; |
201 | |
202 | text = text.drop_front(); |
203 | |
204 | if (!text.empty() && std::isxdigit(text[0])) { |
205 | hex_str[1] = text[0]; |
206 | text = text.drop_front(); |
207 | } |
208 | |
209 | unsigned long hex_value = strtoul(nptr: hex_str, endptr: nullptr, base: 16); |
210 | if (hex_value <= UINT8_MAX) { |
211 | formatted.push_back(c: (char)hex_value); |
212 | } else { |
213 | error.SetErrorString("hex number is larger than a single byte" ); |
214 | return error; |
215 | } |
216 | } else { |
217 | formatted.push_back(c: desens_char); |
218 | } |
219 | break; |
220 | } |
221 | |
222 | default: |
223 | // Just desensitize any other character by just printing what came |
224 | // after the '\' |
225 | formatted.push_back(c: desens_char); |
226 | break; |
227 | } |
228 | } |
229 | return error; |
230 | } |
231 | |
232 | // logMessage will be divided into array of LogMessagePart as two kinds: |
233 | // 1. raw print text message, and |
234 | // 2. interpolated expression for evaluation which is inside matching curly |
235 | // braces. |
236 | // |
237 | // The function tries to parse logMessage into a list of LogMessageParts |
238 | // for easy later access in BreakpointHitCallback. |
239 | void SourceBreakpoint::SetLogMessage() { |
240 | m_log_message_parts.clear(); |
241 | |
242 | // Contains unmatched open curly braces indices. |
243 | std::vector<int> unmatched_curly_braces; |
244 | |
245 | // Contains all matched curly braces in logMessage. |
246 | // Loop invariant: matched_curly_braces_ranges are sorted by start index in |
247 | // ascending order without any overlap between them. |
248 | std::vector<std::pair<int, int>> matched_curly_braces_ranges; |
249 | |
250 | lldb::SBError error; |
251 | // Part1 - parse matched_curly_braces_ranges. |
252 | // locating all curly braced expression ranges in logMessage. |
253 | // The algorithm takes care of nested and imbalanced curly braces. |
254 | for (size_t i = 0; i < m_log_message.size(); ++i) { |
255 | if (m_log_message[i] == '{') { |
256 | unmatched_curly_braces.push_back(x: i); |
257 | } else if (m_log_message[i] == '}') { |
258 | if (unmatched_curly_braces.empty()) |
259 | // Nothing to match. |
260 | continue; |
261 | |
262 | int last_unmatched_index = unmatched_curly_braces.back(); |
263 | unmatched_curly_braces.pop_back(); |
264 | |
265 | // Erase any matched ranges included in the new match. |
266 | while (!matched_curly_braces_ranges.empty()) { |
267 | assert(matched_curly_braces_ranges.back().first != |
268 | last_unmatched_index && |
269 | "How can a curley brace be matched twice?" ); |
270 | if (matched_curly_braces_ranges.back().first < last_unmatched_index) |
271 | break; |
272 | |
273 | // This is a nested range let's earse it. |
274 | assert((size_t)matched_curly_braces_ranges.back().second < i); |
275 | matched_curly_braces_ranges.pop_back(); |
276 | } |
277 | |
278 | // Assert invariant. |
279 | assert(matched_curly_braces_ranges.empty() || |
280 | matched_curly_braces_ranges.back().first < last_unmatched_index); |
281 | matched_curly_braces_ranges.emplace_back(args&: last_unmatched_index, args&: i); |
282 | } |
283 | } |
284 | |
285 | // Part2 - parse raw text and expresions parts. |
286 | // All expression ranges have been parsed in matched_curly_braces_ranges. |
287 | // The code below uses matched_curly_braces_ranges to divide logMessage |
288 | // into raw text parts and expression parts. |
289 | int last_raw_text_start = 0; |
290 | for (const std::pair<int, int> &curly_braces_range : |
291 | matched_curly_braces_ranges) { |
292 | // Raw text before open curly brace. |
293 | assert(curly_braces_range.first >= last_raw_text_start); |
294 | size_t raw_text_len = curly_braces_range.first - last_raw_text_start; |
295 | if (raw_text_len > 0) { |
296 | error = AppendLogMessagePart( |
297 | part: llvm::StringRef(m_log_message.c_str() + last_raw_text_start, |
298 | raw_text_len), |
299 | /*is_expr=*/false); |
300 | if (error.Fail()) { |
301 | NotifyLogMessageError(error: error.GetCString()); |
302 | return; |
303 | } |
304 | } |
305 | |
306 | // Expression between curly braces. |
307 | assert(curly_braces_range.second > curly_braces_range.first); |
308 | size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1; |
309 | error = AppendLogMessagePart( |
310 | part: llvm::StringRef(m_log_message.c_str() + curly_braces_range.first + 1, |
311 | expr_len), |
312 | /*is_expr=*/true); |
313 | if (error.Fail()) { |
314 | NotifyLogMessageError(error: error.GetCString()); |
315 | return; |
316 | } |
317 | |
318 | last_raw_text_start = curly_braces_range.second + 1; |
319 | } |
320 | // Trailing raw text after close curly brace. |
321 | assert(last_raw_text_start >= 0); |
322 | if (m_log_message.size() > (size_t)last_raw_text_start) { |
323 | error = AppendLogMessagePart( |
324 | part: llvm::StringRef(m_log_message.c_str() + last_raw_text_start, |
325 | m_log_message.size() - last_raw_text_start), |
326 | /*is_expr=*/false); |
327 | if (error.Fail()) { |
328 | NotifyLogMessageError(error: error.GetCString()); |
329 | return; |
330 | } |
331 | } |
332 | |
333 | m_bp.SetCallback(callback: BreakpointHitCallback, baton: this); |
334 | } |
335 | |
336 | void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) { |
337 | std::string message = "Log message has error: " ; |
338 | message += error; |
339 | m_dap.SendOutput(o: OutputType::Console, output: message); |
340 | } |
341 | |
342 | /*static*/ |
343 | bool SourceBreakpoint::BreakpointHitCallback( |
344 | void *baton, lldb::SBProcess &process, lldb::SBThread &thread, |
345 | lldb::SBBreakpointLocation &location) { |
346 | if (!baton) |
347 | return true; |
348 | |
349 | SourceBreakpoint *bp = (SourceBreakpoint *)baton; |
350 | lldb::SBFrame frame = thread.GetSelectedFrame(); |
351 | |
352 | std::string output; |
353 | for (const SourceBreakpoint::LogMessagePart &messagePart : |
354 | bp->m_log_message_parts) { |
355 | if (messagePart.is_expr) { |
356 | // Try local frame variables first before fall back to expression |
357 | // evaluation |
358 | const std::string &expr_str = messagePart.text; |
359 | const char *expr = expr_str.c_str(); |
360 | lldb::SBValue value = |
361 | frame.GetValueForVariablePath(var_expr_cstr: expr, use_dynamic: lldb::eDynamicDontRunTarget); |
362 | if (value.GetError().Fail()) |
363 | value = frame.EvaluateExpression(expr); |
364 | output += VariableDescription( |
365 | value, bp->m_dap.configuration.enableAutoVariableSummaries) |
366 | .display_value; |
367 | } else { |
368 | output += messagePart.text; |
369 | } |
370 | } |
371 | if (!output.empty() && output.back() != '\n') |
372 | output.push_back(c: '\n'); // Ensure log message has line break. |
373 | bp->m_dap.SendOutput(o: OutputType::Console, output: output.c_str()); |
374 | |
375 | // Do not stop. |
376 | return false; |
377 | } |
378 | |
379 | } // namespace lldb_dap |
380 | |