[lldb] Add evaluation modes to DIL (#178747)
Adding more supported operators to DIL breaks tests in `DWIMPrint` and `lldb-dap`, which shouldn't be simply adjusted for new DIL capabilities. They act as a check for the boundaries of what subset of expressions `DWIMPrint` and `lldb-dap` expect to be evaluated when using `GetValueForVariableExpressionPath` function. With this patch, the caller can now pick a mode that limits the expressions DIL can evaluate, which ensures the expected preexisting behavior. More operators can now be safely added to DIL, which can still be evaluated by DIL when using `frame var` command or the API call with Full mode selected (or not specified at all). DIL will only attempt evaluating expressions that contain operations allowed by a selected mode: - Simple: identifiers, operators: '.' - Legacy: identifiers, integers, operators: '.', '->', '*', '&', '[]' - Full: everything supported by DIL
This commit is contained in:
parent
b447f5d976
commit
80fffd527c
@ -182,12 +182,16 @@ public:
|
||||
// expression result and is not a constant object like
|
||||
// SBFrame::EvaluateExpression(...) returns, but a child object of the
|
||||
// variable value.
|
||||
lldb::SBValue GetValueForVariablePath(const char *var_expr_cstr,
|
||||
DynamicValueType use_dynamic);
|
||||
lldb::SBValue
|
||||
GetValueForVariablePath(const char *var_expr_cstr,
|
||||
DynamicValueType use_dynamic,
|
||||
lldb::DILMode mode = lldb::eDILModeFull);
|
||||
|
||||
/// The version that doesn't supply a 'use_dynamic' value will use the
|
||||
/// target's default.
|
||||
lldb::SBValue GetValueForVariablePath(const char *var_path);
|
||||
lldb::SBValue
|
||||
GetValueForVariablePath(const char *var_path,
|
||||
lldb::DILMode mode = lldb::eDILModeFull);
|
||||
|
||||
/// Find variables, register sets, registers, or persistent variables using
|
||||
/// the frame as the scope.
|
||||
|
||||
@ -86,7 +86,8 @@ public:
|
||||
|
||||
lldb::ValueObjectSP GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode = lldb::eDILModeFull) override;
|
||||
|
||||
bool HasDebugInformation() override;
|
||||
|
||||
|
||||
@ -316,11 +316,16 @@ public:
|
||||
/// \param[in] error
|
||||
/// Record any errors encountered while evaluating var_expr.
|
||||
///
|
||||
/// \param[in] mode
|
||||
/// Data Inspection Language (DIL) evaluation mode.
|
||||
/// \see lldb::DILMode
|
||||
///
|
||||
/// \return
|
||||
/// A shared pointer to the ValueObject described by var_expr.
|
||||
virtual lldb::ValueObjectSP GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error);
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode = lldb::eDILModeFull);
|
||||
|
||||
/// Determine whether this StackFrame has debug information available or not.
|
||||
///
|
||||
@ -615,7 +620,8 @@ private:
|
||||
|
||||
lldb::ValueObjectSP DILGetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error);
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode = lldb::eDILModeFull);
|
||||
|
||||
StackFrame(const StackFrame &) = delete;
|
||||
const StackFrame &operator=(const StackFrame &) = delete;
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#ifndef LLDB_VALUEOBJECT_DILLEXER_H
|
||||
#define LLDB_VALUEOBJECT_DILLEXER_H
|
||||
|
||||
#include "lldb/lldb-enumerations.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/FormatVariadic.h"
|
||||
@ -74,7 +75,8 @@ class DILLexer {
|
||||
public:
|
||||
/// Lexes all the tokens in expr and calls the private constructor
|
||||
/// with the lexed tokens.
|
||||
static llvm::Expected<DILLexer> Create(llvm::StringRef expr);
|
||||
static llvm::Expected<DILLexer>
|
||||
Create(llvm::StringRef expr, lldb::DILMode mode = lldb::eDILModeFull);
|
||||
|
||||
/// Return the current token to be handled by the DIL parser.
|
||||
const Token &GetCurrentToken() { return m_lexed_tokens[m_tokens_idx]; }
|
||||
|
||||
@ -1420,6 +1420,19 @@ enum NameMatchStyle {
|
||||
eNameMatchStyleRegex = eFunctionNameTypeSelector << 1
|
||||
};
|
||||
|
||||
/// Data Inspection Language (DIL) evaluation modes.
|
||||
/// DIL will only attempt evaluating expressions that contain tokens
|
||||
/// allowed by a selected mode.
|
||||
enum DILMode {
|
||||
/// Allowed: identifiers, operators: '.'.
|
||||
eDILModeSimple,
|
||||
/// Allowed: identifiers, integers, operators: '.', '->', '*', '&', '[]'.
|
||||
eDILModeLegacy,
|
||||
/// Allowed: everything supported by DIL.
|
||||
/// \see lldb/docs/dil-expr-lang.ebnf
|
||||
eDILModeFull
|
||||
};
|
||||
|
||||
} // namespace lldb
|
||||
|
||||
#endif // LLDB_LLDB_ENUMERATIONS_H
|
||||
|
||||
@ -363,7 +363,8 @@ void SBFrame::Clear() {
|
||||
m_opaque_sp->Clear();
|
||||
}
|
||||
|
||||
lldb::SBValue SBFrame::GetValueForVariablePath(const char *var_path) {
|
||||
lldb::SBValue SBFrame::GetValueForVariablePath(const char *var_path,
|
||||
lldb::DILMode mode) {
|
||||
LLDB_INSTRUMENT_VA(this, var_path);
|
||||
|
||||
SBValue sb_value;
|
||||
@ -377,13 +378,14 @@ lldb::SBValue SBFrame::GetValueForVariablePath(const char *var_path) {
|
||||
if (StackFrame *frame = exe_ctx->GetFramePtr()) {
|
||||
lldb::DynamicValueType use_dynamic =
|
||||
frame->CalculateTarget()->GetPreferDynamicValue();
|
||||
sb_value = GetValueForVariablePath(var_path, use_dynamic);
|
||||
sb_value = GetValueForVariablePath(var_path, use_dynamic, mode);
|
||||
}
|
||||
return sb_value;
|
||||
}
|
||||
|
||||
lldb::SBValue SBFrame::GetValueForVariablePath(const char *var_path,
|
||||
DynamicValueType use_dynamic) {
|
||||
DynamicValueType use_dynamic,
|
||||
lldb::DILMode mode) {
|
||||
LLDB_INSTRUMENT_VA(this, var_path, use_dynamic);
|
||||
|
||||
SBValue sb_value;
|
||||
@ -405,7 +407,7 @@ lldb::SBValue SBFrame::GetValueForVariablePath(const char *var_path,
|
||||
var_path, eNoDynamicValues,
|
||||
StackFrame::eExpressionPathOptionCheckPtrVsMember |
|
||||
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess,
|
||||
var_sp, error));
|
||||
var_sp, error, mode));
|
||||
sb_value.SetSP(value_sp, use_dynamic);
|
||||
}
|
||||
return sb_value;
|
||||
|
||||
@ -170,8 +170,8 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
|
||||
Status status;
|
||||
auto valobj_sp = frame->GetValueForVariableExpressionPath(
|
||||
expr, eval_options.GetUseDynamic(),
|
||||
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess, var_sp,
|
||||
status);
|
||||
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess, var_sp, status,
|
||||
lldb::eDILModeSimple);
|
||||
if (valobj_sp && status.Success() && valobj_sp->GetError().Success()) {
|
||||
if (!suppress_result) {
|
||||
if (auto persisted_valobj = valobj_sp->Persist())
|
||||
|
||||
@ -316,7 +316,8 @@ lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
|
||||
|
||||
lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error) {
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode) {
|
||||
// Unless the frame implementation knows how to create variables (which it
|
||||
// doesn't), we can't construct anything for the variable. This may seem
|
||||
// somewhat out of place, but it's basically because of how this API is used -
|
||||
|
||||
@ -76,7 +76,8 @@ public:
|
||||
|
||||
lldb::ValueObjectSP GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode = lldb::eDILModeFull) override;
|
||||
|
||||
bool isA(const void *ClassID) const override {
|
||||
return ClassID == &ID || StackFrame::isA(ClassID);
|
||||
|
||||
@ -99,9 +99,9 @@ BorrowedStackFrame::GetInScopeVariableList(bool get_file_globals,
|
||||
|
||||
ValueObjectSP BorrowedStackFrame::GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options,
|
||||
VariableSP &var_sp, Status &error) {
|
||||
VariableSP &var_sp, Status &error, lldb::DILMode mode) {
|
||||
return m_borrowed_frame_sp->GetValueForVariableExpressionPath(
|
||||
var_expr, use_dynamic, options, var_sp, error);
|
||||
var_expr, use_dynamic, options, var_sp, error, mode);
|
||||
}
|
||||
|
||||
bool BorrowedStackFrame::HasDebugInformation() {
|
||||
|
||||
@ -524,13 +524,13 @@ StackFrame::GetInScopeVariableList(bool get_file_globals,
|
||||
|
||||
ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options,
|
||||
VariableSP &var_sp, Status &error) {
|
||||
VariableSP &var_sp, Status &error, lldb::DILMode mode) {
|
||||
ExecutionContext exe_ctx;
|
||||
CalculateExecutionContext(exe_ctx);
|
||||
bool use_DIL = exe_ctx.GetTargetRef().GetUseDIL(&exe_ctx);
|
||||
if (use_DIL)
|
||||
return DILGetValueForVariableExpressionPath(var_expr, use_dynamic, options,
|
||||
var_sp, error);
|
||||
var_sp, error, mode);
|
||||
|
||||
return LegacyGetValueForVariableExpressionPath(var_expr, use_dynamic, options,
|
||||
var_sp, error);
|
||||
@ -538,7 +538,8 @@ ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
|
||||
|
||||
ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath(
|
||||
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error) {
|
||||
uint32_t options, lldb::VariableSP &var_sp, Status &error,
|
||||
lldb::DILMode mode) {
|
||||
|
||||
const bool check_ptr_vs_member =
|
||||
(options & eExpressionPathOptionCheckPtrVsMember) != 0;
|
||||
@ -548,7 +549,7 @@ ValueObjectSP StackFrame::DILGetValueForVariableExpressionPath(
|
||||
(options & eExpressionPathOptionsNoSyntheticChildren) != 0;
|
||||
|
||||
// Lex the expression.
|
||||
auto lex_or_err = dil::DILLexer::Create(var_expr);
|
||||
auto lex_or_err = dil::DILLexer::Create(var_expr, mode);
|
||||
if (!lex_or_err) {
|
||||
error = Status::FromError(lex_or_err.takeError());
|
||||
return ValueObjectConstResult::Create(nullptr, std::move(error));
|
||||
|
||||
@ -111,12 +111,41 @@ static std::optional<llvm::StringRef> IsNumber(llvm::StringRef &remainder,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
llvm::Expected<DILLexer> DILLexer::Create(llvm::StringRef expr) {
|
||||
static llvm::Error IsNotAllowedByMode(llvm::StringRef expr, Token token,
|
||||
lldb::DILMode mode) {
|
||||
switch (mode) {
|
||||
case lldb::eDILModeSimple:
|
||||
if (!token.IsOneOf({Token::identifier, Token::period, Token::eof})) {
|
||||
return llvm::make_error<DILDiagnosticError>(
|
||||
expr, llvm::formatv("{0} is not allowed in DIL simple mode", token),
|
||||
token.GetLocation());
|
||||
}
|
||||
break;
|
||||
case lldb::eDILModeLegacy:
|
||||
if (!token.IsOneOf({Token::identifier, Token::integer_constant,
|
||||
Token::period, Token::arrow, Token::star, Token::amp,
|
||||
Token::l_square, Token::r_square, Token::eof})) {
|
||||
return llvm::make_error<DILDiagnosticError>(
|
||||
expr, llvm::formatv("{0} is not allowed in DIL legacy mode", token),
|
||||
token.GetLocation());
|
||||
}
|
||||
break;
|
||||
case lldb::eDILModeFull:
|
||||
break;
|
||||
}
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Expected<DILLexer> DILLexer::Create(llvm::StringRef expr,
|
||||
lldb::DILMode mode) {
|
||||
std::vector<Token> tokens;
|
||||
llvm::StringRef remainder = expr;
|
||||
do {
|
||||
if (llvm::Expected<Token> t = Lex(expr, remainder)) {
|
||||
tokens.push_back(std::move(*t));
|
||||
Token token = *t;
|
||||
if (llvm::Error error = IsNotAllowedByMode(expr, token, mode))
|
||||
return error;
|
||||
tokens.push_back(std::move(token));
|
||||
} else {
|
||||
return t.takeError();
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ class TestFrameVarDILGlobalVariableLookup(TestBase):
|
||||
|
||||
def test_frame_var(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
@ -36,3 +36,10 @@ class TestFrameVarDILGlobalVariableLookup(TestBase):
|
||||
self.expect_var_path("&globalVar", True, type="int *")
|
||||
self.expect_var_path("&s_str", True, type="const char **")
|
||||
self.expect_var_path("&argc", True, type="int *")
|
||||
|
||||
# Check that '&' is not allowed in simple mode, but allowed in legacy mode
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("&x", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("&x", lldb.eDILModeLegacy)
|
||||
self.assertFailure(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
@ -13,7 +13,7 @@ class TestFrameVarDILArraySubscript(TestBase):
|
||||
|
||||
def test_subscript(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
@ -87,6 +87,13 @@ class TestFrameVarDILArraySubscript(TestBase):
|
||||
substrs=["subscript of pointer to incomplete type 'void'"],
|
||||
)
|
||||
|
||||
# Check that subscription is not allowed in simple mode, but allowed in legacy mode
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("int_arr[0]", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("int_arr[0]", lldb.eDILModeLegacy)
|
||||
self.assertFailure(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
def test_subscript_synthetic(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
|
||||
@ -16,7 +16,7 @@ class TestFrameVarDILLocalVars(TestBase):
|
||||
|
||||
def test_frame_var(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
@ -25,3 +25,10 @@ class TestFrameVarDILLocalVars(TestBase):
|
||||
self.expect_var_path("b", value="2")
|
||||
self.expect_var_path("c", value="'\\xfd'")
|
||||
self.expect_var_path("s", value="4")
|
||||
|
||||
# Check that identifiers are allowed in both simple and legacy modes
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("a", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("a", lldb.eDILModeLegacy)
|
||||
self.assertSuccess(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
@ -11,6 +11,7 @@ import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
|
||||
class TestFrameVarDILMemberOf(TestBase):
|
||||
# If your test case doesn't stress debug info, then
|
||||
# set this to true. That way it won't be run once for
|
||||
@ -19,11 +20,11 @@ class TestFrameVarDILMemberOf(TestBase):
|
||||
|
||||
def test_frame_var(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(self, "Set a breakpoint here",
|
||||
lldb.SBFileSpec("main.cpp"))
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
self.expect("settings set target.experimental.use-DIL true",
|
||||
substrs=[""])
|
||||
self.expect("settings set target.experimental.use-DIL true", substrs=[""])
|
||||
self.expect_var_path("s.x", value="1")
|
||||
self.expect_var_path("s.r", type="int &")
|
||||
self.expect_var_path("sr.x", value="1")
|
||||
@ -48,3 +49,16 @@ class TestFrameVarDILMemberOf(TestBase):
|
||||
# Test for record typedefs.
|
||||
self.expect_var_path("sa.x", value="3")
|
||||
self.expect_var_path("sa.y", value="'\\x04'")
|
||||
|
||||
# Check that '.' is allowed in both simple and legacy modes
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("s.x", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("s.x", lldb.eDILModeLegacy)
|
||||
self.assertSuccess(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
# Check that '->' is not allowed in simple mode, but allowed in legacy mode
|
||||
simple = frame.GetValueForVariablePath("sp->x", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("sp->x", lldb.eDILModeLegacy)
|
||||
self.assertFailure(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
@ -17,7 +17,7 @@ class TestFrameVarDILPointerDereference(TestBase):
|
||||
|
||||
def test_frame_var(self):
|
||||
self.build()
|
||||
lldbutil.run_to_source_breakpoint(
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
)
|
||||
|
||||
@ -46,3 +46,10 @@ class TestFrameVarDILPointerDereference(TestBase):
|
||||
pp_int0_2stars_got.GetValueAsAddress(),
|
||||
pp_int0_2stars_exp.GetValueAsAddress(),
|
||||
)
|
||||
|
||||
# Check that * is not allowed in simple mode, but allowed in legacy mode
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("*p_int0", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("*p_int0", lldb.eDILModeLegacy)
|
||||
self.assertFailure(simple.GetError())
|
||||
self.assertSuccess(legacy.GetError())
|
||||
|
||||
@ -285,3 +285,10 @@ class TestFrameVarDILCast(TestBase):
|
||||
error=True,
|
||||
substrs=["expected 'eof', got: <'InnerFoo' (identifier)>"],
|
||||
)
|
||||
|
||||
# Check that casts are not allowed in both simple and legacy modes
|
||||
frame = thread.GetFrameAtIndex(0)
|
||||
simple = frame.GetValueForVariablePath("(char)a", lldb.eDILModeSimple)
|
||||
legacy = frame.GetValueForVariablePath("(char)a", lldb.eDILModeLegacy)
|
||||
self.assertFailure(simple.GetError())
|
||||
self.assertFailure(legacy.GetError())
|
||||
|
||||
@ -253,6 +253,7 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase):
|
||||
self.assertEvaluateFailure("a_function(1)")
|
||||
self.assertEvaluateFailure("var2 + struct1.foo")
|
||||
self.assertEvaluateFailure("foo_func")
|
||||
self.assertEvaluateFailure("(float) var2")
|
||||
self.assertEvaluate("foo_var", "44")
|
||||
|
||||
# Expressions at breakpoint 2, which is an anonymous block
|
||||
|
||||
@ -54,8 +54,8 @@ static lldb::SBValue EvaluateVariableExpression(lldb::SBTarget &target,
|
||||
// Check if it is a variable or an expression path for a variable. i.e.
|
||||
// 'foo->bar' finds the 'bar' variable. It is more reliable than the
|
||||
// expression parser in many cases and it is faster.
|
||||
value = frame.GetValueForVariablePath(expression_cstr,
|
||||
lldb::eDynamicDontRunTarget);
|
||||
value = frame.GetValueForVariablePath(
|
||||
expression_cstr, lldb::eDynamicDontRunTarget, lldb::eDILModeLegacy);
|
||||
if (value || !run_as_expression)
|
||||
return value;
|
||||
|
||||
|
||||
@ -397,8 +397,8 @@ bool SourceBreakpoint::BreakpointHitCallback(
|
||||
// evaluation
|
||||
const std::string &expr_str = messagePart.text;
|
||||
const char *expr = expr_str.c_str();
|
||||
lldb::SBValue value =
|
||||
frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
|
||||
lldb::SBValue value = frame.GetValueForVariablePath(
|
||||
expr, lldb::eDynamicDontRunTarget, lldb::eDILModeLegacy);
|
||||
if (value.GetError().Fail())
|
||||
value = frame.EvaluateExpression(expr);
|
||||
output += VariableDescription(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user