Repository: llvm/llvm-project
Pull request #136025: [lldb] Add summary for NSIndirectTaggedPointerString
Author: login: kastiglione
Labels: name: lldb
Created: 2025-04-16T20:35:48Z | Merged: 2025-04-17T00:31:01Z

rdar://143164164


Diff:
diff --git a/lldb/source/Plugins/Language/ObjC/NSString.cpp b/lldb/source/Plugins/Language/ObjC/NSString.cpp
index a99d042572bfe..2626b9a3f7b8a 100644
--- a/lldb/source/Plugins/Language/ObjC/NSString.cpp
+++ b/lldb/source/Plugins/Language/ObjC/NSString.cpp
@@ -63,12 +63,17 @@ bool lldb_private::formatters::NSStringSummaryProvider(
   if (class_name.empty())
     return false;
 
-  bool is_tagged_ptr = class_name == "NSTaggedPointerString" &&
-                       descriptor->GetTaggedPointerInfo();
-  // for a tagged pointer, the descriptor has everything we need
-  if (is_tagged_ptr)
-    return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
-                                          summary_options);
+  // For tagged pointers, the descriptor has everything needed.
+  bool is_tagged = descriptor->GetTaggedPointerInfo();
+  if (is_tagged) {
+    if (class_name == "NSTaggedPointerString")
+      return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
+                                            summary_options);
+
+    if (class_name == "NSIndirectTaggedPointerString")
+      return NSIndirectTaggedString_SummaryProvider(valobj, descriptor, stream,
+                                                    summary_options);
+  }
 
   auto &additionals_map(NSString_Additionals::GetAdditionalSummaries());
   auto iter = additionals_map.find(class_name_cs), end = additionals_map.end();
@@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  std::vector<char> buf(size);
+  if (auto process_sp = valobj.GetProcessSP())
+    if (process_sp->ReadMemory(ptr, buf.data(), size, status)) {
+      llvm::StringRef prefix, suffix;
+      if (auto *language = Language::FindPlugin(summary_options.GetLanguage()))
+        std::tie(prefix, suffix) =
+            language->GetFormatterPrefixSuffix("NSString");
+      stream << prefix << '"';
+      stream.PutCString({buf.data(), size});
+      stream << '"' << suffix;
+      return true;
+    }
+
+  if (status.Fail())
+    stream.Format("<{0}>", status);
+  return false;
+}
diff --git a/lldb/source/Plugins/Language/ObjC/NSString.h b/lldb/source/Plugins/Language/ObjC/NSString.h
index 8c9fcf955f1f8..5d405b30b6817 100644
--- a/lldb/source/Plugins/Language/ObjC/NSString.h
+++ b/lldb/source/Plugins/Language/ObjC/NSString.h
@@ -25,6 +25,10 @@ bool NSTaggedString_SummaryProvider(
     ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
     Stream &stream, const TypeSummaryOptions &summary_options);
 
+bool NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options);
+
 bool NSAttributedStringSummaryProvider(ValueObject &valobj, Stream &stream,
                                        const TypeSummaryOptions &options);
 
diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
index c835b439a64dd..3b8e21cbb9269 100644
--- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
+++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
@@ -691,6 +691,10 @@ static void LoadObjCFormatters(TypeCategoryImplSP objc_category_sp) {
   AddCXXSummary(
       objc_category_sp, lldb_private::formatters::NSStringSummaryProvider,
       "NSString summary provider", "NSTaggedPointerString", appkit_flags);
+  AddCXXSummary(objc_category_sp,
+                lldb_private::formatters::NSStringSummaryProvider,
+                "NSString summary provider", "NSIndirectTaggedPointerString",
+                appkit_flags);
 
   AddCXXSummary(objc_category_sp,
                 lldb_private::formatters::NSAttributedStringSummaryProvider,
diff --git a/lldb/test/API/lang/objc/foundation/tagged/strings/Makefile b/lldb/test/API/lang/objc/foundation/tagged/strings/Makefile
new file mode 100644
index 0000000000000..a3198db9e8e88
--- /dev/null
+++ b/lldb/test/API/lang/objc/foundation/tagged/strings/Makefile
@@ -0,0 +1,3 @@
+OBJC_SOURCES := main.m
+LD_EXTRAS := -framework Foundation
+include Makefile.rules
diff --git a/lldb/test/API/lang/objc/foundation/tagged/strings/TestObjCTaggedStrings.py b/lldb/test/API/lang/objc/foundation/tagged/strings/TestObjCTaggedStrings.py
new file mode 100644
index 0000000000000..66dc895b6a9ed
--- /dev/null
+++ b/lldb/test/API/lang/objc/foundation/tagged/strings/TestObjCTaggedStrings.py
@@ -0,0 +1,12 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+    def test(self):
+        """Verify summary formatter for tagged strings."""
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.m"))
+        self.expect("v str1 str2", patterns=['@"nineDigit"', '@"tenDigitXX"'])
diff --git a/lldb/test/API/lang/objc/foundation/tagged/strings/main.m b/lldb/test/API/lang/objc/foundation/tagged/strings/main.m
new file mode 100644
index 0000000000000..c2b5575986a99
--- /dev/null
+++ b/lldb/test/API/lang/objc/foundation/tagged/strings/main.m
@@ -0,0 +1,17 @@
+#import <Foundation/Foundation.h>
+
+@interface NSObject (Fake)
+// 9 digit selector
+- (void)nineDigit;
+// 10 digit selector
+- (void)tenDigitXX;
+@end
+
+int main() {
+  SEL sel1 = @selector(nineDigit);
+  NSString *str1 = NSStringFromSelector(sel1);
+  SEL sel2 = @selector(tenDigitXX);
+  NSString *str2 = NSStringFromSelector(sel2);
+  NSLog(@"break here %@, %@", str1, str2);
+  return 0;
+}


Reviews:
user: login: adrian-prantl
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-16T22:31:26Z
user: login: kastiglione
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T22:53:54Z
user: login: adrian-prantl
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T23:56:37Z
user: login: kastiglione
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T00:29:57Z

Review comments:
diff_hunk: @@ -63,12 +63,17 @@ bool lldb_private::formatters::NSStringSummaryProvider(
   if (class_name.empty())
     return false;
 
-  bool is_tagged_ptr = class_name == "NSTaggedPointerString" &&
-                       descriptor->GetTaggedPointerInfo();
-  // for a tagged pointer, the descriptor has everything we need
-  if (is_tagged_ptr)
-    return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
-                                          summary_options);
+  // for tagged pointers, the descriptor has everything needed.
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: adrian-prantl
body: ```suggestion
  // For tagged pointers, the descriptor has everything needed.
```
created_at: 2025-04-16T22:26:55Z
author_association: COLLABORATOR
diff_hunk: @@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  char buf[8192];
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: adrian-prantl
body: Given how deeply nested lldb stacks often are, I would probably use a std::vector<char> here and allocate this on the heap.
created_at: 2025-04-16T22:29:26Z
author_association: COLLABORATOR
diff_hunk: @@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  char buf[8192];
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: adrian-prantl
body: and then you can `reserve()` exactly `size`
created_at: 2025-04-16T22:30:53Z
author_association: COLLABORATOR
diff_hunk: @@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  char buf[8192];
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: kastiglione
body: good call, done
created_at: 2025-04-16T22:53:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  std::vector<char> buf(size);
+  if (auto process_sp = valobj.GetProcessSP())
+    if (process_sp->ReadMemory(ptr, buf.data(), size, status)) {
+      llvm::StringRef prefix, suffix;
+      if (auto *language = Language::FindPlugin(summary_options.GetLanguage()))
+        std::tie(prefix, suffix) =
+            language->GetFormatterPrefixSuffix("NSString");
+      stream << prefix << '"';
+      stream.PutCString({buf.data(), size});
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: adrian-prantl
body: Does PutCString scan through the buffer until it finds a `NUL` byte or does it copy `size` bytes (which would be faster).

I doubt this affects the performance measurably. Just being curious.
created_at: 2025-04-16T23:56:37Z
author_association: COLLABORATOR
diff_hunk: @@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  std::vector<char> buf(size);
+  if (auto process_sp = valobj.GetProcessSP())
+    if (process_sp->ReadMemory(ptr, buf.data(), size, status)) {
+      llvm::StringRef prefix, suffix;
+      if (auto *language = Language::FindPlugin(summary_options.GetLanguage()))
+        std::tie(prefix, suffix) =
+            language->GetFormatterPrefixSuffix("NSString");
+      stream << prefix << '"';
+      stream.PutCString({buf.data(), size});
path: lldb/source/Plugins/Language/ObjC/NSString.cpp
user: login: kastiglione
body: > or does it copy `size` bytes (which would be faster).

It does this, the faster option.
created_at: 2025-04-17T00:29:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: llvmbot
created_at: 2025-04-16T20:36:23Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Dave Lee (kastiglione)

<details>
<summary>Changes</summary>



---
Full diff: https://github.com/llvm/llvm-project/pull/136025.diff


3 Files Affected:

- (modified) lldb/source/Plugins/Language/ObjC/NSString.cpp (+45-6) 
- (modified) lldb/source/Plugins/Language/ObjC/NSString.h (+4) 
- (modified) lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp (+4) 


``````````diff
diff --git a/lldb/source/Plugins/Language/ObjC/NSString.cpp b/lldb/source/Plugins/Language/ObjC/NSString.cpp
index a99d042572bfe..11b4c6c5df8af 100644
--- a/lldb/source/Plugins/Language/ObjC/NSString.cpp
+++ b/lldb/source/Plugins/Language/ObjC/NSString.cpp
@@ -63,12 +63,17 @@ bool lldb_private::formatters::NSStringSummaryProvider(
   if (class_name.empty())
     return false;
 
-  bool is_tagged_ptr = class_name == "NSTaggedPointerString" &&
-                       descriptor->GetTaggedPointerInfo();
-  // for a tagged pointer, the descriptor has everything we need
-  if (is_tagged_ptr)
-    return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
-                                          summary_options);
+  // for tagged pointers, the descriptor has everything needed.
+  bool is_tagged = descriptor->GetTaggedPointerInfo();
+  if (is_tagged) {
+    if (class_name == "NSTaggedPointerString")
+      return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
+                                            summary_options);
+
+    if (class_name == "NSIndirectTaggedPointerString")
+      return NSIndirectTaggedString_SummaryProvider(valobj, descriptor, stream,
+                                                    summary_options);
+  }
 
   auto &additionals_map(NSString_Additionals::GetAdditionalSummaries());
   auto iter = additionals_map.find(class_name_cs), end = additionals_map.end();
@@ -368,3 +373,37 @@ bool lldb_private::formatters::NSTaggedString_SummaryProvider(
   stream << suffix;
   return true;
 }
+
+bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options) {
+  if (!descriptor)
+    return false;
+
+  uint64_t payload = 0;
+  if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
+    return false;
+
+  // First 47 bits are the address of the contents.
+  addr_t ptr = payload & 0x7fffffffffffULL;
+  // Next 13 bits are the string's length.
+  size_t size = (payload >> 47) & 0x1fff;
+
+  Status status;
+  char buf[8192];
+  if (auto process_sp = valobj.GetProcessSP())
+    if (process_sp->ReadMemory(ptr, buf, size, status)) {
+      llvm::StringRef prefix, suffix;
+      if (auto *language = Language::FindPlugin(summary_options.GetLanguage()))
+        std::tie(prefix, suffix) =
+            language->GetFormatterPrefixSuffix("NSString");
+      stream << prefix << '"';
+      stream.PutCString({buf, size});
+      stream << '"' << suffix;
+      return true;
+    }
+
+  if (status.Fail())
+    stream.Format("<{0}>", status);
+  return false;
+}
diff --git a/lldb/source/Plugins/Language/ObjC/NSString.h b/lldb/source/Plugins/Language/ObjC/NSString.h
index 8c9fcf955f1f8..5d405b30b6817 100644
--- a/lldb/source/Plugins/Language/ObjC/NSString.h
+++ b/lldb/source/Plugins/Language/ObjC/NSString.h
@@ -25,6 +25,10 @@ bool NSTaggedString_SummaryProvider(
     ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
     Stream &stream, const TypeSummaryOptions &summary_options);
 
+bool NSIndirectTaggedString_SummaryProvider(
+    ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
+    Stream &stream, const TypeSummaryOptions &summary_options);
+
 bool NSAttributedStringSummaryProvider(ValueObject &valobj, Stream &stream,
                                        const TypeSummaryOptions &options);
 
diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
index c835b439a64dd..3b8e21cbb9269 100644
--- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
+++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
@@ -691,6 +691,10 @@ static void LoadObjCFormatters(TypeCategoryImplSP objc_category_sp) {
   AddCXXSummary(
       objc_category_sp, lldb_private::formatters::NSStringSummaryProvider,
       "NSString summary provider", "NSTaggedPointerString", appkit_flags);
+  AddCXXSummary(objc_category_sp,
+                lldb_private::formatters::NSStringSummaryProvider,
+                "NSString summary provider", "NSIndirectTaggedPointerString",
+                appkit_flags);
 
   AddCXXSummary(objc_category_sp,
                 lldb_private::formatters::NSAttributedStringSummaryProvider,

``````````

</details>

author_association: MEMBER
user: login: adrian-prantl
created_at: 2025-04-16T22:31:44Z
body: LGTM with some nits
author_association: COLLABORATOR