//===-- JSONUtils.cpp -------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include <algorithm> #include <iomanip> #include <optional> #include <sstream> #include <string.h> #include "llvm/ADT/StringRef.h" #include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBBreakpointLocation.h" #include "lldb/API/SBDeclaration.h" #include "lldb/API/SBStringList.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBValue.h" #include "lldb/Host/PosixApi.h" #include "DAP.h" #include "ExceptionBreakpoint.h" #include "JSONUtils.h" #include "LLDBUtils.h" namespace lldb_dap { void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key, llvm::StringRef str) { … } llvm::StringRef GetAsString(const llvm::json::Value &value) { … } // Gets a string from a JSON object using the key, or returns an empty string. llvm::StringRef GetString(const llvm::json::Object &obj, llvm::StringRef key, llvm::StringRef defaultValue) { … } llvm::StringRef GetString(const llvm::json::Object *obj, llvm::StringRef key, llvm::StringRef defaultValue) { … } // Gets an unsigned integer from a JSON object using the key, or returns the // specified fail value. uint64_t GetUnsigned(const llvm::json::Object &obj, llvm::StringRef key, uint64_t fail_value) { … } uint64_t GetUnsigned(const llvm::json::Object *obj, llvm::StringRef key, uint64_t fail_value) { … } bool GetBoolean(const llvm::json::Object &obj, llvm::StringRef key, bool fail_value) { … } bool GetBoolean(const llvm::json::Object *obj, llvm::StringRef key, bool fail_value) { … } int64_t GetSigned(const llvm::json::Object &obj, llvm::StringRef key, int64_t fail_value) { … } int64_t GetSigned(const llvm::json::Object *obj, llvm::StringRef key, int64_t fail_value) { … } bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key) { … } std::string EncodeMemoryReference(lldb::addr_t addr) { … } std::optional<lldb::addr_t> DecodeMemoryReference(llvm::StringRef memoryReference) { … } std::vector<std::string> GetStrings(const llvm::json::Object *obj, llvm::StringRef key) { … } static bool IsClassStructOrUnionType(lldb::SBType t) { … } /// Create a short summary for a container that contains the summary of its /// first children, so that the user can get a glimpse of its contents at a /// glance. static std::optional<std::string> TryCreateAutoSummaryForContainer(lldb::SBValue &v) { … } /// Try to create a summary string for the given value that doesn't have a /// summary of its own. static std::optional<std::string> TryCreateAutoSummary(lldb::SBValue value) { … } void FillResponse(const llvm::json::Object &request, llvm::json::Object &response) { … } // "Scope": { // "type": "object", // "description": "A Scope is a named container for variables. Optionally // a scope can map to a source or a range within a source.", // "properties": { // "name": { // "type": "string", // "description": "Name of the scope such as 'Arguments', 'Locals'." // }, // "presentationHint": { // "type": "string", // "description": "An optional hint for how to present this scope in the // UI. If this attribute is missing, the scope is shown // with a generic UI.", // "_enum": [ "arguments", "locals", "registers" ], // }, // "variablesReference": { // "type": "integer", // "description": "The variables of this scope can be retrieved by // passing the value of variablesReference to the // VariablesRequest." // }, // "namedVariables": { // "type": "integer", // "description": "The number of named variables in this scope. The // client can use this optional information to present // the variables in a paged UI and fetch them in chunks." // }, // "indexedVariables": { // "type": "integer", // "description": "The number of indexed variables in this scope. The // client can use this optional information to present // the variables in a paged UI and fetch them in chunks." // }, // "expensive": { // "type": "boolean", // "description": "If true, the number of variables in this scope is // large or expensive to retrieve." // }, // "source": { // "$ref": "#/definitions/Source", // "description": "Optional source for this scope." // }, // "line": { // "type": "integer", // "description": "Optional start line of the range covered by this // scope." // }, // "column": { // "type": "integer", // "description": "Optional start column of the range covered by this // scope." // }, // "endLine": { // "type": "integer", // "description": "Optional end line of the range covered by this scope." // }, // "endColumn": { // "type": "integer", // "description": "Optional end column of the range covered by this // scope." // } // }, // "required": [ "name", "variablesReference", "expensive" ] // } llvm::json::Value CreateScope(const llvm::StringRef name, int64_t variablesReference, int64_t namedVariables, bool expensive) { … } // "Breakpoint": { // "type": "object", // "description": "Information about a Breakpoint created in setBreakpoints // or setFunctionBreakpoints.", // "properties": { // "id": { // "type": "integer", // "description": "An optional unique identifier for the breakpoint." // }, // "verified": { // "type": "boolean", // "description": "If true breakpoint could be set (but not necessarily // at the desired location)." // }, // "message": { // "type": "string", // "description": "An optional message about the state of the breakpoint. // This is shown to the user and can be used to explain // why a breakpoint could not be verified." // }, // "source": { // "$ref": "#/definitions/Source", // "description": "The source where the breakpoint is located." // }, // "line": { // "type": "integer", // "description": "The start line of the actual range covered by the // breakpoint." // }, // "column": { // "type": "integer", // "description": "An optional start column of the actual range covered // by the breakpoint." // }, // "endLine": { // "type": "integer", // "description": "An optional end line of the actual range covered by // the breakpoint." // }, // "endColumn": { // "type": "integer", // "description": "An optional end column of the actual range covered by // the breakpoint. If no end line is given, then the end // column is assumed to be in the start line." // } // }, // "required": [ "verified" ] // } llvm::json::Value CreateBreakpoint(BreakpointBase *bp, std::optional<llvm::StringRef> request_path, std::optional<uint32_t> request_line, std::optional<uint32_t> request_column) { … } static uint64_t GetDebugInfoSizeInSection(lldb::SBSection section) { … } static uint64_t GetDebugInfoSize(lldb::SBModule module) { … } static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) { … } llvm::json::Value CreateModule(lldb::SBModule &module) { … } void AppendBreakpoint(BreakpointBase *bp, llvm::json::Array &breakpoints, std::optional<llvm::StringRef> request_path, std::optional<uint32_t> request_line) { … } // "Event": { // "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { // "type": "object", // "description": "Server-initiated event.", // "properties": { // "type": { // "type": "string", // "enum": [ "event" ] // }, // "event": { // "type": "string", // "description": "Type of event." // }, // "body": { // "type": [ "array", "boolean", "integer", "null", "number" , // "object", "string" ], // "description": "Event-specific information." // } // }, // "required": [ "type", "event" ] // }] // }, // "ProtocolMessage": { // "type": "object", // "description": "Base class of requests, responses, and events.", // "properties": { // "seq": { // "type": "integer", // "description": "Sequence number." // }, // "type": { // "type": "string", // "description": "Message type.", // "_enum": [ "request", "response", "event" ] // } // }, // "required": [ "seq", "type" ] // } llvm::json::Object CreateEventObject(const llvm::StringRef event_name) { … } // "ExceptionBreakpointsFilter": { // "type": "object", // "description": "An ExceptionBreakpointsFilter is shown in the UI as an // option for configuring how exceptions are dealt with.", // "properties": { // "filter": { // "type": "string", // "description": "The internal ID of the filter. This value is passed // to the setExceptionBreakpoints request." // }, // "label": { // "type": "string", // "description": "The name of the filter. This will be shown in the UI." // }, // "default": { // "type": "boolean", // "description": "Initial value of the filter. If not specified a value // 'false' is assumed." // } // }, // "required": [ "filter", "label" ] // } llvm::json::Value CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { … } // "Source": { // "type": "object", // "description": "A Source is a descriptor for source code. It is returned // from the debug adapter as part of a StackFrame and it is // used by clients when specifying breakpoints.", // "properties": { // "name": { // "type": "string", // "description": "The short name of the source. Every source returned // from the debug adapter has a name. When sending a // source to the debug adapter this name is optional." // }, // "path": { // "type": "string", // "description": "The path of the source to be shown in the UI. It is // only used to locate and load the content of the // source if no sourceReference is specified (or its // value is 0)." // }, // "sourceReference": { // "type": "number", // "description": "If sourceReference > 0 the contents of the source must // be retrieved through the SourceRequest (even if a path // is specified). A sourceReference is only valid for a // session, so it must not be used to persist a source." // }, // "presentationHint": { // "type": "string", // "description": "An optional hint for how to present the source in the // UI. A value of 'deemphasize' can be used to indicate // that the source is not available or that it is // skipped on stepping.", // "enum": [ "normal", "emphasize", "deemphasize" ] // }, // "origin": { // "type": "string", // "description": "The (optional) origin of this source: possible values // 'internal module', 'inlined content from source map', // etc." // }, // "sources": { // "type": "array", // "items": { // "$ref": "#/definitions/Source" // }, // "description": "An optional list of sources that are related to this // source. These may be the source that generated this // source." // }, // "adapterData": { // "type":["array","boolean","integer","null","number","object","string"], // "description": "Optional data that a debug adapter might want to loop // through the client. The client should leave the data // intact and persist it across sessions. The client // should not interpret the data." // }, // "checksums": { // "type": "array", // "items": { // "$ref": "#/definitions/Checksum" // }, // "description": "The checksums associated with this file." // } // } // } llvm::json::Value CreateSource(const lldb::SBFileSpec &file) { … } llvm::json::Value CreateSource(const lldb::SBLineEntry &line_entry) { … } llvm::json::Value CreateSource(llvm::StringRef source_path) { … } std::optional<llvm::json::Value> CreateSource(lldb::SBFrame &frame) { … } // "StackFrame": { // "type": "object", // "description": "A Stackframe contains the source location.", // "properties": { // "id": { // "type": "integer", // "description": "An identifier for the stack frame. It must be unique // across all threads. This id can be used to retrieve // the scopes of the frame with the 'scopesRequest' or // to restart the execution of a stackframe." // }, // "name": { // "type": "string", // "description": "The name of the stack frame, typically a method name." // }, // "source": { // "$ref": "#/definitions/Source", // "description": "The optional source of the frame." // }, // "line": { // "type": "integer", // "description": "The line within the file of the frame. If source is // null or doesn't exist, line is 0 and must be ignored." // }, // "column": { // "type": "integer", // "description": "The column within the line. If source is null or // doesn't exist, column is 0 and must be ignored." // }, // "endLine": { // "type": "integer", // "description": "An optional end line of the range covered by the // stack frame." // }, // "endColumn": { // "type": "integer", // "description": "An optional end column of the range covered by the // stack frame." // }, // "instructionPointerReference": { // "type": "string", // "description": "A memory reference for the current instruction // pointer in this frame." // }, // "moduleId": { // "type": ["integer", "string"], // "description": "The module associated with this frame, if any." // }, // "presentationHint": { // "type": "string", // "enum": [ "normal", "label", "subtle" ], // "description": "An optional hint for how to present this frame in // the UI. A value of 'label' can be used to indicate // that the frame is an artificial frame that is used // as a visual label or separator. A value of 'subtle' // can be used to change the appearance of a frame in // a 'subtle' way." // } // }, // "required": [ "id", "name", "line", "column" ] // } llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { … } llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread) { … } // Response to `setInstructionBreakpoints` request. // "Breakpoint": { // "type": "object", // "description": "Response to `setInstructionBreakpoints` request.", // "properties": { // "id": { // "type": "number", // "description": "The identifier for the breakpoint. It is needed if // breakpoint events are used to update or remove breakpoints." // }, // "verified": { // "type": "boolean", // "description": "If true, the breakpoint could be set (but not // necessarily at the desired location." // }, // "message": { // "type": "string", // "description": "A message about the state of the breakpoint. // This is shown to the user and can be used to explain why a breakpoint // could not be verified." // }, // "source": { // "type": "Source", // "description": "The source where the breakpoint is located." // }, // "line": { // "type": "number", // "description": "The start line of the actual range covered by the // breakpoint." // }, // "column": { // "type": "number", // "description": "The start column of the actual range covered by the // breakpoint." // }, // "endLine": { // "type": "number", // "description": "The end line of the actual range covered by the // breakpoint." // }, // "endColumn": { // "type": "number", // "description": "The end column of the actual range covered by the // breakpoint. If no end line is given, then the end column is assumed to // be in the start line." // }, // "instructionReference": { // "type": "string", // "description": "A memory reference to where the breakpoint is set." // }, // "offset": { // "type": "number", // "description": "The offset from the instruction reference. // This can be negative." // }, // }, // "required": [ "id", "verified", "line"] // } llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) { … } // "Thread": { // "type": "object", // "description": "A Thread", // "properties": { // "id": { // "type": "integer", // "description": "Unique identifier for the thread." // }, // "name": { // "type": "string", // "description": "A name of the thread." // } // }, // "required": [ "id", "name" ] // } llvm::json::Value CreateThread(lldb::SBThread &thread) { … } // "StoppedEvent": { // "allOf": [ { "$ref": "#/definitions/Event" }, { // "type": "object", // "description": "Event message for 'stopped' event type. The event // indicates that the execution of the debuggee has stopped // due to some condition. This can be caused by a break // point previously set, a stepping action has completed, // by executing a debugger statement etc.", // "properties": { // "event": { // "type": "string", // "enum": [ "stopped" ] // }, // "body": { // "type": "object", // "properties": { // "reason": { // "type": "string", // "description": "The reason for the event. For backward // compatibility this string is shown in the UI if // the 'description' attribute is missing (but it // must not be translated).", // "_enum": [ "step", "breakpoint", "exception", "pause", "entry" ] // }, // "description": { // "type": "string", // "description": "The full reason for the event, e.g. 'Paused // on exception'. This string is shown in the UI // as is." // }, // "threadId": { // "type": "integer", // "description": "The thread which was stopped." // }, // "text": { // "type": "string", // "description": "Additional information. E.g. if reason is // 'exception', text contains the exception name. // This string is shown in the UI." // }, // "allThreadsStopped": { // "type": "boolean", // "description": "If allThreadsStopped is true, a debug adapter // can announce that all threads have stopped. // The client should use this information to // enable that all threads can be expanded to // access their stacktraces. If the attribute // is missing or false, only the thread with the // given threadId can be expanded." // } // }, // "required": [ "reason" ] // } // }, // "required": [ "event", "body" ] // }] // } llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, uint32_t stop_id) { … } const char *GetNonNullVariableName(lldb::SBValue v) { … } std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v, bool is_name_duplicated) { … } VariableDescription::VariableDescription(lldb::SBValue v, bool format_hex, bool is_name_duplicated, std::optional<std::string> custom_name) : … { … } llvm::json::Object VariableDescription::GetVariableExtensionsJSON() { … } std::string VariableDescription::GetResult(llvm::StringRef context) { … } // "Variable": { // "type": "object", // "description": "A Variable is a name/value pair. Optionally a variable // can have a 'type' that is shown if space permits or when // hovering over the variable's name. An optional 'kind' is // used to render additional properties of the variable, // e.g. different icons can be used to indicate that a // variable is public or private. If the value is // structured (has children), a handle is provided to // retrieve the children with the VariablesRequest. If // the number of named or indexed children is large, the // numbers should be returned via the optional // 'namedVariables' and 'indexedVariables' attributes. The // client can use this optional information to present the // children in a paged UI and fetch them in chunks.", // "properties": { // "name": { // "type": "string", // "description": "The variable's name." // }, // "value": { // "type": "string", // "description": "The variable's value. This can be a multi-line text, // e.g. for a function the body of a function." // }, // "type": { // "type": "string", // "description": "The type of the variable's value. Typically shown in // the UI when hovering over the value." // }, // "presentationHint": { // "$ref": "#/definitions/VariablePresentationHint", // "description": "Properties of a variable that can be used to determine // how to render the variable in the UI." // }, // "evaluateName": { // "type": "string", // "description": "Optional evaluatable name of this variable which can // be passed to the 'EvaluateRequest' to fetch the // variable's value." // }, // "variablesReference": { // "type": "integer", // "description": "If variablesReference is > 0, the variable is // structured and its children can be retrieved by // passing variablesReference to the VariablesRequest." // }, // "namedVariables": { // "type": "integer", // "description": "The number of named child variables. The client can // use this optional information to present the children // in a paged UI and fetch them in chunks." // }, // "indexedVariables": { // "type": "integer", // "description": "The number of indexed child variables. The client // can use this optional information to present the // children in a paged UI and fetch them in chunks." // }, // "memoryReference": { // "type": "string", // "description": "A memory reference associated with this variable. // For pointer type variables, this is generally a // reference to the memory address contained in the // pointer. For executable data, this reference may later // be used in a `disassemble` request. This attribute may // be returned by a debug adapter if corresponding // capability `supportsMemoryReferences` is true." // }, // "declarationLocationReference": { // "type": "integer", // "description": "A reference that allows the client to request the // location where the variable is declared. This should be // present only if the adapter is likely to be able to // resolve the location.\n\nThis reference shares the same // lifetime as the `variablesReference`. See 'Lifetime of // Object References' in the Overview section for // details." // }, // // "$__lldb_extensions": { // "description": "Unofficial extensions to the protocol", // "properties": { // "declaration": { // "type": "object", // "description": "The source location where the variable was // declared. This value won't be present if no // declaration is available. // Superseded by `declarationLocationReference`", // "properties": { // "path": { // "type": "string", // "description": "The source file path where the variable was // declared." // }, // "line": { // "type": "number", // "description": "The 1-indexed source line where the variable // was declared." // }, // "column": { // "type": "number", // "description": "The 1-indexed source column where the variable // was declared." // } // } // }, // "value": { // "type": "string", // "description": "The internal value of the variable as returned by // This is effectively SBValue.GetValue(). The other // `value` entry in the top-level variable response // is, on the other hand, just a display string for // the variable." // }, // "summary": { // "type": "string", // "description": "The summary string of the variable. This is // effectively SBValue.GetSummary()." // }, // "autoSummary": { // "type": "string", // "description": "The auto generated summary if using // `enableAutoVariableSummaries`." // }, // "error": { // "type": "string", // "description": "An error message generated if LLDB couldn't inspect // the variable." // } // } // } // }, // "required": [ "name", "value", "variablesReference" ] // } llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, bool format_hex, bool is_name_duplicated, std::optional<std::string> custom_name) { … } llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) { … } /// See /// https://microsoft.github.io/debug-adapter-protocol/specification#Reverse_Requests_RunInTerminal llvm::json::Object CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, llvm::StringRef debug_adaptor_path, llvm::StringRef comm_file, lldb::pid_t debugger_pid) { … } // Keep all the top level items from the statistics dump, except for the // "modules" array. It can be huge and cause delay // Array and dictionary value will return as <key, JSON string> pairs void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key, llvm::json::Object &out) { … } void addStatistic(llvm::json::Object &event) { … } llvm::json::Object CreateTerminatedEventObject() { … } std::string JSONToString(const llvm::json::Value &json) { … } } // namespace lldb_dap