[Lldb-commits] [lldb] 8f5beb4 - [lldb/Dataformatter] Add support for CoreFoundation Dictionaries and Sets.

Med Ismail Bennani via lldb-commits lldb-commits at lists.llvm.org
Mon Apr 27 13:12:16 PDT 2020


Author: Med Ismail Bennani
Date: 2020-04-27T22:10:11+02:00
New Revision: 8f5beb4c4b114f2cd827b10b5358f0d79d8bb691

URL: https://github.com/llvm/llvm-project/commit/8f5beb4c4b114f2cd827b10b5358f0d79d8bb691
DIFF: https://github.com/llvm/llvm-project/commit/8f5beb4c4b114f2cd827b10b5358f0d79d8bb691.diff

LOG: [lldb/Dataformatter] Add support for CoreFoundation Dictionaries and Sets.

This patch improves data formatting for CoreFoundation containers:
CFDictionary and CFSet.

These data formatters make the containers and their children appear in Xcode's
variables view (and on the command line) without having to expand the
data structure.

Previous implementation only supported showing the container's element count.

```
(lldb) frame var dict
(__NSCFDictionary *) dict = 0x00000001004062b0 2 key/value pairs

(lldb) frame var set
(__NSCFSet *) set = 0x0000000100406330 2 elements
```
Now the variable can be dereferenced to dispaly the container's children:

```
(lldb) frame var *dict
(__NSCFDictionary) *dict = {
  [0] = {
    key = 0x0000000100004050 @"123"
    value = 0x0000000100004090 @"456"
  }
  [1] = {
    key = 0x0000000100004030 @"abc"
    value = 0x0000000100004070 @"def"
  }
}

(lldb) frame var *set
(__NSCFSet) *set = {
  [0] = 0x0000000100004050 @"123"
  [1] = 0x0000000100004030 @"abc"
}
```

rdar://39882287

Differential Revision: https://reviews.llvm.org/D78396

Signed-off-by: Med Ismail Bennani <medismail.bennani at gmail.com>

Added: 
    lldb/source/Plugins/Language/ObjC/CFBasicHash.cpp
    lldb/source/Plugins/Language/ObjC/CFBasicHash.h

Modified: 
    lldb/source/Plugins/Language/ObjC/CMakeLists.txt
    lldb/source/Plugins/Language/ObjC/NSDictionary.cpp
    lldb/source/Plugins/Language/ObjC/NSDictionary.h
    lldb/source/Plugins/Language/ObjC/NSSet.cpp
    lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
    lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSContainer.py
    lldb/test/API/functionalities/data-formatter/data-formatter-objc/main.m

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Language/ObjC/CFBasicHash.cpp b/lldb/source/Plugins/Language/ObjC/CFBasicHash.cpp
new file mode 100644
index 000000000000..42cda0146f2e
--- /dev/null
+++ b/lldb/source/Plugins/Language/ObjC/CFBasicHash.cpp
@@ -0,0 +1,114 @@
+#include "CFBasicHash.h"
+
+#include "lldb/Utility/Endian.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+bool CFBasicHash::IsValid() const {
+  if (m_address != LLDB_INVALID_ADDRESS) {
+    if (m_ptr_size == 4 && m_ht_32)
+      return true;
+    else if (m_ptr_size == 8 && m_ht_64)
+      return true;
+    else
+      return false;
+  }
+  return false;
+}
+
+bool CFBasicHash::Update(addr_t addr, ExecutionContextRef exe_ctx_rf) {
+  if (addr == LLDB_INVALID_ADDRESS || !addr)
+    return false;
+
+  m_address = addr;
+  m_exe_ctx_ref = exe_ctx_rf;
+  m_ptr_size =
+      m_exe_ctx_ref.GetTargetSP()->GetArchitecture().GetAddressByteSize();
+  m_byte_order = m_exe_ctx_ref.GetTargetSP()->GetArchitecture().GetByteOrder();
+
+  if (m_ptr_size == 4)
+    return UpdateFor(m_ht_32);
+  else if (m_ptr_size == 8)
+    return UpdateFor(m_ht_64);
+  return false;
+
+  llvm_unreachable(
+      "Unsupported architecture. Only 32bits and 64bits supported.");
+}
+
+template <typename T>
+bool CFBasicHash::UpdateFor(std::unique_ptr<__CFBasicHash<T>> &m_ht) {
+  if (m_byte_order != endian::InlHostByteOrder())
+    return false;
+  
+  Status error;
+  Target *target = m_exe_ctx_ref.GetTargetSP().get();
+  addr_t addr = m_address.GetLoadAddress(target);
+  size_t size = sizeof(typename __CFBasicHash<T>::RuntimeBase) +
+                sizeof(typename __CFBasicHash<T>::Bits);
+
+  m_ht = std::make_unique<__CFBasicHash<T>>();
+  m_exe_ctx_ref.GetProcessSP()->ReadMemory(addr, m_ht.get(),
+                                           size, error);
+  if (error.Fail())
+    return false;
+
+  m_mutable = !(m_ht->base.cfinfoa & (1 << 6));
+  m_multi = m_ht->bits.counts_offset;
+  m_type = static_cast<HashType>(m_ht->bits.keys_offset);
+  addr_t ptr_offset = addr + size;
+  size_t ptr_count = GetPointerCount();
+  size = ptr_count * sizeof(T);
+
+  m_exe_ctx_ref.GetProcessSP()->ReadMemory(ptr_offset, m_ht->pointers, size,
+                                           error);
+
+  if (error.Fail()) {
+    m_ht = nullptr;
+    return false;
+  }
+
+  return true;
+}
+
+size_t CFBasicHash::GetCount() const {
+  if (!IsValid())
+    return 0;
+
+  if (!m_multi)
+    return (m_ptr_size == 4) ? m_ht_32->bits.used_buckets
+                             : m_ht_64->bits.used_buckets;
+
+  //  FIXME: Add support for multi
+  return 0;
+}
+
+size_t CFBasicHash::GetPointerCount() const {
+  if (!IsValid())
+    return 0;
+
+  if (m_multi)
+    return 3; // Bits::counts_offset;
+  return (m_type == HashType::dict) + 1;
+}
+
+addr_t CFBasicHash::GetKeyPointer() const {
+  if (!IsValid())
+    return LLDB_INVALID_ADDRESS;
+
+  if (m_ptr_size == 4)
+    return m_ht_32->pointers[m_ht_32->bits.keys_offset];
+
+  return m_ht_64->pointers[m_ht_64->bits.keys_offset];
+}
+
+addr_t CFBasicHash::GetValuePointer() const {
+  if (!IsValid())
+    return LLDB_INVALID_ADDRESS;
+
+  if (m_ptr_size == 4)
+    return m_ht_32->pointers[0];
+
+  return m_ht_64->pointers[0];
+}

