Coverage Report

Created: 2023-09-21 18:56

/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/tools/lldb-vscode/VSCode.cpp
Line
Count
Source (jump to first uncovered line)
1
//===-- VSCode.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 <chrono>
10
#include <cstdarg>
11
#include <fstream>
12
#include <mutex>
13
#include <sstream>
14
15
#include "LLDBUtils.h"
16
#include "VSCode.h"
17
#include "llvm/ADT/StringExtras.h"
18
#include "llvm/Support/FormatVariadic.h"
19
20
#if defined(_WIN32)
21
#define NOMINMAX
22
#include <fcntl.h>
23
#include <io.h>
24
#include <windows.h>
25
#endif
26
27
using namespace lldb_vscode;
28
29
namespace lldb_vscode {
30
31
VSCode g_vsc;
32
33
VSCode::VSCode()
34
5
    : broadcaster("lldb-vscode"),
35
5
      exception_breakpoints(
36
5
          {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
37
5
           {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
38
5
           {"objc_catch", "Objective-C Catch", lldb::eLanguageTypeObjC},
39
5
           {"objc_throw", "Objective-C Throw", lldb::eLanguageTypeObjC},
40
5
           {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
41
5
           {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
42
5
      focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
43
5
      stop_at_entry(false), is_attach(false),
44
5
      enable_auto_variable_summaries(false),
45
5
      enable_synthetic_child_debugging(false),
46
5
      restarting_process_id(LLDB_INVALID_PROCESS_ID),
47
5
      configuration_done_sent(false), waiting_for_run_in_terminal(false),
48
5
      progress_event_reporter(
49
5
          [&](const ProgressEvent &event) 
{ SendJSON(event.ToJSON()); }0
),
50
5
      reverse_request_seq(0), repl_mode(ReplMode::Auto),
51
5
      auto_repl_mode_collision_warning(false) {
52
5
  const char *log_file_path = getenv("LLDBVSCODE_LOG");
53
#if defined(_WIN32)
54
  // Windows opens stdout and stdin in text mode which converts \n to 13,10
55
  // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
56
  // fixes this.
57
  int result = _setmode(fileno(stdout), _O_BINARY);
58
  assert(result);
59
  result = _setmode(fileno(stdin), _O_BINARY);
60
  (void)result;
61
  assert(result);
62
#endif
63
5
  if (log_file_path)
64
4
    log.reset(new std::ofstream(log_file_path));
65
5
}
66
67
0
VSCode::~VSCode() = default;
68
69
0
ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
70
0
  for (auto &bp : exception_breakpoints) {
71
0
    if (bp.filter == filter)
72
0
      return &bp;
73
0
  }
74
0
  return nullptr;
75
0
}
76
77
ExceptionBreakpoint *
78
1
VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
79
6
  for (auto &bp : exception_breakpoints) {
80
6
    if (bp.bp.GetID() == bp_id)
81
0
      return &bp;
82
6
  }
83
1
  return nullptr;
84
1
}
85
86
// Send the JSON in "json_str" to the "out" stream. Correctly send the
87
// "Content-Length:" field followed by the length, followed by the raw
88
// JSON bytes.
89
32
void VSCode::SendJSON(const std::string &json_str) {
90
32
  output.write_full("Content-Length: ");
91
32
  output.write_full(llvm::utostr(json_str.size()));
92
32
  output.write_full("\r\n\r\n");
93
32
  output.write_full(json_str);
94
32
}
95
96
// Serialize the JSON value into a string and send the JSON packet to
97
// the "out" stream.
98
32
void VSCode::SendJSON(const llvm::json::Value &json) {
99
32
  std::string s;
100
32
  llvm::raw_string_ostream strm(s);
101
32
  strm << json;
102
32
  static std::mutex mutex;
103
32
  std::lock_guard<std::mutex> locker(mutex);
104
32
  std::string json_str = strm.str();
105
32
  SendJSON(json_str);
106
107
32
  if (log) {
108
32
    *log << "<-- " << std::endl
109
32
         << "Content-Length: " << json_str.size() << "\r\n\r\n"
110
32
         << llvm::formatv("{0:2}", json).str() << std::endl;
111
32
  }
112
32
}
113
114
// Read a JSON packet from the "in" stream.
115
15
std::string VSCode::ReadJSON() {
116
15
  std::string length_str;
117
15
  std::string json_str;
118
15
  int length;
119
120
15
  if (!input.read_expected(log.get(), "Content-Length: "))
121
0
    return json_str;
122
123
15
  if (!input.read_line(log.get(), length_str))
124
0
    return json_str;
125
126
15
  if (!llvm::to_integer(length_str, length))
127
0
    return json_str;
128
129
15
  if (!input.read_expected(log.get(), "\r\n"))
130
0
    return json_str;
131
132
15
  if (!input.read_full(log.get(), length, json_str))
133
0
    return json_str;
134
135
15
  if (log)
136
15
    *log << "--> " << std::endl << "Content-Length: " << length << "\r\n\r\n";
137
138
15
  return json_str;
139
15
}
140
141
// "OutputEvent": {
142
//   "allOf": [ { "$ref": "#/definitions/Event" }, {
143
//     "type": "object",
144
//     "description": "Event message for 'output' event type. The event
145
//                     indicates that the target has produced some output.",
146
//     "properties": {
147
//       "event": {
148
//         "type": "string",
149
//         "enum": [ "output" ]
150
//       },
151
//       "body": {
152
//         "type": "object",
153
//         "properties": {
154
//           "category": {
155
//             "type": "string",
156
//             "description": "The output category. If not specified,
157
//                             'console' is assumed.",
158
//             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
159
//           },
160
//           "output": {
161
//             "type": "string",
162
//             "description": "The output to report."
163
//           },
164
//           "variablesReference": {
165
//             "type": "number",
166
//             "description": "If an attribute 'variablesReference' exists
167
//                             and its value is > 0, the output contains
168
//                             objects which can be retrieved by passing
169
//                             variablesReference to the VariablesRequest."
170
//           },
171
//           "source": {
172
//             "$ref": "#/definitions/Source",
173
//             "description": "An optional source location where the output
174
//                             was produced."
175
//           },
176
//           "line": {
177
//             "type": "integer",
178
//             "description": "An optional source location line where the
179
//                             output was produced."
180
//           },
181
//           "column": {
182
//             "type": "integer",
183
//             "description": "An optional source location column where the
184
//                             output was produced."
185
//           },
186
//           "data": {
187
//             "type":["array","boolean","integer","null","number","object",
188
//                     "string"],
189
//             "description": "Optional data to report. For the 'telemetry'
190
//                             category the data will be sent to telemetry, for
191
//                             the other categories the data is shown in JSON
192
//                             format."
193
//           }
194
//         },
195
//         "required": ["output"]
196
//       }
197
//     },
198
//     "required": [ "event", "body" ]
199
//   }]
200
// }
201
16
void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
202
16
  if (output.empty())
203
12
    return;
204
205
4
  llvm::json::Object event(CreateEventObject("output"));
206
4
  llvm::json::Object body;
207
4
  const char *category = nullptr;
208
4
  switch (o) {
209
4
  case OutputType::Console:
210
4
    category = "console";
211
4
    break;
212
0
  case OutputType::Stdout:
213
0
    category = "stdout";
214
0
    break;
215
0
  case OutputType::Stderr:
216
0
    category = "stderr";
217
0
    break;
218
0
  case OutputType::Telemetry:
219
0
    category = "telemetry";
220
0
    break;
221
4
  }
222
4
  body.try_emplace("category", category);
223
4
  EmplaceSafeString(body, "output", output.str());
224
4
  event.try_emplace("body", std::move(body));
225
4
  SendJSON(llvm::json::Value(std::move(event)));
226
4
}
227
228
// interface ProgressStartEvent extends Event {
229
//   event: 'progressStart';
230
//
231
//   body: {
232
//     /**
233
//      * An ID that must be used in subsequent 'progressUpdate' and
234
//      'progressEnd'
235
//      * events to make them refer to the same progress reporting.
236
//      * IDs must be unique within a debug session.
237
//      */
238
//     progressId: string;
239
//
240
//     /**
241
//      * Mandatory (short) title of the progress reporting. Shown in the UI to
242
//      * describe the long running operation.
243
//      */
244
//     title: string;
245
//
246
//     /**
247
//      * The request ID that this progress report is related to. If specified a
248
//      * debug adapter is expected to emit
249
//      * progress events for the long running request until the request has
250
//      been
251
//      * either completed or cancelled.
252
//      * If the request ID is omitted, the progress report is assumed to be
253
//      * related to some general activity of the debug adapter.
254
//      */
255
//     requestId?: number;
256
//
257
//     /**
258
//      * If true, the request that reports progress may be canceled with a
259
//      * 'cancel' request.
260
//      * So this property basically controls whether the client should use UX
261
//      that
262
//      * supports cancellation.
263
//      * Clients that don't support cancellation are allowed to ignore the
264
//      * setting.
265
//      */
266
//     cancellable?: boolean;
267
//
268
//     /**
269
//      * Optional, more detailed progress message.
270
//      */
271
//     message?: string;
272
//
273
//     /**
274
//      * Optional progress percentage to display (value range: 0 to 100). If
275
//      * omitted no percentage will be shown.
276
//      */
277
//     percentage?: number;
278
//   };
279
// }
280
//
281
// interface ProgressUpdateEvent extends Event {
282
//   event: 'progressUpdate';
283
//
284
//   body: {
285
//     /**
286
//      * The ID that was introduced in the initial 'progressStart' event.
287
//      */
288
//     progressId: string;
289
//
290
//     /**
291
//      * Optional, more detailed progress message. If omitted, the previous
292
//      * message (if any) is used.
293
//      */
294
//     message?: string;
295
//
296
//     /**
297
//      * Optional progress percentage to display (value range: 0 to 100). If
298
//      * omitted no percentage will be shown.
299
//      */
300
//     percentage?: number;
301
//   };
302
// }
303
//
304
// interface ProgressEndEvent extends Event {
305
//   event: 'progressEnd';
306
//
307
//   body: {
308
//     /**
309
//      * The ID that was introduced in the initial 'ProgressStartEvent'.
310
//      */
311
//     progressId: string;
312
//
313
//     /**
314
//      * Optional, more detailed progress message. If omitted, the previous
315
//      * message (if any) is used.
316
//      */
317
//     message?: string;
318
//   };
319
// }
320
321
void VSCode::SendProgressEvent(uint64_t progress_id, const char *message,
322
562
                               uint64_t completed, uint64_t total) {
323
562
  progress_event_reporter.Push(progress_id, message, completed, total);
324
562
}
325
326
void __attribute__((format(printf, 3, 4)))
327
0
VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
328
0
  char buffer[1024];
329
0
  va_list args;
330
0
  va_start(args, format);
331
0
  int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
332
0
  va_end(args);
333
0
  SendOutput(
334
0
      o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
335
0
}
336
337
ExceptionBreakpoint *
338
1
VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
339
1
  const auto num = thread.GetStopReasonDataCount();
340
  // Check to see if have hit an exception breakpoint and change the
341
  // reason to "exception", but only do so if all breakpoints that were
342
  // hit are exception breakpoints.
343
1
  ExceptionBreakpoint *exc_bp = nullptr;
344
1
  for (size_t i = 0; i < num; 
i += 20
) {
345
    // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
346
    // thread.GetStopReasonDataAtIndex(i+1) will return the location
347
    // within that breakpoint. We only care about the bp ID so we can
348
    // see if this is an exception breakpoint that is getting hit.
349
1
    lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
350
1
    exc_bp = GetExceptionBreakpoint(bp_id);
351
    // If any breakpoint is not an exception breakpoint, then stop and
352
    // report this as a normal breakpoint
353
1
    if (exc_bp == nullptr)
354
1
      return nullptr;
355
1
  }
356
0
  return exc_bp;
357
1
}
358
359
1
lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
360
1
  auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
361
1
  return target.GetProcess().GetThreadByID(tid);
362
1
}
363
364
1
lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
365
1
  const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
366
1
  lldb::SBProcess process = target.GetProcess();
367
  // Upper 32 bits is the thread index ID
368
1
  lldb::SBThread thread =
369
1
      process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
370
  // Lower 32 bits is the frame index
371
1
  return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
372
1
}
373
374
0
llvm::json::Value VSCode::CreateTopLevelScopes() {
375
0
  llvm::json::Array scopes;
376
0
  scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS,
377
0
                                  g_vsc.variables.locals.GetSize(), false));
