| //===-- NSException.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 "clang/AST/DeclCXX.h" | |
| #include "Cocoa.h" | |
| #include "llvm/Support/ErrorExtras.h" | |
| #include "lldb/DataFormatters/FormattersHelpers.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/Endian.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/ValueObject/ValueObject.h" | |
| #include "lldb/ValueObject/ValueObjectConstResult.h" | |
| #include "Plugins/Language/ObjC/NSString.h" | |
| #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" | |
| #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace lldb_private::formatters; | |
| static bool ExtractFields(ValueObject &valobj, ValueObjectSP *name_sp, | |
| ValueObjectSP *reason_sp, ValueObjectSP *userinfo_sp, | |
| ValueObjectSP *reserved_sp, bool owned_by_valobj) { | |
| ProcessSP process_sp(valobj.GetProcessSP()); | |
| if (!process_sp) | |
| return false; | |
| lldb::addr_t ptr = LLDB_INVALID_ADDRESS; | |
| CompilerType valobj_type(valobj.GetCompilerType()); | |
| Flags type_flags(valobj_type.GetTypeInfo()); | |
| if (type_flags.AllClear(eTypeHasValue)) { | |
| if (valobj.IsBaseClass() && valobj.GetParent()) | |
| ptr = valobj.GetParent()->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); | |
| } else { | |
| ptr = valobj.GetValueAsUnsigned(LLDB_INVALID_ADDRESS); | |
| } | |
| if (ptr == LLDB_INVALID_ADDRESS) | |
| return false; | |
| size_t ptr_size = process_sp->GetAddressByteSize(); | |
| // Read the ivar at the given pointer-sized slot in the NSException object. | |
| // Returns std::nullopt if the read fails. | |
| auto read_field = [&](size_t slot) { | |
| return llvm::expectedToOptional( | |
| process_sp->ReadPointerFromMemory(ptr + slot * ptr_size)); | |
| }; | |
| std::optional<lldb::addr_t> name = read_field(1); | |
| if (!name) | |
| return false; | |
| std::optional<lldb::addr_t> reason = read_field(2); | |
| if (!reason) | |
| return false; | |
| std::optional<lldb::addr_t> userinfo = read_field(3); | |
| if (!userinfo) | |
| return false; | |
| std::optional<lldb::addr_t> reserved = read_field(4); | |
| if (!reserved) | |
| return false; | |
| InferiorSizedWord name_isw(*name, *process_sp); | |
| InferiorSizedWord reason_isw(*reason, *process_sp); | |
| InferiorSizedWord userinfo_isw(*userinfo, *process_sp); | |
| InferiorSizedWord reserved_isw(*reserved, *process_sp); | |
| TypeSystemClangSP scratch_ts_sp = | |
| ScratchTypeSystemClang::GetForTarget(process_sp->GetTarget()); | |
| if (!scratch_ts_sp) | |
| return false; | |
| CompilerType voidstar = | |
| scratch_ts_sp->GetBasicType(lldb::eBasicTypeVoid).GetPointerType(); | |
| ExecutionContextRef exe_ref = valobj.GetExecutionContextRef(); | |
| ByteOrder byte_order = process_sp->GetByteOrder(); | |
| auto set_sp = [&](llvm::StringRef name, InferiorSizedWord &data_source, | |
| ValueObjectSP *set_me_sp) { | |
| if (!set_me_sp) | |
| return; | |
| if (owned_by_valobj) | |
| *set_me_sp = valobj.CreateChildValueObjectFromData( | |
| name, data_source.GetAsData(byte_order), exe_ref, voidstar); | |
| else | |
| *set_me_sp = valobj.CreateValueObjectFromData( | |
| name, data_source.GetAsData(byte_order), exe_ref, voidstar); | |
| }; | |
| set_sp("name", name_isw, name_sp); | |
| set_sp("reason", reason_isw, reason_sp); | |
| set_sp("userInfo", userinfo_isw, userinfo_sp); | |
| set_sp("reserved", reserved_isw, reserved_sp); | |
| return true; | |
| } | |
| bool lldb_private::formatters::NSException_SummaryProvider( | |
| ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { | |
| lldb::ValueObjectSP reason_sp; | |
| if (!ExtractFields(valobj, nullptr, &reason_sp, nullptr, nullptr, | |
| /*owned_by_valobj=*/false)) | |
| return false; | |
| if (!reason_sp) { | |
| stream.Printf("No reason"); | |
| return false; | |
| } | |
| StreamString reason_str_summary; | |
| if (NSStringSummaryProvider(*reason_sp, reason_str_summary, options) && | |
| !reason_str_summary.Empty()) { | |
| stream.Printf("%s", reason_str_summary.GetData()); | |
| return true; | |
| } else | |
| return false; | |
| } | |
| class NSExceptionSyntheticFrontEnd : public SyntheticChildrenFrontEnd { | |
| public: | |
| NSExceptionSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) | |
| : SyntheticChildrenFrontEnd(*valobj_sp) {} | |
| ~NSExceptionSyntheticFrontEnd() override = default; | |
| llvm::Expected<uint32_t> CalculateNumChildren() override { return 4; } | |
| lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { | |
| switch (idx) { | |
| case 0: return m_name_sp; | |
| case 1: return m_reason_sp; | |
| case 2: return m_userinfo_sp; | |
| case 3: return m_reserved_sp; | |
| } | |
| return lldb::ValueObjectSP(); | |
| } | |
| lldb::ChildCacheState Update() override { | |
| m_name_sp.reset(); | |
| m_reason_sp.reset(); | |
| m_userinfo_sp.reset(); | |
| m_reserved_sp.reset(); | |
| const auto ret = ExtractFields(m_backend, &m_name_sp, &m_reason_sp, | |
| &m_userinfo_sp, &m_reserved_sp, | |
| /*owned_by_valobj=*/true); | |
| return ret ? lldb::ChildCacheState::eReuse | |
| : lldb::ChildCacheState::eRefetch; | |
| } | |
| llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { | |
| // NSException has 4 members: | |
| // NSString *name; | |
| // NSString *reason; | |
| // NSDictionary *userInfo; | |
| // id reserved; | |
| static constexpr llvm::StringLiteral g_name("name"); | |
| static constexpr llvm::StringLiteral g_reason("reason"); | |
| static constexpr llvm::StringLiteral g_userInfo("userInfo"); | |
| static constexpr llvm::StringLiteral g_reserved("reserved"); | |
| if (name == g_name) return 0; | |
| if (name == g_reason) return 1; | |
| if (name == g_userInfo) return 2; | |
| if (name == g_reserved) return 3; | |
| return llvm::createStringErrorV("type has no child named '{0}'", name); | |
| } | |
| private: | |
| ValueObjectSP m_name_sp; | |
| ValueObjectSP m_reason_sp; | |
| ValueObjectSP m_userinfo_sp; | |
| ValueObjectSP m_reserved_sp; | |
| }; | |
| SyntheticChildrenFrontEnd * | |
| lldb_private::formatters::NSExceptionSyntheticFrontEndCreator( | |
| CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { | |
| lldb::ProcessSP process_sp(valobj_sp->GetProcessSP()); | |
| if (!process_sp) | |
| return nullptr; | |
| ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp); | |
| if (!runtime) | |
| return nullptr; | |
| ObjCLanguageRuntime::ClassDescriptorSP descriptor( | |
| runtime->GetClassDescriptor(*valobj_sp.get())); | |
| if (!descriptor.get() || !descriptor->IsValid()) | |
| return nullptr; | |
| const char *class_name = descriptor->GetClassName().GetCString(); | |
| if (!class_name || !*class_name) | |
| return nullptr; | |
| if (!strcmp(class_name, "NSException")) | |
| return (new NSExceptionSyntheticFrontEnd(valobj_sp)); | |
| else if (!strcmp(class_name, "NSCFException")) | |
| return (new NSExceptionSyntheticFrontEnd(valobj_sp)); | |
| else if (!strcmp(class_name, "__NSCFException")) | |
| return (new NSExceptionSyntheticFrontEnd(valobj_sp)); | |
| return nullptr; | |
| } |