xref: /llvm-project/lldb/tools/lldb-dap/SourceBreakpoint.cpp (revision b99d4112585302cbd01f9b851a04adc6e4fb5218)
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/SBTarget.h"
17 #include "lldb/API/SBThread.h"
18 #include "lldb/API/SBValue.h"
19 #include "lldb/lldb-enumerations.h"
20 #include <cassert>
21 #include <cctype>
22 #include <cstdlib>
23 #include <utility>
24 
25 namespace lldb_dap {
26 
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)) {
31 }
32 
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())
38     SetLogMessage();
39   Breakpoint::SetBreakpoint();
40 }
41 
42 void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) {
43   if (logMessage != request_bp.logMessage) {
44     logMessage = request_bp.logMessage;
45     SetLogMessage();
46   }
47   BreakpointBase::UpdateBreakpoint(request_bp);
48 }
49 
50 lldb::SBError SourceBreakpoint::AppendLogMessagePart(llvm::StringRef part,
51                                                      bool is_expr) {
52   if (is_expr) {
53     logMessageParts.emplace_back(part, is_expr);
54   } else {
55     std::string formatted;
56     lldb::SBError error = FormatLogText(part, formatted);
57     if (error.Fail())
58       return error;
59     logMessageParts.emplace_back(formatted, is_expr);
60   }
61   return lldb::SBError();
62 }
63 
64 // TODO: consolidate this code with the implementation in
65 // FormatEntity::ParseInternal().
66 lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text,
67                                               std::string &formatted) {
68   lldb::SBError error;
69   while (!text.empty()) {
70     size_t backslash_pos = text.find_first_of('\\');
71     if (backslash_pos == std::string::npos) {
72       formatted += text.str();
73       return error;
74     }
75 
76     formatted += text.substr(0, backslash_pos).str();
77     // Skip the characters before and including '\'.
78     text = text.drop_front(backslash_pos + 1);
79 
80     if (text.empty()) {
81       error.SetErrorString(
82           "'\\' character was not followed by another character");
83       return error;
84     }
85 
86     const char desens_char = text[0];
87     text = text.drop_front(); // Skip the desensitized char character
88     switch (desens_char) {
89     case 'a':
90       formatted.push_back('\a');
91       break;
92     case 'b':
93       formatted.push_back('\b');
94       break;
95     case 'f':
96       formatted.push_back('\f');
97       break;
98     case 'n':
99       formatted.push_back('\n');
100       break;
101     case 'r':
102       formatted.push_back('\r');
103       break;
104     case 't':
105       formatted.push_back('\t');
106       break;
107     case 'v':
108       formatted.push_back('\v');
109       break;
110     case '\'':
111       formatted.push_back('\'');
112       break;
113     case '\\':
114       formatted.push_back('\\');
115       break;
116     case '0':
117       // 1 to 3 octal chars
118       {
119         if (text.empty()) {
120           error.SetErrorString("missing octal number following '\\0'");
121           return error;
122         }
123 
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};
127 
128         size_t i;
129         for (i = 0;
130              i < text.size() && i < 4 && (text[i] >= '0' && text[i] <= '7');
131              ++i) {
132           oct_str[i] = text[i];
133         }
134 
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);
139         } else {
140           error.SetErrorString("octal number is larger than a single byte");
141           return error;
142         }
143       }
144       break;
145 
146     case 'x': {
147       if (text.empty()) {
148         error.SetErrorString("missing hex number following '\\x'");
149         return error;
150       }
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
154         // NULL terminator
155         char hex_str[3] = {0, 0, 0};
156         hex_str[0] = text[0];
157 
158         text = text.drop_front();
159 
160         if (!text.empty() && std::isxdigit(text[0])) {
161           hex_str[1] = text[0];
162           text = text.drop_front();
163         }
164 
165         unsigned long hex_value = strtoul(hex_str, nullptr, 16);
166         if (hex_value <= UINT8_MAX) {
167           formatted.push_back((char)hex_value);
168         } else {
169           error.SetErrorString("hex number is larger than a single byte");
170           return error;
171         }
172       } else {
173         formatted.push_back(desens_char);
174       }
175       break;
176     }
177 
178     default:
179       // Just desensitize any other character by just printing what came
180       // after the '\'
181       formatted.push_back(desens_char);
182       break;
183     }
184   }
185   return error;
186 }
187 
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
191 //    braces.
192 //
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();
197 
198   // Contains unmatched open curly braces indices.
199   std::vector<int> unmatched_curly_braces;
200 
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;
205 
206   lldb::SBError error;
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())
215         // Nothing to match.
216         continue;
217 
218       int last_unmatched_index = unmatched_curly_braces.back();
219       unmatched_curly_braces.pop_back();
220 
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)
227           break;
228 
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();
232       }
233 
234       // Assert invariant.
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);
238     }
239   }
240 
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,
254                           raw_text_len),
255           /*is_expr=*/false);
256       if (error.Fail()) {
257         NotifyLogMessageError(error.GetCString());
258         return;
259       }
260     }
261 
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,
267                         expr_len),
268         /*is_expr=*/true);
269     if (error.Fail()) {
270       NotifyLogMessageError(error.GetCString());
271       return;
272     }
273 
274     last_raw_text_start = curly_braces_range.second + 1;
275   }
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),
282         /*is_expr=*/false);
283     if (error.Fail()) {
284       NotifyLogMessageError(error.GetCString());
285       return;
286     }
287   }
288 
289   bp.SetCallback(BreakpointHitCallback, this);
290 }
291 
292 void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) {
293   std::string message = "Log message has error: ";
294   message += error;
295   dap.SendOutput(OutputType::Console, message);
296 }
297 
298 /*static*/
299 bool SourceBreakpoint::BreakpointHitCallback(
300     void *baton, lldb::SBProcess &process, lldb::SBThread &thread,
301     lldb::SBBreakpointLocation &location) {
302   if (!baton)
303     return true;
304 
305   SourceBreakpoint *bp = (SourceBreakpoint *)baton;
306   lldb::SBFrame frame = thread.GetSelectedFrame();
307 
308   std::string output;
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
313       // evaluation
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);
320       output +=
321           VariableDescription(value, bp->dap.enable_auto_variable_summaries)
322               .display_value;
323     } else {
324       output += messagePart.text;
325     }
326   }
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());
330 
331   // Do not stop.
332   return false;
333 }
334 
335 } // namespace lldb_dap
336