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> & |
28 | NSString_Additionals::GetAdditionalSummaries() { |
29 | static std::map<ConstString, CXXFunctionSummaryFormat::Callback> g_map; |
30 | return g_map; |
31 | } |
32 | |
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&: *process_sp); |
43 | |
44 | if (!runtime) |
45 | return false; |
46 | |
47 | ObjCLanguageRuntime::ClassDescriptorSP descriptor( |
48 | runtime->GetClassDescriptor(in_value&: 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(fail_value: 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(x: 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 | load_addr: info_bits_location, byte_size: 1, fail_value: 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 | load_addr: explicit_length_offset, byte_size: 4, fail_value: 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(Key: class_name) == 0) { |
129 | // not one of us - but tell me class name |
130 | stream.Printf(format: "class name = %s" , class_name_cs.GetCString()); |
131 | return true; |
132 | } |
133 | |
134 | llvm::StringRef prefix, suffix; |
135 | if (Language *language = Language::FindPlugin(language: summary_options.GetLanguage())) |
136 | std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: 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(vm_addr: 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(vm_addr: 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 | basic_type: lldb::eBasicTypeUnsignedInt); |
216 | ValueObjectSP length_valobj_sp = |
217 | valobj.GetSyntheticChildAtOffset(offset: length_ivar_offset, type: length_type, can_create: true, |
218 | name_const_str: ConstString("_lengthAndRefCount" )); |
219 | if (!length_valobj_sp) |
220 | return false; |
221 | // Get the length out of _lengthAndRefCount. |
222 | explicit_length = length_valobj_sp->GetValueAsUnsigned(fail_value: 0) >> 20; |
223 | lldb::addr_t location = valobj.GetValueAsUnsigned(fail_value: 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(load_addr: location, byte_size: 1, fail_value: 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(vm_addr: 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 | |
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(fail_value: 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 | name: "string_ptr" , address: 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 | name: "string_data" , data, exe_ctx, type)); |
306 | child_sp->GetValueAsUnsigned(fail_value: 0); |
307 | if (child_sp) |
308 | return NSStringSummaryProvider(valobj&: *child_sp, stream, summary_options: options); |
309 | return false; |
310 | } |
311 | |
312 | bool lldb_private::formatters::NSMutableAttributedStringSummaryProvider( |
313 | ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { |
314 | return NSAttributedStringSummaryProvider(valobj, stream, options); |
315 | } |
316 | |
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(info_bits: &len_bits, value_bits: &data_bits, payload: 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(language: summary_options.GetLanguage())) |
340 | std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: 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(format: "\"%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(new_size: len_bits); |
366 | for (; len_bits > 0; data_bits >>= shift_offset, --len_bits) { |
367 | uint8_t packed = data_bits & bitmask; |
368 | bytes.insert(position: bytes.begin(), x: sixBitToCharLookup[packed]); |
369 | } |
370 | |
371 | stream << prefix; |
372 | stream.Printf(format: "\"%s\"" , &bytes[0]); |
373 | stream << suffix; |
374 | return true; |
375 | } |
376 | |
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(info_bits: nullptr, value_bits: nullptr, payload: &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(vm_addr: ptr, buf: buf.data(), size, error&: status)) { |
396 | llvm::StringRef prefix, suffix; |
397 | if (auto *language = Language::FindPlugin(language: summary_options.GetLanguage())) |
398 | std::tie(args&: prefix, args&: suffix) = |
399 | language->GetFormatterPrefixSuffix(type_hint: "NSString" ); |
400 | stream << prefix << '"'; |
401 | stream.PutCString(cstr: {buf.data(), size}); |
402 | stream << '"' << suffix; |
403 | return true; |
404 | } |
405 | |
406 | if (status.Fail()) |
407 | stream.Format(format: "<{0}>" , args&: status); |
408 | return false; |
409 | } |
410 | |