1 | //===-- ProtocolRequests.cpp ----------------------------------------------===// |
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 "Protocol/ProtocolRequests.h" |
10 | #include "llvm/ADT/DenseMap.h" |
11 | #include "llvm/ADT/StringMap.h" |
12 | #include "llvm/ADT/StringRef.h" |
13 | #include "llvm/Support/JSON.h" |
14 | #include <utility> |
15 | |
16 | using namespace llvm; |
17 | |
18 | // The 'env' field is either an object as a map of strings or as an array of |
19 | // strings formatted like 'key=value'. |
20 | static bool parseEnv(const json::Value &Params, StringMap<std::string> &env, |
21 | json::Path P) { |
22 | const json::Object *O = Params.getAsObject(); |
23 | if (!O) { |
24 | P.report(Message: "expected object" ); |
25 | return false; |
26 | } |
27 | |
28 | const json::Value *value = O->get(K: "env" ); |
29 | if (!value) |
30 | return true; |
31 | |
32 | if (const json::Object *env_obj = value->getAsObject()) { |
33 | for (const auto &kv : *env_obj) { |
34 | const std::optional<StringRef> value = kv.second.getAsString(); |
35 | if (!value) { |
36 | P.field(Field: "env" ).field(Field: kv.first).report(Message: "expected string value" ); |
37 | return false; |
38 | } |
39 | env.insert(KV: {kv.first.str(), value->str()}); |
40 | } |
41 | return true; |
42 | } |
43 | |
44 | if (const json::Array *env_arr = value->getAsArray()) { |
45 | for (size_t i = 0; i < env_arr->size(); ++i) { |
46 | const std::optional<StringRef> value = (*env_arr)[i].getAsString(); |
47 | if (!value) { |
48 | P.field(Field: "env" ).index(Index: i).report(Message: "expected string" ); |
49 | return false; |
50 | } |
51 | std::pair<StringRef, StringRef> kv = value->split(Separator: "=" ); |
52 | env.insert(KV: {kv.first, kv.second.str()}); |
53 | } |
54 | |
55 | return true; |
56 | } |
57 | |
58 | P.field(Field: "env" ).report(Message: "invalid format, expected array or object" ); |
59 | return false; |
60 | } |
61 | |
62 | static bool parseTimeout(const json::Value &Params, std::chrono::seconds &S, |
63 | json::Path P) { |
64 | const json::Object *O = Params.getAsObject(); |
65 | if (!O) { |
66 | P.report(Message: "expected object" ); |
67 | return false; |
68 | } |
69 | |
70 | const json::Value *value = O->get(K: "timeout" ); |
71 | if (!value) |
72 | return true; |
73 | std::optional<double> timeout = value->getAsNumber(); |
74 | if (!timeout) { |
75 | P.field(Field: "timeout" ).report(Message: "expected number" ); |
76 | return false; |
77 | } |
78 | |
79 | S = std::chrono::duration_cast<std::chrono::seconds>( |
80 | d: std::chrono::duration<double>(*value->getAsNumber())); |
81 | return true; |
82 | } |
83 | |
84 | static bool |
85 | parseSourceMap(const json::Value &Params, |
86 | std::vector<std::pair<std::string, std::string>> &sourceMap, |
87 | json::Path P) { |
88 | const json::Object *O = Params.getAsObject(); |
89 | if (!O) { |
90 | P.report(Message: "expected object" ); |
91 | return false; |
92 | } |
93 | |
94 | const json::Value *value = O->get(K: "sourceMap" ); |
95 | if (!value) |
96 | return true; |
97 | |
98 | if (const json::Object *map_obj = value->getAsObject()) { |
99 | for (const auto &kv : *map_obj) { |
100 | const std::optional<StringRef> value = kv.second.getAsString(); |
101 | if (!value) { |
102 | P.field(Field: "sourceMap" ).field(Field: kv.first).report(Message: "expected string value" ); |
103 | return false; |
104 | } |
105 | sourceMap.emplace_back(args: std::make_pair(x: kv.first.str(), y: value->str())); |
106 | } |
107 | return true; |
108 | } |
109 | |
110 | if (const json::Array *env_arr = value->getAsArray()) { |
111 | for (size_t i = 0; i < env_arr->size(); ++i) { |
112 | const json::Array *kv = (*env_arr)[i].getAsArray(); |
113 | if (!kv) { |
114 | P.field(Field: "sourceMap" ).index(Index: i).report(Message: "expected array" ); |
115 | return false; |
116 | } |
117 | if (kv->size() != 2) { |
118 | P.field(Field: "sourceMap" ).index(Index: i).report(Message: "expected array of pairs" ); |
119 | return false; |
120 | } |
121 | const std::optional<StringRef> first = (*kv)[0].getAsString(); |
122 | if (!first) { |
123 | P.field(Field: "sourceMap" ).index(Index: 0).report(Message: "expected string" ); |
124 | return false; |
125 | } |
126 | const std::optional<StringRef> second = (*kv)[1].getAsString(); |
127 | if (!second) { |
128 | P.field(Field: "sourceMap" ).index(Index: 1).report(Message: "expected string" ); |
129 | return false; |
130 | } |
131 | sourceMap.emplace_back(args: std::make_pair(x: *first, y: second->str())); |
132 | } |
133 | |
134 | return true; |
135 | } |
136 | |
137 | P.report(Message: "invalid format, expected array or object" ); |
138 | return false; |
139 | } |
140 | |
141 | namespace lldb_dap::protocol { |
142 | |
143 | bool fromJSON(const json::Value &Params, CancelArguments &CA, json::Path P) { |
144 | json::ObjectMapper O(Params, P); |
145 | return O && O.map(Prop: "requestId" , Out&: CA.requestId) && |
146 | O.map(Prop: "progressId" , Out&: CA.progressId); |
147 | } |
148 | |
149 | bool fromJSON(const json::Value &Params, DisconnectArguments &DA, |
150 | json::Path P) { |
151 | json::ObjectMapper O(Params, P); |
152 | return O && O.mapOptional(Prop: "restart" , Out&: DA.restart) && |
153 | O.mapOptional(Prop: "terminateDebuggee" , Out&: DA.terminateDebuggee) && |
154 | O.mapOptional(Prop: "suspendDebuggee" , Out&: DA.suspendDebuggee); |
155 | } |
156 | |
157 | bool fromJSON(const json::Value &Params, PathFormat &PF, json::Path P) { |
158 | auto rawPathFormat = Params.getAsString(); |
159 | if (!rawPathFormat) { |
160 | P.report(Message: "expected a string" ); |
161 | return false; |
162 | } |
163 | |
164 | std::optional<PathFormat> pathFormat = |
165 | StringSwitch<std::optional<PathFormat>>(*rawPathFormat) |
166 | .Case(S: "path" , Value: ePatFormatPath) |
167 | .Case(S: "uri" , Value: ePathFormatURI) |
168 | .Default(Value: std::nullopt); |
169 | if (!pathFormat) { |
170 | P.report(Message: "unexpected value, expected 'path' or 'uri'" ); |
171 | return false; |
172 | } |
173 | |
174 | PF = *pathFormat; |
175 | return true; |
176 | } |
177 | |
178 | static const StringMap<ClientFeature> ClientFeatureByKey{ |
179 | {"supportsVariableType" , eClientFeatureVariableType}, |
180 | {"supportsVariablePaging" , eClientFeatureVariablePaging}, |
181 | {"supportsRunInTerminalRequest" , eClientFeatureRunInTerminalRequest}, |
182 | {"supportsMemoryReferences" , eClientFeatureMemoryReferences}, |
183 | {"supportsProgressReporting" , eClientFeatureProgressReporting}, |
184 | {"supportsInvalidatedEvent" , eClientFeatureInvalidatedEvent}, |
185 | {"supportsMemoryEvent" , eClientFeatureMemoryEvent}, |
186 | {"supportsArgsCanBeInterpretedByShell" , |
187 | eClientFeatureArgsCanBeInterpretedByShell}, |
188 | {"supportsStartDebuggingRequest" , eClientFeatureStartDebuggingRequest}, |
189 | {"supportsANSIStyling" , eClientFeatureANSIStyling}}; |
190 | |
191 | bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA, |
192 | json::Path P) { |
193 | json::ObjectMapper OM(Params, P); |
194 | if (!OM) |
195 | return false; |
196 | |
197 | const json::Object *O = Params.getAsObject(); |
198 | |
199 | for (auto &kv : ClientFeatureByKey) { |
200 | const json::Value *value_ref = O->get(K: kv.first()); |
201 | if (!value_ref) |
202 | continue; |
203 | |
204 | const std::optional<bool> value = value_ref->getAsBoolean(); |
205 | if (!value) { |
206 | P.field(Field: kv.first()).report(Message: "expected bool" ); |
207 | return false; |
208 | } |
209 | |
210 | if (*value) |
211 | IRA.supportedFeatures.insert(V: kv.second); |
212 | } |
213 | |
214 | return OM.map(Prop: "adapterID" , Out&: IRA.adapterID) && |
215 | OM.map(Prop: "clientID" , Out&: IRA.clientID) && |
216 | OM.map(Prop: "clientName" , Out&: IRA.clientName) && OM.map(Prop: "locale" , Out&: IRA.locale) && |
217 | OM.map(Prop: "linesStartAt1" , Out&: IRA.linesStartAt1) && |
218 | OM.map(Prop: "columnsStartAt1" , Out&: IRA.columnsStartAt1) && |
219 | OM.map(Prop: "pathFormat" , Out&: IRA.pathFormat) && |
220 | OM.map(Prop: "$__lldb_sourceInitFile" , Out&: IRA.lldbExtSourceInitFile); |
221 | } |
222 | |
223 | bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) { |
224 | json::ObjectMapper O(Params, P); |
225 | return O.mapOptional(Prop: "debuggerRoot" , Out&: C.debuggerRoot) && |
226 | O.mapOptional(Prop: "enableAutoVariableSummaries" , |
227 | Out&: C.enableAutoVariableSummaries) && |
228 | O.mapOptional(Prop: "enableSyntheticChildDebugging" , |
229 | Out&: C.enableSyntheticChildDebugging) && |
230 | O.mapOptional(Prop: "displayExtendedBacktrace" , |
231 | Out&: C.displayExtendedBacktrace) && |
232 | O.mapOptional(Prop: "stopOnEntry" , Out&: C.stopOnEntry) && |
233 | O.mapOptional(Prop: "commandEscapePrefix" , Out&: C.commandEscapePrefix) && |
234 | O.mapOptional(Prop: "customFrameFormat" , Out&: C.customFrameFormat) && |
235 | O.mapOptional(Prop: "customThreadFormat" , Out&: C.customThreadFormat) && |
236 | O.mapOptional(Prop: "sourcePath" , Out&: C.sourcePath) && |
237 | O.mapOptional(Prop: "initCommands" , Out&: C.initCommands) && |
238 | O.mapOptional(Prop: "preRunCommands" , Out&: C.preRunCommands) && |
239 | O.mapOptional(Prop: "postRunCommands" , Out&: C.postRunCommands) && |
240 | O.mapOptional(Prop: "stopCommands" , Out&: C.stopCommands) && |
241 | O.mapOptional(Prop: "exitCommands" , Out&: C.exitCommands) && |
242 | O.mapOptional(Prop: "terminateCommands" , Out&: C.terminateCommands) && |
243 | O.mapOptional(Prop: "program" , Out&: C.program) && |
244 | O.mapOptional(Prop: "targetTriple" , Out&: C.targetTriple) && |
245 | O.mapOptional(Prop: "platformName" , Out&: C.platformName) && |
246 | parseSourceMap(Params, sourceMap&: C.sourceMap, P) && |
247 | parseTimeout(Params, S&: C.timeout, P); |
248 | } |
249 | |
250 | bool fromJSON(const json::Value &Params, BreakpointLocationsArguments &BLA, |
251 | json::Path P) { |
252 | json::ObjectMapper O(Params, P); |
253 | return O && O.map(Prop: "source" , Out&: BLA.source) && O.map(Prop: "line" , Out&: BLA.line) && |
254 | O.mapOptional(Prop: "column" , Out&: BLA.column) && |
255 | O.mapOptional(Prop: "endLine" , Out&: BLA.endLine) && |
256 | O.mapOptional(Prop: "endColumn" , Out&: BLA.endColumn); |
257 | } |
258 | |
259 | json::Value toJSON(const BreakpointLocationsResponseBody &BLRB) { |
260 | return json::Object{{.K: "breakpoints" , .V: BLRB.breakpoints}}; |
261 | } |
262 | |
263 | bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, |
264 | json::Path P) { |
265 | json::ObjectMapper O(Params, P); |
266 | return O && fromJSON(Params, C&: LRA.configuration, P) && |
267 | O.mapOptional(Prop: "noDebug" , Out&: LRA.noDebug) && |
268 | O.mapOptional(Prop: "launchCommands" , Out&: LRA.launchCommands) && |
269 | O.mapOptional(Prop: "cwd" , Out&: LRA.cwd) && O.mapOptional(Prop: "args" , Out&: LRA.args) && |
270 | O.mapOptional(Prop: "detachOnError" , Out&: LRA.detachOnError) && |
271 | O.mapOptional(Prop: "disableASLR" , Out&: LRA.disableASLR) && |
272 | O.mapOptional(Prop: "disableSTDIO" , Out&: LRA.disableSTDIO) && |
273 | O.mapOptional(Prop: "shellExpandArguments" , Out&: LRA.shellExpandArguments) && |
274 | |
275 | O.mapOptional(Prop: "runInTerminal" , Out&: LRA.runInTerminal) && |
276 | parseEnv(Params, env&: LRA.env, P); |
277 | } |
278 | |
279 | bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, |
280 | json::Path P) { |
281 | json::ObjectMapper O(Params, P); |
282 | return O && fromJSON(Params, C&: ARA.configuration, P) && |
283 | O.mapOptional(Prop: "attachCommands" , Out&: ARA.attachCommands) && |
284 | O.mapOptional(Prop: "pid" , Out&: ARA.pid) && |
285 | O.mapOptional(Prop: "waitFor" , Out&: ARA.waitFor) && |
286 | O.mapOptional(Prop: "gdb-remote-port" , Out&: ARA.gdbRemotePort) && |
287 | O.mapOptional(Prop: "gdb-remote-hostname" , Out&: ARA.gdbRemoteHostname) && |
288 | O.mapOptional(Prop: "coreFile" , Out&: ARA.coreFile); |
289 | } |
290 | |
291 | bool fromJSON(const json::Value &Params, ContinueArguments &CA, json::Path P) { |
292 | json::ObjectMapper O(Params, P); |
293 | return O && O.map(Prop: "threadId" , Out&: CA.threadId) && |
294 | O.mapOptional(Prop: "singleThread" , Out&: CA.singleThread); |
295 | } |
296 | |
297 | json::Value toJSON(const ContinueResponseBody &CRB) { |
298 | json::Object Body{{.K: "allThreadsContinued" , .V: CRB.allThreadsContinued}}; |
299 | return std::move(Body); |
300 | } |
301 | |
302 | bool fromJSON(const json::Value &Params, SetVariableArguments &SVA, |
303 | json::Path P) { |
304 | json::ObjectMapper O(Params, P); |
305 | return O && O.map(Prop: "variablesReference" , Out&: SVA.variablesReference) && |
306 | O.map(Prop: "name" , Out&: SVA.name) && O.map(Prop: "value" , Out&: SVA.value) && |
307 | O.mapOptional(Prop: "format" , Out&: SVA.format); |
308 | } |
309 | |
310 | json::Value toJSON(const SetVariableResponseBody &SVR) { |
311 | json::Object Body{{.K: "value" , .V: SVR.value}}; |
312 | if (SVR.type.has_value()) |
313 | Body.insert(E: {.K: "type" , .V: SVR.type}); |
314 | |
315 | if (SVR.variablesReference.has_value()) |
316 | Body.insert(E: {.K: "variablesReference" , .V: SVR.variablesReference}); |
317 | |
318 | if (SVR.namedVariables.has_value()) |
319 | Body.insert(E: {.K: "namedVariables" , .V: SVR.namedVariables}); |
320 | |
321 | if (SVR.indexedVariables.has_value()) |
322 | Body.insert(E: {.K: "indexedVariables" , .V: SVR.indexedVariables}); |
323 | |
324 | if (SVR.memoryReference.has_value()) |
325 | Body.insert(E: {.K: "memoryReference" , .V: SVR.memoryReference}); |
326 | |
327 | if (SVR.valueLocationReference.has_value()) |
328 | Body.insert(E: {.K: "valueLocationReference" , .V: SVR.valueLocationReference}); |
329 | |
330 | return json::Value(std::move(Body)); |
331 | } |
332 | bool fromJSON(const json::Value &Params, ScopesArguments &SCA, json::Path P) { |
333 | json::ObjectMapper O(Params, P); |
334 | return O && O.map(Prop: "frameId" , Out&: SCA.frameId); |
335 | } |
336 | |
337 | json::Value toJSON(const ScopesResponseBody &SCR) { |
338 | return json::Object{{.K: "scopes" , .V: SCR.scopes}}; |
339 | } |
340 | |
341 | bool fromJSON(const json::Value &Params, SourceArguments &SA, json::Path P) { |
342 | json::ObjectMapper O(Params, P); |
343 | return O && O.map(Prop: "source" , Out&: SA.source) && |
344 | O.map(Prop: "sourceReference" , Out&: SA.sourceReference); |
345 | } |
346 | |
347 | json::Value toJSON(const SourceResponseBody &SA) { |
348 | json::Object Result{{.K: "content" , .V: SA.content}}; |
349 | |
350 | if (SA.mimeType) |
351 | Result.insert(E: {.K: "mimeType" , .V: SA.mimeType}); |
352 | |
353 | return std::move(Result); |
354 | } |
355 | |
356 | bool fromJSON(const json::Value &Params, NextArguments &NA, json::Path P) { |
357 | json::ObjectMapper OM(Params, P); |
358 | return OM && OM.map(Prop: "threadId" , Out&: NA.threadId) && |
359 | OM.mapOptional(Prop: "singleThread" , Out&: NA.singleThread) && |
360 | OM.mapOptional(Prop: "granularity" , Out&: NA.granularity); |
361 | } |
362 | |
363 | bool fromJSON(const json::Value &Params, StepInArguments &SIA, json::Path P) { |
364 | json::ObjectMapper OM(Params, P); |
365 | return OM && OM.map(Prop: "threadId" , Out&: SIA.threadId) && |
366 | OM.map(Prop: "targetId" , Out&: SIA.targetId) && |
367 | OM.mapOptional(Prop: "singleThread" , Out&: SIA.singleThread) && |
368 | OM.mapOptional(Prop: "granularity" , Out&: SIA.granularity); |
369 | } |
370 | |
371 | bool fromJSON(const json::Value &Params, StepOutArguments &SOA, json::Path P) { |
372 | json::ObjectMapper OM(Params, P); |
373 | return OM && OM.map(Prop: "threadId" , Out&: SOA.threadId) && |
374 | OM.mapOptional(Prop: "singleThread" , Out&: SOA.singleThread) && |
375 | OM.mapOptional(Prop: "granularity" , Out&: SOA.granularity); |
376 | } |
377 | |
378 | bool fromJSON(const json::Value &Params, SetBreakpointsArguments &SBA, |
379 | json::Path P) { |
380 | json::ObjectMapper O(Params, P); |
381 | return O && O.map(Prop: "source" , Out&: SBA.source) && |
382 | O.map(Prop: "breakpoints" , Out&: SBA.breakpoints) && O.map(Prop: "lines" , Out&: SBA.lines) && |
383 | O.map(Prop: "sourceModified" , Out&: SBA.sourceModified); |
384 | } |
385 | |
386 | json::Value toJSON(const SetBreakpointsResponseBody &SBR) { |
387 | return json::Object{{.K: "breakpoints" , .V: SBR.breakpoints}}; |
388 | } |
389 | |
390 | bool fromJSON(const json::Value &Params, SetFunctionBreakpointsArguments &SFBA, |
391 | json::Path P) { |
392 | json::ObjectMapper O(Params, P); |
393 | return O && O.map(Prop: "breakpoints" , Out&: SFBA.breakpoints); |
394 | } |
395 | |
396 | json::Value toJSON(const SetFunctionBreakpointsResponseBody &SFBR) { |
397 | return json::Object{{.K: "breakpoints" , .V: SFBR.breakpoints}}; |
398 | } |
399 | |
400 | bool fromJSON(const json::Value &Params, |
401 | SetInstructionBreakpointsArguments &SIBA, json::Path P) { |
402 | json::ObjectMapper O(Params, P); |
403 | return O && O.map(Prop: "breakpoints" , Out&: SIBA.breakpoints); |
404 | } |
405 | |
406 | json::Value toJSON(const SetInstructionBreakpointsResponseBody &SIBR) { |
407 | return json::Object{{.K: "breakpoints" , .V: SIBR.breakpoints}}; |
408 | } |
409 | |
410 | bool fromJSON(const json::Value &Params, DataBreakpointInfoArguments &DBIA, |
411 | json::Path P) { |
412 | json::ObjectMapper O(Params, P); |
413 | return O && O.map(Prop: "variablesReference" , Out&: DBIA.variablesReference) && |
414 | O.map(Prop: "name" , Out&: DBIA.name) && O.map(Prop: "frameId" , Out&: DBIA.frameId) && |
415 | O.map(Prop: "bytes" , Out&: DBIA.bytes) && O.map(Prop: "asAddress" , Out&: DBIA.asAddress) && |
416 | O.map(Prop: "mode" , Out&: DBIA.mode); |
417 | } |
418 | |
419 | json::Value toJSON(const DataBreakpointInfoResponseBody &DBIRB) { |
420 | json::Object result{{.K: "dataId" , .V: DBIRB.dataId}, |
421 | {.K: "description" , .V: DBIRB.description}}; |
422 | |
423 | if (DBIRB.accessTypes) |
424 | result["accessTypes" ] = *DBIRB.accessTypes; |
425 | if (DBIRB.canPersist) |
426 | result["canPersist" ] = *DBIRB.canPersist; |
427 | |
428 | return result; |
429 | } |
430 | |
431 | bool fromJSON(const json::Value &Params, SetDataBreakpointsArguments &SDBA, |
432 | json::Path P) { |
433 | json::ObjectMapper O(Params, P); |
434 | return O && O.map(Prop: "breakpoints" , Out&: SDBA.breakpoints); |
435 | } |
436 | |
437 | json::Value toJSON(const SetDataBreakpointsResponseBody &SDBR) { |
438 | return json::Object{{.K: "breakpoints" , .V: SDBR.breakpoints}}; |
439 | } |
440 | |
441 | json::Value toJSON(const ThreadsResponseBody &TR) { |
442 | return json::Object{{.K: "threads" , .V: TR.threads}}; |
443 | } |
444 | |
445 | bool fromJSON(const llvm::json::Value &Params, DisassembleArguments &DA, |
446 | llvm::json::Path P) { |
447 | json::ObjectMapper O(Params, P); |
448 | return O && O.map(Prop: "memoryReference" , Out&: DA.memoryReference) && |
449 | O.mapOptional(Prop: "offset" , Out&: DA.offset) && |
450 | O.mapOptional(Prop: "instructionOffset" , Out&: DA.instructionOffset) && |
451 | O.map(Prop: "instructionCount" , Out&: DA.instructionCount) && |
452 | O.mapOptional(Prop: "resolveSymbols" , Out&: DA.resolveSymbols); |
453 | } |
454 | |
455 | json::Value toJSON(const DisassembleResponseBody &DRB) { |
456 | return json::Object{{.K: "instructions" , .V: DRB.instructions}}; |
457 | } |
458 | |
459 | } // namespace lldb_dap::protocol |
460 | |