
This patch replaces the use of `UINT32_MAX` as the error return value of `GetIndexOfChildWithName` with `llvm::Expected`. # Tasks to do in another PR 1. Replace `CalculateNumChildrenIgnoringErrors` with `CalculateNumChildren`. See [this comment](https://github.com/llvm/llvm-project/pull/136693#discussion_r2056319358). 2. Update `lldb_private::formatters::ExtractIndexFromString` to use `llvm::Expected`. See [this comment](https://github.com/llvm/llvm-project/pull/136693#discussion_r2054217536). 3. Create a new class which carries both user and internal errors. See [this comment](https://github.com/llvm/llvm-project/pull/136693#discussion_r2056439608).
583 lines
19 KiB
C++
583 lines
19 KiB
C++
//===-- FormatterBytecode.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 "FormatterBytecode.h"
|
|
#include "lldb/Utility/LLDBLog.h"
|
|
#include "lldb/ValueObject/ValueObject.h"
|
|
#include "lldb/ValueObject/ValueObjectConstResult.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/DataExtractor.h"
|
|
#include "llvm/Support/Format.h"
|
|
#include "llvm/Support/FormatProviders.h"
|
|
#include "llvm/Support/FormatVariadicDetails.h"
|
|
|
|
using namespace lldb;
|
|
namespace lldb_private {
|
|
|
|
std::string toString(FormatterBytecode::OpCodes op) {
|
|
switch (op) {
|
|
#define DEFINE_OPCODE(OP, MNEMONIC, NAME) \
|
|
case OP: { \
|
|
const char *s = MNEMONIC; \
|
|
return s ? s : #NAME; \
|
|
}
|
|
#include "FormatterBytecode.def"
|
|
#undef DEFINE_SIGNATURE
|
|
}
|
|
return llvm::utostr(op);
|
|
}
|
|
|
|
std::string toString(FormatterBytecode::Selectors sel) {
|
|
switch (sel) {
|
|
#define DEFINE_SELECTOR(ID, NAME) \
|
|
case ID: \
|
|
return "@" #NAME;
|
|
#include "FormatterBytecode.def"
|
|
#undef DEFINE_SIGNATURE
|
|
}
|
|
return "@" + llvm::utostr(sel);
|
|
}
|
|
|
|
std::string toString(FormatterBytecode::Signatures sig) {
|
|
switch (sig) {
|
|
#define DEFINE_SIGNATURE(ID, NAME) \
|
|
case ID: \
|
|
return "@" #NAME;
|
|
#include "FormatterBytecode.def"
|
|
#undef DEFINE_SIGNATURE
|
|
}
|
|
return llvm::utostr(sig);
|
|
}
|
|
|
|
std::string toString(const FormatterBytecode::DataStack &data) {
|
|
std::string s;
|
|
llvm::raw_string_ostream os(s);
|
|
os << "[ ";
|
|
for (auto &d : data) {
|
|
if (auto s = std::get_if<std::string>(&d))
|
|
os << '"' << *s << '"';
|
|
else if (auto u = std::get_if<uint64_t>(&d))
|
|
os << *u << 'u';
|
|
else if (auto i = std::get_if<int64_t>(&d))
|
|
os << *i;
|
|
else if (auto valobj = std::get_if<ValueObjectSP>(&d)) {
|
|
if (!valobj->get())
|
|
os << "null";
|
|
else
|
|
os << "object(" << valobj->get()->GetValueAsCString() << ')';
|
|
} else if (auto type = std::get_if<CompilerType>(&d)) {
|
|
os << '(' << type->GetTypeName(true) << ')';
|
|
} else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&d)) {
|
|
os << toString(*sel);
|
|
}
|
|
os << ' ';
|
|
}
|
|
os << ']';
|
|
return s;
|
|
}
|
|
|
|
namespace FormatterBytecode {
|
|
|
|
/// Implement the @format function.
|
|
static llvm::Error FormatImpl(DataStack &data) {
|
|
auto fmt = data.Pop<std::string>();
|
|
auto replacements =
|
|
llvm::formatv_object_base::parseFormatString(fmt, 0, false);
|
|
std::string s;
|
|
llvm::raw_string_ostream os(s);
|
|
unsigned num_args = 0;
|
|
for (const auto &r : replacements)
|
|
if (r.Type == llvm::ReplacementType::Format)
|
|
num_args = std::max(num_args, r.Index + 1);
|
|
|
|
if (data.size() < num_args)
|
|
return llvm::createStringError("not enough arguments");
|
|
|
|
for (const auto &r : replacements) {
|
|
if (r.Type == llvm::ReplacementType::Literal) {
|
|
os << r.Spec;
|
|
continue;
|
|
}
|
|
using namespace llvm::support::detail;
|
|
auto arg = data[data.size() - num_args + r.Index];
|
|
auto format = [&](format_adapter &&adapter) {
|
|
llvm::FmtAlign Align(adapter, r.Where, r.Width, r.Pad);
|
|
Align.format(os, r.Options);
|
|
};
|
|
|
|
if (auto s = std::get_if<std::string>(&arg))
|
|
format(build_format_adapter(s->c_str()));
|
|
else if (auto u = std::get_if<uint64_t>(&arg))
|
|
format(build_format_adapter(u));
|
|
else if (auto i = std::get_if<int64_t>(&arg))
|
|
format(build_format_adapter(i));
|
|
else if (auto valobj = std::get_if<ValueObjectSP>(&arg)) {
|
|
if (!valobj->get())
|
|
format(build_format_adapter("null object"));
|
|
else
|
|
format(build_format_adapter(valobj->get()->GetValueAsCString()));
|
|
} else if (auto type = std::get_if<CompilerType>(&arg))
|
|
format(build_format_adapter(type->GetDisplayTypeName()));
|
|
else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg))
|
|
format(build_format_adapter(toString(*sel)));
|
|
}
|
|
data.Push(s);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type) {
|
|
if (data.size() < 1)
|
|
return llvm::createStringError("not enough elements on data stack");
|
|
|
|
auto &elem = data.back();
|
|
switch (type) {
|
|
case Any:
|
|
break;
|
|
case String:
|
|
if (!std::holds_alternative<std::string>(elem))
|
|
return llvm::createStringError("expected String");
|
|
break;
|
|
case UInt:
|
|
if (!std::holds_alternative<uint64_t>(elem))
|
|
return llvm::createStringError("expected UInt");
|
|
break;
|
|
case Int:
|
|
if (!std::holds_alternative<int64_t>(elem))
|
|
return llvm::createStringError("expected Int");
|
|
break;
|
|
case Object:
|
|
if (!std::holds_alternative<ValueObjectSP>(elem))
|
|
return llvm::createStringError("expected Object");
|
|
break;
|
|
case Type:
|
|
if (!std::holds_alternative<CompilerType>(elem))
|
|
return llvm::createStringError("expected Type");
|
|
break;
|
|
case Selector:
|
|
if (!std::holds_alternative<Selectors>(elem))
|
|
return llvm::createStringError("expected Selector");
|
|
break;
|
|
}
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type1, DataType type2) {
|
|
if (auto error = TypeCheck(data, type2))
|
|
return error;
|
|
return TypeCheck(data.drop_back(), type1);
|
|
}
|
|
|
|
static llvm::Error TypeCheck(llvm::ArrayRef<DataStackElement> data,
|
|
DataType type1, DataType type2, DataType type3) {
|
|
if (auto error = TypeCheck(data, type3))
|
|
return error;
|
|
return TypeCheck(data.drop_back(1), type2, type1);
|
|
}
|
|
|
|
llvm::Error Interpret(std::vector<ControlStackElement> &control,
|
|
DataStack &data, Selectors sel) {
|
|
if (control.empty())
|
|
return llvm::Error::success();
|
|
// Since the only data types are single endian and ULEBs, the
|
|
// endianness should not matter.
|
|
llvm::DataExtractor cur_block(control.back(), true, 64);
|
|
llvm::DataExtractor::Cursor pc(0);
|
|
|
|
while (!control.empty()) {
|
|
/// Activate the top most block from the control stack.
|
|
auto activate_block = [&]() {
|
|
// Save the return address.
|
|
if (control.size() > 1)
|
|
control[control.size() - 2] = cur_block.getData().drop_front(pc.tell());
|
|
cur_block = llvm::DataExtractor(control.back(), true, 64);
|
|
if (pc)
|
|
pc = llvm::DataExtractor::Cursor(0);
|
|
};
|
|
|
|
/// Fetch the next byte in the instruction stream.
|
|
auto next_byte = [&]() -> uint8_t {
|
|
// At the end of the current block?
|
|
while (pc.tell() >= cur_block.size() && !control.empty()) {
|
|
if (control.size() == 1) {
|
|
control.pop_back();
|
|
return 0;
|
|
}
|
|
control.pop_back();
|
|
activate_block();
|
|
}
|
|
|
|
// Fetch the next instruction.
|
|
return cur_block.getU8(pc);
|
|
};
|
|
|
|
// Fetch the next opcode.
|
|
OpCodes opcode = (OpCodes)next_byte();
|
|
if (control.empty() || !pc)
|
|
return pc.takeError();
|
|
|
|
LLDB_LOGV(GetLog(LLDBLog::DataFormatters),
|
|
"[eval {0}] opcode={1}, control={2}, data={3}", toString(sel),
|
|
toString(opcode), control.size(), toString(data));
|
|
|
|
// Various shorthands to improve the readability of error handling.
|
|
#define TYPE_CHECK(...) \
|
|
if (auto error = TypeCheck(data, __VA_ARGS__)) \
|
|
return error;
|
|
|
|
auto error = [&](llvm::Twine msg) {
|
|
return llvm::createStringError(msg + "(opcode=" + toString(opcode) + ")");
|
|
};
|
|
|
|
switch (opcode) {
|
|
// Data stack manipulation.
|
|
case op_dup:
|
|
TYPE_CHECK(Any);
|
|
data.Push(data.back());
|
|
continue;
|
|
case op_drop:
|
|
TYPE_CHECK(Any);
|
|
data.pop_back();
|
|
continue;
|
|
case op_pick: {
|
|
TYPE_CHECK(UInt);
|
|
uint64_t idx = data.Pop<uint64_t>();
|
|
if (idx >= data.size())
|
|
return error("index out of bounds");
|
|
data.Push(data[idx]);
|
|
continue;
|
|
}
|
|
case op_over:
|
|
TYPE_CHECK(Any, Any);
|
|
data.Push(data[data.size() - 2]);
|
|
continue;
|
|
case op_swap: {
|
|
TYPE_CHECK(Any, Any);
|
|
auto x = data.PopAny();
|
|
auto y = data.PopAny();
|
|
data.Push(x);
|
|
data.Push(y);
|
|
continue;
|
|
}
|
|
case op_rot: {
|
|
TYPE_CHECK(Any, Any, Any);
|
|
auto z = data.PopAny();
|
|
auto y = data.PopAny();
|
|
auto x = data.PopAny();
|
|
data.Push(z);
|
|
data.Push(x);
|
|
data.Push(y);
|
|
continue;
|
|
}
|
|
|
|
// Control stack manipulation.
|
|
case op_begin: {
|
|
uint64_t length = cur_block.getULEB128(pc);
|
|
if (!pc)
|
|
return pc.takeError();
|
|
llvm::StringRef block = cur_block.getBytes(pc, length);
|
|
if (!pc)
|
|
return pc.takeError();
|
|
control.push_back(block);
|
|
continue;
|
|
}
|
|
case op_if:
|
|
TYPE_CHECK(UInt);
|
|
if (data.Pop<uint64_t>() != 0) {
|
|
if (!cur_block.size())
|
|
return error("empty control stack");
|
|
activate_block();
|
|
} else
|
|
control.pop_back();
|
|
continue;
|
|
case op_ifelse:
|
|
TYPE_CHECK(UInt);
|
|
if (cur_block.size() < 2)
|
|
return error("empty control stack");
|
|
if (data.Pop<uint64_t>() == 0)
|
|
control[control.size() - 2] = control.back();
|
|
control.pop_back();
|
|
activate_block();
|
|
continue;
|
|
case op_return:
|
|
control.clear();
|
|
return pc.takeError();
|
|
|
|
// Literals.
|
|
case op_lit_uint:
|
|
data.Push(cur_block.getULEB128(pc));
|
|
continue;
|
|
case op_lit_int:
|
|
data.Push(cur_block.getSLEB128(pc));
|
|
continue;
|
|
case op_lit_selector:
|
|
data.Push(Selectors(cur_block.getU8(pc)));
|
|
continue;
|
|
case op_lit_string: {
|
|
uint64_t length = cur_block.getULEB128(pc);
|
|
llvm::StringRef bytes = cur_block.getBytes(pc, length);
|
|
data.Push(bytes.str());
|
|
continue;
|
|
}
|
|
case op_as_uint: {
|
|
TYPE_CHECK(Int);
|
|
uint64_t casted;
|
|
int64_t val = data.Pop<int64_t>();
|
|
memcpy(&casted, &val, sizeof(val));
|
|
data.Push(casted);
|
|
continue;
|
|
}
|
|
case op_as_int: {
|
|
TYPE_CHECK(UInt);
|
|
int64_t casted;
|
|
uint64_t val = data.Pop<uint64_t>();
|
|
memcpy(&casted, &val, sizeof(val));
|
|
data.Push(casted);
|
|
continue;
|
|
}
|
|
case op_is_null: {
|
|
TYPE_CHECK(Object);
|
|
data.Push(data.Pop<ValueObjectSP>() ? (uint64_t)0 : (uint64_t)1);
|
|
continue;
|
|
}
|
|
|
|
// Arithmetic, logic, etc.
|
|
#define BINOP_IMPL(OP, CHECK_ZERO) \
|
|
{ \
|
|
TYPE_CHECK(Any, Any); \
|
|
auto y = data.PopAny(); \
|
|
if (std::holds_alternative<uint64_t>(y)) { \
|
|
if (CHECK_ZERO && !std::get<uint64_t>(y)) \
|
|
return error(#OP " by zero"); \
|
|
TYPE_CHECK(UInt); \
|
|
data.Push((uint64_t)(data.Pop<uint64_t>() OP std::get<uint64_t>(y))); \
|
|
} else if (std::holds_alternative<int64_t>(y)) { \
|
|
if (CHECK_ZERO && !std::get<int64_t>(y)) \
|
|
return error(#OP " by zero"); \
|
|
TYPE_CHECK(Int); \
|
|
data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
#define BINOP(OP) BINOP_IMPL(OP, false)
|
|
#define BINOP_CHECKZERO(OP) BINOP_IMPL(OP, true)
|
|
case op_plus:
|
|
BINOP(+);
|
|
continue;
|
|
case op_minus:
|
|
BINOP(-);
|
|
continue;
|
|
case op_mul:
|
|
BINOP(*);
|
|
continue;
|
|
case op_div:
|
|
BINOP_CHECKZERO(/);
|
|
continue;
|
|
case op_mod:
|
|
BINOP_CHECKZERO(%);
|
|
continue;
|
|
case op_shl:
|
|
#define SHIFTOP(OP, LEFT) \
|
|
{ \
|
|
TYPE_CHECK(Any, UInt); \
|
|
uint64_t y = data.Pop<uint64_t>(); \
|
|
if (y > 64) \
|
|
return error("shift out of bounds"); \
|
|
if (std::holds_alternative<uint64_t>(data.back())) { \
|
|
uint64_t x = data.Pop<uint64_t>(); \
|
|
data.Push(x OP y); \
|
|
} else if (std::holds_alternative<int64_t>(data.back())) { \
|
|
int64_t x = data.Pop<int64_t>(); \
|
|
if (x < 0 && LEFT) \
|
|
return error("left shift of negative value"); \
|
|
if (y > 64) \
|
|
return error("shift out of bounds"); \
|
|
data.Push(x OP y); \
|
|
} else \
|
|
return error("unsupported data types"); \
|
|
}
|
|
SHIFTOP(<<, true);
|
|
continue;
|
|
case op_shr:
|
|
SHIFTOP(>>, false);
|
|
continue;
|
|
case op_and:
|
|
BINOP(&);
|
|
continue;
|
|
case op_or:
|
|
BINOP(|);
|
|
continue;
|
|
case op_xor:
|
|
BINOP(^);
|
|
continue;
|
|
case op_not:
|
|
TYPE_CHECK(UInt);
|
|
data.Push(~data.Pop<uint64_t>());
|
|
continue;
|
|
case op_eq:
|
|
BINOP(==);
|
|
continue;
|
|
case op_neq:
|
|
BINOP(!=);
|
|
continue;
|
|
case op_lt:
|
|
BINOP(<);
|
|
continue;
|
|
case op_gt:
|
|
BINOP(>);
|
|
continue;
|
|
case op_le:
|
|
BINOP(<=);
|
|
continue;
|
|
case op_ge:
|
|
BINOP(>=);
|
|
continue;
|
|
case op_call: {
|
|
TYPE_CHECK(Selector);
|
|
Selectors sel = data.Pop<Selectors>();
|
|
|
|
// Shorthand to improve readability.
|
|
#define POP_VALOBJ(VALOBJ) \
|
|
auto VALOBJ = data.Pop<ValueObjectSP>(); \
|
|
if (!VALOBJ) \
|
|
return error("null object");
|
|
|
|
auto sel_error = [&](const char *msg) {
|
|
return llvm::createStringError("{0} (opcode={1}, selector={2})", msg,
|
|
toString(opcode).c_str(),
|
|
toString(sel).c_str());
|
|
};
|
|
|
|
switch (sel) {
|
|
case sel_summary: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
const char *summary = valobj->GetSummaryAsCString();
|
|
data.Push(summary ? std::string(valobj->GetSummaryAsCString())
|
|
: std::string());
|
|
break;
|
|
}
|
|
case sel_get_num_children: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
auto result = valobj->GetNumChildren();
|
|
if (!result)
|
|
return result.takeError();
|
|
data.Push((uint64_t)*result);
|
|
break;
|
|
}
|
|
case sel_get_child_at_index: {
|
|
TYPE_CHECK(Object, UInt);
|
|
auto index = data.Pop<uint64_t>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetChildAtIndex(index));
|
|
break;
|
|
}
|
|
case sel_get_child_with_name: {
|
|
TYPE_CHECK(Object, String);
|
|
auto name = data.Pop<std::string>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->GetChildMemberWithName(name));
|
|
break;
|
|
}
|
|
case sel_get_child_index: {
|
|
TYPE_CHECK(Object, String);
|
|
auto name = data.Pop<std::string>();
|
|
POP_VALOBJ(valobj);
|
|
if (auto index_or_err = valobj->GetIndexOfChildWithName(name))
|
|
data.Push((uint64_t)*index_or_err);
|
|
else
|
|
return index_or_err.takeError();
|
|
break;
|
|
}
|
|
case sel_get_type: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
// FIXME: do we need to control dynamic type resolution?
|
|
data.Push(valobj->GetTypeImpl().GetCompilerType(false));
|
|
break;
|
|
}
|
|
case sel_get_template_argument_type: {
|
|
TYPE_CHECK(Type, UInt);
|
|
auto index = data.Pop<uint64_t>();
|
|
auto type = data.Pop<CompilerType>();
|
|
// FIXME: There is more code in SBType::GetTemplateArgumentType().
|
|
data.Push(type.GetTypeTemplateArgument(index, true));
|
|
break;
|
|
}
|
|
case sel_get_value: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
data.Push(std::string(valobj->GetValueAsCString()));
|
|
break;
|
|
}
|
|
case sel_get_value_as_unsigned: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
uint64_t val = valobj->GetValueAsUnsigned(0, &success);
|
|
data.Push(val);
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
break;
|
|
}
|
|
case sel_get_value_as_signed: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
int64_t val = valobj->GetValueAsSigned(0, &success);
|
|
data.Push(val);
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
break;
|
|
}
|
|
case sel_get_value_as_address: {
|
|
TYPE_CHECK(Object);
|
|
POP_VALOBJ(valobj);
|
|
bool success;
|
|
uint64_t addr = valobj->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
return sel_error("failed to get value");
|
|
if (auto process_sp = valobj->GetProcessSP())
|
|
addr = process_sp->FixDataAddress(addr);
|
|
data.Push(addr);
|
|
break;
|
|
}
|
|
case sel_cast: {
|
|
TYPE_CHECK(Object, Type);
|
|
auto type = data.Pop<CompilerType>();
|
|
POP_VALOBJ(valobj);
|
|
data.Push(valobj->Cast(type));
|
|
break;
|
|
}
|
|
case sel_strlen: {
|
|
TYPE_CHECK(String);
|
|
data.Push((uint64_t)data.Pop<std::string>().size());
|
|
break;
|
|
}
|
|
case sel_fmt: {
|
|
TYPE_CHECK(String);
|
|
if (auto error = FormatImpl(data))
|
|
return error;
|
|
break;
|
|
}
|
|
default:
|
|
return sel_error("selector not implemented");
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
return error("opcode not implemented");
|
|
}
|
|
return pc.takeError();
|
|
}
|
|
} // namespace FormatterBytecode
|
|
|
|
} // namespace lldb_private
|