diff  --git a/lldb/source/Plugins/Language/ObjC/CFBasicHash.h b/lldb/source/Plugins/Language/ObjC/CFBasicHash.h
new file mode 100644
index 000000000000..e7d9dfb1a663
--- /dev/null
+++ b/lldb/source/Plugins/Language/ObjC/CFBasicHash.h
@@ -0,0 +1,77 @@
+//===-- CFBasicHash.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_SOURCE_PLUGINS_LANGUAGE_OBJC_CFBASICHASH_H
+#define LLDB_SOURCE_PLUGINS_LANGUAGE_OBJC_CFBASICHASH_H
+
+#include "lldb/Target/Process.h"
+#include "lldb/Target/Target.h"
+
+namespace lldb_private {
+
+class CFBasicHash {
+public:
+  enum class HashType { set = 0, dict };
+
+  CFBasicHash() = default;
+  ~CFBasicHash() = default;
+
+  bool Update(lldb::addr_t addr, ExecutionContextRef exe_ctx_rf);
+
+  bool IsValid() const;
+
+  bool IsMutable() const { return m_mutable; };
+  bool IsMultiVariant() const { return m_multi; }
+  HashType GetType() const { return m_type; }
+
+  size_t GetCount() const;
+  lldb::addr_t GetKeyPointer() const;
+  lldb::addr_t GetValuePointer() const;
+
+private:
+  template <typename T> struct __CFBasicHash {
+    struct RuntimeBase {
+      T cfisa;
+      T cfinfoa;
+    } base;
+
+    struct Bits {
+      uint16_t __reserved0;
+      uint16_t __reserved1 : 2;
+      uint16_t keys_offset : 1;
+      uint16_t counts_offset : 2;
+      uint16_t counts_width : 2;
+      uint16_t __reserved2 : 9;
+      uint32_t used_buckets;        // number of used buckets
+      uint64_t deleted : 16;        // number of elements deleted
+      uint64_t num_buckets_idx : 8; // index to number of buckets
+      uint64_t __reserved3 : 40;
+      uint64_t __reserved4;
+    } bits;
+
+    T pointers[3];
+  };
+  template <typename T> bool UpdateFor(std::unique_ptr<__CFBasicHash<T>> &m_ht);
+
+  size_t GetPointerCount() const;
+
+private:
+  uint32_t m_ptr_size = UINT32_MAX;
+  lldb::ByteOrder m_byte_order = lldb::eByteOrderInvalid;
+  Address m_address = LLDB_INVALID_ADDRESS;
+  std::unique_ptr<__CFBasicHash<uint32_t>> m_ht_32 = nullptr;
+  std::unique_ptr<__CFBasicHash<uint64_t>> m_ht_64 = nullptr;
+  ExecutionContextRef m_exe_ctx_ref;
+  bool m_mutable = true;
+  bool m_multi = false;
+  HashType m_type;
+};
+
+}; // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_OBJC_CFBASICHASH_H

