//===-- 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/Symbol/CompilerType.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" #include 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(&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 ap = std::get_if(&d)) os << *ap; 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); } else if (auto *dict = std::get_if>( &d)) { os << "dict(" << (*dict ? (*dict)->size() : 0) << ')'; } os << ' '; } os << ']'; return s; } namespace FormatterBytecode { /// Check a format string, and remove the layout and options of each /// replacement. The format string may contain literal text, escaped braces /// ("{{"), and automatically indexed replacements: /// /// "{" [ "," layout ] [ ":" options ] "}" /// /// Explicit indexes are not supported. Layouts and options are accepted, so /// that support for them can be added without changing the ABI, but they are /// currently ignored. The result contains only literal text, escaped braces, /// and "{}", so malformed format strings never reach formatv. static llvm::Expected SanitizeFormatString(llvm::StringRef fmt) { const llvm::StringRef original = fmt; std::string result; while (!fmt.empty()) { // Copy the literal text up to the next brace. llvm::StringRef literal = fmt.take_front(fmt.find('{')); result += literal; fmt = fmt.drop_front(literal.size()); if (fmt.empty()) break; if (fmt.consume_front("{{")) { result += "{{"; continue; } llvm::StringRef replacement = fmt; fmt = fmt.drop_front(); // Skip the layout, which ends at the options or the closing brace. if (fmt.consume_front(",")) fmt = fmt.substr(fmt.find_first_of("{}:")); // Skip the options, which end at the closing brace. if (fmt.consume_front(":")) fmt = fmt.substr(fmt.find_first_of("{}")); if (!fmt.consume_front("}")) return llvm::createStringErrorV( "invalid replacement in format string at offset {0}", original.size() - replacement.size()); replacement = replacement.drop_back(fmt.size()); if (replacement != "{}") LLDB_LOG(GetLog(LLDBLog::DataFormatters), "ignoring unsupported layout or options in format replacement " "'{0}'", replacement); result += "{}"; } return result; } /// Implement the @format function. static llvm::Error FormatImpl(DataStack &data, uint32_t version) { auto fmt = data.Pop(); if (version >= 2) { auto sanitized = SanitizeFormatString(fmt); if (!sanitized) return sanitized.takeError(); fmt = std::move(*sanitized); } 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(&arg)) format(FormatFunctor(s->c_str())); else if (auto u = std::get_if(&arg)) format(FormatFunctor(u)); else if (auto i = std::get_if(&arg)) format(FormatFunctor(i)); else if (auto ap = std::get_if(&arg)) format(FormatFunctor(*ap)); else if (auto valobj = std::get_if(&arg)) { if (!valobj->get()) format(FormatFunctor("null object")); else format(FormatFunctor(valobj->get()->GetValueAsCString())); } else if (auto type = std::get_if(&arg)) format(FormatFunctor(type->GetDisplayTypeName())); else if (auto sel = std::get_if(&arg)) format(FormatFunctor(toString(*sel))); else if (std::holds_alternative(arg)) format(FormatFunctor("dict")); } // Consume the arguments, leaving only the result. data.erase(data.end() - num_args, data.end()); 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; case Integer: if (!std::holds_alternative(elem)) return llvm::createStringError("expected Integer"); break; case Dict: if (!std::holds_alternative>( elem)) return llvm::createStringError("expected Dictionary"); 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(), type1, type2); } static llvm::Error TypeCheck(llvm::ArrayRef data, DataType type1, DataType type2, DataType type3, DataType type4) { if (auto error = TypeCheck(data, type4)) return error; return TypeCheck(data.drop_back(), type1, type2, type3); } /// 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 static DataStackElement WrapAPSIntResult(T result, unsigned bit_width, bool is_unsigned) { if constexpr (std::is_same_v) 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 visited; llvm::SmallVector 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(&entry.second)) worklist.push_back(nested->get()); } return false; } template using EnableIfSigned = std::enable_if_t && std::is_signed_v, int>; template using EnableIfUnsigned = std::enable_if_t && std::is_unsigned_v && !std::is_same_v, int>; template = 0> static DataStackElement MakeInt(T value, uint32_t version) { if (version < 2) return int64_t(value); return llvm::APSInt::get(value); } template = 0> static DataStackElement MakeInt(T value, uint32_t version) { if (version < 2) return uint64_t(value); unsigned width = uint64_t(value) > uint64_t(INT64_MAX) ? 65 : 64; return llvm::APSInt(llvm::APInt(width, value), /*isUnsigned=*/false); } llvm::Error Interpret(ControlStack &control, DataStack &data, Signatures sig, uint32_t version) { 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(); 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(&cond)) truthy = !ap->isZero(); else if (auto *u = std::get_if(&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(&cond)) truthy = !ap->isZero(); else if (auto *u = std::get_if(&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(); 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 operations. #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 if (std::holds_alternative(y)) { \ TYPE_CHECK(Integer); \ llvm::APSInt rhs = std::get(y); \ llvm::APSInt lhs = data.Pop(); \ 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(y)) { \ TYPE_CHECK(UInt); \ data.Push((uint64_t)(data.Pop() OP std::get(y))); \ } else if (std::holds_alternative(y)) { \ TYPE_CHECK(Int); \ data.Push((int64_t)(data.Pop() OP std::get(y))); \ } else if (std::holds_alternative(y)) { \ TYPE_CHECK(Integer); \ llvm::APSInt rhs = std::get(y); \ llvm::APSInt lhs = data.Pop(); \ 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(y)) { \ TYPE_CHECK(UInt); \ data.Push((uint64_t)(data.Pop() OP std::get(y))); \ } else if (std::holds_alternative(y)) { \ TYPE_CHECK(Int); \ data.Push((int64_t)(data.Pop() OP std::get(y))); \ } else if (std::holds_alternative(y)) { \ TYPE_CHECK(Integer); \ llvm::APSInt rhs = std::get(y); \ llvm::APSInt lhs = data.Pop(); \ unsigned width = std::max(lhs.getBitWidth(), rhs.getBitWidth()); \ llvm::APInt lhs_bits = \ static_cast(lhs).zext(width); \ llvm::APInt rhs_bits = \ static_cast(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(); \ 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 if (std::holds_alternative(data.back())) { \ llvm::APSInt x = data.Pop(); \ 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(x)) data.Push(~std::get(x)); else if (auto *ap = std::get_if(&x)) { llvm::APInt bits = ~static_cast(*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(); // 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::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(MakeInt(*result, version)); break; } case sel_get_child_at_index: { uint64_t index; if (version >= 2) { TYPE_CHECK(Object, Integer); index = data.Pop().getLimitedValue(UINT32_MAX); } else { TYPE_CHECK(Object, UInt); 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(MakeInt(*index_or_err, version)); 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: { uint64_t index; if (version >= 2) { TYPE_CHECK(Type, Integer); index = data.Pop().getLimitedValue(); } else { TYPE_CHECK(Type, UInt); 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_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(MakeInt(val, version)); 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(MakeInt(val, version)); 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(MakeInt(addr, version)); break; } case sel_cast: { TYPE_CHECK(Object, Type); auto type = data.Pop(); POP_VALOBJ(valobj); data.Push(valobj->Cast(type)); break; } case sel_clone: { TYPE_CHECK(Object, String); auto new_name = data.Pop(); POP_VALOBJ(valobj); data.Push(valobj->Clone(new_name)); break; } case sel_get_pointee_type: { TYPE_CHECK(Type); auto type = data.Pop(); data.Push(type.GetPointeeType()); break; } case sel_get_byte_size: { TYPE_CHECK(Type); auto type = data.Pop(); uint64_t size = llvm::expectedToOptional(type.GetByteSize(nullptr)).value_or(0); data.Push(llvm::APSInt::get(size)); break; } case sel_create_child_at_offset: { TYPE_CHECK(Object, String, Integer, Type); auto type = data.Pop(); auto offset = data.Pop().getLimitedValue(UINT32_MAX); ConstString name(data.Pop()); POP_VALOBJ(valobj); data.Push(valobj->GetSyntheticChildAtOffset(offset, type, true, name)); break; } case sel_strlen: { TYPE_CHECK(String); auto size = data.Pop().size(); data.Push(MakeInt(size, version)); break; } case sel_fmt: { TYPE_CHECK(String); if (auto error = FormatImpl(data, version)) return error; break; } default: return sel_error("selector not implemented"); } continue; } // Dictionary operations. case op_dict: data.Push(std::make_shared()); continue; case op_dict_set: { TYPE_CHECK(Dict, String, Any); auto value = data.PopAny(); auto key = data.Pop(); auto dict_sp = data.Pop(); if (auto *nested_sp = std::get_if(&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(); auto dict_sp = data.Pop(); 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(); auto dict_sp = data.Pop(); 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