378
0
  scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS,
379
0
                                  g_vsc.variables.globals.GetSize(), false));
380
0
  scopes.emplace_back(CreateScope("Registers", VARREF_REGS,
381
0
                                  g_vsc.variables.registers.GetSize(), false));
382
0
  return llvm::json::Value(std::move(scopes));
383
0
}
384
385
ExpressionContext VSCode::DetectExpressionContext(lldb::SBFrame &frame,
386
0
                                                  std::string &text) {
387
  // Include ` as an escape hatch.
388
0
  if (!text.empty() && text[0] == '`') {
389
0
    text = text.substr(1);
390
0
    return ExpressionContext::Command;
391
0
  }
392
393
0
  switch (repl_mode) {
394
0
  case ReplMode::Variable:
395
0
    return ExpressionContext::Variable;
396
0
  case ReplMode::Command:
397
0
    return ExpressionContext::Command;
398
0
  case ReplMode::Auto:
399
    // If the frame is invalid then there is no variables to complete, assume
400
    // this is an lldb command instead.
401
0
    if (!frame.IsValid()) {
402
0
      return ExpressionContext::Command;
403
0
    }
404
405
0
    lldb::SBCommandReturnObject result;
406
0
    debugger.GetCommandInterpreter().ResolveCommand(text.data(), result);
407
408
    // If this command is a simple expression like `var + 1` check if there is
409
    // a local variable name that is in the current expression. If so, ensure
410
    // the expression runs in the variable context.
411
0
    lldb::SBValueList variables = frame.GetVariables(true, true, true, true);
412
0
    llvm::StringRef input = text;
413
0
    for (uint32_t i = 0; i < variables.GetSize(); i++) {
414
0
      llvm::StringRef name = variables.GetValueAtIndex(i).GetName();
415
      // Check both directions in case the input is a partial of a variable
416
      // (e.g. input = `va` and local variable = `var1`).
417
0
      if (input.contains(name) || name.contains(input)) {
418
0
        if (!auto_repl_mode_collision_warning) {
419
0
          llvm::errs() << "Variable expression '" << text
420
0
                       << "' is hiding an lldb command, prefix an expression "
421
0
                          "with ` to ensure it runs as a lldb command.\n";
422
0
          auto_repl_mode_collision_warning = true;
423
0
        }
424
0
        return ExpressionContext::Variable;
425
0
      }
426
0
    }
427
428
0
    if (result.Succeeded()) {
429
0
      return ExpressionContext::Command;
430
0
    }
431
0
  }
432
433
0
  return ExpressionContext::Variable;
434
0
}
435
436
void VSCode::RunLLDBCommands(llvm::StringRef prefix,
437
16
                             const std::vector<std::string> &commands) {
438
16
  SendOutput(OutputType::Console,
439
16
             llvm::StringRef(::RunLLDBCommands(prefix, commands)));
440
16
}
441
442
3
void VSCode::RunInitCommands() {
443
3
  RunLLDBCommands("Running initCommands:", init_commands);
444
3
}
445
446
3
void VSCode::RunPreRunCommands() {
447
3
  RunLLDBCommands("Running preRunCommands:", pre_run_commands);
448
3
}
449
450
1
void VSCode::RunStopCommands() {
451
1
  RunLLDBCommands("Running stopCommands:", stop_commands);
452
1
}
453
454
2
void VSCode::RunExitCommands() {
455
2
  RunLLDBCommands("Running exitCommands:", exit_commands);
456
2
}
457
458
4
void VSCode::RunTerminateCommands() {
459
4
  RunLLDBCommands("Running terminateCommands:", terminate_commands);
460
4
}
461
462
lldb::SBTarget
463
VSCode::CreateTargetFromArguments(const llvm::json::Object &arguments,
464
3
                                  lldb::SBError &error) {
465
  // Grab the name of the program we need to debug and create a target using
466
  // the given program as an argument. Executable file can be a source of target
467
  // architecture and platform, if they differ from the host. Setting exe path
468
  // in launch info is useless because Target.Launch() will not change
469
  // architecture and platform, therefore they should be known at the target
470
  // creation. We also use target triple and platform from the launch
471
  // configuration, if given, since in some cases ELF file doesn't contain
472
  // enough information to determine correct arch and platform (or ELF can be
473
  // omitted at all), so it is good to leave the user an apportunity to specify
474
  // those. Any of those three can be left empty.
475
3
  llvm::StringRef target_triple = GetString(arguments, "targetTriple");
476
3
  llvm::StringRef platform_name = GetString(arguments, "platformName");
477
3
  llvm::StringRef program = GetString(arguments, "program");
478
3
  auto target = this->debugger.CreateTarget(
479
3
      program.data(), target_triple.data(), platform_name.data(),
480
3
      true, // Add dependent modules.
481
3
      error);
482
483
3
  if (error.Fail()) {
484
    // Update message if there was an error.
485
0
    error.SetErrorStringWithFormat(
486
0
        "Could not create a target for a program '%s': %s.", program.data(),
487
0
        error.GetCString());
488
0
  }
489
490
3
  return target;
491
3
}
492
493
3
void VSCode::SetTarget(const lldb::SBTarget target) {
494
3
  this->target = target;
495
496
3
  if (target.IsValid()) {
497
    // Configure breakpoint event listeners for the target.
498
3
    lldb::SBListener listener = this->debugger.GetListener();
499
3
    listener.StartListeningForEvents(
500
3
        this->target.GetBroadcaster(),
501
3
        lldb::SBTarget::eBroadcastBitBreakpointChanged);
502
3
    listener.StartListeningForEvents(this->broadcaster,
503
3
                                     eBroadcastBitStopEventThread);
504
3
  }
505
3
}
506
507
15
PacketStatus VSCode::GetNextObject(llvm::json::Object &object) {
508
15
  std::string json = ReadJSON();
509
15
  if (json.empty())
510
0
    return PacketStatus::EndOfFile;
511
512
15
  llvm::StringRef json_sref(json);
513
15
  llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(json_sref);
514
15
  if (!json_value) {
515
0
    auto error = json_value.takeError();
516
0
    if (log) {
517
0
      std::string error_str;
518
0
      llvm::raw_string_ostream strm(error_str);
519
0
      strm << error;
520
0
      strm.flush();
521
0
      *log << "error: failed to parse JSON: " << error_str << std::endl
522
0
           << json << std::endl;
523
0
    }
524
0
    return PacketStatus::JSONMalformed;
525
0
  }
526
527
15
  if (log) {
528
15
    *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
529
15
  }
530
531
15
  llvm::json::Object *object_ptr = json_value->getAsObject();
532
15
  if (!object_ptr) {
533
0
    if (log)
534
0
      *log << "error: json packet isn't a object" << std::endl;
535
0
    return PacketStatus::JSONNotObject;
536
0
  }
537
15
  object = *object_ptr;
538
15
  return PacketStatus::Success;
539
15
}
540
541
15
bool VSCode::HandleObject(const llvm::json::Object &object) {
542
15
  const auto packet_type = GetString(object, "type");
543
15
  if (packet_type == "request") {
544
15
    const auto command = GetString(object, "command");
545
15
    auto handler_pos = request_handlers.find(std::string(command));
546
15
    if (handler_pos != request_handlers.end()) {
547
15
      handler_pos->second(object);
548
15
      return true; // Success
549
15
    } else {
550
0
      if (log)
551
0
        *log << "error: unhandled command \"" << command.data() << "\""
552
0
             << std::endl;
553
0
      return false; // Fail
554
0
    }
555
15
  }
556
557
0
  if (packet_type == "response") {
558
0
    auto id = GetSigned(object, "request_seq", 0);
559
0
    ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
560
0
      llvm::errs() << "Unhandled response\n";
561
0
    };
562
563
0
    {
564
0
      std::lock_guard<std::mutex> locker(call_mutex);
565
0
      auto inflight = inflight_reverse_requests.find(id);
566
0
      if (inflight != inflight_reverse_requests.end()) {
567
0
        response_handler = std::move(inflight->second);
568
0
        inflight_reverse_requests.erase(inflight);
569
0
      }
570
0
    }
571
572
    // Result should be given, use null if not.
573
0
    if (GetBoolean(object, "success", false)) {
574
0
      llvm::json::Value Result = nullptr;
575
0
      if (auto *B = object.get("body")) {
576
0
        Result = std::move(*B);
577
0
      }
578
0
      response_handler(Result);
579
0
    } else {
580
0
      llvm::StringRef message = GetString(object, "message");
581
0
      if (message.empty()) {
582
0
        message = "Unknown error, response failed";
583
0
      }
584
0
      response_handler(llvm::createStringError(
585
0
          std::error_code(-1, std::generic_category()), message));
586
0
    }
587
588
0
    return true;
589
0
  }
