1//===-- Cocoa.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 "Cocoa.h"
10#include "NSString.h"
11#include "ObjCConstants.h"
12
13#include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
14#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
15#include "lldb/Core/Mangled.h"
16#include "lldb/DataFormatters/FormattersHelpers.h"
17#include "lldb/DataFormatters/StringPrinter.h"
18#include "lldb/DataFormatters/TypeSummary.h"
19#include "lldb/Host/Time.h"
20#include "lldb/Target/Language.h"
21#include "lldb/Target/Process.h"
22#include "lldb/Target/Target.h"
23#include "lldb/Utility/DataBufferHeap.h"
24#include "lldb/Utility/Endian.h"
25#include "lldb/Utility/LLDBLog.h"
26#include "lldb/Utility/Status.h"
27#include "lldb/Utility/Stream.h"
28#include "lldb/ValueObject/ValueObject.h"
29#include "lldb/ValueObject/ValueObjectConstResult.h"
30
31#include "llvm/ADT/APInt.h"
32#include "llvm/ADT/bit.h"
33
34using namespace lldb;
35using namespace lldb_private;
36using namespace lldb_private::formatters;
37
38bool lldb_private::formatters::NSBundleSummaryProvider(
39 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
40 ProcessSP process_sp = valobj.GetProcessSP();
41 if (!process_sp)
42 return false;
43
44 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
45
46 if (!runtime)
47 return false;
48
49 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
50 runtime->GetClassDescriptor(in_value&: valobj));
51
52 if (!descriptor || !descriptor->IsValid())
53 return false;
54
55 uint32_t ptr_size = process_sp->GetAddressByteSize();
56
57 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
58
59 if (!valobj_addr)
60 return false;
61
62 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
63
64 if (class_name.empty())
65 return false;
66
67 if (class_name == "NSBundle") {
68 uint64_t offset = 5 * ptr_size;
69 ValueObjectSP text(valobj.GetSyntheticChildAtOffset(
70 offset,
71 type: valobj.GetCompilerType().GetBasicTypeFromAST(basic_type: lldb::eBasicTypeObjCID),
72 can_create: true));
73
74 if (!text)
75 return false;
76
77 StreamString summary_stream;
78 bool was_nsstring_ok =
79 NSStringSummaryProvider(valobj&: *text, stream&: summary_stream, options);
80 if (was_nsstring_ok && summary_stream.GetSize() > 0) {
81 stream.Printf(format: "%s", summary_stream.GetData());
82 return true;
83 }
84 }
85
86 return false;
87}
88
89bool lldb_private::formatters::NSTimeZoneSummaryProvider(
90 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
91 ProcessSP process_sp = valobj.GetProcessSP();
92 if (!process_sp)
93 return false;
94
95 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
96
97 if (!runtime)
98 return false;
99
100 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
101 runtime->GetClassDescriptor(in_value&: valobj));
102
103 if (!descriptor || !descriptor->IsValid())
104 return false;
105
106 uint32_t ptr_size = process_sp->GetAddressByteSize();
107
108 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
109
110 if (!valobj_addr)
111 return false;
112
113 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
114
115 if (class_name.empty())
116 return false;
117
118 if (class_name == "__NSTimeZone") {
119 uint64_t offset = ptr_size;
120 ValueObjectSP text(valobj.GetSyntheticChildAtOffset(
121 offset, type: valobj.GetCompilerType(), can_create: true));
122
123 if (!text)
124 return false;
125
126 StreamString summary_stream;
127 bool was_nsstring_ok =
128 NSStringSummaryProvider(valobj&: *text, stream&: summary_stream, options);
129 if (was_nsstring_ok && summary_stream.GetSize() > 0) {
130 stream.Printf(format: "%s", summary_stream.GetData());
131 return true;
132 }
133 }
134
135 return false;
136}
137
138bool lldb_private::formatters::NSNotificationSummaryProvider(
139 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
140 ProcessSP process_sp = valobj.GetProcessSP();
141 if (!process_sp)
142 return false;
143
144 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
145
146 if (!runtime)
147 return false;
148
149 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
150 runtime->GetClassDescriptor(in_value&: valobj));
151
152 if (!descriptor || !descriptor->IsValid())
153 return false;
154
155 uint32_t ptr_size = process_sp->GetAddressByteSize();
156
157 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
158
159 if (!valobj_addr)
160 return false;
161
162 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
163
164 if (class_name.empty())
165 return false;
166
167 if (class_name == "NSConcreteNotification") {
168 uint64_t offset = ptr_size;
169 ValueObjectSP text(valobj.GetSyntheticChildAtOffset(
170 offset, type: valobj.GetCompilerType(), can_create: true));
171
172 if (!text)
173 return false;
174
175 StreamString summary_stream;
176 bool was_nsstring_ok =
177 NSStringSummaryProvider(valobj&: *text, stream&: summary_stream, options);
178 if (was_nsstring_ok && summary_stream.GetSize() > 0) {
179 stream.Printf(format: "%s", summary_stream.GetData());
180 return true;
181 }
182 }
183
184 return false;
185}
186
187bool lldb_private::formatters::NSMachPortSummaryProvider(
188 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
189 ProcessSP process_sp = valobj.GetProcessSP();
190 if (!process_sp)
191 return false;
192
193 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
194
195 if (!runtime)
196 return false;
197
198 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
199 runtime->GetClassDescriptor(in_value&: valobj));
200
201 if (!descriptor || !descriptor->IsValid())
202 return false;
203
204 uint32_t ptr_size = process_sp->GetAddressByteSize();
205
206 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
207
208 if (!valobj_addr)
209 return false;
210
211 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
212
213 if (class_name.empty())
214 return false;
215
216 uint64_t port_number = 0;
217
218 if (class_name == "NSMachPort") {
219 uint64_t offset = (ptr_size == 4 ? 12 : 20);
220 Status error;
221 port_number = process_sp->ReadUnsignedIntegerFromMemory(
222 load_addr: offset + valobj_addr, byte_size: 4, fail_value: 0, error);
223 if (error.Success()) {
224 stream.Printf(format: "mach port: %u",
225 (uint32_t)(port_number & 0x00000000FFFFFFFF));
226 return true;
227 }
228 }
229
230 return false;
231}
232
233bool lldb_private::formatters::NSIndexSetSummaryProvider(
234 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
235 ProcessSP process_sp = valobj.GetProcessSP();
236 if (!process_sp)
237 return false;
238
239 AppleObjCRuntime *runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
240 Val: ObjCLanguageRuntime::Get(process&: *process_sp));
241
242 if (!runtime)
243 return false;
244
245 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
246 runtime->GetClassDescriptor(in_value&: valobj));
247
248 if (!descriptor || !descriptor->IsValid())
249 return false;
250
251 uint32_t ptr_size = process_sp->GetAddressByteSize();
252
253 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
254
255 if (!valobj_addr)
256 return false;
257
258 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
259
260 if (class_name.empty())
261 return false;
262
263 uint64_t count = 0;
264
265 do {
266 if (class_name == "NSIndexSet" || class_name == "NSMutableIndexSet") {
267 // Foundation version 2000 added a bitmask if the index set fit in 64 bits
268 // and a Tagged Pointer version if the bitmask is small enough to fit in
269 // the tagged pointer payload.
270 // It also changed the layout (but not the size) of the set descriptor.
271
272 // First check whether this is a tagged pointer. The bitmask will be in
273 // the payload of the tagged pointer.
274 uint64_t payload;
275 if (runtime->GetFoundationVersion() >= 2000 &&
276 descriptor->GetTaggedPointerInfo(info_bits: nullptr, value_bits: nullptr, payload: &payload)) {
277 count = llvm::popcount(Value: payload);
278 break;
279 }
280 // The first 32 bits describe the index set in all cases:
281 Status error;
282 uint32_t mode = process_sp->ReadUnsignedIntegerFromMemory(
283 load_addr: valobj_addr + ptr_size, byte_size: 4, fail_value: 0, error);
284 if (error.Fail())
285 return false;
286 // Now check if the index is held in a bitmask in the object:
287 if (runtime->GetFoundationVersion() >= 2000) {
288 // The first two bits are "isSingleRange" and "isBitfield". If this is
289 // a bitfield we handle it here, otherwise set mode appropriately and
290 // the rest of the treatment is in common.
291 if ((mode & 2) == 2) {
292 // The bitfield is a 64 bit uint at the beginning of the data var.
293 uint64_t bitfield = process_sp->ReadUnsignedIntegerFromMemory(
294 load_addr: valobj_addr + 2 * ptr_size, byte_size: 8, fail_value: 0, error);
295 if (error.Fail())
296 return false;
297 count = llvm::popcount(Value: bitfield);
298 break;
299 }
300 // It wasn't a bitfield, so read the isSingleRange from its new loc:
301 if ((mode & 1) == 1)
302 mode = 1; // this means the set only has one range
303 else
304 mode = 2; // this means the set has multiple ranges
305 } else {
306 // this means the set is empty - count = 0
307 if ((mode & 1) == 1) {
308 count = 0;
309 break;
310 }
311
312 if ((mode & 2) == 2)
313 mode = 1; // this means the set only has one range
314 else
315 mode = 2; // this means the set has multiple ranges
316 }
317 if (mode == 1) {
318 count = process_sp->ReadUnsignedIntegerFromMemory(
319 load_addr: valobj_addr + 3 * ptr_size, byte_size: ptr_size, fail_value: 0, error);
320 if (error.Fail())
321 return false;
322 } else {
323 // read a pointer to the data at 2*ptr_size
324 count = process_sp->ReadUnsignedIntegerFromMemory(
325 load_addr: valobj_addr + 2 * ptr_size, byte_size: ptr_size, fail_value: 0, error);
326 if (error.Fail())
327 return false;
328 // read the data at 2*ptr_size from the first location
329 count = process_sp->ReadUnsignedIntegerFromMemory(load_addr: count + 2 * ptr_size,
330 byte_size: ptr_size, fail_value: 0, error);
331 if (error.Fail())
332 return false;
333 }
334 } else
335 return false;
336 } while (false);
337 stream.Printf(format: "%" PRIu64 " index%s", count, (count == 1 ? "" : "es"));
338 return true;
339}
340
341static void NSNumber_FormatChar(ValueObject &valobj, Stream &stream, char value,
342 lldb::LanguageType lang) {
343 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:char");
344
345 llvm::StringRef prefix, suffix;
346 if (Language *language = Language::FindPlugin(language: lang))
347 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
348
349 stream << prefix;
350 stream.Printf(format: "%hhd", value);
351 stream << suffix;
352}
353
354static void NSNumber_FormatShort(ValueObject &valobj, Stream &stream,
355 short value, lldb::LanguageType lang) {
356 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:short");
357
358 llvm::StringRef prefix, suffix;
359 if (Language *language = Language::FindPlugin(language: lang))
360 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
361
362 stream << prefix;
363 stream.Printf(format: "%hd", value);
364 stream << suffix;
365}
366
367static void NSNumber_FormatInt(ValueObject &valobj, Stream &stream, int value,
368 lldb::LanguageType lang) {
369 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:int");
370
371 llvm::StringRef prefix, suffix;
372 if (Language *language = Language::FindPlugin(language: lang))
373 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
374
375 stream << prefix;
376 stream.Printf(format: "%d", value);
377 stream << suffix;
378}
379
380static void NSNumber_FormatLong(ValueObject &valobj, Stream &stream,
381 int64_t value, lldb::LanguageType lang) {
382 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:long");
383
384 llvm::StringRef prefix, suffix;
385 if (Language *language = Language::FindPlugin(language: lang))
386 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
387
388 stream << prefix;
389 stream.Printf(format: "%" PRId64 "", value);
390 stream << suffix;
391}
392
393static void NSNumber_FormatInt128(ValueObject &valobj, Stream &stream,
394 const llvm::APInt &value,
395 lldb::LanguageType lang) {
396 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:int128_t");
397
398 llvm::StringRef prefix, suffix;
399 if (Language *language = Language::FindPlugin(language: lang))
400 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
401
402 stream << prefix;
403 const int radix = 10;
404 const bool isSigned = true;
405 std::string str = llvm::toString(I: value, Radix: radix, Signed: isSigned);
406 stream.PutCString(cstr: str.c_str());
407 stream << suffix;
408}
409
410static void NSNumber_FormatFloat(ValueObject &valobj, Stream &stream,
411 float value, lldb::LanguageType lang) {
412 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:float");
413
414 llvm::StringRef prefix, suffix;
415 if (Language *language = Language::FindPlugin(language: lang))
416 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
417
418 stream << prefix;
419 stream.Printf(format: "%f", value);
420 stream << suffix;
421}
422
423static void NSNumber_FormatDouble(ValueObject &valobj, Stream &stream,
424 double value, lldb::LanguageType lang) {
425 static constexpr llvm::StringLiteral g_TypeHint("NSNumber:double");
426
427 llvm::StringRef prefix, suffix;
428 if (Language *language = Language::FindPlugin(language: lang))
429 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
430
431 stream << prefix;
432 stream.Printf(format: "%g", value);
433 stream << suffix;
434}
435
436bool lldb_private::formatters::NSNumberSummaryProvider(
437 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
438 ProcessSP process_sp = valobj.GetProcessSP();
439 if (!process_sp)
440 return false;
441
442 Log *log = GetLog(mask: LLDBLog::DataFormatters);
443 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
444
445 if (!runtime)
446 return false;
447
448 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
449 runtime->GetClassDescriptor(in_value&: valobj));
450
451 if (!descriptor || !descriptor->IsValid())
452 return false;
453
454 uint32_t ptr_size = process_sp->GetAddressByteSize();
455
456 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
457
458 if (!valobj_addr)
459 return false;
460
461 llvm::StringRef class_name(descriptor->GetClassName().GetCString());
462
463 if (class_name.empty())
464 return false;
465
466 if (class_name == "__NSCFBoolean")
467 return ObjCBooleanSummaryProvider(valobj, stream, options);
468
469 if (class_name == "NSDecimalNumber")
470 return NSDecimalNumberSummaryProvider(valobj, stream, options);
471
472 if (class_name == "NSConstantIntegerNumber") {
473 Status error;
474 int64_t value = process_sp->ReadSignedIntegerFromMemory(
475 load_addr: valobj_addr + 2 * ptr_size, byte_size: 8, fail_value: 0, error);
476 if (error.Fail())
477 return false;
478 uint64_t encoding_addr = process_sp->ReadUnsignedIntegerFromMemory(
479 load_addr: valobj_addr + ptr_size, byte_size: ptr_size, fail_value: 0, error);
480 if (error.Fail())
481 return false;
482 char encoding =
483 process_sp->ReadUnsignedIntegerFromMemory(load_addr: encoding_addr, byte_size: 1, fail_value: 0, error);
484 if (error.Fail())
485 return false;
486
487 switch (encoding) {
488 case _C_CHR:
489 NSNumber_FormatChar(valobj, stream, value: (char)value, lang: options.GetLanguage());
490 return true;
491 case _C_SHT:
492 NSNumber_FormatShort(valobj, stream, value: (short)value, lang: options.GetLanguage());
493 return true;
494 case _C_INT:
495 NSNumber_FormatInt(valobj, stream, value: (int)value, lang: options.GetLanguage());
496 return true;
497 case _C_LNG:
498 case _C_LNG_LNG:
499 NSNumber_FormatLong(valobj, stream, value, lang: options.GetLanguage());
500 return true;
501
502 case _C_UCHR:
503 case _C_USHT:
504 case _C_UINT:
505 case _C_ULNG:
506 case _C_ULNG_LNG:
507 stream.Printf(format: "%" PRIu64, value);
508 return true;
509 }
510
511 return false;
512 }
513
514 if (class_name == "NSConstantFloatNumber") {
515 Status error;
516 uint32_t flt_as_int = process_sp->ReadUnsignedIntegerFromMemory(
517 load_addr: valobj_addr + ptr_size, byte_size: 4, fail_value: 0, error);
518 if (error.Fail())
519 return false;
520 float flt_value = 0.0f;
521 memcpy(dest: &flt_value, src: &flt_as_int, n: sizeof(flt_as_int));
522 NSNumber_FormatFloat(valobj, stream, value: flt_value, lang: options.GetLanguage());
523 return true;
524 }
525
526 if (class_name == "NSConstantDoubleNumber") {
527 Status error;
528 uint64_t dbl_as_lng = process_sp->ReadUnsignedIntegerFromMemory(
529 load_addr: valobj_addr + ptr_size, byte_size: 8, fail_value: 0, error);
530 if (error.Fail())
531 return false;
532 double dbl_value = 0.0;
533 memcpy(dest: &dbl_value, src: &dbl_as_lng, n: sizeof(dbl_as_lng));
534 NSNumber_FormatDouble(valobj, stream, value: dbl_value, lang: options.GetLanguage());
535 return true;
536 }
537
538 if (class_name == "NSNumber" || class_name == "__NSCFNumber") {
539 int64_t value = 0;
540 uint64_t i_bits = 0;
541 if (descriptor->GetTaggedPointerInfoSigned(info_bits: &i_bits, value_bits: &value)) {
542 // Check for "preserved" numbers. We still don't support them yet.
543 if (i_bits & 0x8) {
544 if (log)
545 log->Printf(
546 format: "Unsupported (preserved) NSNumber tagged pointer 0x%" PRIu64,
547 valobj_addr);
548 return false;
549 }
550
551 switch (i_bits) {
552 case 0:
553 NSNumber_FormatChar(valobj, stream, value: (char)value, lang: options.GetLanguage());
554 break;
555 case 1:
556 case 4:
557 NSNumber_FormatShort(valobj, stream, value: (short)value,
558 lang: options.GetLanguage());
559 break;
560 case 2:
561 case 8:
562 NSNumber_FormatInt(valobj, stream, value: (int)value, lang: options.GetLanguage());
563 break;
564 case 3:
565 case 12:
566 NSNumber_FormatLong(valobj, stream, value, lang: options.GetLanguage());
567 break;
568 default:
569 return false;
570 }
571 return true;
572 } else {
573 Status error;
574
575 AppleObjCRuntime *runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
576 Val: ObjCLanguageRuntime::Get(process&: *process_sp));
577
578 const bool new_format =
579 (runtime && runtime->GetFoundationVersion() >= 1400);
580
581 enum class TypeCodes : int {
582 sint8 = 0x0,
583 sint16 = 0x1,
584 sint32 = 0x2,
585 sint64 = 0x3,
586 f32 = 0x4,
587 f64 = 0x5,
588 sint128 = 0x6
589 };
590
591 uint64_t data_location = valobj_addr + 2 * ptr_size;
592 TypeCodes type_code;
593
594 if (new_format) {
595 uint64_t cfinfoa = process_sp->ReadUnsignedIntegerFromMemory(
596 load_addr: valobj_addr + ptr_size, byte_size: ptr_size, fail_value: 0, error);
597
598 if (error.Fail())
599 return false;
600
601 bool is_preserved_number = cfinfoa & 0x8;
602 if (is_preserved_number) {
603 if (log)
604 log->Printf(
605 format: "Unsupported preserved NSNumber tagged pointer 0x%" PRIu64,
606 valobj_addr);
607 return false;
608 }
609
610 type_code = static_cast<TypeCodes>(cfinfoa & 0x7);
611 } else {
612 uint8_t data_type = process_sp->ReadUnsignedIntegerFromMemory(
613 load_addr: valobj_addr + ptr_size, byte_size: 1, fail_value: 0, error) &
614 0x1F;
615
616 if (error.Fail())
617 return false;
618
619 switch (data_type) {
620 case 1:
621 type_code = TypeCodes::sint8;
622 break;
623 case 2:
624 type_code = TypeCodes::sint16;
625 break;
626 case 3:
627 type_code = TypeCodes::sint32;
628 break;
629 case 17:
630 data_location += 8;
631 [[fallthrough]];
632 case 4:
633 type_code = TypeCodes::sint64;
634 break;
635 case 5:
636 type_code = TypeCodes::f32;
637 break;
638 case 6:
639 type_code = TypeCodes::f64;
640 break;
641 default:
642 return false;
643 }
644 }
645
646 uint64_t value = 0;
647 bool success = false;
648 switch (type_code) {
649 case TypeCodes::sint8:
650 value = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location, byte_size: 1, fail_value: 0,
651 error);
652 if (error.Fail())
653 return false;
654 NSNumber_FormatChar(valobj, stream, value: (char)value, lang: options.GetLanguage());
655 success = true;
656 break;
657 case TypeCodes::sint16:
658 value = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location, byte_size: 2, fail_value: 0,
659 error);
660 if (error.Fail())
661 return false;
662 NSNumber_FormatShort(valobj, stream, value: (short)value,
663 lang: options.GetLanguage());
664 success = true;
665 break;
666 case TypeCodes::sint32:
667 value = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location, byte_size: 4, fail_value: 0,
668 error);
669 if (error.Fail())
670 return false;
671 NSNumber_FormatInt(valobj, stream, value: (int)value, lang: options.GetLanguage());
672 success = true;
673 break;
674 case TypeCodes::sint64:
675 value = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location, byte_size: 8, fail_value: 0,
676 error);
677 if (error.Fail())
678 return false;
679 NSNumber_FormatLong(valobj, stream, value, lang: options.GetLanguage());
680 success = true;
681 break;
682 case TypeCodes::f32: {
683 uint32_t flt_as_int = process_sp->ReadUnsignedIntegerFromMemory(
684 load_addr: data_location, byte_size: 4, fail_value: 0, error);
685 if (error.Fail())
686 return false;
687 float flt_value = 0.0f;
688 memcpy(dest: &flt_value, src: &flt_as_int, n: sizeof(flt_as_int));
689 NSNumber_FormatFloat(valobj, stream, value: flt_value, lang: options.GetLanguage());
690 success = true;
691 break;
692 }
693 case TypeCodes::f64: {
694 uint64_t dbl_as_lng = process_sp->ReadUnsignedIntegerFromMemory(
695 load_addr: data_location, byte_size: 8, fail_value: 0, error);
696 if (error.Fail())
697 return false;
698 double dbl_value = 0.0;
699 memcpy(dest: &dbl_value, src: &dbl_as_lng, n: sizeof(dbl_as_lng));
700 NSNumber_FormatDouble(valobj, stream, value: dbl_value, lang: options.GetLanguage());
701 success = true;
702 break;
703 }
704 case TypeCodes::sint128: // internally, this is the same
705 {
706 uint64_t words[2];
707 words[1] = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location, byte_size: 8,
708 fail_value: 0, error);
709 if (error.Fail())
710 return false;
711 words[0] = process_sp->ReadUnsignedIntegerFromMemory(load_addr: data_location + 8,
712 byte_size: 8, fail_value: 0, error);
713 if (error.Fail())
714 return false;
715 llvm::APInt i128_value(128, words);
716 NSNumber_FormatInt128(valobj, stream, value: i128_value,
717 lang: options.GetLanguage());
718 success = true;
719 break;
720 }
721 }
722 return success;
723 }
724 }
725
726 return false;
727}
728
729bool lldb_private::formatters::NSDecimalNumberSummaryProvider(
730 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
731 ProcessSP process_sp = valobj.GetProcessSP();
732 if (!process_sp)
733 return false;
734
735 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
736 uint32_t ptr_size = process_sp->GetAddressByteSize();
737
738 Status error;
739 int8_t exponent = process_sp->ReadUnsignedIntegerFromMemory(
740 load_addr: valobj_addr + ptr_size, byte_size: 1, fail_value: 0, error);
741 if (error.Fail())
742 return false;
743
744 uint8_t length_and_negative = process_sp->ReadUnsignedIntegerFromMemory(
745 load_addr: valobj_addr + ptr_size + 1, byte_size: 1, fail_value: 0, error);
746 if (error.Fail())
747 return false;
748
749 // Fifth bit marks negativity.
750 const bool is_negative = (length_and_negative >> 4) & 1;
751
752 // Zero length and negative means NaN.
753 uint8_t length = length_and_negative & 0xf;
754 const bool is_nan = is_negative && (length == 0);
755
756 if (is_nan) {
757 stream.Printf(format: "NaN");
758 return true;
759 }
760
761 if (length == 0) {
762 stream.Printf(format: "0");
763 return true;
764 }
765
766 uint64_t mantissa = process_sp->ReadUnsignedIntegerFromMemory(
767 load_addr: valobj_addr + ptr_size + 4, byte_size: 8, fail_value: 0, error);
768 if (error.Fail())
769 return false;
770
771 if (is_negative)
772 stream.Printf(format: "-");
773
774 stream.Printf(format: "%" PRIu64 " x 10^%" PRIi8, mantissa, exponent);
775 return true;
776}
777
778bool lldb_private::formatters::NSURLSummaryProvider(
779 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
780 ProcessSP process_sp = valobj.GetProcessSP();
781 if (!process_sp)
782 return false;
783
784 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
785
786 if (!runtime)
787 return false;
788
789 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
790 runtime->GetClassDescriptor(in_value&: valobj));
791
792 if (!descriptor || !descriptor->IsValid())
793 return false;
794
795 uint32_t ptr_size = process_sp->GetAddressByteSize();
796
797 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
798
799 if (!valobj_addr)
800 return false;
801
802 llvm::StringRef class_name = descriptor->GetClassName().GetStringRef();
803
804 if (class_name != "NSURL")
805 return false;
806
807 uint64_t offset_text = ptr_size + ptr_size +
808 8; // ISA + pointer + 8 bytes of data (even on 32bit)
809 uint64_t offset_base = offset_text + ptr_size;
810 CompilerType type(valobj.GetCompilerType());
811 ValueObjectSP text(valobj.GetSyntheticChildAtOffset(offset: offset_text, type, can_create: true));
812 ValueObjectSP base(valobj.GetSyntheticChildAtOffset(offset: offset_base, type, can_create: true));
813 if (!text || text->GetValueAsUnsigned(fail_value: 0) == 0)
814 return false;
815
816 StreamString base_summary;
817 if (base && base->GetValueAsUnsigned(fail_value: 0)) {
818 if (!NSURLSummaryProvider(valobj&: *base, stream&: base_summary, options))
819 base_summary.Clear();
820 }
821 if (base_summary.Empty())
822 return NSStringSummaryProvider(valobj&: *text, stream, options);
823
824 StreamString summary;
825 if (!NSStringSummaryProvider(valobj&: *text, stream&: summary, options) || summary.Empty())
826 return false;
827
828 static constexpr llvm::StringLiteral quote_char("\"");
829 static constexpr llvm::StringLiteral g_TypeHint("NSString");
830 llvm::StringRef prefix, suffix;
831 if (Language *language = Language::FindPlugin(language: options.GetLanguage()))
832 std::tie(args&: prefix, args&: suffix) = language->GetFormatterPrefixSuffix(type_hint: g_TypeHint);
833
834 // @"A" -> @"A
835 llvm::StringRef summary_str = summary.GetString();
836 bool back_consumed =
837 summary_str.consume_back(Suffix: suffix) && summary_str.consume_back(Suffix: quote_char);
838 assert(back_consumed);
839 UNUSED_IF_ASSERT_DISABLED(back_consumed);
840 // @"B" -> B"
841 llvm::StringRef base_summary_str = base_summary.GetString();
842 bool front_consumed = base_summary_str.consume_front(Prefix: prefix) &&
843 base_summary_str.consume_front(Prefix: quote_char);
844 assert(front_consumed);
845 UNUSED_IF_ASSERT_DISABLED(front_consumed);
846 // @"A -- B"
847 if (!summary_str.empty() && !base_summary_str.empty()) {
848 stream << summary_str << " -- " << base_summary_str;
849 return true;
850 }
851
852 return false;
853}
854
855/// Bias value for tagged pointer exponents.
856/// Recommended values:
857/// 0x3e3: encodes all dates between distantPast and distantFuture
858/// except for the range within about 1e-28 second of the reference date.
859/// 0x3ef: encodes all dates for a few million years beyond distantPast and
860/// distantFuture, except within about 1e-25 second of the reference date.
861const int TAGGED_DATE_EXPONENT_BIAS = 0x3ef;
862
863struct DoubleBits {
864 uint64_t fraction : 52; // unsigned
865 uint64_t exponent : 11; // signed
866 uint64_t sign : 1;
867};
868
869struct TaggedDoubleBits {
870 uint64_t fraction : 52; // unsigned
871 uint64_t exponent : 7; // signed
872 uint64_t sign : 1;
873 uint64_t unused : 4; // placeholder for pointer tag bits
874};
875
876static uint64_t decodeExponent(uint64_t exp) {
877 // Tagged exponent field is 7-bit signed. Sign-extend the value to 64 bits
878 // before performing arithmetic.
879 return llvm::SignExtend64<7>(x: exp) + TAGGED_DATE_EXPONENT_BIAS;
880}
881
882static double decodeTaggedTimeInterval(uint64_t encodedTimeInterval) {
883 if (encodedTimeInterval == 0)
884 return 0.0;
885 if (encodedTimeInterval == std::numeric_limits<uint64_t>::max())
886 return (uint64_t)-0.0;
887
888 TaggedDoubleBits encodedBits =
889 llvm::bit_cast<TaggedDoubleBits>(from: encodedTimeInterval);
890 assert(encodedBits.unused == 0);
891
892 // Sign and fraction are represented exactly.
893 // Exponent is encoded.
894 DoubleBits decodedBits;
895 decodedBits.sign = encodedBits.sign;
896 decodedBits.fraction = encodedBits.fraction;
897 decodedBits.exponent = decodeExponent(exp: encodedBits.exponent);
898
899 return llvm::bit_cast<double>(from: decodedBits);
900}
901
902bool lldb_private::formatters::NSDateSummaryProvider(
903 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
904 ProcessSP process_sp = valobj.GetProcessSP();
905 if (!process_sp)
906 return false;
907
908 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
909
910 if (!runtime)
911 return false;
912
913 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
914 runtime->GetClassDescriptor(in_value&: valobj));
915
916 if (!descriptor || !descriptor->IsValid())
917 return false;
918
919 uint32_t ptr_size = process_sp->GetAddressByteSize();
920
921 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
922
923 if (!valobj_addr)
924 return false;
925
926 uint64_t date_value_bits = 0;
927 double date_value = 0.0;
928
929 ConstString class_name = descriptor->GetClassName();
930
931 static const ConstString g_NSDate("NSDate");
932 static const ConstString g_dunder_NSDate("__NSDate");
933 static const ConstString g_NSTaggedDate("__NSTaggedDate");
934 static const ConstString g_NSCalendarDate("NSCalendarDate");
935 static const ConstString g_NSConstantDate("NSConstantDate");
936
937 if (class_name.IsEmpty())
938 return false;
939
940 uint64_t info_bits = 0, value_bits = 0;
941 if ((class_name == g_NSDate) || (class_name == g_dunder_NSDate) ||
942 (class_name == g_NSTaggedDate) || (class_name == g_NSConstantDate)) {
943 if (descriptor->GetTaggedPointerInfo(info_bits: &info_bits, value_bits: &value_bits)) {
944 date_value_bits = ((value_bits << 8) | (info_bits << 4));
945 memcpy(dest: &date_value, src: &date_value_bits, n: sizeof(date_value_bits));
946 } else {
947 llvm::Triple triple(
948 process_sp->GetTarget().GetArchitecture().GetTriple());
949 uint32_t delta =
950 (triple.isWatchOS() && triple.isWatchABI()) ? 8 : ptr_size;
951 Status error;
952 date_value_bits = process_sp->ReadUnsignedIntegerFromMemory(
953 load_addr: valobj_addr + delta, byte_size: 8, fail_value: 0, error);
954 memcpy(dest: &date_value, src: &date_value_bits, n: sizeof(date_value_bits));
955 if (error.Fail())
956 return false;
957 }
958 } else if (class_name == g_NSCalendarDate) {
959 Status error;
960 date_value_bits = process_sp->ReadUnsignedIntegerFromMemory(
961 load_addr: valobj_addr + 2 * ptr_size, byte_size: 8, fail_value: 0, error);
962 memcpy(dest: &date_value, src: &date_value_bits, n: sizeof(date_value_bits));
963 if (error.Fail())
964 return false;
965 } else
966 return false;
967
968 // FIXME: It seems old dates are not formatted according to NSDate's calendar
969 // so we hardcode distantPast's value so that it looks like LLDB is doing
970 // the right thing.
971
972 // The relative time in seconds from Cocoa Epoch to [NSDate distantPast].
973 const double RelSecondsFromCocoaEpochToNSDateDistantPast = -63114076800;
974 if (date_value == RelSecondsFromCocoaEpochToNSDateDistantPast) {
975 stream.Printf(format: "0001-01-01 00:00:00 UTC");
976 return true;
977 }
978
979 // Accomodate for the __NSTaggedDate format introduced in Foundation 1600.
980 if (class_name == g_NSTaggedDate) {
981 auto *runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
982 Val: ObjCLanguageRuntime::Get(process&: *process_sp));
983 if (runtime && runtime->GetFoundationVersion() >= 1600)
984 date_value = decodeTaggedTimeInterval(encodedTimeInterval: value_bits << 4);
985 }
986
987 // this snippet of code assumes that time_t == seconds since Jan-1-1970 this
988 // is generally true and POSIXly happy, but might break if a library vendor
989 // decides to get creative
990 time_t epoch = GetOSXEpoch();
991 epoch = epoch + static_cast<time_t>(std::floor(x: date_value));
992 tm *tm_date = gmtime(timer: &epoch);
993 if (!tm_date)
994 return false;
995 std::string buffer(1024, 0);
996 if (strftime(s: &buffer[0], maxsize: 1023, format: "%Z", tp: tm_date) == 0)
997 return false;
998 stream.Printf(format: "%04d-%02d-%02d %02d:%02d:%02d %s", tm_date->tm_year + 1900,
999 tm_date->tm_mon + 1, tm_date->tm_mday, tm_date->tm_hour,
1000 tm_date->tm_min, tm_date->tm_sec, buffer.c_str());
1001 return true;
1002}
1003
1004bool lldb_private::formatters::ObjCClassSummaryProvider(
1005 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
1006 ProcessSP process_sp = valobj.GetProcessSP();
1007 if (!process_sp)
1008 return false;
1009
1010 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
1011
1012 if (!runtime)
1013 return false;
1014
1015 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
1016 runtime->GetClassDescriptorFromISA(isa: valobj.GetValueAsUnsigned(fail_value: 0)));
1017
1018 if (!descriptor || !descriptor->IsValid())
1019 return false;
1020
1021 ConstString class_name = descriptor->GetClassName();
1022
1023 if (class_name.IsEmpty())
1024 return false;
1025
1026 if (ConstString cs = Mangled(class_name).GetDemangledName())
1027 class_name = cs;
1028
1029 stream.Printf(format: "%s", class_name.AsCString(value_if_empty: "<unknown class>"));
1030 return true;
1031}
1032
1033class ObjCClassSyntheticChildrenFrontEnd : public SyntheticChildrenFrontEnd {
1034public:
1035 ObjCClassSyntheticChildrenFrontEnd(lldb::ValueObjectSP valobj_sp)
1036 : SyntheticChildrenFrontEnd(*valobj_sp) {}
1037
1038 ~ObjCClassSyntheticChildrenFrontEnd() override = default;
1039
1040 llvm::Expected<uint32_t> CalculateNumChildren() override { return 0; }
1041
1042 lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override {
1043 return lldb::ValueObjectSP();
1044 }
1045
1046 lldb::ChildCacheState Update() override {
1047 return lldb::ChildCacheState::eRefetch;
1048 }
1049
1050 bool MightHaveChildren() override { return false; }
1051
1052 llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
1053 return llvm::createStringError(Fmt: "Type has no child named '%s'",
1054 Vals: name.AsCString());
1055 }
1056};
1057
1058SyntheticChildrenFrontEnd *
1059lldb_private::formatters::ObjCClassSyntheticFrontEndCreator(
1060 CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
1061 return new ObjCClassSyntheticChildrenFrontEnd(valobj_sp);
1062}
1063
1064template <bool needs_at>
1065bool lldb_private::formatters::NSDataSummaryProvider(
1066 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
1067 ProcessSP process_sp = valobj.GetProcessSP();
1068 if (!process_sp)
1069 return false;
1070
1071 ObjCLanguageRuntime *runtime = ObjCLanguageRuntime::Get(process&: *process_sp);
1072
1073 if (!runtime)
1074 return false;
1075
1076 ObjCLanguageRuntime::ClassDescriptorSP descriptor(
1077 runtime->GetClassDescriptor(in_value&: valobj));
1078
1079 if (!descriptor || !descriptor->IsValid())
1080 return false;
1081
1082 bool is_64bit = (process_sp->GetAddressByteSize() == 8);
1083 lldb::addr_t valobj_addr = valobj.GetValueAsUnsigned(fail_value: 0);
1084
1085 if (!valobj_addr)
1086 return false;
1087
1088 uint64_t value = 0;
1089
1090 llvm::StringRef class_name = descriptor->GetClassName().GetCString();
1091
1092 if (class_name.empty())
1093 return false;
1094
1095 bool isNSConcreteData = class_name == "NSConcreteData";
1096 bool isNSConcreteMutableData = class_name == "NSConcreteMutableData";
1097 bool isNSCFData = class_name == "__NSCFData";
1098 if (isNSConcreteData || isNSConcreteMutableData || isNSCFData) {
1099 uint32_t offset;
1100 if (isNSConcreteData)
1101 offset = is_64bit ? 8 : 4;
1102 else
1103 offset = is_64bit ? 16 : 8;
1104
1105 Status error;
1106 value = process_sp->ReadUnsignedIntegerFromMemory(
1107 load_addr: valobj_addr + offset, byte_size: is_64bit ? 8 : 4, fail_value: 0, error);
1108 if (error.Fail())
1109 return false;
1110 } else if (class_name == "_NSInlineData") {
1111 uint32_t offset = (is_64bit ? 8 : 4);
1112 Status error;
1113 value = process_sp->ReadUnsignedIntegerFromMemory(load_addr: valobj_addr + offset, byte_size: 2,
1114 fail_value: 0, error);
1115 if (error.Fail())
1116 return false;
1117 } else if (class_name == "_NSZeroData") {
1118 value = 0;
1119 } else
1120 return false;
1121
1122 stream.Printf(format: "%s%" PRIu64 " byte%s%s", (needs_at ? "@\"" : ""), value,
1123 (value != 1 ? "s" : ""), (needs_at ? "\"" : ""));
1124
1125 return true;
1126}
1127
1128bool lldb_private::formatters::ObjCBOOLSummaryProvider(
1129 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
1130 const uint32_t type_info = valobj.GetCompilerType().GetTypeInfo();
1131
1132 ValueObjectSP real_guy_sp = valobj.GetSP();
1133
1134 if (type_info & eTypeIsPointer) {
1135 Status err;
1136 real_guy_sp = valobj.Dereference(error&: err);
1137 if (err.Fail() || !real_guy_sp)
1138 return false;
1139 } else if (type_info & eTypeIsReference) {
1140 real_guy_sp = valobj.GetChildAtIndex(idx: 0);
1141 if (!real_guy_sp)
1142 return false;
1143 }
1144 int8_t value = (real_guy_sp->GetValueAsSigned(fail_value: 0) & 0xFF);
1145 switch (value) {
1146 case 0:
1147 stream.Printf(format: "NO");
1148 break;
1149 case 1:
1150 stream.Printf(format: "YES");
1151 break;
1152 default:
1153 stream.Printf(format: "%d", value);
1154 break;
1155 }
1156 return true;
1157}
1158
1159bool lldb_private::formatters::ObjCBooleanSummaryProvider(
1160 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
1161 lldb::addr_t valobj_ptr_value =
1162 valobj.GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
1163 if (valobj_ptr_value == LLDB_INVALID_ADDRESS)
1164 return false;
1165
1166 ProcessSP process_sp(valobj.GetProcessSP());
1167 if (!process_sp)
1168 return false;
1169
1170 if (AppleObjCRuntime *objc_runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
1171 Val: ObjCLanguageRuntime::Get(process&: *process_sp))) {
1172 lldb::addr_t cf_true = LLDB_INVALID_ADDRESS,
1173 cf_false = LLDB_INVALID_ADDRESS;
1174 objc_runtime->GetValuesForGlobalCFBooleans(cf_true, cf_false);
1175 if (valobj_ptr_value == cf_true) {
1176 stream.PutCString(cstr: "YES");
1177 return true;
1178 }
1179 if (valobj_ptr_value == cf_false) {
1180 stream.PutCString(cstr: "NO");
1181 return true;
1182 }
1183 }
1184
1185 return false;
1186}
1187
1188template <bool is_sel_ptr>
1189bool lldb_private::formatters::ObjCSELSummaryProvider(
1190 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
1191 lldb::ValueObjectSP valobj_sp;
1192
1193 CompilerType charstar(valobj.GetCompilerType()
1194 .GetBasicTypeFromAST(basic_type: eBasicTypeChar)
1195 .GetPointerType());
1196
1197 if (!charstar)
1198 return false;
1199
1200 ExecutionContext exe_ctx(valobj.GetExecutionContextRef());
1201
1202 if (is_sel_ptr) {
1203 lldb::addr_t data_address = valobj.GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
1204 if (data_address == LLDB_INVALID_ADDRESS)
1205 return false;
1206 valobj_sp = ValueObject::CreateValueObjectFromAddress(name: "text", address: data_address,
1207 exe_ctx, type: charstar);
1208 } else {
1209 DataExtractor data;
1210 Status error;
1211 valobj.GetData(data, error);
1212 if (error.Fail())
1213 return false;
1214 valobj_sp =
1215 ValueObject::CreateValueObjectFromData(name: "text", data, exe_ctx, type: charstar);
1216 }
1217
1218 if (!valobj_sp)
1219 return false;
1220
1221 stream.Printf(format: "%s", valobj_sp->GetSummaryAsCString());
1222 return true;
1223}
1224
1225// POSIX has an epoch on Jan-1-1970, but Cocoa prefers Jan-1-2001
1226// this call gives the POSIX equivalent of the Cocoa epoch
1227time_t lldb_private::formatters::GetOSXEpoch() {
1228 static time_t epoch = 0;
1229 if (!epoch) {
1230#if !defined(_WIN32) && !defined(_AIX)
1231 tzset();
1232 tm tm_epoch;
1233 tm_epoch.tm_sec = 0;
1234 tm_epoch.tm_hour = 0;
1235 tm_epoch.tm_min = 0;
1236 tm_epoch.tm_mon = 0;
1237 tm_epoch.tm_mday = 1;
1238 tm_epoch.tm_year = 2001 - 1900;
1239 tm_epoch.tm_isdst = -1;
1240 tm_epoch.tm_gmtoff = 0;
1241 tm_epoch.tm_zone = nullptr;
1242 epoch = timegm(tp: &tm_epoch);
1243#endif
1244 }
1245 return epoch;
1246}
1247
1248template bool lldb_private::formatters::NSDataSummaryProvider<true>(
1249 ValueObject &, Stream &, const TypeSummaryOptions &);
1250
1251template bool lldb_private::formatters::NSDataSummaryProvider<false>(
1252 ValueObject &, Stream &, const TypeSummaryOptions &);
1253
1254template bool lldb_private::formatters::ObjCSELSummaryProvider<true>(
1255 ValueObject &, Stream &, const TypeSummaryOptions &);
1256
1257template bool lldb_private::formatters::ObjCSELSummaryProvider<false>(
1258 ValueObject &, Stream &, const TypeSummaryOptions &);
1259

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of lldb/source/Plugins/Language/ObjC/Cocoa.cpp