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