[lldb] Implement a formatter bytecode interpreter in C++

Compared to the python version, this also does type checking and error
handling, so it's slightly longer, however, it's still comfortably
under 500 lines.
This commit is contained in:
Adrian Prantl
2024-10-28 17:18:34 -07:00
parent 1a650fde4a
commit 9a9c1d4a61
22 changed files with 1365 additions and 83 deletions

View File

@@ -0,0 +1,26 @@
//===-- FormattersSection.h -------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_DATAFORMATTERS_FORMATTERSECTION_H
#define LLDB_DATAFORMATTERS_FORMATTERSECTION_H
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
namespace lldb_private {
/// Load type summaries embedded in the binary. These are type summaries
/// provided by the authors of the code.
void LoadTypeSummariesForModule(lldb::ModuleSP module_sp);
/// Load data formatters embedded in the binary. These are formatters provided
/// by the authors of the code using LLDB formatter bytecode.
void LoadFormattersForModule(lldb::ModuleSP module_sp);
} // namespace lldb_private
#endif // LLDB_DATAFORMATTERS_FORMATTERSECTION_H

View File

@@ -1,5 +1,4 @@
//===-- FormattersHelpers.h --------------------------------------*- C++
//-*-===//
//===-- FormattersHelpers.h -------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.

View File

