AppleObjCTypeEncodingParser.cpp revision 360784
1//===-- AppleObjCTypeEncodingParser.cpp -------------------------*- C++ -*-===// 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 "AppleObjCTypeEncodingParser.h" 10 11#include "lldb/Symbol/ClangASTContext.h" 12#include "lldb/Symbol/ClangUtil.h" 13#include "lldb/Symbol/CompilerType.h" 14#include "lldb/Target/Process.h" 15#include "lldb/Target/Target.h" 16#include "lldb/Utility/StringLexer.h" 17 18#include <vector> 19 20using namespace lldb_private; 21 22AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser( 23 ObjCLanguageRuntime &runtime) 24 : ObjCLanguageRuntime::EncodingToType(), m_runtime(runtime) { 25 if (!m_scratch_ast_ctx_up) 26 m_scratch_ast_ctx_up.reset(new ClangASTContext(runtime.GetProcess() 27 ->GetTarget() 28 .GetArchitecture() 29 .GetTriple())); 30} 31 32std::string AppleObjCTypeEncodingParser::ReadStructName(StringLexer &type) { 33 StreamString buffer; 34 while (type.HasAtLeast(1) && type.Peek() != '=') 35 buffer.Printf("%c", type.Next()); 36 return buffer.GetString(); 37} 38 39std::string AppleObjCTypeEncodingParser::ReadQuotedString(StringLexer &type) { 40 StreamString buffer; 41 while (type.HasAtLeast(1) && type.Peek() != '"') 42 buffer.Printf("%c", type.Next()); 43 StringLexer::Character next = type.Next(); 44 UNUSED_IF_ASSERT_DISABLED(next); 45 assert(next == '"'); 46 return buffer.GetString(); 47} 48 49uint32_t AppleObjCTypeEncodingParser::ReadNumber(StringLexer &type) { 50 uint32_t total = 0; 51 while (type.HasAtLeast(1) && isdigit(type.Peek())) 52 total = 10 * total + (type.Next() - '0'); 53 return total; 54} 55 56// as an extension to the published grammar recent runtimes emit structs like 57// this: 58// "{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}" 59 60AppleObjCTypeEncodingParser::StructElement::StructElement() 61 : name(""), type(clang::QualType()), bitfield(0) {} 62 63AppleObjCTypeEncodingParser::StructElement 64AppleObjCTypeEncodingParser::ReadStructElement(ClangASTContext &ast_ctx, 65 StringLexer &type, 66 bool for_expression) { 67 StructElement retval; 68 if (type.NextIf('"')) 69 retval.name = ReadQuotedString(type); 70 if (!type.NextIf('"')) 71 return retval; 72 uint32_t bitfield_size = 0; 73 retval.type = BuildType(ast_ctx, type, for_expression, &bitfield_size); 74 retval.bitfield = bitfield_size; 75 return retval; 76} 77 78clang::QualType AppleObjCTypeEncodingParser::BuildStruct( 79 ClangASTContext &ast_ctx, StringLexer &type, bool for_expression) { 80 return BuildAggregate(ast_ctx, type, for_expression, '{', '}', 81 clang::TTK_Struct); 82} 83 84clang::QualType AppleObjCTypeEncodingParser::BuildUnion( 85 ClangASTContext &ast_ctx, StringLexer &type, bool for_expression) { 86 return BuildAggregate(ast_ctx, type, for_expression, '(', ')', 87 clang::TTK_Union); 88} 89 90clang::QualType AppleObjCTypeEncodingParser::BuildAggregate( 91 ClangASTContext &ast_ctx, StringLexer &type, bool for_expression, 92 char opener, char closer, uint32_t kind) { 93 if (!type.NextIf(opener)) 94 return clang::QualType(); 95 std::string name(ReadStructName(type)); 96 97 // We do not handle templated classes/structs at the moment. If the name has 98 // a < in it, we are going to abandon this. We're still obliged to parse it, 99 // so we just set a flag that means "Don't actually build anything." 100 101 const bool is_templated = name.find('<') != std::string::npos; 102 103 if (!type.NextIf('=')) 104 return clang::QualType(); 105 bool in_union = true; 106 std::vector<StructElement> elements; 107 while (in_union && type.HasAtLeast(1)) { 108 if (type.NextIf(closer)) { 109 in_union = false; 110 break; 111 } else { 112 auto element = ReadStructElement(ast_ctx, type, for_expression); 113 if (element.type.isNull()) 114 break; 115 else 116 elements.push_back(element); 117 } 118 } 119 if (in_union) 120 return clang::QualType(); 121 122 if (is_templated) 123 return clang::QualType(); // This is where we bail out. Sorry! 124 125 CompilerType union_type(ast_ctx.CreateRecordType( 126 nullptr, lldb::eAccessPublic, name, kind, lldb::eLanguageTypeC)); 127 if (union_type) { 128 ClangASTContext::StartTagDeclarationDefinition(union_type); 129 130 unsigned int count = 0; 131 for (auto element : elements) { 132 if (element.name.empty()) { 133 StreamString elem_name; 134 elem_name.Printf("__unnamed_%u", count); 135 element.name = elem_name.GetString(); 136 } 137 ClangASTContext::AddFieldToRecordType( 138 union_type, element.name.c_str(), ast_ctx.GetType(element.type), 139 lldb::eAccessPublic, element.bitfield); 140 ++count; 141 } 142 ClangASTContext::CompleteTagDeclarationDefinition(union_type); 143 } 144 return ClangUtil::GetQualType(union_type); 145} 146 147clang::QualType AppleObjCTypeEncodingParser::BuildArray( 148 ClangASTContext &ast_ctx, StringLexer &type, bool for_expression) { 149 if (!type.NextIf('[')) 150 return clang::QualType(); 151 uint32_t size = ReadNumber(type); 152 clang::QualType element_type(BuildType(ast_ctx, type, for_expression)); 153 if (!type.NextIf(']')) 154 return clang::QualType(); 155 CompilerType array_type(ast_ctx.CreateArrayType( 156 CompilerType(&ast_ctx, element_type.getAsOpaquePtr()), size, false)); 157 return ClangUtil::GetQualType(array_type); 158} 159 160// the runtime can emit these in the form of @"SomeType", giving more specifics 161// this would be interesting for expression parser interop, but since we 162// actually try to avoid exposing the ivar info to the expression evaluator, 163// consume but ignore the type info and always return an 'id'; if anything, 164// dynamic typing will resolve things for us anyway 165clang::QualType AppleObjCTypeEncodingParser::BuildObjCObjectPointerType( 166 ClangASTContext &clang_ast_ctx, StringLexer &type, bool for_expression) { 167 if (!type.NextIf('@')) 168 return clang::QualType(); 169 170 clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext(); 171 172 std::string name; 173 174 if (type.NextIf('"')) { 175 // We have to be careful here. We're used to seeing 176 // @"NSString" 177 // but in records it is possible that the string following an @ is the name 178 // of the next field and @ means "id". This is the case if anything 179 // unquoted except for "}", the end of the type, or another name follows 180 // the quoted string. 181 // 182 // E.g. 183 // - @"NSString"@ means "id, followed by a field named NSString of type id" 184 // - @"NSString"} means "a pointer to NSString and the end of the struct" - 185 // @"NSString""nextField" means "a pointer to NSString and a field named 186 // nextField" - @"NSString" followed by the end of the string means "a 187 // pointer to NSString" 188 // 189 // As a result, the rule is: If we see @ followed by a quoted string, we 190 // peek. - If we see }, ), ], the end of the string, or a quote ("), the 191 // quoted string is a class name. - If we see anything else, the quoted 192 // string is a field name and we push it back onto type. 193 194 name = ReadQuotedString(type); 195 196 if (type.HasAtLeast(1)) { 197 switch (type.Peek()) { 198 default: 199 // roll back 200 type.PutBack(name.length() + 201 2); // undo our consumption of the string and of the quotes 202 name.clear(); 203 break; 204 case '}': 205 case ')': 206 case ']': 207 case '"': 208 // the quoted string is a class name ��� see the rule 209 break; 210 } 211 } else { 212 // the quoted string is a class name ��� see the rule 213 } 214 } 215 216 if (for_expression && !name.empty()) { 217 size_t less_than_pos = name.find('<'); 218 219 if (less_than_pos != std::string::npos) { 220 if (less_than_pos == 0) 221 return ast_ctx.getObjCIdType(); 222 else 223 name.erase(less_than_pos); 224 } 225 226 DeclVendor *decl_vendor = m_runtime.GetDeclVendor(); 227 if (!decl_vendor) 228 return clang::QualType(); 229 230 auto types = decl_vendor->FindTypes(ConstString(name), /*max_matches*/ 1); 231 232// The user can forward-declare something that has no definition. The runtime 233// doesn't prohibit this at all. This is a rare and very weird case. We keep 234// this assert in debug builds so we catch other weird cases. 235#ifdef LLDB_CONFIGURATION_DEBUG 236 assert(!types.empty()); 237#else 238 if (types.empty()) 239 return ast_ctx.getObjCIdType(); 240#endif 241 242 return ClangUtil::GetQualType(types.front().GetPointerType()); 243 } else { 244 // We're going to resolve this dynamically anyway, so just smile and wave. 245 return ast_ctx.getObjCIdType(); 246 } 247} 248 249clang::QualType 250AppleObjCTypeEncodingParser::BuildType(ClangASTContext &clang_ast_ctx, 251 StringLexer &type, bool for_expression, 252 uint32_t *bitfield_bit_size) { 253 if (!type.HasAtLeast(1)) 254 return clang::QualType(); 255 256 clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext(); 257 258 switch (type.Peek()) { 259 default: 260 break; 261 case '{': 262 return BuildStruct(clang_ast_ctx, type, for_expression); 263 case '[': 264 return BuildArray(clang_ast_ctx, type, for_expression); 265 case '(': 266 return BuildUnion(clang_ast_ctx, type, for_expression); 267 case '@': 268 return BuildObjCObjectPointerType(clang_ast_ctx, type, for_expression); 269 } 270 271 switch (type.Next()) { 272 default: 273 type.PutBack(1); 274 return clang::QualType(); 275 case 'c': 276 return ast_ctx.CharTy; 277 case 'i': 278 return ast_ctx.IntTy; 279 case 's': 280 return ast_ctx.ShortTy; 281 case 'l': 282 return ast_ctx.getIntTypeForBitwidth(32, true); 283 // this used to be done like this: 284 // return clang_ast_ctx->GetIntTypeFromBitSize(32, true).GetQualType(); 285 // which uses one of the constants if one is available, but we don't think 286 // all this work is necessary. 287 case 'q': 288 return ast_ctx.LongLongTy; 289 case 'C': 290 return ast_ctx.UnsignedCharTy; 291 case 'I': 292 return ast_ctx.UnsignedIntTy; 293 case 'S': 294 return ast_ctx.UnsignedShortTy; 295 case 'L': 296 return ast_ctx.getIntTypeForBitwidth(32, false); 297 // see note for 'l' 298 case 'Q': 299 return ast_ctx.UnsignedLongLongTy; 300 case 'f': 301 return ast_ctx.FloatTy; 302 case 'd': 303 return ast_ctx.DoubleTy; 304 case 'B': 305 return ast_ctx.BoolTy; 306 case 'v': 307 return ast_ctx.VoidTy; 308 case '*': 309 return ast_ctx.getPointerType(ast_ctx.CharTy); 310 case '#': 311 return ast_ctx.getObjCClassType(); 312 case ':': 313 return ast_ctx.getObjCSelType(); 314 case 'b': { 315 uint32_t size = ReadNumber(type); 316 if (bitfield_bit_size) { 317 *bitfield_bit_size = size; 318 return ast_ctx.UnsignedIntTy; // FIXME: the spec is fairly vague here. 319 } else 320 return clang::QualType(); 321 } 322 case 'r': { 323 clang::QualType target_type = 324 BuildType(clang_ast_ctx, type, for_expression); 325 if (target_type.isNull()) 326 return clang::QualType(); 327 else if (target_type == ast_ctx.UnknownAnyTy) 328 return ast_ctx.UnknownAnyTy; 329 else 330 return ast_ctx.getConstType(target_type); 331 } 332 case '^': { 333 if (!for_expression && type.NextIf('?')) { 334 // if we are not supporting the concept of unknownAny, but what is being 335 // created here is an unknownAny*, then we can just get away with a void* 336 // this is theoretically wrong (in the same sense as 'theoretically 337 // nothing exists') but is way better than outright failure in many 338 // practical cases 339 return ast_ctx.VoidPtrTy; 340 } else { 341 clang::QualType target_type = 342 BuildType(clang_ast_ctx, type, for_expression); 343 if (target_type.isNull()) 344 return clang::QualType(); 345 else if (target_type == ast_ctx.UnknownAnyTy) 346 return ast_ctx.UnknownAnyTy; 347 else 348 return ast_ctx.getPointerType(target_type); 349 } 350 } 351 case '?': 352 return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType(); 353 } 354} 355 356CompilerType AppleObjCTypeEncodingParser::RealizeType(ClangASTContext &ast_ctx, 357 const char *name, 358 bool for_expression) { 359 if (name && name[0]) { 360 StringLexer lexer(name); 361 clang::QualType qual_type = BuildType(ast_ctx, lexer, for_expression); 362 return ast_ctx.GetType(qual_type); 363 } 364 return CompilerType(); 365} 366