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