@@ -22,6 +22,10 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
namespace llvm {
class MemoryBuffer;
}
namespace lldb_private {
class TypeSummaryOptions {
public:
@@ -44,7 +48,7 @@ private:
class TypeSummaryImpl {
public:
enum class Kind { eSummaryString, eScript, eCallback, eInternal };
enum class Kind { eSummaryString, eScript, eBytecode, eCallback, eInternal };
virtual ~TypeSummaryImpl() = default;
@@ -409,6 +413,23 @@ private:
ScriptSummaryFormat(const ScriptSummaryFormat &) = delete;
const ScriptSummaryFormat &operator=(const ScriptSummaryFormat &) = delete;
};
/// A summary formatter that is defined in LLDB formmater bytecode.
class BytecodeSummaryFormat : public TypeSummaryImpl {
std::unique_ptr<llvm::MemoryBuffer> m_bytecode;
public:
BytecodeSummaryFormat(const TypeSummaryImpl::Flags &flags,
std::unique_ptr<llvm::MemoryBuffer> bytecode);
bool FormatObject(ValueObject *valobj, std::string &dest,
const TypeSummaryOptions &options) override;
std::string GetDescription() override;
std::string GetName() override;
static bool classof(const TypeSummaryImpl *S) {
return S->GetKind() == Kind::eBytecode;
}
};
} // namespace lldb_private
#endif // LLDB_DATAFORMATTERS_TYPESUMMARY_H

View File

@@ -764,6 +764,7 @@ enum SectionType {
eSectionTypeDWARFDebugTuIndex,
eSectionTypeCTF,
eSectionTypeLLDBTypeSummaries,
eSectionTypeLLDBFormatters,
eSectionTypeSwiftModules,
};

View File

@@ -343,6 +343,7 @@ bool SBTypeSummary::IsEqualTo(lldb::SBTypeSummary &rhs) {
case TypeSummaryImpl::Kind::eCallback:
return llvm::dyn_cast<CXXFunctionSummaryFormat>(m_opaque_sp.get()) ==
llvm::dyn_cast<CXXFunctionSummaryFormat>(rhs.m_opaque_sp.get());
case TypeSummaryImpl::Kind::eBytecode:
case TypeSummaryImpl::Kind::eScript:
if (IsFunctionCode() != rhs.IsFunctionCode())
return false;

View File

@@ -149,10 +149,12 @@ const char *Section::GetTypeAsCString() const {
return "ctf";
case eSectionTypeLLDBTypeSummaries:
return "lldb-type-summaries";
case eSectionTypeOther:
return "regular";
case eSectionTypeLLDBFormatters:
return "lldb-formatters";
case eSectionTypeSwiftModules:
return "swift-modules";
case eSectionTypeOther:
return "regular";
}
return "unknown";
}
@@ -460,6 +462,7 @@ bool Section::ContainsOnlyDebugInfo() const {
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
case eSectionTypeLLDBTypeSummaries:
case eSectionTypeLLDBFormatters:
case eSectionTypeSwiftModules:
return true;
}

View File

@@ -5,7 +5,9 @@ add_lldb_library(lldbDataFormatters NO_PLUGIN_DEPENDENCIES
FormatCache.cpp
FormatClasses.cpp
FormatManager.cpp
FormatterBytecode.cpp
FormattersHelpers.cpp
FormatterSection.cpp
LanguageCategory.cpp
StringPrinter.cpp
TypeCategory.cpp

View File

@@ -0,0 +1,575 @@
//===-- 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;
// 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>() ? 0ULL : 1ULL);
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) \
{ \
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 (y > 64) \
return error("shift out of bounds"); \
if (y < 0) \
return error("shift out of bounds"); \
data.Push(x OP y); \
} else \
return error("unsupported data types"); \
}
SHIFTOP(<<);
continue;
case op_shr:
SHIFTOP(<<);
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(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(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

View File

@@ -0,0 +1,101 @@
//===-- FormatterBytecode.def -----------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef DEFINE_OPCODE
#define DEFINE_OPCODE(OP, MNEMONIC, NAME)
#endif
#ifndef DEFINE_SELECTOR
#define DEFINE_SELECTOR(ID, NAME)
#endif
#ifndef DEFINE_SIGNATURE
#define DEFINE_SIGNATURE(ID, NAME)
#endif
// Opcodes.
DEFINE_OPCODE(0x01, "dup", dup)
DEFINE_OPCODE(0x02, "drop", drop)
DEFINE_OPCODE(0x03, "pick", pick)
DEFINE_OPCODE(0x04, "over", over)
DEFINE_OPCODE(0x05, "swap", swap)
DEFINE_OPCODE(0x06, "rot", rot)
DEFINE_OPCODE(0x10, "{", begin)
DEFINE_OPCODE(0x11, "if", if)
DEFINE_OPCODE(0x12, "ifelse", ifelse)
DEFINE_OPCODE(0x20, nullptr, lit_uint)
DEFINE_OPCODE(0x21, nullptr, lit_int)
DEFINE_OPCODE(0x22, nullptr, lit_string)
DEFINE_OPCODE(0x23, nullptr, lit_selector)
DEFINE_OPCODE(0x2a, "as_int", as_int)
DEFINE_OPCODE(0x2b, "as_uint", as_uint)
DEFINE_OPCODE(0x2c, "is_null", is_null)
DEFINE_OPCODE(0x30, "+", plus)
DEFINE_OPCODE(0x31, "-", minus)
DEFINE_OPCODE(0x32, "*", mul)
DEFINE_OPCODE(0x33, "/", div)
DEFINE_OPCODE(0x34, "%", mod)
DEFINE_OPCODE(0x35, "<<", shl)
DEFINE_OPCODE(0x36, ">>", shr)
DEFINE_OPCODE(0x40, "&", and)
DEFINE_OPCODE(0x41, "|", or)
DEFINE_OPCODE(0x42, "^", xor)
DEFINE_OPCODE(0x43, "~", not)
DEFINE_OPCODE(0x50, "=", eq)
DEFINE_OPCODE(0x51, "!=", neq)
DEFINE_OPCODE(0x52, "<", lt)
DEFINE_OPCODE(0x53, ">", gt)
DEFINE_OPCODE(0x54, "=<", le)
DEFINE_OPCODE(0x55, ">=", ge)
DEFINE_OPCODE(0x60, "call", call)
// Selectors.
DEFINE_SELECTOR(0x00, summary)
DEFINE_SELECTOR(0x01, type_summary)
DEFINE_SELECTOR(0x10, get_num_children)
DEFINE_SELECTOR(0x11, get_child_at_index)
DEFINE_SELECTOR(0x12, get_child_with_name)
DEFINE_SELECTOR(0x13, get_child_index)
DEFINE_SELECTOR(0x15, get_type)
DEFINE_SELECTOR(0x16, get_template_argument_type)
DEFINE_SELECTOR(0x17, cast)
DEFINE_SELECTOR(0x20, get_value)
DEFINE_SELECTOR(0x21, get_value_as_unsigned)
DEFINE_SELECTOR(0x22, get_value_as_signed)
DEFINE_SELECTOR(0x23, get_value_as_address)
DEFINE_SELECTOR(0x40, read_memory_byte)
DEFINE_SELECTOR(0x41, read_memory_uint32)
DEFINE_SELECTOR(0x42, read_memory_int32)
DEFINE_SELECTOR(0x43, read_memory_unsigned)
DEFINE_SELECTOR(0x44, read_memory_signed)
DEFINE_SELECTOR(0x45, read_memory_address)
DEFINE_SELECTOR(0x46, read_memory)
DEFINE_SELECTOR(0x50, fmt)
DEFINE_SELECTOR(0x51, sprintf)
DEFINE_SELECTOR(0x52, strlen)
// Formatter signatures.
DEFINE_SIGNATURE(0, summary)
DEFINE_SIGNATURE(1, init)
DEFINE_SIGNATURE(2, get_num_children)
DEFINE_SIGNATURE(3, get_child_index)
DEFINE_SIGNATURE(4, get_child_at_index)
DEFINE_SIGNATURE(5, get_value)
#undef DEFINE_OPCODE
#undef DEFINE_SELECTOR
#undef DEFINE_SIGNATURE

View File

@@ -0,0 +1,64 @@
//===-- FormatterBytecode.h -------------------------------------*- 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 "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Symbol/CompilerType.h"
namespace lldb_private {
namespace FormatterBytecode {
enum DataType : uint8_t { Any, String, Int, UInt, Object, Type, Selector };
enum OpCodes : uint8_t {
#define DEFINE_OPCODE(OP, MNEMONIC, NAME) op_##NAME = OP,
#include "FormatterBytecode.def"
#undef DEFINE_OPCODE
};
enum Selectors : uint8_t {
#define DEFINE_SELECTOR(ID, NAME) sel_##NAME = ID,
#include "FormatterBytecode.def"
#undef DEFINE_SELECTOR
};
enum Signatures : uint8_t {
#define DEFINE_SIGNATURE(ID, NAME) sig_##NAME = ID,
#include "FormatterBytecode.def"
#undef DEFINE_SIGNATURE
};
using ControlStackElement = llvm::StringRef;
using DataStackElement =
std::variant<std::string, uint64_t, int64_t, lldb::ValueObjectSP,
CompilerType, Selectors>;
struct DataStack : public std::vector<DataStackElement> {
DataStack() = default;
DataStack(lldb::ValueObjectSP initial_value)
: std::vector<DataStackElement>({initial_value}) {}
void Push(DataStackElement el) { push_back(el); }
template <typename T> T Pop() {
T el = std::get<T>(back());
pop_back();
return el;
}
DataStackElement PopAny() {
DataStackElement el = back();
pop_back();
return el;
}
};
llvm::Error Interpret(std::vector<ControlStackElement> &control,
DataStack &data, Selectors sel);
} // namespace FormatterBytecode
std::string toString(FormatterBytecode::OpCodes op);
std::string toString(FormatterBytecode::Selectors sel);
std::string toString(FormatterBytecode::Signatures sig);
} // namespace lldb_private

View File

@@ -0,0 +1,162 @@
//===-- 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/Core/Module.h"
#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/Utility/LLDBLog.h"
using namespace lldb;
namespace lldb_private {
static void ForEachFormatterInModule(
Module &module, SectionType section_type,
std::function<void(llvm::DataExtractor, llvm::StringRef)> fn) {
auto *sections = module.GetSectionList();
if (!sections)
return;
auto section_sp = sections->FindSectionByType(section_type, true);
if (!section_sp)
return;
TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString("default"), category);
// The type summary record is serialized as follows.
//
// Each record contains, in order:
// * Version number of the record format
// * The remaining size of the record
// * The size of the type identifier
// * The type identifier, either a type name, or a regex
// * The size of the entry
// * The entry
//
// Integers are encoded using ULEB.
//
// Strings are encoded with first a length (ULEB), then the string contents,
// and lastly a null terminator. The length includes the null.
DataExtractor lldb_extractor;
auto section_size = section_sp->GetSectionData(lldb_extractor);
llvm::DataExtractor section = lldb_extractor.GetAsLLVM();
bool le = section.isLittleEndian();
uint8_t addr_size = section.getAddressSize();
llvm::DataExtractor::Cursor cursor(0);
while (cursor && cursor.tell() < section_size) {
uint64_t version = section.getULEB128(cursor);
uint64_t record_size = section.getULEB128(cursor);
if (version == 1) {
llvm::DataExtractor record(section.getData().drop_front(cursor.tell()),
le, addr_size);
llvm::DataExtractor::Cursor cursor(0);
uint64_t type_size = record.getULEB128(cursor);
llvm::StringRef type_name = record.getBytes(cursor, type_size);
llvm::Error error = cursor.takeError();
if (!error)
fn(llvm::DataExtractor(record.getData().drop_front(cursor.tell()), le,
addr_size),
type_name);
else
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), std::move(error),
"{0}");
} else {
// Skip unsupported record.
LLDB_LOG(
GetLog(LLDBLog::DataFormatters),
"Skipping unsupported embedded type summary of version {0} in {1}.",
version, module.GetFileSpec());
}
section.skip(cursor, record_size);
}
if (!cursor)
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(), "{0}");
}
void LoadTypeSummariesForModule(ModuleSP module_sp) {
ForEachFormatterInModule(
*module_sp, eSectionTypeLLDBTypeSummaries,
[&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString("default"),
category);
// The type summary record is serialized as follows.
//
// * The size of the summary string
// * The summary string
//
// Integers are encoded using ULEB.
llvm::DataExtractor::Cursor cursor(0);
uint64_t summary_size = extractor.getULEB128(cursor);
llvm::StringRef summary_string =
extractor.getBytes(cursor, summary_size);
if (!cursor) {
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
"{0}");
return;
}
if (type_name.empty() || summary_string.empty()) {
LLDB_LOG(GetLog(LLDBLog::DataFormatters),
"Missing string(s) in embedded type summary in {0}.",
module_sp->GetFileSpec());
return;
}
TypeSummaryImpl::Flags flags;
auto summary_sp = std::make_shared<StringSummaryFormat>(
flags, summary_string.str().c_str());
FormatterMatchType match_type = eFormatterMatchExact;
if (type_name.front() == '^')
match_type = eFormatterMatchRegex;
category->AddTypeSummary(type_name, match_type, summary_sp);
LLDB_LOG(GetLog(LLDBLog::DataFormatters),
"Loaded embedded type summary for '{0}' from {1}.", type_name,
module_sp->GetFileSpec());
});
}
void LoadFormattersForModule(ModuleSP module_sp) {
ForEachFormatterInModule(
*module_sp, eSectionTypeLLDBFormatters,
[&](llvm::DataExtractor extractor, llvm::StringRef type_name) {
// * Function signature (1 byte)
// * Length of the program (ULEB128)
// * The program bytecode
TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString("default"),
category);
llvm::DataExtractor::Cursor cursor(0);
uint64_t flags = extractor.getULEB128(cursor);
while (cursor && cursor.tell() < extractor.size()) {
uint8_t signature = extractor.getU8(cursor);
uint64_t size = extractor.getULEB128(cursor);
llvm::StringRef bytecode = extractor.getBytes(cursor, size);
if (!cursor) {
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters), cursor.takeError(),
"{0}");
return;
}
if (signature == 0) {
auto summary_sp = std::make_shared<BytecodeSummaryFormat>(
TypeSummaryImpl::Flags(flags),
llvm::MemoryBuffer::getMemBufferCopy(bytecode));
FormatterMatchType match_type = eFormatterMatchExact;
if (type_name.front() == '^')
match_type = eFormatterMatchRegex;
category->AddTypeSummary(type_name, match_type, summary_sp);
LLDB_LOG(GetLog(LLDBLog::DataFormatters),
"Loaded embedded type summary for '{0}' from {1}.",
type_name, module_sp->GetFileSpec());
} else
LLDB_LOG(GetLog(LLDBLog::DataFormatters),
"Unsupported formatter signature {0} for '{1}' in {2}",
signature, type_name, module_sp->GetFileSpec());
}
});
}
} // namespace lldb_private

