
EmplaceSafeString accepts StringRef for the last parameter, str, and then internally creates a copy of str via StringRef::str or llvm::json::fixUTF8, so caller do not need to create their own temporary instances of std::string.
235 lines
9.8 KiB
C++
235 lines
9.8 KiB
C++
//===-- EvaluateRequestHandler.cpp ----------------------------------------===//
|
|
//
|
|
// 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 "DAP.h"
|
|
#include "EventHelper.h"
|
|
#include "JSONUtils.h"
|
|
#include "LLDBUtils.h"
|
|
#include "RequestHandler.h"
|
|
|
|
namespace lldb_dap {
|
|
|
|
// "EvaluateRequest": {
|
|
// "allOf": [ { "$ref": "#/definitions/Request" }, {
|
|
// "type": "object",
|
|
// "description": "Evaluate request; value of command field is 'evaluate'.
|
|
// Evaluates the given expression in the context of the
|
|
// top most stack frame. The expression has access to any
|
|
// variables and arguments that are in scope.",
|
|
// "properties": {
|
|
// "command": {
|
|
// "type": "string",
|
|
// "enum": [ "evaluate" ]
|
|
// },
|
|
// "arguments": {
|
|
// "$ref": "#/definitions/EvaluateArguments"
|
|
// }
|
|
// },
|
|
// "required": [ "command", "arguments" ]
|
|
// }]
|
|
// },
|
|
// "EvaluateArguments": {
|
|
// "type": "object",
|
|
// "description": "Arguments for 'evaluate' request.",
|
|
// "properties": {
|
|
// "expression": {
|
|
// "type": "string",
|
|
// "description": "The expression to evaluate."
|
|
// },
|
|
// "frameId": {
|
|
// "type": "integer",
|
|
// "description": "Evaluate the expression in the scope of this stack
|
|
// frame. If not specified, the expression is evaluated
|
|
// in the global scope."
|
|
// },
|
|
// "context": {
|
|
// "type": "string",
|
|
// "_enum": [ "watch", "repl", "hover" ],
|
|
// "enumDescriptions": [
|
|
// "evaluate is run in a watch.",
|
|
// "evaluate is run from REPL console.",
|
|
// "evaluate is run from a data hover."
|
|
// ],
|
|
// "description": "The context in which the evaluate request is run."
|
|
// },
|
|
// "format": {
|
|
// "$ref": "#/definitions/ValueFormat",
|
|
// "description": "Specifies details on how to format the Evaluate
|
|
// result."
|
|
// }
|
|
// },
|
|
// "required": [ "expression" ]
|
|
// },
|
|
// "EvaluateResponse": {
|
|
// "allOf": [ { "$ref": "#/definitions/Response" }, {
|
|
// "type": "object",
|
|
// "description": "Response to 'evaluate' request.",
|
|
// "properties": {
|
|
// "body": {
|
|
// "type": "object",
|
|
// "properties": {
|
|
// "result": {
|
|
// "type": "string",
|
|
// "description": "The result of the evaluate request."
|
|
// },
|
|
// "type": {
|
|
// "type": "string",
|
|
// "description": "The optional type of the evaluate result."
|
|
// },
|
|
// "presentationHint": {
|
|
// "$ref": "#/definitions/VariablePresentationHint",
|
|
// "description": "Properties of a evaluate result that can be
|
|
// used to determine how to render the result in
|
|
// the UI."
|
|
// },
|
|
// "variablesReference": {
|
|
// "type": "number",
|
|
// "description": "If variablesReference is > 0, the evaluate
|
|
// result is structured and its children can be
|
|
// retrieved by passing variablesReference to the
|
|
// VariablesRequest."
|
|
// },
|
|
// "namedVariables": {
|
|
// "type": "number",
|
|
// "description": "The number of named child variables. The
|
|
// client can use this optional information to
|
|
// present the variables in a paged UI and fetch
|
|
// them in chunks."
|
|
// },
|
|
// "indexedVariables": {
|
|
// "type": "number",
|
|
// "description": "The number of indexed child variables. The
|
|
// client can use this optional information to
|
|
// present the variables in a paged UI and fetch
|
|
// them in chunks."
|
|
// },
|
|
// "valueLocationReference": {
|
|
// "type": "integer",
|
|
// "description": "A reference that allows the client to request
|
|
// the location where the returned value is
|
|
// declared. For example, if a function pointer is
|
|
// returned, the adapter may be able to look up the
|
|
// function's location. 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."
|
|
// }
|
|
// "memoryReference": {
|
|
// "type": "string",
|
|
// "description": "A memory reference to a location appropriate
|
|
// for this result. For pointer type eval
|
|
// results, this is generally a reference to the
|
|
// memory address contained in the pointer. This
|
|
// attribute may be returned by a debug adapter
|
|
// if corresponding capability
|
|
// `supportsMemoryReferences` is true."
|
|
// },
|
|
// },
|
|
// "required": [ "result", "variablesReference" ]
|
|
// }
|
|
// },
|
|
// "required": [ "body" ]
|
|
// }]
|
|
// }
|
|
void EvaluateRequestHandler::operator()(
|
|
const llvm::json::Object &request) const {
|
|
llvm::json::Object response;
|
|
FillResponse(request, response);
|
|
llvm::json::Object body;
|
|
const auto *arguments = request.getObject("arguments");
|
|
lldb::SBFrame frame = dap.GetLLDBFrame(*arguments);
|
|
std::string expression =
|
|
GetString(arguments, "expression").value_or("").str();
|
|
const llvm::StringRef context = GetString(arguments, "context").value_or("");
|
|
bool repeat_last_command =
|
|
expression.empty() && dap.last_nonempty_var_expression.empty();
|
|
|
|
if (context == "repl" &&
|
|
(repeat_last_command ||
|
|
(!expression.empty() &&
|
|
dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) {
|
|
// Since the current expression is not for a variable, clear the
|
|
// last_nonempty_var_expression field.
|
|
dap.last_nonempty_var_expression.clear();
|
|
// If we're evaluating a command relative to the current frame, set the
|
|
// focus_tid to the current frame for any thread related events.
|
|
if (frame.IsValid()) {
|
|
dap.focus_tid = frame.GetThread().GetThreadID();
|
|
}
|
|
|
|
bool required_command_failed = false;
|
|
std::string result = RunLLDBCommands(
|
|
dap.debugger, llvm::StringRef(), {expression}, required_command_failed,
|
|
/*parse_command_directives=*/false, /*echo_commands=*/false);
|
|
|
|
EmplaceSafeString(body, "result", result);
|
|
body.try_emplace("variablesReference", (int64_t)0);
|
|
} else {
|
|
if (context == "repl") {
|
|
// If the expression is empty and the last expression was for a
|
|
// variable, set the expression to the previous expression (repeat the
|
|
// evaluation); otherwise save the current non-empty expression for the
|
|
// next (possibly empty) variable expression.
|
|
if (expression.empty())
|
|
expression = dap.last_nonempty_var_expression;
|
|
else
|
|
dap.last_nonempty_var_expression = expression;
|
|
}
|
|
// Always try to get the answer from the local variables if possible. If
|
|
// this fails, then if the context is not "hover", actually evaluate an
|
|
// expression using the expression parser.
|
|
//
|
|
// "frame variable" is more reliable than the expression parser in
|
|
// many cases and it is faster.
|
|
lldb::SBValue value = frame.GetValueForVariablePath(
|
|
expression.data(), lldb::eDynamicDontRunTarget);
|
|
|
|
// Freeze dry the value in case users expand it later in the debug console
|
|
if (value.GetError().Success() && context == "repl")
|
|
value = value.Persist();
|
|
|
|
if (value.GetError().Fail() && context != "hover")
|
|
value = frame.EvaluateExpression(expression.data());
|
|
|
|
if (value.GetError().Fail()) {
|
|
response["success"] = llvm::json::Value(false);
|
|
// This error object must live until we're done with the pointer returned
|
|
// by GetCString().
|
|
lldb::SBError error = value.GetError();
|
|
const char *error_cstr = error.GetCString();
|
|
if (error_cstr && error_cstr[0])
|
|
EmplaceSafeString(response, "message", error_cstr);
|
|
else
|
|
EmplaceSafeString(response, "message", "evaluate failed");
|
|
} else {
|
|
VariableDescription desc(value,
|
|
dap.configuration.enableAutoVariableSummaries);
|
|
EmplaceSafeString(body, "result", desc.GetResult(context));
|
|
EmplaceSafeString(body, "type", desc.display_type_name);
|
|
int64_t var_ref = 0;
|
|
if (value.MightHaveChildren() || ValuePointsToCode(value))
|
|
var_ref = dap.variables.InsertVariable(
|
|
value, /*is_permanent=*/context == "repl");
|
|
if (value.MightHaveChildren())
|
|
body.try_emplace("variablesReference", var_ref);
|
|
else
|
|
body.try_emplace("variablesReference", (int64_t)0);
|
|
if (lldb::addr_t addr = value.GetLoadAddress();
|
|
addr != LLDB_INVALID_ADDRESS)
|
|
body.try_emplace("memoryReference", EncodeMemoryReference(addr));
|
|
if (ValuePointsToCode(value))
|
|
body.try_emplace("valueLocationReference", var_ref);
|
|
}
|
|
}
|
|
response.try_emplace("body", std::move(body));
|
|
dap.SendJSON(llvm::json::Value(std::move(response)));
|
|
}
|
|
} // namespace lldb_dap
|