1 //===-- VSCode.cpp ----------------------------------------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include <stdarg.h>
11 #include <fstream>
12 #include <mutex>
13 
14 #include "VSCode.h"
15 #include "LLDBUtils.h"
16 
17 #if defined(_WIN32)
18 #include <io.h>
19 #include <fcntl.h>
20 #endif
21 
22 using namespace lldb_vscode;
23 
24 namespace {
25   inline bool IsEmptyLine(llvm::StringRef S) {
26     return S.ltrim().empty();
27   }
28 } // namespace
29 
30 namespace lldb_vscode {
31 
32 VSCode g_vsc;
33 
34 VSCode::VSCode()
35     : in(stdin), out(stdout), launch_info(nullptr), variables(),
36       broadcaster("lldb-vscode"), num_regs(0), num_locals(0), num_globals(0),
37       log(), exception_breakpoints(
38                  {{"cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus},
39                   {"cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus},
40                   {"objc_catch", "Objective C Catch", lldb::eLanguageTypeObjC},
41                   {"objc_throw", "Objective C Throw", lldb::eLanguageTypeObjC},
42                   {"swift_catch", "Swift Catch", lldb::eLanguageTypeSwift},
43                   {"swift_throw", "Swift Throw", lldb::eLanguageTypeSwift}}),
44       focus_tid(LLDB_INVALID_THREAD_ID), sent_terminated_event(false),
45       stop_at_entry(false) {
46   const char *log_file_path = getenv("LLDBVSCODE_LOG");
47 #if defined(_WIN32)
48 // Windows opens stdout and stdin in text mode which converts \n to 13,10
49 // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
50 // fixes this.
51   assert(_setmode(fileno(stdout), _O_BINARY));
52   assert(_setmode(fileno(stdin), _O_BINARY));
53 #endif
54   if (log_file_path)
55     log.reset(new std::ofstream(log_file_path));
56 }
57 
58 VSCode::~VSCode() {
59   CloseInputStream();
60   CloseOutputStream();
61 }
62 
63 void VSCode::CloseInputStream() {
64   if (in != stdin) {
65     fclose(in);
66     in = nullptr;
67   }
68 }
69 
70 void VSCode::CloseOutputStream() {
71   if (out != stdout) {
72     fclose(out);
73     out = nullptr;
74   }
75 }
76 
77 int64_t VSCode::GetLineForPC(int64_t sourceReference, lldb::addr_t pc) const {
78   auto pos = source_map.find(sourceReference);
79   if (pos != source_map.end())
80     return pos->second.GetLineForPC(pc);
81   return 0;
82 }
83 
84 ExceptionBreakpoint *VSCode::GetExceptionBreakpoint(const std::string &filter) {
85   for (auto &bp : exception_breakpoints) {
86     if (bp.filter == filter)
87       return &bp;
88   }
89   return nullptr;
90 }
91 
92 ExceptionBreakpoint *
93 VSCode::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
94   for (auto &bp : exception_breakpoints) {
95     if (bp.bp.GetID() == bp_id)
96       return &bp;
97   }
98   return nullptr;
99 }
100 
101 //----------------------------------------------------------------------
102 // Send the JSON in "json_str" to the "out" stream. Correctly send the
103 // "Content-Length:" field followed by the length, followed by the raw
104 // JSON bytes.
105 //----------------------------------------------------------------------
106 void VSCode::SendJSON(const std::string &json_str) {
107   fprintf(out, "Content-Length: %u\r\n\r\n%s", (uint32_t)json_str.size(),
108           json_str.c_str());
109   fflush(out);
110   if (log) {
111     *log << "<-- " << std::endl
112          << "Content-Length: " << json_str.size() << "\r\n\r\n"
113          << json_str << std::endl;
114   }
115 }
116 
117 //----------------------------------------------------------------------
118 // Serialize the JSON value into a string and send the JSON packet to
119 // the "out" stream.
120 //----------------------------------------------------------------------
121 void VSCode::SendJSON(const llvm::json::Value &json) {
122   std::string s;
123   llvm::raw_string_ostream strm(s);
124   strm << json;
125   static std::mutex mutex;
126   std::lock_guard<std::mutex> locker(mutex);
127   SendJSON(strm.str());
128 }
129 
130 //----------------------------------------------------------------------
131 // Read a JSON packet from the "in" stream.
132 //----------------------------------------------------------------------
133 std::string VSCode::ReadJSON() {
134   static std::string header("Content-Length: ");
135 
136   uint32_t packet_len = 0;
137   std::string json_str;
138   char line[1024];
139 
140   while (fgets(line, sizeof(line), in)) {
141     if (strncmp(line, header.data(), header.size()) == 0) {
142       packet_len = atoi(line + header.size());
143       if (fgets(line, sizeof(line), in)) {
144         if (!IsEmptyLine(line))
145           if (log)
146             *log << "warning: expected empty line but got: \"" << line << "\""
147                  << std::endl;
148         break;
149       }
150     } else {
151       if (log)
152         *log << "warning: expected \"" << header << "\" but got: \"" << line
153              << "\"" << std::endl;
154     }
155   }
156   // This is followed by two windows newline sequences ("\r\n\r\n") so eat
157   // two the newline sequences
158   if (packet_len > 0) {
159     json_str.resize(packet_len);
160     auto bytes_read = fread(&json_str[0], 1, packet_len, in);
161     if (bytes_read < packet_len) {
162       if (log)
163         *log << "error: read fewer bytes (" << bytes_read
164              << ") than requested (" << packet_len << ")" << std::endl;
165       json_str.erase(bytes_read);
166     }
167     if (log) {
168       *log << "--> " << std::endl;
169       *log << header << packet_len << "\r\n\r\n" << json_str << std::endl;
170     }
171   }
172   return json_str;
173 }
174 
175 //----------------------------------------------------------------------
176 // "OutputEvent": {
177 //   "allOf": [ { "$ref": "#/definitions/Event" }, {
178 //     "type": "object",
179 //     "description": "Event message for 'output' event type. The event
180 //                     indicates that the target has produced some output.",
181 //     "properties": {
182 //       "event": {
183 //         "type": "string",
184 //         "enum": [ "output" ]
185 //       },
186 //       "body": {
187 //         "type": "object",
188 //         "properties": {
189 //           "category": {
190 //             "type": "string",
191 //             "description": "The output category. If not specified,
192 //                             'console' is assumed.",
193 //             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
194 //           },
195 //           "output": {
196 //             "type": "string",
197 //             "description": "The output to report."
198 //           },
199 //           "variablesReference": {
200 //             "type": "number",
201 //             "description": "If an attribute 'variablesReference' exists
202 //                             and its value is > 0, the output contains
203 //                             objects which can be retrieved by passing
204 //                             variablesReference to the VariablesRequest."
205 //           },
206 //           "source": {
207 //             "$ref": "#/definitions/Source",
208 //             "description": "An optional source location where the output
209 //                             was produced."
210 //           },
211 //           "line": {
212 //             "type": "integer",
213 //             "description": "An optional source location line where the
214 //                             output was produced."
215 //           },
216 //           "column": {
217 //             "type": "integer",
218 //             "description": "An optional source location column where the
219 //                             output was produced."
220 //           },
221 //           "data": {
222 //             "type":["array","boolean","integer","null","number","object",
223 //                     "string"],
224 //             "description": "Optional data to report. For the 'telemetry'
225 //                             category the data will be sent to telemetry, for
226 //                             the other categories the data is shown in JSON
227 //                             format."
228 //           }
229 //         },
230 //         "required": ["output"]
231 //       }
232 //     },
233 //     "required": [ "event", "body" ]
234 //   }]
235 // }
236 //----------------------------------------------------------------------
237 void VSCode::SendOutput(OutputType o, const llvm::StringRef output) {
238   if (output.empty())
239     return;
240 
241   llvm::json::Object event(CreateEventObject("output"));
242   llvm::json::Object body;
243   const char *category = nullptr;
244   switch (o) {
245   case OutputType::Console:
246     category = "console";
247     break;
248   case OutputType::Stdout:
249     category = "stdout";
250     break;
251   case OutputType::Stderr:
252     category = "stderr";
253     break;
254   case OutputType::Telemetry:
255     category = "telemetry";
256     break;
257   }
258   body.try_emplace("category", category);
259   EmplaceSafeString(body, "output", output.str());
260   event.try_emplace("body", std::move(body));
261   SendJSON(llvm::json::Value(std::move(event)));
262 }
263 
264 void __attribute__((format(printf, 3, 4)))
265 VSCode::SendFormattedOutput(OutputType o, const char *format, ...) {
266   char buffer[1024];
267   va_list args;
268   va_start(args, format);
269   int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
270   va_end(args);
271   SendOutput(o, llvm::StringRef(buffer,
272                                 std::min<int>(actual_length, sizeof(buffer))));
273 }
274 
275 int64_t VSCode::GetNextSourceReference() {
276   static int64_t ref = 0;
277   return ++ref;
278 }
279 
280 ExceptionBreakpoint *
281 VSCode::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
282   const auto num = thread.GetStopReasonDataCount();
283   // Check to see if have hit an exception breakpoint and change the
284   // reason to "exception", but only do so if all breakpoints that were
285   // hit are exception breakpoints.
286   ExceptionBreakpoint *exc_bp = nullptr;
287   for (size_t i = 0; i < num; i += 2) {
288     // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
289     // thread.GetStopReasonDataAtIndex(i+1) will return the location
290     // within that breakpoint. We only care about the bp ID so we can
291     // see if this is an exception breakpoint that is getting hit.
292     lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
293     exc_bp = GetExceptionBreakpoint(bp_id);
294     // If any breakpoint is not an exception breakpoint, then stop and
295     // report this as a normal breakpoint
296     if (exc_bp == nullptr)
297       return nullptr;
298   }
299   return exc_bp;
300 }
301 
302 lldb::SBThread VSCode::GetLLDBThread(const llvm::json::Object &arguments) {
303   auto tid = GetSigned(arguments, "threadId", LLDB_INVALID_THREAD_ID);
304   return target.GetProcess().GetThreadByID(tid);
305 }
306 
307 lldb::SBFrame VSCode::GetLLDBFrame(const llvm::json::Object &arguments) {
308   const uint64_t frame_id = GetUnsigned(arguments, "frameId", UINT64_MAX);
309   lldb::SBProcess process = target.GetProcess();
310   // Upper 32 bits is the thread index ID
311   lldb::SBThread thread =
312       process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
313   // Lower 32 bits is the frame index
314   return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
315 }
316 
317 llvm::json::Value VSCode::CreateTopLevelScopes() {
318   llvm::json::Array scopes;
319   scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, num_locals, false));
320   scopes.emplace_back(
321       CreateScope("Globals", VARREF_GLOBALS, num_globals, false));
322   scopes.emplace_back(CreateScope("Registers", VARREF_REGS, num_regs, false));
323   return llvm::json::Value(std::move(scopes));
324 }
325 
326 void VSCode::RunLLDBCommands(llvm::StringRef prefix,
327                              const std::vector<std::string> &commands) {
328   SendOutput(OutputType::Console,
329              llvm::StringRef(::RunLLDBCommands(prefix, commands)));
330 }
331 
332 void VSCode::RunInitCommands() {
333   RunLLDBCommands("Running initCommands:", init_commands);
334 }
335 
336 void VSCode::RunPreRunCommands() {
337   RunLLDBCommands("Running preRunCommands:", pre_run_commands);
338 }
339 
340 void VSCode::RunStopCommands() {
341   RunLLDBCommands("Running stopCommands:", stop_commands);
342 }
343 
344 void VSCode::RunExitCommands() {
345   RunLLDBCommands("Running exitCommands:", exit_commands);
346 }
347 
348 } // namespace lldb_vscode
349 
350