590
591
0
  return false;
592
0
}
593
594
4
llvm::Error VSCode::Loop() {
595
19
  while (!sent_terminated_event) {
596
15
    llvm::json::Object object;
597
15
    lldb_vscode::PacketStatus status = GetNextObject(object);
598
599
15
    if (status == lldb_vscode::PacketStatus::EndOfFile) {
600
0
      break;
601
0
    }
602
603
15
    if (status != lldb_vscode::PacketStatus::Success) {
604
0
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
605
0
                                     "failed to send packet");
606
0
    }
607
608
15
    if (!HandleObject(object)) {
609
0
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
610
0
                                     "unhandled packet");
611
0
    }
612
15
  }
613
614
4
  return llvm::Error::success();
615
4
}
616
617
void VSCode::SendReverseRequest(llvm::StringRef command,
618
                                llvm::json::Value arguments,
619
0
                                ResponseCallback callback) {
620
0
  int64_t id;
621
0
  {
622
0
    std::lock_guard<std::mutex> locker(call_mutex);
623
0
    id = ++reverse_request_seq;
624
0
    inflight_reverse_requests.emplace(id, std::move(callback));
625
0
  }
626
627
0
  SendJSON(llvm::json::Object{
628
0
      {"type", "request"},
629
0
      {"seq", id},
630
0
      {"command", command},
631
0
      {"arguments", std::move(arguments)},
632
0
  });
633
0
}
634
635
void VSCode::RegisterRequestCallback(std::string request,
636
108
                                     RequestCallback callback) {
637
108
  request_handlers[request] = callback;
638
108
}
639
640
0
lldb::SBError VSCode::WaitForProcessToStop(uint32_t seconds) {
641
0
  lldb::SBError error;
642
0
  lldb::SBProcess process = target.GetProcess();
643
0
  if (!process.IsValid()) {
644
0
    error.SetErrorString("invalid process");
645
0
    return error;
646
0
  }
647
0
  auto timeout_time =
648
0
      std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
649
0
  while (std::chrono::steady_clock::now() < timeout_time) {
650
0
    const auto state = process.GetState();
651
0
    switch (state) {
652
0
      case lldb::eStateAttaching:
653
0
      case lldb::eStateConnected:
654
0
      case lldb::eStateInvalid:
655
0
      case lldb::eStateLaunching:
656
0
      case lldb::eStateRunning:
657
0
      case lldb::eStateStepping:
658
0
      case lldb::eStateSuspended:
659
0
        break;
660
0
      case lldb::eStateDetached:
661
0
        error.SetErrorString("process detached during launch or attach");
662
0
        return error;
663
0
      case lldb::eStateExited:
664
0
        error.SetErrorString("process exited during launch or attach");
665
0
        return error;
666
0
      case lldb::eStateUnloaded:
667
0
        error.SetErrorString("process unloaded during launch or attach");
668
0
        return error;
669
0
      case lldb::eStateCrashed:
670
0
      case lldb::eStateStopped:
671
0
        return lldb::SBError(); // Success!
672
0
    }
673
0
    std::this_thread::sleep_for(std::chrono::microseconds(250));
674
0
  }
675
0
  error.SetErrorStringWithFormat("process failed to stop within %u seconds",
676
0
                                 seconds);
677
0
  return error;
678
0
}
679
680
1
void Variables::Clear() {
681
1
  locals.Clear();
682
1
  globals.Clear();
683
1
  registers.Clear();
684
1
  expandable_variables.clear();
685
1
}
686
687
0
int64_t Variables::GetNewVariableReference(bool is_permanent) {
688
0
  if (is_permanent)
689
0
    return next_permanent_var_ref++;
690
0
  return next_temporary_var_ref++;
691
0
}
692
693
0
bool Variables::IsPermanentVariableReference(int64_t var_ref) {
694
0
  return var_ref >= PermanentVariableStartIndex;
695
0
}
696
697
0
lldb::SBValue Variables::GetVariable(int64_t var_ref) const {
698
0
  if (IsPermanentVariableReference(var_ref)) {
699
0
    auto pos = expandable_permanent_variables.find(var_ref);
700
0
    if (pos != expandable_permanent_variables.end())
701
0
      return pos->second;
702
0
  } else {
703
0
    auto pos = expandable_variables.find(var_ref);
704
0
    if (pos != expandable_variables.end())
705
0
      return pos->second;
706
0
  }
707
0
  return lldb::SBValue();
708
0
}
709
710
int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
711
0
                                            bool is_permanent) {
712
0
  int64_t var_ref = GetNewVariableReference(is_permanent);
713
0
  if (is_permanent)
714
0
    expandable_permanent_variables.insert(std::make_pair(var_ref, variable));
715
0
  else
716
0
    expandable_variables.insert(std::make_pair(var_ref, variable));
717
0
  return var_ref;
718
0
}
719
720
bool StartDebuggingRequestHandler::DoExecute(
721
    lldb::SBDebugger debugger, char **command,
722
0
    lldb::SBCommandReturnObject &result) {
723
  // Command format like: `startDebugging <launch|attach> <configuration>`
724
0
  if (!command) {
725
0
    result.SetError("Invalid use of startDebugging");
726
0
    result.SetStatus(lldb::eReturnStatusFailed);
727
0
    return false;
728
0
  }
729
730
0
  if (!command[0] || llvm::StringRef(command[0]).empty()) {
731
0
    result.SetError("startDebugging request type missing.");
732
0
    result.SetStatus(lldb::eReturnStatusFailed);
733
0
    return false;
734
0
  }
735
736
0
  if (!command[1] || llvm::StringRef(command[1]).empty()) {
737
0
    result.SetError("configuration missing.");
738
0
    result.SetStatus(lldb::eReturnStatusFailed);
739
0
    return false;
740
0
  }
741
742
0
  llvm::StringRef request{command[0]};
743
0
  std::string raw_configuration{command[1]};
744
745
0
  int i = 2;
746
0
  while (command[i]) {
747
0
    raw_configuration.append(" ").append(command[i]);
748
0
  }
749
750
0
  llvm::Expected<llvm::json::Value> configuration =
751
0
      llvm::json::parse(raw_configuration);
752
753
0
  if (!configuration) {
754
0
    llvm::Error err = configuration.takeError();
755
0
    std::string msg =
756
0
        "Failed to parse json configuration: " + llvm::toString(std::move(err));
757
0
    result.SetError(msg.c_str());
758
0
    result.SetStatus(lldb::eReturnStatusFailed);
759
0
    return false;
760
0
  }
761
762
0
  g_vsc.SendReverseRequest(
763
0
      "startDebugging",
764
0
      llvm::json::Object{{"request", request},
765
0
                         {"configuration", std::move(*configuration)}},
766
0
      [](llvm::Expected<llvm::json::Value> value) {
767
0
        if (!value) {
768
0
          llvm::Error err = value.takeError();
769
0
          llvm::errs() << "reverse start debugging request failed: "
770
0
                       << llvm::toString(std::move(err)) << "\n";
771
0
        }
772
0
      });
773
774
0
  result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
775
776
0
  return true;
777
0
}
778
779
bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
780
                                       char **command,