View File

@@ -8,9 +8,7 @@
#include "lldb/DataFormatters/TypeSummary.h"
#include "FormatterBytecode.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-public.h"
@@ -58,6 +56,8 @@ std::string TypeSummaryImpl::GetSummaryKindName() {
return "python";
case Kind::eInternal:
return "c++";
case Kind::eBytecode:
return "bytecode";
}
}
@@ -230,3 +230,74 @@ std::string ScriptSummaryFormat::GetDescription() {
}
std::string ScriptSummaryFormat::GetName() { return m_script_formatter_name; }
BytecodeSummaryFormat::BytecodeSummaryFormat(
const TypeSummaryImpl::Flags &flags,
std::unique_ptr<llvm::MemoryBuffer> bytecode)
: TypeSummaryImpl(Kind::eBytecode, flags), m_bytecode(std::move(bytecode)) {
}
bool BytecodeSummaryFormat::FormatObject(ValueObject *valobj,
std::string &retval,
const TypeSummaryOptions &options) {
if (!valobj)
return false;
TargetSP target_sp(valobj->GetTargetSP());
if (!target_sp) {
retval.assign("error: no target");
return false;
}
std::vector<FormatterBytecode::ControlStackElement> control(
{m_bytecode->getBuffer()});
FormatterBytecode::DataStack data({valobj->GetSP()});
llvm::Error error = FormatterBytecode::Interpret(
control, data, FormatterBytecode::sel_summary);
if (error) {
retval = llvm::toString(std::move(error));
return false;
}
if (!data.size()) {
retval = "empty stack";
return false;
}
auto &top = data.back();
retval = "";
llvm::raw_string_ostream os(retval);
if (auto s = std::get_if<std::string>(&top))
os << *s;
else if (auto u = std::get_if<uint64_t>(&top))
os << *u;
else if (auto i = std::get_if<int64_t>(&top))
os << *i;
else if (auto valobj = std::get_if<ValueObjectSP>(&top)) {
if (!valobj->get())
os << "empty object";
else
os << valobj->get()->GetValueAsCString();
} else if (auto type = std::get_if<CompilerType>(&top)) {
os << type->TypeDescription();
} else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&top)) {
os << toString(*sel);
}
return true;
}
std::string BytecodeSummaryFormat::GetDescription() {
StreamString sstr;
sstr.Printf("%s%s%s%s%s%s%s\n ", Cascades() ? "" : " (not cascading)",
!DoesPrintChildren(nullptr) ? "" : " (show children)",
!DoesPrintValue(nullptr) ? " (hide value)" : "",
IsOneLiner() ? " (one-line printout)" : "",
SkipsPointers() ? " (skip pointers)" : "",
SkipsReferences() ? " (skip references)" : "",
HideNames(nullptr) ? " (hide member names)" : "");
// FIXME: sstr.PutCString(disassembly);
return std::string(sstr.GetString());
}
std::string BytecodeSummaryFormat::GetName() {
return "LLDB bytecode formatter";
}