diff  --git a/lldb/source/Plugins/Language/ObjC/CMakeLists.txt b/lldb/source/Plugins/Language/ObjC/CMakeLists.txt
index 7b220e4c0c21..c998d5455de3 100644
--- a/lldb/source/Plugins/Language/ObjC/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/ObjC/CMakeLists.txt
@@ -11,6 +11,7 @@ endif ()
 add_lldb_library(lldbPluginObjCLanguage PLUGIN
   ObjCLanguage.cpp
   CF.cpp
+  CFBasicHash.cpp
   Cocoa.cpp
   CoreMedia.cpp
   NSArray.cpp

diff  --git a/lldb/source/Plugins/Language/ObjC/NSDictionary.cpp b/lldb/source/Plugins/Language/ObjC/NSDictionary.cpp
index e76096ba7881..995b8751ec55 100644
--- a/lldb/source/Plugins/Language/ObjC/NSDictionary.cpp
+++ b/lldb/source/Plugins/Language/ObjC/NSDictionary.cpp
@@ -10,6 +10,7 @@
 
 #include "clang/AST/DeclCXX.h"
 
+#include "CFBasicHash.h"
 #include "NSDictionary.h"
 
 #include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
@@ -140,6 +141,37 @@ class NSDictionaryISyntheticFrontEnd : public SyntheticChildrenFrontEnd {
   std::vector<DictionaryItemDescriptor> m_children;
 };
 
+class NSCFDictionarySyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  NSCFDictionarySyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  size_t CalculateNumChildren() override;
+
+  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
+
+  bool Update() override;
+
+  bool MightHaveChildren() override;
+
+  size_t GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  struct DictionaryItemDescriptor {
+    lldb::addr_t key_ptr;
+    lldb::addr_t val_ptr;
+    lldb::ValueObjectSP valobj_sp;
+  };
+
+  ExecutionContextRef m_exe_ctx_ref;
+  uint8_t m_ptr_size;
+  lldb::ByteOrder m_order;
+
+  CFBasicHash m_hashtable;
+
+  CompilerType m_pair_type;
+  std::vector<DictionaryItemDescriptor> m_children;
+};
+
 class NSDictionary1SyntheticFrontEnd : public SyntheticChildrenFrontEnd {
 public:
   NSDictionary1SyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
@@ -377,6 +409,7 @@ bool lldb_private::formatters::NSDictionarySummaryProvider(
   static const ConstString g_Dictionary1("__NSSingleEntryDictionaryI");
   static const ConstString g_Dictionary0("__NSDictionary0");
   static const ConstString g_DictionaryCF("__NSCFDictionary");
+  static const ConstString g_DictionaryCFRef("CFDictionaryRef");
 
   if (class_name.IsEmpty())
     return false;
@@ -388,8 +421,7 @@ bool lldb_private::formatters::NSDictionarySummaryProvider(
     if (error.Fail())
       return false;
     value &= (is_64bit ? ~0xFC00000000000000UL : ~0xFC000000U);
-  } else if (class_name == g_DictionaryM || class_name == g_DictionaryMLegacy ||
-             class_name == g_DictionaryCF) {
+  } else if (class_name == g_DictionaryM || class_name == g_DictionaryMLegacy) {
     AppleObjCRuntime *apple_runtime =
     llvm::dyn_cast_or_null<AppleObjCRuntime>(runtime);
     Status error;
@@ -407,8 +439,13 @@ bool lldb_private::formatters::NSDictionarySummaryProvider(
     value = 1;
   } else if (class_name == g_Dictionary0) {
     value = 0;
-  }
-  else {
+  } else if (class_name == g_DictionaryCF || class_name == g_DictionaryCFRef) {
+    ExecutionContext exe_ctx(process_sp);
+    CFBasicHash cfbh;
+    if (!cfbh.Update(valobj_addr, exe_ctx))
+      return false;
+    value = cfbh.GetCount();
+  } else {
     auto &map(NSDictionary_Additionals::GetAdditionalSummaries());
     for (auto &candidate : map) {
       if (candidate.first && candidate.first->Match(class_name))
@@ -466,6 +503,8 @@ lldb_private::formatters::NSDictionarySyntheticFrontEndCreator(
   static const ConstString g_DictionaryImmutable("__NSDictionaryM_Immutable");
   static const ConstString g_DictionaryMLegacy("__NSDictionaryM_Legacy");
   static const ConstString g_Dictionary0("__NSDictionary0");
+  static const ConstString g_DictionaryCF("__NSCFDictionary");
+  static const ConstString g_DictionaryCFRef("CFDictionaryRef");
 
   if (class_name.IsEmpty())
     return nullptr;
@@ -484,6 +523,8 @@ lldb_private::formatters::NSDictionarySyntheticFrontEndCreator(
       return (new Foundation1100::NSDictionaryMSyntheticFrontEnd(valobj_sp));
   } else if (class_name == g_Dictionary1) {
     return (new NSDictionary1SyntheticFrontEnd(valobj_sp));
+  } else if (class_name == g_DictionaryCF || class_name == g_DictionaryCFRef) {
+    return (new NSCFDictionarySyntheticFrontEnd(valobj_sp));
   } else {
     auto &map(NSDictionary_Additionals::GetAdditionalSynthetics());
     for (auto &candidate : map) {
@@ -641,6 +682,140 @@ lldb_private::formatters::NSDictionaryISyntheticFrontEnd::GetChildAtIndex(
   return dict_item.valobj_sp;
 }
 
+lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::
+    NSCFDictionarySyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_ptr_size(8),
+      m_order(lldb::eByteOrderInvalid), m_hashtable(), m_pair_type() {}
+
+size_t lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::
+    GetIndexOfChildWithName(ConstString name) {
+  const char *item_name = name.GetCString();
+  const uint32_t idx = ExtractIndexFromString(item_name);
+  if (idx < UINT32_MAX && idx >= CalculateNumChildren())
+    return UINT32_MAX;
+  return idx;
+}
+
+size_t lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::
+    CalculateNumChildren() {
+  if (!m_hashtable.IsValid())
+    return 0;
+  return m_hashtable.GetCount();
+}
+
+bool lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::Update() {
+  m_children.clear();
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  m_ptr_size = 0;
+  if (!valobj_sp)
+    return false;
+  m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
+
+  lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
+  if (!process_sp)
+    return false;
+  m_ptr_size = process_sp->GetAddressByteSize();
+  m_order = process_sp->GetByteOrder();
+  return m_hashtable.Update(valobj_sp->GetValueAsUnsigned(0), m_exe_ctx_ref);
+}
+
+bool lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::
+    MightHaveChildren() {
+  return true;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::NSCFDictionarySyntheticFrontEnd::GetChildAtIndex(
+    size_t idx) {
+  lldb::addr_t m_keys_ptr = m_hashtable.GetKeyPointer();
+  lldb::addr_t m_values_ptr = m_hashtable.GetValuePointer();
+
+  const uint32_t num_children = CalculateNumChildren();
+
+  if (idx >= num_children)
+    return lldb::ValueObjectSP();
+
+  if (m_children.empty()) {
+    ProcessSP process_sp = m_exe_ctx_ref.GetProcessSP();
+    if (!process_sp)
+      return lldb::ValueObjectSP();
+
+    Status error;
+    lldb::addr_t key_at_idx = 0, val_at_idx = 0;
+
+    uint32_t tries = 0;
+    uint32_t test_idx = 0;
+
+    // Iterate over inferior memory, reading key/value pointers by shifting each
+    // cursor by test_index * m_ptr_size. Returns an empty ValueObject if a read
+    // fails, otherwise, continue until the number of tries matches the number
+    // of childen.
+    while (tries < num_children) {
+      key_at_idx = m_keys_ptr + (test_idx * m_ptr_size);
+      val_at_idx = m_values_ptr + (test_idx * m_ptr_size);
+
+      key_at_idx = process_sp->ReadPointerFromMemory(key_at_idx, error);
+      if (error.Fail())
+        return lldb::ValueObjectSP();
+      val_at_idx = process_sp->ReadPointerFromMemory(val_at_idx, error);
+      if (error.Fail())
+        return lldb::ValueObjectSP();
+
+      test_idx++;
+
+      if (!key_at_idx || !val_at_idx)
+        continue;
+      tries++;
+
+      DictionaryItemDescriptor descriptor = {key_at_idx, val_at_idx,
+                                             lldb::ValueObjectSP()};
+
+      m_children.push_back(descriptor);
+    }
+  }
+
+  if (idx >= m_children.size()) // should never happen
+    return lldb::ValueObjectSP();
+
+  DictionaryItemDescriptor &dict_item = m_children[idx];
+  if (!dict_item.valobj_sp) {
+    if (!m_pair_type.IsValid()) {
+      TargetSP target_sp(m_backend.GetTargetSP());
+      if (!target_sp)
+        return ValueObjectSP();
+      m_pair_type = GetLLDBNSPairType(target_sp);
+    }
+    if (!m_pair_type.IsValid())
+      return ValueObjectSP();
+
+    DataBufferSP buffer_sp(new DataBufferHeap(2 * m_ptr_size, 0));
+
+    switch (m_ptr_size) {
+    case 0: // architecture has no clue - fail
+      return lldb::ValueObjectSP();
+    case 4: {
+      uint32_t *data_ptr = reinterpret_cast<uint32_t *>(buffer_sp->GetBytes());
+      *data_ptr = dict_item.key_ptr;
+      *(data_ptr + 1) = dict_item.val_ptr;
+    } break;
+    case 8: {
+      uint64_t *data_ptr = reinterpret_cast<uint64_t *>(buffer_sp->GetBytes());
+      *data_ptr = dict_item.key_ptr;
+      *(data_ptr + 1) = dict_item.val_ptr;
+    } break;
+    default:
+      lldbassert(false && "pointer size is not 4 nor 8");
+    }
+
+    StreamString idx_name;
+    idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+    DataExtractor data(buffer_sp, m_order, m_ptr_size);
+    dict_item.valobj_sp = CreateValueObjectFromData(idx_name.GetString(), data,
+                                                    m_exe_ctx_ref, m_pair_type);
+  }
+  return dict_item.valobj_sp;
+}
+
 lldb_private::formatters::NSDictionary1SyntheticFrontEnd::
     NSDictionary1SyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
     : SyntheticChildrenFrontEnd(*valobj_sp.get()), m_pair(nullptr) {}
@@ -733,8 +908,8 @@ lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::
 }
 
 template <typename D32, typename D64>
-size_t
-lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::    GetIndexOfChildWithName(ConstString name) {
+size_t lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<
+    D32, D64>::GetIndexOfChildWithName(ConstString name) {
   const char *item_name = name.GetCString();
   uint32_t idx = ExtractIndexFromString(item_name);
   if (idx < UINT32_MAX && idx >= CalculateNumChildren())
@@ -783,7 +958,7 @@ lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::
   }
   if (error.Fail())
     return false;
-  return false;
+  return true;
 }
 
 template <typename D32, typename D64>
@@ -795,9 +970,8 @@ lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::
 
 template <typename D32, typename D64>
 lldb::ValueObjectSP
-lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::
-    GetChildAtIndex(
-    size_t idx) {
+lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<
+    D32, D64>::GetChildAtIndex(size_t idx) {
   lldb::addr_t m_keys_ptr;
   lldb::addr_t m_values_ptr;
   if (m_data_32) {
@@ -885,7 +1059,6 @@ lldb_private::formatters::GenericNSDictionaryMSyntheticFrontEnd<D32,D64>::
   return dict_item.valobj_sp;
 }
 
-
 lldb_private::formatters::Foundation1100::
   NSDictionaryMSyntheticFrontEnd::
     NSDictionaryMSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)

diff  --git a/lldb/source/Plugins/Language/ObjC/NSDictionary.h b/lldb/source/Plugins/Language/ObjC/NSDictionary.h
index cc338e2f58e8..57dacd6759d2 100644
--- a/lldb/source/Plugins/Language/ObjC/NSDictionary.h
+++ b/lldb/source/Plugins/Language/ObjC/NSDictionary.h
@@ -1,5 +1,4 @@
-//===-- NSDictionary.h ---------------------------------------------------*- C++
-//-*-===//
+//===-- NSDictionary.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.

diff  --git a/lldb/source/Plugins/Language/ObjC/NSSet.cpp b/lldb/source/Plugins/Language/ObjC/NSSet.cpp
index 59597bed85b5..3a24aa6059ca 100644
--- a/lldb/source/Plugins/Language/ObjC/NSSet.cpp
+++ b/lldb/source/Plugins/Language/ObjC/NSSet.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "NSSet.h"
+#include "CFBasicHash.h"
 
 #include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
@@ -79,6 +80,36 @@ class NSSetISyntheticFrontEnd : public SyntheticChildrenFrontEnd {
   std::vector<SetItemDescriptor> m_children;
 };
 
+class NSCFSetSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  NSCFSetSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  size_t CalculateNumChildren() override;
+
+  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
+
+  bool Update() override;
+
+  bool MightHaveChildren() override;
+
+  size_t GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  struct SetItemDescriptor {
+    lldb::addr_t item_ptr;
+    lldb::ValueObjectSP valobj_sp;
+  };
+
+  ExecutionContextRef m_exe_ctx_ref;
+  uint8_t m_ptr_size;
+  lldb::ByteOrder m_order;
+
+  CFBasicHash m_hashtable;
+
+  CompilerType m_pair_type;
+  std::vector<SetItemDescriptor> m_children;
+};
+
 template <typename D32, typename D64>
 class GenericNSSetMSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
 public:
@@ -245,21 +276,24 @@ bool lldb_private::formatters::NSSetSummaryProvider(
 
   uint64_t value = 0;
 
-  ConstString class_name_cs = descriptor->GetClassName();
-  const char *class_name = class_name_cs.GetCString();
+  ConstString class_name(descriptor->GetClassName());
 
-  if (!class_name || !*class_name)
+  static const ConstString g_SetI("__NSSetI");
+  static const ConstString g_OrderedSetI("__NSOrderedSetI");
+  static const ConstString g_SetM("__NSSetM");
+  static const ConstString g_SetCF("__NSCFSet");
+
+  if (class_name.IsEmpty())
     return false;
 
-  if (!strcmp(class_name, "__NSSetI") ||
-      !strcmp(class_name, "__NSOrderedSetI")) {
+  if (class_name == g_SetI || class_name == g_OrderedSetI) {
     Status error;
     value = process_sp->ReadUnsignedIntegerFromMemory(valobj_addr + ptr_size,
                                                       ptr_size, 0, error);
     if (error.Fail())
       return false;
     value &= (is_64bit ? ~0xFC00000000000000UL : ~0xFC000000U);
-  } else if (!strcmp(class_name, "__NSSetM")) {
+  } else if (class_name == g_SetM) {
     AppleObjCRuntime *apple_runtime =
         llvm::dyn_cast_or_null<AppleObjCRuntime>(runtime);
     Status error;
@@ -272,9 +306,15 @@ bool lldb_private::formatters::NSSetSummaryProvider(
     }
     if (error.Fail())
       return false;
+  } else if (class_name == g_SetCF) {
+    ExecutionContext exe_ctx(process_sp);
+    CFBasicHash cfbh;
+    if (!cfbh.Update(valobj_addr, exe_ctx))
+      return false;
+    value = cfbh.GetCount();
   } else {
     auto &map(NSSet_Additionals::GetAdditionalSummaries());
-    auto iter = map.find(class_name_cs), end = map.end();
+    auto iter = map.find(class_name), end = map.end();
     if (iter != end)
       return iter->second(valobj, stream, options);
     else
@@ -321,16 +361,19 @@ lldb_private::formatters::NSSetSyntheticFrontEndCreator(
   if (!descriptor || !descriptor->IsValid())
     return nullptr;
 
-  ConstString class_name_cs = descriptor->GetClassName();
-  const char *class_name = class_name_cs.GetCString();
+  ConstString class_name = descriptor->GetClassName();
 
-  if (!class_name || !*class_name)
+  static const ConstString g_SetI("__NSSetI");
+  static const ConstString g_OrderedSetI("__NSOrderedSetI");
+  static const ConstString g_SetM("__NSSetM");
+  static const ConstString g_SetCF("__NSCFSet");
+
+  if (class_name.IsEmpty())
     return nullptr;
 
-  if (!strcmp(class_name, "__NSSetI") ||
-      !strcmp(class_name, "__NSOrderedSetI")) {
+  if (class_name == g_SetI || class_name == g_OrderedSetI) {
     return (new NSSetISyntheticFrontEnd(valobj_sp));
-  } else if (!strcmp(class_name, "__NSSetM")) {
+  } else if (class_name == g_SetM) {
     AppleObjCRuntime *apple_runtime =
         llvm::dyn_cast_or_null<AppleObjCRuntime>(runtime);
     if (apple_runtime) {
@@ -343,9 +386,11 @@ lldb_private::formatters::NSSetSyntheticFrontEndCreator(
     } else {
       return (new Foundation1300::NSSetMSyntheticFrontEnd(valobj_sp));
     }
+  } else if (class_name == g_SetCF) {
+    return (new NSCFSetSyntheticFrontEnd(valobj_sp));
   } else {
     auto &map(NSSet_Additionals::GetAdditionalSynthetics());
-    auto iter = map.find(class_name_cs), end = map.end();
+    auto iter = map.find(class_name), end = map.end();
     if (iter != end)
       return iter->second(synth, valobj_sp);
     return nullptr;
@@ -475,16 +520,18 @@ lldb_private::formatters::NSSetISyntheticFrontEnd::GetChildAtIndex(size_t idx) {
     auto ptr_size = process_sp->GetAddressByteSize();
     DataBufferHeap buffer(ptr_size, 0);
     switch (ptr_size) {
-    case 0: // architecture has no clue?? - fail
+    case 0: // architecture has no clue - fail
       return lldb::ValueObjectSP();
     case 4:
-      *((uint32_t *)buffer.GetBytes()) = (uint32_t)set_item.item_ptr;
+      *reinterpret_cast<uint32_t *>(buffer.GetBytes()) =
+          static_cast<uint32_t>(set_item.item_ptr);
       break;
     case 8:
-      *((uint64_t *)buffer.GetBytes()) = (uint64_t)set_item.item_ptr;
+      *reinterpret_cast<uint64_t *>(buffer.GetBytes()) =
+          static_cast<uint64_t>(set_item.item_ptr);
       break;
     default:
-      assert(false && "pointer size is not 4 nor 8 - get out of here ASAP");
+      lldbassert(false && "pointer size is not 4 nor 8");
     }
     StreamString idx_name;
     idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
@@ -501,6 +548,128 @@ lldb_private::formatters::NSSetISyntheticFrontEnd::GetChildAtIndex(size_t idx) {
   return set_item.valobj_sp;
 }
 
+lldb_private::formatters::NSCFSetSyntheticFrontEnd::NSCFSetSyntheticFrontEnd(
+    lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp), m_exe_ctx_ref(), m_ptr_size(8),
+      m_order(lldb::eByteOrderInvalid), m_hashtable(), m_pair_type() {}
+
+size_t
+lldb_private::formatters::NSCFSetSyntheticFrontEnd::GetIndexOfChildWithName(
+    ConstString name) {
+  const char *item_name = name.GetCString();
+  const uint32_t idx = ExtractIndexFromString(item_name);
+  if (idx < UINT32_MAX && idx >= CalculateNumChildren())
+    return UINT32_MAX;
+  return idx;
+}
+
+size_t
+lldb_private::formatters::NSCFSetSyntheticFrontEnd::CalculateNumChildren() {
+  if (!m_hashtable.IsValid())
+    return 0;
+  return m_hashtable.GetCount();
+}
+
+bool lldb_private::formatters::NSCFSetSyntheticFrontEnd::Update() {
+  m_children.clear();
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  m_ptr_size = 0;
+  if (!valobj_sp)
+    return false;
+  m_exe_ctx_ref = valobj_sp->GetExecutionContextRef();
+
+  lldb::ProcessSP process_sp(valobj_sp->GetProcessSP());
+  if (!process_sp)
+    return false;
+  m_ptr_size = process_sp->GetAddressByteSize();
+  m_order = process_sp->GetByteOrder();
+  return m_hashtable.Update(valobj_sp->GetValueAsUnsigned(0), m_exe_ctx_ref);
+}
+
+bool lldb_private::formatters::NSCFSetSyntheticFrontEnd::MightHaveChildren() {
+  return true;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::NSCFSetSyntheticFrontEnd::GetChildAtIndex(
+    size_t idx) {
+  lldb::addr_t m_values_ptr = m_hashtable.GetValuePointer();
+
+  const uint32_t num_children = CalculateNumChildren();
+
+  if (idx >= num_children)
+    return lldb::ValueObjectSP();
+
+  if (m_children.empty()) {
+    ProcessSP process_sp = m_exe_ctx_ref.GetProcessSP();
+    if (!process_sp)
+      return lldb::ValueObjectSP();
+
+    Status error;
+    lldb::addr_t val_at_idx = 0;
+
+    uint32_t tries = 0;
+    uint32_t test_idx = 0;
+
+    // Iterate over inferior memory, reading value pointers by shifting the
+    // cursor by test_index * m_ptr_size. Returns an empty ValueObject if a read
+    // fails, otherwise, continue until the number of tries matches the number
+    // of childen.
+    while (tries < num_children) {
+      val_at_idx = m_values_ptr + (test_idx * m_ptr_size);
+
+      val_at_idx = process_sp->ReadPointerFromMemory(val_at_idx, error);
+      if (error.Fail())
+        return lldb::ValueObjectSP();
+
+      test_idx++;
+
+      if (!val_at_idx)
+        continue;
+      tries++;
+
+      SetItemDescriptor descriptor = {val_at_idx, lldb::ValueObjectSP()};
+
+      m_children.push_back(descriptor);
+    }
+  }
+
+  if (idx >= m_children.size()) // should never happen
+    return lldb::ValueObjectSP();
+
+  SetItemDescriptor &set_item = m_children[idx];
+  if (!set_item.valobj_sp) {
+
+    DataBufferSP buffer_sp(new DataBufferHeap(m_ptr_size, 0));
+
+    switch (m_ptr_size) {
+    case 0: // architecture has no clue - fail
+      return lldb::ValueObjectSP();
+    case 4:
+      *reinterpret_cast<uint32_t *>(buffer_sp->GetBytes()) =
+          static_cast<uint32_t>(set_item.item_ptr);
+      break;
+    case 8:
+      *reinterpret_cast<uint64_t *>(buffer_sp->GetBytes()) =
+          static_cast<uint64_t>(set_item.item_ptr);
+      break;
+    default:
+      lldbassert(false && "pointer size is not 4 nor 8");
+    }
+    StreamString idx_name;
+    idx_name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+
+    DataExtractor data(buffer_sp, m_order, m_ptr_size);
+
+    set_item.valobj_sp = CreateValueObjectFromData(
+        idx_name.GetString(), data, m_exe_ctx_ref,
+        m_backend.GetCompilerType().GetBasicTypeFromAST(
+            lldb::eBasicTypeObjCID));
+  }
+
+  return set_item.valobj_sp;
+}
+
 template <typename D32, typename D64>
 lldb_private::formatters::
   GenericNSSetMSyntheticFrontEnd<D32, D64>::GenericNSSetMSyntheticFrontEnd(

diff  --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
index 6b2a5f845d73..4acb4c0d216a 100644
--- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
+++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
@@ -606,6 +606,11 @@ static void LoadObjCFormatters(TypeCategoryImplSP objc_category_sp) {
                   lldb_private::formatters::NSSetSyntheticFrontEndCreator,
                   "__NSSetM synthetic children", ConstString("__NSSetM"),
                   ScriptedSyntheticChildren::Flags());
+  AddCXXSynthetic(objc_category_sp,
+                  lldb_private::formatters::NSSetSyntheticFrontEndCreator,
+                  "__NSCFSet synthetic children", ConstString("__NSCFSet"),
+                  ScriptedSyntheticChildren::Flags());
+
   AddCXXSynthetic(
       objc_category_sp, lldb_private::formatters::NSSetSyntheticFrontEndCreator,
       "NSMutableSet synthetic children", ConstString("NSMutableSet"),

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSContainer.py b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSContainer.py
index 52170082238a..67a5dff6c2d5 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSContainer.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/TestDataFormatterObjCNSContainer.py
@@ -29,6 +29,8 @@ def nscontainers_data_formatter_commands(self):
                 ' 2 key/value pairs',
                 '(NSDictionary *) newDictionary = ',
                 ' 12 key/value pairs',
+                '(NSDictionary *) nscfDictionary = ',
+                ' 4 key/value pairs',
                 '(CFDictionaryRef) cfDictionaryRef = ',
                 ' 3 key/value pairs',
                 '(NSDictionary *) newMutableDictionary = ',
@@ -39,6 +41,36 @@ def nscontainers_data_formatter_commands(self):
                 ' @"11 elements"',
             ])
 
+        self.expect(
+            'frame variable -d run-target *nscfDictionary',
+            patterns=[
+                '\(__NSCFDictionary\) \*nscfDictionary =',
+                'key = 0x.* @"foo"',
+                'value = 0x.* @"foo"',
+                'key = 0x.* @"bar"',
+                'value = 0x.* @"bar"',
+                'key = 0x.* @"baz"',
+                'value = 0x.* @"baz"',
+                'key = 0x.* @"quux"',
+                'value = 0x.* @"quux"',
+                ])
+
+
+        self.expect(
+          'frame var nscfSet',
+          substrs=[
+          '(NSSet *) nscfSet = ',
+          '2 elements',
+          ])
+
+        self.expect(
+          'frame variable -d run-target *nscfSet',
+          patterns=[
+              '\(__NSCFSet\) \*nscfSet =',
+              '\[0\] = 0x.* @".*"',
+              '\[1\] = 0x.* @".*"',
+                    ])
+
         self.expect(
             'frame variable iset1 iset2 imset',
             substrs=['4 indexes', '512 indexes', '10 indexes'])

diff  --git a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/main.m b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/main.m
index 92a616f19121..d3507608ac9d 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-objc/main.m
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-objc/main.m
@@ -376,13 +376,19 @@ int main (int argc, const char * argv[])
 	    [newMutableDictionary setObject:@"foo" forKey:@"bar19"];
 	    [newMutableDictionary setObject:@"foo" forKey:@"bar20"];
 
-	    id cfKeys[2] = { @"foo", @"bar", @"baz", @"quux" };
-	    id cfValues[2] = { @"foo", @"bar", @"baz", @"quux" };
-	    NSDictionary *nsDictionary = CFBridgingRelease(CFDictionaryCreate(nil, (void *)cfKeys, (void *)cfValues, 2, nil, nil));
-	    CFDictionaryRef cfDictionaryRef = CFDictionaryCreate(nil, (void *)cfKeys, (void *)cfValues, 3, nil, nil);
-
-	    NSAttributedString* attrString = [[NSAttributedString alloc] initWithString:@"hello world from foo" attributes:newDictionary];
-	    [attrString isEqual:nil];
+            id cfKeys[4] = {@"foo", @"bar", @"baz", @"quux"};
+            id cfValues[4] = {@"foo", @"bar", @"baz", @"quux"};
+            NSDictionary *nsDictionary = CFBridgingRelease(CFDictionaryCreate(
+                nil, (void *)cfKeys, (void *)cfValues, 2, nil, nil));
+            NSDictionary *nscfDictionary = CFBridgingRelease(CFDictionaryCreate(
+                nil, (void *)cfKeys, (void *)cfValues, 4, nil, nil));
+            CFDictionaryRef cfDictionaryRef = CFDictionaryCreate(
+                nil, (void *)cfKeys, (void *)cfValues, 3, nil, nil);
+
+            NSAttributedString *attrString = [[NSAttributedString alloc]
+                initWithString:@"hello world from foo"
+                    attributes:newDictionary];
+            [attrString isEqual:nil];
 	    NSAttributedString* mutableAttrString = [[NSMutableAttributedString alloc] initWithString:@"hello world from foo" attributes:newDictionary];
 	    [mutableAttrString isEqual:nil];
 
@@ -411,9 +417,11 @@ int main (int argc, const char * argv[])
 
 	    NSSet* nsset = [[NSSet alloc] initWithObjects:str1,str2,str3,nil];
 	    NSSet *nsmutableset = [[NSMutableSet alloc] initWithObjects:str1,str2,str3,nil];
-	    [nsmutableset addObject:str4];
+            [nsmutableset addObject:str4];
+            NSSet *nscfSet =
+                CFBridgingRelease(CFSetCreate(nil, (void *)cfValues, 2, nil));
 
-	    CFDataRef data_ref = CFDataCreate(kCFAllocatorDefault, [immutableData bytes], 5);
+            CFDataRef data_ref = CFDataCreate(kCFAllocatorDefault, [immutableData bytes], 5);
 
 	    CFMutableDataRef mutable_data_ref = CFDataCreateMutable(kCFAllocatorDefault, 8);
 	    CFDataAppendBytes(mutable_data_ref, [mutableData bytes], 5);


        


More information about the lldb-commits mailing list