//===-- 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(&d)) os << '"' << *s << '"'; else if (auto u = std::get_if(&d)) os << *u << 'u'; else if (auto i = std::get_if(&d)) os << *i; else if (auto valobj = std::get_if(&d)) { if (!valobj->get()) os << "null"; else os << "object(" << valobj->get()->GetValueAsCString() << ')'; } else if (auto type = std::get_if(&d)) { os << '(' << type->GetTypeName(true) << ')'; } else if (auto sel = std::get_if(&d)) { os << toString(*sel); } os << ' '; } os << ']'; return s; } namespace FormatterBytecode { /// Implement the @format function. static llvm::Error FormatImpl(DataStack &data) { auto fmt = data.Pop(); 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(&arg)) format(build_format_adapter(s->c_str())); else if (auto u = std::get_if(&arg)) format(build_format_adapter(u)); else if (auto i = std::get_if(&arg)) format(build_format_adapter(i)); else if (auto valobj = std::get_if(&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(&arg)) format(build_format_adapter(type->GetDisplayTypeName())); else if (auto sel = std::get_if(&arg)) format(build_format_adapter(toString(*sel))); } data.Push(s); return llvm::Error::success(); } static llvm::Error TypeCheck(llvm::ArrayRef 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(elem)) return llvm::createStringError("expected String"); break; case UInt: if (!std::holds_alternative(elem)) return llvm::createStringError("expected UInt"); break; case Int: if (!std::holds_alternative(elem)) return llvm::createStringError("expected Int"); break; case Object: if (!std::holds_alternative(elem)) return llvm::createStringError("expected Object"); break; case Type: if (!std::holds_alternative(elem)) return llvm::createStringError("expected Type"); break; case Selector: if (!std::holds_alternative(elem)) return llvm::createStringError("expected Selector"); break; } return llvm::Error::success(); } static llvm::Error TypeCheck(llvm::ArrayRef 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 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 &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(); 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() != 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() == 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(); memcpy(&casted, &val, sizeof(val)); data.Push(casted); continue; } case op_as_int: { TYPE_CHECK(UInt); int64_t casted; uint64_t val = data.Pop(); memcpy(&casted, &val, sizeof(val)); data.Push(casted); continue; } case op_is_null: { TYPE_CHECK(Object); data.Push(data.Pop() ? (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(y)) { \ if (CHECK_ZERO && !std::get(y)) \ return error(#OP " by zero"); \ TYPE_CHECK(UInt); \ data.Push((uint64_t)(data.Pop() OP std::get(y))); \ } else if (std::holds_alternative(y)) { \ if (CHECK_ZERO && !std::get(y)) \ return error(#OP " by zero"); \ TYPE_CHECK(Int); \ data.Push((int64_t)(data.Pop() OP std::get(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(); \ if (y > 64) \ return error("shift out of bounds"); \ if (std::holds_alternative(data.back())) { \ uint64_t x = data.Pop(); \ data.Push(x OP y); \ } else if (std::holds_alternative(data.back())) { \ int64_t x = data.Pop(); \ 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()); 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(); // Shorthand to improve readability. #define POP_VALOBJ(VALOBJ) \ auto VALOBJ = data.Pop(); \ 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(); POP_VALOBJ(valobj); data.Push(valobj->GetChildAtIndex(index)); break; } case sel_get_child_with_name: { TYPE_CHECK(Object, String); auto name = data.Pop(); POP_VALOBJ(valobj); data.Push(valobj->GetChildMemberWithName(name)); break; } case sel_get_child_index: { TYPE_CHECK(Object, String); auto name = data.Pop(); 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(); auto type = data.Pop(); // 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(); POP_VALOBJ(valobj); data.Push(valobj->Cast(type)); break; } case sel_strlen: { TYPE_CHECK(String); data.Push((uint64_t)data.Pop().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