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/Core/ValueObject.h" 12 #include "lldb/Core/ValueObjectConstResult.h" 13 #include "lldb/DataFormatters/FormattersHelpers.h" 14 #include "lldb/DataFormatters/StringPrinter.h" 15 #include "lldb/Target/Language.h" 16 #include "lldb/Target/Target.h" 17 #include "lldb/Utility/ConstString.h" 18 #include "lldb/Utility/DataBufferHeap.h" 19 #include "lldb/Utility/Endian.h" 20 #include "lldb/Utility/Status.h" 21 #include "lldb/Utility/Stream.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_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 bool is_tagged_ptr = class_name == "NSTaggedPointerString" && 67 descriptor->GetTaggedPointerInfo(); 68 // for a tagged pointer, the descriptor has everything we need 69 if (is_tagged_ptr) 70 return NSTaggedString_SummaryProvider(valobj, descriptor, stream, 71 summary_options); 72 73 auto &additionals_map(NSString_Additionals::GetAdditionalSummaries()); 74 auto iter = additionals_map.find(class_name_cs), end = additionals_map.end(); 75 if (iter != end) 76 return iter->second(valobj, stream, summary_options); 77 78 // if not a tagged pointer that we know about, try the normal route 79 uint64_t info_bits_location = valobj_addr + ptr_size; 80 if (process_sp->GetByteOrder() != lldb::eByteOrderLittle) 81 info_bits_location += 3; 82 83 Status error; 84 85 uint8_t info_bits = process_sp->ReadUnsignedIntegerFromMemory( 86 info_bits_location, 1, 0, error); 87 if (error.Fail()) 88 return false; 89 90 bool is_mutable = (info_bits & 1) == 1; 91 bool is_inline = (info_bits & 0x60) == 0; 92 bool has_explicit_length = (info_bits & (1 | 4)) != 4; 93 bool is_unicode = (info_bits & 0x10) == 0x10; 94 bool is_path_store = class_name == "NSPathStore2"; 95 bool has_null = (info_bits & 8) == 8; 96 97 size_t explicit_length = 0; 98 if (!has_null && has_explicit_length && !is_path_store) { 99 lldb::addr_t explicit_length_offset = 2 * ptr_size; 100 if (is_mutable && !is_inline) 101 explicit_length_offset = 102 explicit_length_offset + ptr_size; // notInlineMutable.length; 103 else if (is_inline) 104 explicit_length = explicit_length + 0; // inline1.length; 105 else if (!is_inline && !is_mutable) 106 explicit_length_offset = 107 explicit_length_offset + ptr_size; // notInlineImmutable1.length; 108 else 109 explicit_length_offset = 0; 110 111 if (explicit_length_offset) { 112 explicit_length_offset = valobj_addr + explicit_length_offset; 113 explicit_length = process_sp->ReadUnsignedIntegerFromMemory( 114 explicit_length_offset, 4, 0, error); 115 } 116 } 117 118 const llvm::StringSet<> supported_string_classes = { 119 "NSString", "CFMutableStringRef", 120 "CFStringRef", "__NSCFConstantString", 121 "__NSCFString", "NSCFConstantString", 122 "NSCFString", "NSPathStore2"}; 123 if (supported_string_classes.count(class_name) == 0) { 124 // not one of us - but tell me class name 125 stream.Printf("class name = %s", class_name_cs.GetCString()); 126 return true; 127 } 128 129 llvm::StringRef prefix, suffix; 130 if (Language *language = Language::FindPlugin(summary_options.GetLanguage())) 131 std::tie(prefix, suffix) = language->GetFormatterPrefixSuffix(g_TypeHint); 132 133 StringPrinter::ReadStringAndDumpToStreamOptions options(valobj); 134 options.SetPrefixToken(prefix.str()); 135 options.SetSuffixToken(suffix.str()); 136 137 if (is_mutable) { 138 uint64_t location = 2 * ptr_size + valobj_addr; 139 location = process_sp->ReadPointerFromMemory(location, error); 140 if (error.Fail()) 141 return false; 142 if (has_explicit_length && is_unicode) { 143 options.SetLocation(location); 144 options.SetTargetSP(valobj.GetTargetSP()); 145 options.SetStream(&stream); 146 options.SetQuote('"'); 147 options.SetSourceSize(explicit_length); 148 options.SetHasSourceSize(has_explicit_length); 149 options.SetNeedsZeroTermination(false); 150 options.SetIgnoreMaxLength(summary_options.GetCapping() == 151 TypeSummaryCapping::eTypeSummaryUncapped); 152 options.SetBinaryZeroIsTerminator(false); 153 return StringPrinter::ReadStringAndDumpToStream< 154 StringPrinter::StringElementType::UTF16>(options); 155 } else { 156 options.SetLocation(location + 1); 157 options.SetTargetSP(valobj.GetTargetSP()); 158 options.SetStream(&stream); 159 options.SetSourceSize(explicit_length); 160 options.SetHasSourceSize(has_explicit_length); 161 options.SetNeedsZeroTermination(false); 162 options.SetIgnoreMaxLength(summary_options.GetCapping() == 163 TypeSummaryCapping::eTypeSummaryUncapped); 164 options.SetBinaryZeroIsTerminator(false); 165 return StringPrinter::ReadStringAndDumpToStream< 166 StringPrinter::StringElementType::ASCII>(options); 167 } 168 } else if (is_inline && has_explicit_length && !is_unicode && 169 !is_path_store && !is_mutable) { 170 uint64_t location = 3 * ptr_size + valobj_addr; 171 172 options.SetLocation(location); 173 options.SetTargetSP(valobj.GetTargetSP()); 174 options.SetStream(&stream); 175 options.SetQuote('"'); 176 options.SetSourceSize(explicit_length); 177 options.SetHasSourceSize(has_explicit_length); 178 options.SetIgnoreMaxLength(summary_options.GetCapping() == 179 TypeSummaryCapping::eTypeSummaryUncapped); 180 return StringPrinter::ReadStringAndDumpToStream< 181 StringPrinter::StringElementType::ASCII>(options); 182 } else if (is_unicode) { 183 uint64_t location = valobj_addr + 2 * ptr_size; 184 if (is_inline) { 185 if (!has_explicit_length) { 186 return false; 187 } else 188 location += ptr_size; 189 } else { 190 location = process_sp->ReadPointerFromMemory(location, error); 191 if (error.Fail()) 192 return false; 193 } 194 options.SetLocation(location); 195 options.SetTargetSP(valobj.GetTargetSP()); 196 options.SetStream(&stream); 197 options.SetQuote('"'); 198 options.SetSourceSize(explicit_length); 199 options.SetHasSourceSize(has_explicit_length); 200 options.SetNeedsZeroTermination(!has_explicit_length); 201 options.SetIgnoreMaxLength(summary_options.GetCapping() == 202 TypeSummaryCapping::eTypeSummaryUncapped); 203 options.SetBinaryZeroIsTerminator(!has_explicit_length); 204 return StringPrinter::ReadStringAndDumpToStream< 205 StringPrinter::StringElementType::UTF16>(options); 206 } else if (is_path_store) { 207 // _lengthAndRefCount is the first ivar of NSPathStore2 (after the isa). 208 uint64_t length_ivar_offset = 1 * ptr_size; 209 CompilerType length_type = valobj.GetCompilerType().GetBasicTypeFromAST( 210 lldb::eBasicTypeUnsignedInt); 211 ValueObjectSP length_valobj_sp = 212 valobj.GetSyntheticChildAtOffset(length_ivar_offset, length_type, true, 213 ConstString("_lengthAndRefCount")); 214 if (!length_valobj_sp) 215 return false; 216 // Get the length out of _lengthAndRefCount. 217 explicit_length = length_valobj_sp->GetValueAsUnsigned(0) >> 20; 218 lldb::addr_t location = valobj.GetValueAsUnsigned(0) + ptr_size + 4; 219 220 options.SetLocation(location); 221 options.SetTargetSP(valobj.GetTargetSP()); 222 options.SetStream(&stream); 223 options.SetQuote('"'); 224 options.SetSourceSize(explicit_length); 225 options.SetHasSourceSize(has_explicit_length); 226 options.SetNeedsZeroTermination(!has_explicit_length); 227 options.SetIgnoreMaxLength(summary_options.GetCapping() == 228 TypeSummaryCapping::eTypeSummaryUncapped); 229 options.SetBinaryZeroIsTerminator(!has_explicit_length); 230 return StringPrinter::ReadStringAndDumpToStream< 231 StringPrinter::StringElementType::UTF16>(options); 232 } else if (is_inline) { 233 uint64_t location = valobj_addr + 2 * ptr_size; 234 if (!has_explicit_length) { 235 // in this kind of string, the byte before the string content is a length 236 // byte so let's try and use it to handle the embedded NUL case 237 Status error; 238 explicit_length = 239 process_sp->ReadUnsignedIntegerFromMemory(location, 1, 0, error); 240 has_explicit_length = !(error.Fail() || explicit_length == 0); 241 location++; 242 } 243 options.SetLocation(location); 244 options.SetTargetSP(valobj.GetTargetSP()); 245 options.SetStream(&stream); 246 options.SetSourceSize(explicit_length); 247 options.SetHasSourceSize(has_explicit_length); 248 options.SetNeedsZeroTermination(!has_explicit_length); 249 options.SetIgnoreMaxLength(summary_options.GetCapping() == 250 TypeSummaryCapping::eTypeSummaryUncapped); 251 options.SetBinaryZeroIsTerminator(!has_explicit_length); 252 if (has_explicit_length) 253 return StringPrinter::ReadStringAndDumpToStream< 254 StringPrinter::StringElementType::UTF8>(options); 255 else 256 return StringPrinter::ReadStringAndDumpToStream< 257 StringPrinter::StringElementType::ASCII>(options); 258 } else { 259 uint64_t location = valobj_addr + 2 * ptr_size; 260 location = process_sp->ReadPointerFromMemory(location, error); 261 if (error.Fail()) 262 return false; 263 if (has_explicit_length && !has_null) 264 explicit_length++; // account for the fact that there is no NULL and we 265 // need to have one added 266 options.SetLocation(location); 267 options.SetTargetSP(valobj.GetTargetSP()); 268 options.SetStream(&stream); 269 options.SetSourceSize(explicit_length); 270 options.SetHasSourceSize(has_explicit_length); 271 options.SetIgnoreMaxLength(summary_options.GetCapping() == 272 TypeSummaryCapping::eTypeSummaryUncapped); 273 return StringPrinter::ReadStringAndDumpToStream< 274 StringPrinter::StringElementType::ASCII>(options); 275 } 276 } 277 278 bool lldb_private::formatters::NSAttributedStringSummaryProvider( 279 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 280 TargetSP target_sp(valobj.GetTargetSP()); 281 if (!target_sp) 282 return false; 283 uint32_t addr_size = target_sp->GetArchitecture().GetAddressByteSize(); 284 uint64_t pointer_value = valobj.GetValueAsUnsigned(0); 285 if (!pointer_value) 286 return false; 287 pointer_value += addr_size; 288 CompilerType type(valobj.GetCompilerType()); 289 ExecutionContext exe_ctx(target_sp, false); 290 ValueObjectSP child_ptr_sp(valobj.CreateValueObjectFromAddress( 291 "string_ptr", pointer_value, exe_ctx, type)); 292 if (!child_ptr_sp) 293 return false; 294 DataExtractor data; 295 Status error; 296 child_ptr_sp->GetData(data, error); 297 if (error.Fail()) 298 return false; 299 ValueObjectSP child_sp(child_ptr_sp->CreateValueObjectFromData( 300 "string_data", data, exe_ctx, type)); 301 child_sp->GetValueAsUnsigned(0); 302 if (child_sp) 303 return NSStringSummaryProvider(*child_sp, stream, options); 304 return false; 305 } 306 307 bool lldb_private::formatters::NSMutableAttributedStringSummaryProvider( 308 ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { 309 return NSAttributedStringSummaryProvider(valobj, stream, options); 310 } 311 312 bool lldb_private::formatters::NSTaggedString_SummaryProvider( 313 ValueObject &valobj, ObjCLanguageRuntime::ClassDescriptorSP descriptor, 314 Stream &stream, const TypeSummaryOptions &summary_options) { 315 static constexpr llvm::StringLiteral g_TypeHint("NSString"); 316 317 if (!descriptor) 318 return false; 319 uint64_t len_bits = 0, data_bits = 0; 320 if (!descriptor->GetTaggedPointerInfo(&len_bits, &data_bits, nullptr)) 321 return false; 322 323 static const int g_MaxNonBitmaskedLen = 7; // TAGGED_STRING_UNPACKED_MAXLEN 324 static const int g_SixbitMaxLen = 9; 325 static const int g_fiveBitMaxLen = 11; 326 327 static const char *sixBitToCharLookup = "eilotrm.apdnsIc ufkMShjTRxgC4013" 328 "bDNvwyUL2O856P-B79AFKEWV_zGJ/HYX"; 329 330 if (len_bits > g_fiveBitMaxLen) 331 return false; 332 333 llvm::StringRef prefix, suffix; 334 if (Language *language = Language::FindPlugin(summary_options.GetLanguage())) 335 std::tie(prefix, suffix) = language->GetFormatterPrefixSuffix(g_TypeHint); 336 337 // this is a fairly ugly trick - pretend that the numeric value is actually a 338 // char* this works under a few assumptions: little endian architecture 339 // sizeof(uint64_t) > g_MaxNonBitmaskedLen 340 if (len_bits <= g_MaxNonBitmaskedLen) { 341 stream << prefix; 342 stream.Printf("\"%s\"", (const char *)&data_bits); 343 stream << suffix; 344 return true; 345 } 346 347 // if the data is bitmasked, we need to actually process the bytes 348 uint8_t bitmask = 0; 349 uint8_t shift_offset = 0; 350 351 if (len_bits <= g_SixbitMaxLen) { 352 bitmask = 0x03f; 353 shift_offset = 6; 354 } else { 355 bitmask = 0x01f; 356 shift_offset = 5; 357 } 358 359 std::vector<uint8_t> bytes; 360 bytes.resize(len_bits); 361 for (; len_bits > 0; data_bits >>= shift_offset, --len_bits) { 362 uint8_t packed = data_bits & bitmask; 363 bytes.insert(bytes.begin(), sixBitToCharLookup[packed]); 364 } 365 366 stream << prefix; 367 stream.Printf("\"%s\"", &bytes[0]); 368 stream << suffix; 369 return true; 370 } 371