blob: 7f3dbe0dba37d66b966915edbaf93f4037ce1aa0 [file] [log] [blame]
//===-- 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 "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);
data.Push((uint64_t)valobj->GetIndexOfChildWithName(name));
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