xref: /freebsd/contrib/llvm-project/lldb/source/Plugins/Language/ObjC/NSString.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
1 //===-- NSString.cpp ------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "NSString.h"
10 
11 #include "lldb/DataFormatters/FormattersHelpers.h"
12 #include "lldb/DataFormatters/StringPrinter.h"
13 #include "lldb/Target/Language.h"
14 #include "lldb/Target/Target.h"
15 #include "lldb/Utility/ConstString.h"
16 #include "lldb/Utility/DataBufferHeap.h"
17 #include "lldb/Utility/Endian.h"
18 #include "lldb/Utility/Status.h"
19 #include "lldb/Utility/Stream.h"
20 #include "lldb/ValueObject/ValueObject.h"
21 #include "lldb/ValueObject/ValueObjectConstResult.h"
22 
23 using namespace lldb;
24 using namespace lldb_private;
25 using namespace lldb_private::formatters;
26 
27 std::map<ConstString, CXXFunctionSummaryFormat::Callback> &
GetAdditionalSummaries()28 NSString_Additionals::GetAdditionalSummaries() {
29   static std::map<ConstString, CXXFunctionSummaryFormat::Callback> g_map;
30   return g_map;
31 }
32 
NSStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & summary_options)33 bool lldb_private::formatters::NSStringSummaryProvider(
34     ValueObject &valobj, Stream &stream,
35     const TypeSummaryOptions &summary_options) {
36   static constexpr llvm::StringLiteral g_TypeHint("NSString");
37 
38   ProcessSP process_sp = valobj.GetProcessSP();
39   if (!process_sp)
40     return false;
41 
42   ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(*process_sp);
43 
44   if (!runtime)
45     return false;
46 
47   ObjCLanguageRuntime::ClassDescriptorSP descriptor(
48       runtime->GetClassDescriptor(valobj));
49 
50   if (!descriptor.get() || !descriptor->IsValid())
51     return false;
52 
53   uint32_t ptr_size = process_sp->GetAddressByteSize();
54 
55   lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(0);
56 
57   if (!valobj_addr)
58     return false;
59 
60   ConstString class_name_cs = descriptor->GetClassName();
61   llvm::StringRef class_name = class_name_cs.GetStringRef();
62 
63   if (class_name.empty())
64     return false;
65 
66   // For tagged pointers, the descriptor has everything needed.
67   bool is_tagged = descriptor->GetTaggedPointerInfo();
68   if (is_tagged) {
69     if (class_name == "NSTaggedPointerString")
70       return NSTaggedString_SummaryProvider(valobj, descriptor, stream,
71                                             summary_options);
72 
73     if (class_name == "NSIndirectTaggedPointerString")
74       return NSIndirectTaggedString_SummaryProvider(valobj, descriptor, stream,
75                                                     summary_options);
76   }
77 
78   auto &additionals_map(NSString_Additionals::GetAdditionalSummaries());
79   auto iter = additionals_map.find(class_name_cs), end = additionals_map.end();
80   if (iter != end)
81     return iter->second(valobj, stream, summary_options);
82 
83   // if not a tagged pointer that we know about, try the normal route
84   uint64_t info_bits_location = valobj_addr + ptr_size;
85   if (process_sp->GetByteOrder() != lldb::eByteOrderLittle)
86     info_bits_location += 3;
87 
88   Status error;
89 
90   uint8_t info_bits = process_sp->ReadUnsignedIntegerFromMemory(
91       info_bits_location, 1, 0, error);
92   if (error.Fail())
93     return false;
94 
95   bool is_mutable = (info_bits & 1) == 1;
96   bool is_inline = (info_bits & 0x60) == 0;
97   bool has_explicit_length = (info_bits & (1 | 4)) != 4;
98   bool is_unicode = (info_bits & 0x10) == 0x10;
99   bool is_path_store = class_name == "NSPathStore2";
100   bool has_null = (info_bits & 8) == 8;
101 
102   size_t explicit_length = 0;
103   if (!has_null && has_explicit_length && !is_path_store) {
104     lldb::addr_t explicit_length_offset = 2 * ptr_size;
105     if (is_mutable && !is_inline)
106       explicit_length_offset =
107           explicit_length_offset + ptr_size; //  notInlineMutable.length;
108     else if (is_inline)
109       explicit_length = explicit_length + 0; // inline1.length;
110     else if (!is_inline && !is_mutable)
111       explicit_length_offset =
112           explicit_length_offset + ptr_size; // notInlineImmutable1.length;
113     else
114       explicit_length_offset = 0;
115 
116     if (explicit_length_offset) {
117       explicit_length_offset = valobj_addr + explicit_length_offset;
118       explicit_length = process_sp->ReadUnsignedIntegerFromMemory(
119           explicit_length_offset, 4, 0, error);
120     }
121   }
122 
123   const llvm::StringSet<> supported_string_classes = {
124       "NSString",     "CFMutableStringRef",
125       "CFStringRef",  "__NSCFConstantString",
126       "__NSCFString", "NSCFConstantString",
127       "NSCFString",   "NSPathStore2"};
128   if (supported_string_classes.count(class_name) == 0) {
129     // not one of us - but tell me class name
130     stream.Printf("class name = %s", class_name_cs.GetCString());
131     return true;
132   }
133 
134   llvm::StringRef prefix, suffix;
135   if (Language *language = Language::FindPlugin(summary_options.GetLanguage()))
136     std::tie(prefix, suffix) = language->GetFormatterPrefixSuffix(g_TypeHint);
137 
138   StringPrinter::ReadStringAndDumpToStreamOptions options(valobj);
139   options.SetPrefixToken(prefix.str());
140   options.SetSuffixToken(suffix.str());
141 
142   if (is_mutable) {
143     uint64_t location = 2 * ptr_size + valobj_addr;
144     location = process_sp->ReadPointerFromMemory(location, error);
145     if (error.Fail())
146       return false;
147     if (has_explicit_length && is_unicode) {
148       options.SetLocation(location);
149       options.SetTargetSP(valobj.GetTargetSP());
150       options.SetStream(&stream);
151       options.SetQuote('"');
152       options.SetSourceSize(explicit_length);
153       options.SetHasSourceSize(has_explicit_length);
154       options.SetNeedsZeroTermination(false);
155       options.SetIgnoreMaxLength(summary_options.GetCapping() ==
156                                  TypeSummaryCapping::eTypeSummaryUncapped);
157       options.SetBinaryZeroIsTerminator(false);
158       return StringPrinter::ReadStringAndDumpToStream<
159           StringPrinter::StringElementType::UTF16>(options);
160     } else {
161       options.SetLocation(location + 1);
162       options.SetTargetSP(valobj.GetTargetSP());
163       options.SetStream(&stream);
164       options.SetSourceSize(explicit_length);
165       options.SetHasSourceSize(has_explicit_length);
166       options.SetNeedsZeroTermination(false);
167       options.SetIgnoreMaxLength(summary_options.GetCapping() ==
168                                  TypeSummaryCapping::eTypeSummaryUncapped);
169       options.SetBinaryZeroIsTerminator(false);
170       return StringPrinter::ReadStringAndDumpToStream<
171           StringPrinter::StringElementType::ASCII>(options);
172     }
173   } else if (is_inline && has_explicit_length && !is_unicode &&
174              !is_path_store && !is_mutable) {
175     uint64_t location = 3 * ptr_size + valobj_addr;
176 
177     options.SetLocation(location);
178     options.SetTargetSP(valobj.GetTargetSP());
179     options.SetStream(&stream);
180     options.SetQuote('"');
181     options.SetSourceSize(explicit_length);
182     options.SetHasSourceSize(has_explicit_length);
183     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
184                                TypeSummaryCapping::eTypeSummaryUncapped);
185     return StringPrinter::ReadStringAndDumpToStream<
186         StringPrinter::StringElementType::ASCII>(options);
187   } else if (is_unicode) {
188     uint64_t location = valobj_addr + 2 * ptr_size;
189     if (is_inline) {
190       if (!has_explicit_length) {
191         return false;
192       } else
193         location += ptr_size;
194     } else {
195       location = process_sp->ReadPointerFromMemory(location, error);
196       if (error.Fail())
197         return false;
198     }
199     options.SetLocation(location);
200     options.SetTargetSP(valobj.GetTargetSP());
201     options.SetStream(&stream);
202     options.SetQuote('"');
203     options.SetSourceSize(explicit_length);
204     options.SetHasSourceSize(has_explicit_length);
205     options.SetNeedsZeroTermination(!has_explicit_length);
206     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
207                                TypeSummaryCapping::eTypeSummaryUncapped);
208     options.SetBinaryZeroIsTerminator(!has_explicit_length);
209     return StringPrinter::ReadStringAndDumpToStream<
210         StringPrinter::StringElementType::UTF16>(options);
211   } else if (is_path_store) {
212     // _lengthAndRefCount is the first ivar of NSPathStore2 (after the isa).
213     uint64_t length_ivar_offset = 1 * ptr_size;
214     CompilerType length_type = valobj.GetCompilerType().GetBasicTypeFromAST(
215         lldb::eBasicTypeUnsignedInt);
216     ValueObjectSP length_valobj_sp =
217         valobj.GetSyntheticChildAtOffset(length_ivar_offset, length_type, true,
218                                          ConstString("_lengthAndRefCount"));
219     if (!length_valobj_sp)
220       return false;
221     // Get the length out of _lengthAndRefCount.
222     explicit_length = length_valobj_sp->GetValueAsUnsigned(0) >> 20;
223     lldb::addr_t location = valobj.GetValueAsUnsigned(0) + ptr_size + 4;
224 
225     options.SetLocation(location);
226     options.SetTargetSP(valobj.GetTargetSP());
227     options.SetStream(&stream);
228     options.SetQuote('"');
229     options.SetSourceSize(explicit_length);
230     options.SetHasSourceSize(has_explicit_length);
231     options.SetNeedsZeroTermination(!has_explicit_length);
232     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
233                                TypeSummaryCapping::eTypeSummaryUncapped);
234     options.SetBinaryZeroIsTerminator(!has_explicit_length);
235     return StringPrinter::ReadStringAndDumpToStream<
236         StringPrinter::StringElementType::UTF16>(options);
237   } else if (is_inline) {
238     uint64_t location = valobj_addr + 2 * ptr_size;
239     if (!has_explicit_length) {
240       // in this kind of string, the byte before the string content is a length
241       // byte so let's try and use it to handle the embedded NUL case
242       Status error;
243       explicit_length =
244           process_sp->ReadUnsignedIntegerFromMemory(location, 1, 0, error);
245       has_explicit_length = !(error.Fail() || explicit_length == 0);
246       location++;
247     }
248     options.SetLocation(location);
249     options.SetTargetSP(valobj.GetTargetSP());
250     options.SetStream(&stream);
251     options.SetSourceSize(explicit_length);
252     options.SetHasSourceSize(has_explicit_length);
253     options.SetNeedsZeroTermination(!has_explicit_length);
254     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
255                                TypeSummaryCapping::eTypeSummaryUncapped);
256     options.SetBinaryZeroIsTerminator(!has_explicit_length);
257     if (has_explicit_length)
258       return StringPrinter::ReadStringAndDumpToStream<
259           StringPrinter::StringElementType::UTF8>(options);
260     else
261       return StringPrinter::ReadStringAndDumpToStream<
262           StringPrinter::StringElementType::ASCII>(options);
263   } else {
264     uint64_t location = valobj_addr + 2 * ptr_size;
265     location = process_sp->ReadPointerFromMemory(location, error);
266     if (error.Fail())
267       return false;
268     if (has_explicit_length && !has_null)
269       explicit_length++; // account for the fact that there is no NULL and we
270                          // need to have one added
271     options.SetLocation(location);
272     options.SetTargetSP(valobj.GetTargetSP());
273     options.SetStream(&stream);
274     options.SetSourceSize(explicit_length);
275     options.SetHasSourceSize(has_explicit_length);
276     options.SetIgnoreMaxLength(summary_options.GetCapping() ==
277                                TypeSummaryCapping::eTypeSummaryUncapped);
278     return StringPrinter::ReadStringAndDumpToStream<
279         StringPrinter::StringElementType::ASCII>(options);
280   }
281 }
282 
NSAttributedStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & options)283 bool lldb_private::formatters::NSAttributedStringSummaryProvider(
284     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
285   TargetSP target_sp(valobj.GetTargetSP());
286   if (!target_sp)
287     return false;
288   uint32_t addr_size = target_sp->GetArchitecture().GetAddressByteSize();
289   uint64_t pointer_value = valobj.GetValueAsUnsigned(0);
290   if (!pointer_value)
291     return false;
292   pointer_value += addr_size;
293   CompilerType type(valobj.GetCompilerType());
294   ExecutionContext exe_ctx(target_sp, false);
295   ValueObjectSP child_ptr_sp(valobj.CreateValueObjectFromAddress(
296       "string_ptr", pointer_value, exe_ctx, type));
297   if (!child_ptr_sp)
298     return false;
299   DataExtractor data;
300   Status error;
301   child_ptr_sp->GetData(data, error);
302   if (error.Fail())
303     return false;
304   ValueObjectSP child_sp(child_ptr_sp->CreateValueObjectFromData(
305       "string_data", data, exe_ctx, type));
306   child_sp->GetValueAsUnsigned(0);
307   if (child_sp)
308     return NSStringSummaryProvider(*child_sp, stream, options);
309   return false;
310 }
311 
NSMutableAttributedStringSummaryProvider(ValueObject & valobj,Stream & stream,const TypeSummaryOptions & options)312 bool lldb_private::formatters::NSMutableAttributedStringSummaryProvider(
313     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
314   return NSAttributedStringSummaryProvider(valobj, stream, options);
315 }
316 
NSTaggedString_SummaryProvider(ValueObject & valobj,ObjCLanguageRuntime::ClassDescriptorSP descriptor,Stream & stream,const TypeSummaryOptions & summary_options)317 bool lldb_private::formatters::NSTaggedString_SummaryProvider(
318     ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
319     Stream &stream, const TypeSummaryOptions &summary_options) {
320   static constexpr llvm::StringLiteral g_TypeHint("NSString");
321 
322   if (!descriptor)
323     return false;
324   uint64_t len_bits = 0, data_bits = 0;
325   if (!descriptor->GetTaggedPointerInfo(&len_bits, &data_bits, nullptr))
326     return false;
327 
328   static const int g_MaxNonBitmaskedLen = 7; // TAGGED_STRING_UNPACKED_MAXLEN
329   static const int g_SixbitMaxLen = 9;
330   static const int g_fiveBitMaxLen = 11;
331 
332   static const char *sixBitToCharLookup = "eilotrm.apdnsIc ufkMShjTRxgC4013"
333                                           "bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX";
334 
335   if (len_bits > g_fiveBitMaxLen)
336     return false;
337 
338   llvm::StringRef prefix, suffix;
339   if (Language *language = Language::FindPlugin(summary_options.GetLanguage()))
340     std::tie(prefix, suffix) = language->GetFormatterPrefixSuffix(g_TypeHint);
341 
342   // this is a fairly ugly trick - pretend that the numeric value is actually a
343   // char* this works under a few assumptions: little endian architecture
344   // sizeof(uint64_t) > g_MaxNonBitmaskedLen
345   if (len_bits <= g_MaxNonBitmaskedLen) {
346     stream << prefix;
347     stream.Printf("\"%s\"", (const char *)&data_bits);
348     stream << suffix;
349     return true;
350   }
351 
352   // if the data is bitmasked, we need to actually process the bytes
353   uint8_t bitmask = 0;
354   uint8_t shift_offset = 0;
355 
356   if (len_bits <= g_SixbitMaxLen) {
357     bitmask = 0x03f;
358     shift_offset = 6;
359   } else {
360     bitmask = 0x01f;
361     shift_offset = 5;
362   }
363 
364   std::vector<uint8_t> bytes;
365   bytes.resize(len_bits);
366   for (; len_bits > 0; data_bits >>= shift_offset, --len_bits) {
367     uint8_t packed = data_bits & bitmask;
368     bytes.insert(bytes.begin(), sixBitToCharLookup[packed]);
369   }
370 
371   stream << prefix;
372   stream.Printf("\"%s\"", &bytes[0]);
373   stream << suffix;
374   return true;
375 }
376 
NSIndirectTaggedString_SummaryProvider(ValueObject & valobj,ObjCLanguageRuntime::ClassDescriptorSP descriptor,Stream & stream,const TypeSummaryOptions & summary_options)377 bool lldb_private::formatters::NSIndirectTaggedString_SummaryProvider(
378     ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor,
379     Stream &stream, const TypeSummaryOptions &summary_options) {
380   if (!descriptor)
381     return false;
382 
383   uint64_t payload = 0;
384   if (!descriptor->GetTaggedPointerInfo(nullptr, nullptr, &payload))
385     return false;
386 
387   // First 47 bits are the address of the contents.
388   addr_t ptr = payload & 0x7fffffffffffULL;
389   // Next 13 bits are the string's length.
390   size_t size = (payload >> 47) & 0x1fff;
391 
392   Status status;
393   std::vector<char> buf(size);
394   if (auto process_sp = valobj.GetProcessSP())
395     if (process_sp->ReadMemory(ptr, buf.data(), size, status)) {
396       llvm::StringRef prefix, suffix;
397       if (auto *language = Language::FindPlugin(summary_options.GetLanguage()))
398         std::tie(prefix, suffix) =
399             language->GetFormatterPrefixSuffix("NSString");
400       stream << prefix << '"';
401       stream.PutCString({buf.data(), size});
402       stream << '"' << suffix;
403       return true;
404     }
405 
406   if (status.Fail())
407     stream.Format("<{0}>", status);
408   return false;
409 }
410