| //===-- 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 "lldb/DataFormatters/FormatterBytecode.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "lldb/ValueObject/ValueObjectConstResult.h" | |
| #include "lldb/lldb-forward.h" | |
| #include "llvm/ADT/APSInt.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/Support/DataExtractor.h" | |
| #include "llvm/Support/Error.h" | |
| #include "llvm/Support/ErrorExtras.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 "lldb/DataFormatters/FormatterBytecode.def" | |
| #undef DEFINE_OPCODE | |
| } | |
| return llvm::utostr(op); | |
| } | |
| std::string toString(FormatterBytecode::Selectors sel) { | |
| switch (sel) { | |
| #define DEFINE_SELECTOR(ID, NAME) \ | |
| case ID: \ | |
| return "@" #NAME; | |
| #include "lldb/DataFormatters/FormatterBytecode.def" | |
| #undef DEFINE_SELECTOR | |
| } | |
| return "@" + llvm::utostr(sel); | |
| } | |
| std::string toString(FormatterBytecode::Signatures sig) { | |
| switch (sig) { | |
| #define DEFINE_SIGNATURE(ID, NAME) \ | |
| case ID: \ | |
| return "@" #NAME; | |
| #include "lldb/DataFormatters/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 ap = std::get_if<llvm::APSInt>(&d)) | |
| os << *ap; | |
| 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); | |
| } else if (auto *dict = | |
| std::get_if<std::shared_ptr<FormatterBytecode::Dictionary>>( | |
| &d)) { | |
| os << "dict(" << (*dict ? (*dict)->size() : 0) << ')'; | |
| } | |
| 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 = [&](FormatFunctorRef &&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(FormatFunctor(s->c_str())); | |
| else if (auto u = std::get_if<uint64_t>(&arg)) | |
| format(FormatFunctor(u)); | |
| else if (auto i = std::get_if<int64_t>(&arg)) | |
| format(FormatFunctor(i)); | |
| else if (auto ap = std::get_if<llvm::APSInt>(&arg)) | |
| format(FormatFunctor(*ap)); | |
| else if (auto valobj = std::get_if<ValueObjectSP>(&arg)) { | |
| if (!valobj->get()) | |
| format(FormatFunctor("null object")); | |
| else | |
| format(FormatFunctor(valobj->get()->GetValueAsCString())); | |
| } else if (auto type = std::get_if<CompilerType>(&arg)) | |
| format(FormatFunctor(type->GetDisplayTypeName())); | |
| else if (auto sel = std::get_if<FormatterBytecode::Selectors>(&arg)) | |
| format(FormatFunctor(toString(*sel))); | |
| else if (std::holds_alternative<DictionarySP>(arg)) | |
| format(FormatFunctor("dict")); | |
| } | |
| 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; | |
| case Integer: | |
| if (!std::holds_alternative<llvm::APSInt>(elem)) | |
| return llvm::createStringError("expected Integer"); | |
| break; | |
| case Dict: | |
| if (!std::holds_alternative<std::shared_ptr<FormatterBytecode::Dictionary>>( | |
| elem)) | |
| return llvm::createStringError("expected Dictionary"); | |
| 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(), type1, type2); | |
| } | |
| /// Wrap the result of a binary operator applied to two APSInts back into a | |
| /// DataStackElement. Comparison operators yield bool and need bit_width/ | |
| /// is_unsigned to construct the boolean's APSInt representation; arithmetic | |
| /// operators already yield a correctly-tagged APSInt and ignore them. | |
| template <typename T> | |
| static DataStackElement WrapAPSIntResult(T result, unsigned bit_width, | |
| bool is_unsigned) { | |
| if constexpr (std::is_same_v<T, bool>) | |
| return llvm::APSInt(llvm::APInt(bit_width, result), is_unsigned); | |
| else | |
| return DataStackElement(std::move(result)); | |
| } | |
| /// Returns true if `target` is transitively reachable via `from`. Likewise, | |
| /// returns true if they are the same dictionary. This is used to prevent memory | |
| /// leaks caused by retain cycles. Dictionaries can be shared by multiple | |
| /// parents, so each one is visited only once to prevent exponential running | |
| /// time. | |
| static bool Reaches(const Dictionary *from, const Dictionary *target) { | |
| llvm::SmallPtrSet<const Dictionary *, 8> visited; | |
| llvm::SmallVector<const Dictionary *, 8> worklist = {from}; | |
| while (!worklist.empty()) { | |
| const Dictionary *dict = worklist.pop_back_val(); | |
| if (dict == target) | |
| return true; | |
| if (!visited.insert(dict).second) | |
| continue; | |
| for (const auto &entry : *dict) | |
| if (auto *nested = std::get_if<DictionarySP>(&entry.second)) | |
| worklist.push_back(nested->get()); | |
| } | |
| return false; | |
| } | |
| llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig) { | |
| 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); | |
| 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); | |
| 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_LOG_VERBOSE(GetLog(LLDBLog::DataFormatters), | |
| "[eval {0}] opcode={1}, control={2}, data={3}", | |
| toString(sig), 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: { | |
| auto cond = data.PopAny(); | |
| bool truthy; | |
| if (auto *ap = std::get_if<llvm::APSInt>(&cond)) | |
| truthy = !ap->isZero(); | |
| else if (auto *u = std::get_if<uint64_t>(&cond)) | |
| // Deprecated. | |
| truthy = *u != 0; | |
| else | |
| return error("expected Integer or UInt"); | |
| if (truthy) { | |
| if (!cur_block.size()) | |
| return error("empty control stack"); | |
| activate_block(); | |
| } else | |
| control.pop_back(); | |
| continue; | |
| } | |
| case op_ifelse: { | |
| if (cur_block.size() < 2) | |
| return error("empty control stack"); | |
| auto cond = data.PopAny(); | |
| bool truthy; | |
| if (auto *ap = std::get_if<llvm::APSInt>(&cond)) | |
| truthy = !ap->isZero(); | |
| else if (auto *u = std::get_if<uint64_t>(&cond)) | |
| // Deprecated. | |
| truthy = *u != 0; | |
| else | |
| return error("expected Integer or UInt"); | |
| if (!truthy) | |
| 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_integer: | |
| data.Push(cur_block.getSLEB128APSInt(pc)); | |
| continue; | |
| case op_lit_selector: | |
| data.Push(Selectors(cur_block.getU8(pc))); | |
| continue; | |
| case op_lit_null: | |
| data.Push(ValueObjectSP()); | |
| 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 operations. | |
| #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 if (std::holds_alternative<llvm::APSInt>(y)) { \ | |
| TYPE_CHECK(Integer); \ | |
| llvm::APSInt rhs = std::get<llvm::APSInt>(y); \ | |
| llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \ | |
| if (lhs.isUnsigned() || rhs.isUnsigned()) \ | |
| return error("unsupported unsigned value"); \ | |
| unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \ | |
| lhs = lhs.extend(width); \ | |
| rhs = rhs.extend(width); \ | |
| if (CHECK_ZERO && rhs.isZero()) \ | |
| return error(#OP " by zero"); \ | |
| data.Push(WrapAPSIntResult(lhs OP rhs, width, lhs.isUnsigned())); \ | |
| } else \ | |
| return error("unsupported data types"); \ | |
| } | |
| #define BINOP(OP) BINOP_IMPL(OP, false) | |
| #define BINOP_CHECKZERO(OP) BINOP_IMPL(OP, true) | |
| // Comparison operations. | |
| #define CMPOP(OP) \ | |
| { \ | |
| TYPE_CHECK(Any, Any); \ | |
| auto y = data.PopAny(); \ | |
| if (std::holds_alternative<uint64_t>(y)) { \ | |
| 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)) { \ | |
| TYPE_CHECK(Int); \ | |
| data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \ | |
| } else if (std::holds_alternative<llvm::APSInt>(y)) { \ | |
| TYPE_CHECK(Integer); \ | |
| llvm::APSInt rhs = std::get<llvm::APSInt>(y); \ | |
| llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \ | |
| if (lhs.isUnsigned() || rhs.isUnsigned()) \ | |
| return error("unsupported unsigned value"); \ | |
| unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \ | |
| lhs = lhs.extend(width); \ | |
| rhs = rhs.extend(width); \ | |
| data.Push(WrapAPSIntResult(lhs OP rhs, width, lhs.isUnsigned())); \ | |
| } else \ | |
| return error("unsupported data types"); \ | |
| } | |
| // Bitwise operations use an Integer's underlying bit pattern, not its | |
| // mathematical value (ie signed-ness is ignored). This means >> is always a | |
| // logical (zero-filling) shift, never an arithmetic shift. Mismatched bit | |
| // widths are implicitly zero-extended (not sign-extended). | |
| #define BITOP(OP) \ | |
| { \ | |
| TYPE_CHECK(Any, Any); \ | |
| auto y = data.PopAny(); \ | |
| if (std::holds_alternative<uint64_t>(y)) { \ | |
| 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)) { \ | |
| TYPE_CHECK(Int); \ | |
| data.Push((int64_t)(data.Pop<int64_t>() OP std::get<int64_t>(y))); \ | |
| } else if (std::holds_alternative<llvm::APSInt>(y)) { \ | |
| TYPE_CHECK(Integer); \ | |
| llvm::APSInt rhs = std::get<llvm::APSInt>(y); \ | |
| llvm::APSInt lhs = data.Pop<llvm::APSInt>(); \ | |
| unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \ | |
| llvm::APInt lhs_bits = \ | |
| static_cast<const llvm::APInt &>(lhs).zext(width); \ | |
| llvm::APInt rhs_bits = \ | |
| static_cast<const llvm::APInt &>(rhs).zext(width); \ | |
| llvm::APInt bits = lhs_bits OP rhs_bits; \ | |
| data.Push(llvm::APSInt(std::move(bits), /*isUnsigned=*/false)); \ | |
| } else \ | |
| return error("unsupported data types"); \ | |
| } | |
| 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 if (std::holds_alternative<llvm::APSInt>(data.back())) { \ | |
| llvm::APSInt x = data.Pop<llvm::APSInt>(); \ | |
| if (y > x.getBitWidth()) \ | |
| return error("shift out of bounds"); \ | |
| const llvm::APInt &bits = x; \ | |
| llvm::APInt shifted = \ | |
| LEFT ? bits.shl((unsigned)y) : bits.lshr((unsigned)y); \ | |
| data.Push(llvm::APSInt(std::move(shifted), /*isUnsigned=*/false)); \ | |
| } else \ | |
| return error("unsupported data types"); \ | |
| } | |
| SHIFTOP(<<, true); | |
| continue; | |
| case op_shr: | |
| SHIFTOP(>>, false); | |
| continue; | |
| case op_and: | |
| BITOP(&); | |
| continue; | |
| case op_or: | |
| BITOP(|); | |
| continue; | |
| case op_xor: | |
| BITOP(^); | |
| continue; | |
| case op_not: { | |
| TYPE_CHECK(Any); | |
| auto x = data.PopAny(); | |
| if (std::holds_alternative<uint64_t>(x)) | |
| data.Push(~std::get<uint64_t>(x)); | |
| else if (auto *ap = std::get_if<llvm::APSInt>(&x)) { | |
| llvm::APInt bits = ~static_cast<const llvm::APInt &>(*ap); | |
| data.Push(llvm::APSInt(std::move(bits), /*isUnsigned=*/false)); | |
| } else | |
| return error("unsupported data types"); | |
| continue; | |
| } | |
| case op_eq: | |
| CMPOP(==); | |
| continue; | |
| case op_neq: | |
| CMPOP(!=); | |
| continue; | |
| case op_lt: | |
| CMPOP(<); | |
| continue; | |
| case op_gt: | |
| CMPOP(>); | |
| continue; | |
| case op_le: | |
| CMPOP(<=); | |
| continue; | |
| case op_ge: | |
| CMPOP(>=); | |
| 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::createStringErrorV("{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_parent: { | |
| TYPE_CHECK(Object); | |
| POP_VALOBJ(valobj); | |
| auto *parent = valobj->GetParent(); | |
| data.Push(parent ? parent->GetSP() : ValueObjectSP()); | |
| 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_synthetic_value: { | |
| TYPE_CHECK(Object); | |
| POP_VALOBJ(valobj); | |
| data.Push(valobj->GetSyntheticValue()); | |
| break; | |
| } | |
| case sel_get_non_synthetic_value: { | |
| TYPE_CHECK(Object); | |
| POP_VALOBJ(valobj); | |
| data.Push(valobj->GetNonSyntheticValue()); | |
| 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_clone: { | |
| TYPE_CHECK(Object, String); | |
| auto new_name = data.Pop<std::string>(); | |
| POP_VALOBJ(valobj); | |
| data.Push(valobj->Clone(new_name)); | |
| 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; | |
| } | |
| // Dictionary operations. | |
| case op_dict: | |
| data.Push(std::make_shared<Dictionary>()); | |
| continue; | |
| case op_dict_set: { | |
| TYPE_CHECK(Dict, String, Any); | |
| auto value = data.PopAny(); | |
| auto key = data.Pop<std::string>(); | |
| auto dict_sp = data.Pop<DictionarySP>(); | |
| if (auto *nested_sp = std::get_if<DictionarySP>(&value)) | |
| if (Reaches(nested_sp->get(), dict_sp.get())) | |
| return error("dict_set would create a reference cycle"); | |
| (*dict_sp)[key] = std::move(value); | |
| continue; | |
| } | |
| case op_dict_get: { | |
| TYPE_CHECK(Dict, String); | |
| auto key = data.Pop<std::string>(); | |
| auto dict_sp = data.Pop<DictionarySP>(); | |
| auto it = dict_sp->find(key); | |
| if (it == dict_sp->end()) | |
| return error("key not found in dictionary"); | |
| data.Push(it->second); | |
| continue; | |
| } | |
| case op_dict_has: { | |
| TYPE_CHECK(Dict, String); | |
| auto key = data.Pop<std::string>(); | |
| auto dict_sp = data.Pop<DictionarySP>(); | |
| bool found = dict_sp->find(key) != dict_sp->end(); | |
| data.Push(llvm::APSInt::get(found)); | |
| continue; | |
| } | |
| } | |
| return error("opcode not implemented"); | |
| } | |
| return pc.takeError(); | |
| } | |
| } // namespace FormatterBytecode | |
| } // namespace lldb_private |