781
0
                                       lldb::SBCommandReturnObject &result) {
782
  // Command format like: `repl-mode <variable|command|auto>?`
783
  // If a new mode is not specified report the current mode.
784
0
  if (!command || llvm::StringRef(command[0]).empty()) {
785
0
    std::string mode;
786
0
    switch (g_vsc.repl_mode) {
787
0
    case ReplMode::Variable:
788
0
      mode = "variable";
789
0
      break;
790
0
    case ReplMode::Command:
791
0
      mode = "command";
792
0
      break;
793
0
    case ReplMode::Auto:
794
0
      mode = "auto";
795
0
      break;
796
0
    }
797
798
0
    result.Printf("lldb-vscode repl-mode %s.\n", mode.c_str());
799
0
    result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
800
801
0
    return true;
802
0
  }
803
804
0
  llvm::StringRef new_mode{command[0]};
805
806
0
  if (new_mode == "variable") {
807
0
    g_vsc.repl_mode = ReplMode::Variable;
808
0
  } else if (new_mode == "command") {
809
0
    g_vsc.repl_mode = ReplMode::Command;
810
0
  } else if (new_mode == "auto") {
811
0
    g_vsc.repl_mode = ReplMode::Auto;
812
0
  } else {
813
0
    lldb::SBStream error_message;
814
0
    error_message.Printf("Invalid repl-mode '%s'. Expected one of 'variable', "
815
0
                         "'command' or 'auto'.\n",
816
0
                         new_mode.data());
817
0
    result.SetError(error_message.GetData());
818
0
    return false;
819
0
  }
820
821
0
  result.Printf("lldb-vscode repl-mode %s set.\n", new_mode.data());
822
0
  result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
823
0
  return true;
824
0
}
825
826
} // namespace lldb_vscode