View File

@@ -1697,6 +1697,7 @@ static SectionType GetSectionTypeFromName(llvm::StringRef Name) {
.Case(".gosymtab", eSectionTypeGoSymtab)
.Case(".text", eSectionTypeCode)
.Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
.Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
.Case(".swift_ast", eSectionTypeSwiftModules)
.Default(eSectionTypeOther);
}

View File

@@ -1210,6 +1210,7 @@ AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) {
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
case eSectionTypeLLDBTypeSummaries:
case eSectionTypeLLDBFormatters:
case eSectionTypeSwiftModules:
return AddressClass::eDebug;
@@ -1486,6 +1487,7 @@ static lldb::SectionType GetSectionType(uint32_t flags,
static ConstString g_sect_name_go_symtab("__gosymtab");
static ConstString g_sect_name_ctf("__ctf");
static ConstString g_sect_name_lldb_summaries("__lldbsummaries");
static ConstString g_sect_name_lldb_formatters("__lldbformatters");
static ConstString g_sect_name_swift_ast("__swift_ast");
if (section_name == g_sect_name_dwarf_debug_abbrev)
@@ -1568,6 +1570,8 @@ static lldb::SectionType GetSectionType(uint32_t flags,
return eSectionTypeCTF;
if (section_name == g_sect_name_lldb_summaries)
return lldb::eSectionTypeLLDBTypeSummaries;
if (section_name == g_sect_name_lldb_formatters)
return lldb::eSectionTypeLLDBFormatters;
if (section_name == g_sect_name_swift_ast)
return eSectionTypeSwiftModules;
if (section_name == g_sect_name_objc_data ||

View File

@@ -1011,6 +1011,7 @@ SectionType ObjectFilePECOFF::GetSectionType(llvm::StringRef sect_name,
.Cases(".eh_frame", ".eh_fram", eSectionTypeEHFrame)
.Case(".gosymtab", eSectionTypeGoSymtab)
.Case(".lldbsummaries", lldb::eSectionTypeLLDBTypeSummaries)
.Case(".lldbformatters", lldb::eSectionTypeLLDBFormatters)
.Case("swiftast", eSectionTypeSwiftModules)
.Default(eSectionTypeInvalid);
if (section_type != eSectionTypeInvalid)

View File

@@ -366,6 +366,7 @@ AddressClass ObjectFile::GetAddressClass(addr_t file_addr) {
case eSectionTypeDWARFAppleObjC:
case eSectionTypeDWARFGNUDebugAltLink:
case eSectionTypeCTF:
case eSectionTypeLLDBFormatters:
case eSectionTypeLLDBTypeSummaries:
case eSectionTypeSwiftModules:
return AddressClass::eDebug;

View File

@@ -24,9 +24,7 @@
#include "lldb/Core/Section.h"
#include "lldb/Core/SourceManager.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/DataFormatters/FormatterSection.h"
#include "lldb/Expression/DiagnosticManager.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Expression/REPL.h"
@@ -66,8 +64,6 @@
#include "lldb/Utility/State.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/Timer.h"
#include "lldb/ValueObject/ValueObject.h"
#include "lldb/ValueObject/ValueObjectConstResult.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SetVector.h"
@@ -1541,76 +1537,6 @@ static void LoadScriptingResourceForModule(const ModuleSP &module_sp,
feedback_stream.GetData());
}
// Load type summaries embedded in the binary. These are type summaries provided
// by the authors of the code.
static void LoadTypeSummariesForModule(ModuleSP module_sp) {
auto *sections = module_sp->GetSectionList();
if (!sections)
return;
auto summaries_sp =
sections->FindSectionByType(eSectionTypeLLDBTypeSummaries, true);
if (!summaries_sp)
return;
Log *log = GetLog(LLDBLog::DataFormatters);
const char *module_name = module_sp->GetObjectName().GetCString();
TypeCategoryImplSP category;
DataVisualization::Categories::GetCategory(ConstString("default"), category);
// The type summary record is serialized as follows.
//
// Each record contains, in order:
// * Version number of the record format
// * The remaining size of the record
// * The size of the type identifier
// * The type identifier, either a type name, or a regex
// * The size of the summary string
// * The summary string
//
// Integers are encoded using ULEB.
//
// Strings are encoded with first a length (ULEB), then the string contents,
// and lastly a null terminator. The length includes the null.
DataExtractor extractor;
auto section_size = summaries_sp->GetSectionData(extractor);
lldb::offset_t offset = 0;
while (offset < section_size) {
uint64_t version = extractor.GetULEB128(&offset);
uint64_t record_size = extractor.GetULEB128(&offset);
if (version == 1) {
uint64_t type_size = extractor.GetULEB128(&offset);
llvm::StringRef type_name = extractor.GetCStr(&offset, type_size);
uint64_t summary_size = extractor.GetULEB128(&offset);
llvm::StringRef summary_string = extractor.GetCStr(&offset, summary_size);
if (!type_name.empty() && !summary_string.empty()) {
TypeSummaryImpl::Flags flags;
auto summary_sp =
std::make_shared<StringSummaryFormat>(flags, summary_string.data());
FormatterMatchType match_type = eFormatterMatchExact;
if (summary_string.front() == '^' && summary_string.back() == '$')
match_type = eFormatterMatchRegex;
category->AddTypeSummary(type_name, match_type, summary_sp);
LLDB_LOGF(log, "Loaded embedded type summary for '%s' from %s.",
type_name.data(), module_name);
} else {
if (type_name.empty())
LLDB_LOGF(log, "Missing string(s) in embedded type summary in %s.",
module_name);
}
} else {
// Skip unsupported record.
offset += record_size;
LLDB_LOGF(
log,
"Skipping unsupported embedded type summary of version %llu in %s.",
version, module_name);
}
}
}
void Target::ClearModules(bool delete_locations) {
ModulesDidUnload(m_images, delete_locations);
m_section_load_history.Clear();
@@ -1892,6 +1818,7 @@ void Target::ModulesDidLoad(ModuleList &module_list) {
ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
LoadScriptingResourceForModule(module_sp, this);
LoadTypeSummariesForModule(module_sp);
LoadFormattersForModule(module_sp);
}
m_breakpoint_list.UpdateBreakpoints(module_list, true, false);
m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false);

View File

@@ -0,0 +1,2 @@
C_SOURCES := main.c
include Makefile.rules

View File

@@ -0,0 +1,17 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestCase(TestBase):
@skipUnlessDarwin
def test(self):
self.build()
if self.TraceOn():
self.expect("log enable -v lldb formatters")
lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.cpp")
)
self.expect("v x", substrs=["(MyOptional<int>) x = None"])
self.expect("v y", substrs=["(MyOptional<int>) y = 42"])

View File

@@ -0,0 +1,41 @@
// A bare-bones llvm::Optional reimplementation.
template <typename T> struct MyOptionalStorage {
MyOptionalStorage(T val) : value(val), hasVal(true) {}
MyOptionalStorage() {}
T value;
bool hasVal = false;
};
template <typename T> struct MyOptional {
MyOptionalStorage<T> Storage;
MyOptional(T val) : Storage(val) {}
MyOptional() {}
T &operator*() { return Storage.value; }
};
void stop() {}
int main(int argc, char **argv) {
MyOptional<int> x, y = 42;
stop(); // break here
return *y;
}
__attribute__((used, section("__DATA_CONST,__lldbformatters"))) unsigned char
_MyOptional_type_summary[] =
"\x01" // version
"\xa4" // record size
"\x01" // record size
"\x10" // type name size
"^MyOptional<.+>$" // type name
"\x00" // flags
"\x00" // sig_summary
"\x8d" // program size
"\x01" // program size
"\x1\x22\x7Storage#\x12\x60\x1,C\x10\x1\x5\x11\x2\x1\x22\x6hasVal#"
"\x12\x60\x1,\x10\x1e\x2\x22\x1b<could not read MyOptional>\x10G#!\x60 "
"\x0P\x10\x6\x22\x4None\x10\x36\x1#\x15\x60 "
"\x0#\x16\x60\x5\x22\x5value#\x12\x60\x5#\x17\x60\x1,"
"\x10\x6\x22\x4None\x10\x11\x1#\x0\x60\x1#R\x60\x10\x3# "
"\x60\x10\x1\x2\x12\x12\x12\x12"; // summary function

View File

@@ -1,6 +1,7 @@
add_lldb_unittest(LLDBFormatterTests
FormatManagerTests.cpp
FormattersContainerTest.cpp
FormatterBytecodeTest.cpp
StringPrinterTests.cpp
LINK_LIBS

View File

@@ -0,0 +1,261 @@
#include "DataFormatters/FormatterBytecode.h"
#include "lldb/Utility/StreamString.h"
#include "gtest/gtest.h"
using namespace lldb_private;
using namespace lldb;
using namespace FormatterBytecode;
using llvm::StringRef;
namespace {
class FormatterBytecodeTest : public ::testing::Test {};
bool Interpret(std::vector<uint8_t> code, DataStack &data) {
auto buf =
StringRef(reinterpret_cast<const char *>(code.data()), code.size());
std::vector<ControlStackElement> control({buf});
if (auto error = Interpret(control, data, sel_summary)) {
#ifndef NDEBUG
llvm::errs() << llvm::toString(std::move(error)) << '\n';
#else
llvm::consumeError(std::move(error));
#endif
return false;
}
return true;
}
} // namespace
TEST_F(FormatterBytecodeTest, StackOps) {
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 23, op_dup, op_plus}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 46u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_drop}, data));
ASSERT_EQ(data.size(), 0u);
}
{
for (unsigned char i = 0; i < 3; ++i) {
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_lit_uint, 2,
op_lit_uint, i, op_pick},
data));
ASSERT_EQ(data.Pop<uint64_t>(), i);
}
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_over}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_swap}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
{
DataStack data;
ASSERT_TRUE(Interpret(
{op_lit_uint, 0, op_lit_uint, 1, op_lit_uint, 2, op_rot}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_EQ(data.Pop<uint64_t>(), 2u);
}
}
TEST_F(FormatterBytecodeTest, ControlOps) {
{
DataStack data;
ASSERT_TRUE(
Interpret({op_lit_uint, 0, op_begin, 2, op_lit_uint, 42, op_if}, data));
ASSERT_EQ(data.size(), 0u);
}
{
DataStack data;
ASSERT_TRUE(
Interpret({op_lit_uint, 1, op_begin, 2, op_lit_uint, 42, op_if}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 42u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_begin, 2, op_lit_uint, 42,
op_begin, 2, op_lit_uint, 23, op_ifelse},
data));
ASSERT_EQ(data.Pop<uint64_t>(), 23u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_begin, 2, op_lit_uint, 42,
op_begin, 2, op_lit_uint, 23, op_ifelse},
data));
ASSERT_EQ(data.Pop<uint64_t>(), 42u);
}
{
DataStack data(lldb::ValueObjectSP{});
ASSERT_TRUE(Interpret({op_is_null}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1u, op_as_int}, data));
ASSERT_EQ(data.Pop<int64_t>(), 1);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_int, 126, op_as_uint}, data));
ASSERT_EQ(data.Pop<uint64_t>(), ~1ULL);
}
}
TEST_F(FormatterBytecodeTest, ArithOps) {
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 2, op_lit_uint, 3, op_plus}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 5u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 3, op_lit_uint, 2, op_minus}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 3, op_lit_uint, 2, op_mul}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 6u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 6, op_lit_uint, 2, op_div}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 3u);
}
{
DataStack data;
ASSERT_FALSE(Interpret({op_lit_uint, 23, op_lit_uint, 0, op_div}, data));
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 2, op_shl}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 4u);
}
{
DataStack data;
unsigned char minus_one = 127;
ASSERT_TRUE(
Interpret({op_lit_int, minus_one, op_lit_uint, 2, op_shl}, data));
ASSERT_EQ(data.Pop<int64_t>(), -4);
}
{
DataStack data;
ASSERT_FALSE(Interpret({op_lit_uint, 1, op_lit_uint, 65, op_shl}, data));
ASSERT_FALSE(Interpret({op_lit_uint, 1, op_lit_uint, 65, op_shr}, data));
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_and}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_and}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_or}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_or}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_or}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_xor}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_xor}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_xor}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_not}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0xffffffffffffffff);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_eq}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_eq}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_neq}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 0, op_neq}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_lt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_lt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_lt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_gt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_gt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_gt}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_le}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_le}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_le}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
{
DataStack data;
ASSERT_TRUE(Interpret({op_lit_uint, 0, op_lit_uint, 1, op_ge}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 0u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 0, op_ge}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
ASSERT_TRUE(Interpret({op_lit_uint, 1, op_lit_uint, 1, op_ge}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 1u);
}
}
TEST_F(FormatterBytecodeTest, CallOps) {
{
DataStack data;
data.Push(std::string{"hello"});
ASSERT_TRUE(Interpret({op_lit_selector, sel_strlen, op_call}, data));
ASSERT_EQ(data.Pop<uint64_t>(), 5u);
}
{
DataStack data;
data.Push(std::string{"A"});
data.Push(std::string{"B"});
data.Push(std::string{"{1}{0}"});
ASSERT_TRUE(Interpret({op_lit_selector, sel_fmt, op_call}, data));
ASSERT_EQ(data.Pop<std::string>(), "BA");
}
{
DataStack data;
data.Push(std::string{"{0}"});
ASSERT_FALSE(Interpret({op_lit_selector, sel_fmt, op_call}, data));
}
}