//===-- AppleObjCTypeEncodingParser.cpp -------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "AppleObjCTypeEncodingParser.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/ClangUtil.h" #include "lldb/Symbol/CompilerType.h" #include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Utility/StringLexer.h" #include using namespace lldb_private; using namespace lldb_utility; AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser( ObjCLanguageRuntime &runtime) : ObjCLanguageRuntime::EncodingToType(), m_runtime(runtime) { if (!m_scratch_ast_ctx_ap) m_scratch_ast_ctx_ap.reset(new ClangASTContext(runtime.GetProcess() ->GetTarget() .GetArchitecture() .GetTriple() .str() .c_str())); } std::string AppleObjCTypeEncodingParser::ReadStructName(lldb_utility::StringLexer &type) { StreamString buffer; while (type.HasAtLeast(1) && type.Peek() != '=') buffer.Printf("%c", type.Next()); return buffer.GetString(); } std::string AppleObjCTypeEncodingParser::ReadQuotedString(lldb_utility::StringLexer &type) { StreamString buffer; while (type.HasAtLeast(1) && type.Peek() != '"') buffer.Printf("%c", type.Next()); StringLexer::Character next = type.Next(); UNUSED_IF_ASSERT_DISABLED(next); assert(next == '"'); return buffer.GetString(); } uint32_t AppleObjCTypeEncodingParser::ReadNumber(lldb_utility::StringLexer &type) { uint32_t total = 0; while (type.HasAtLeast(1) && isdigit(type.Peek())) total = 10 * total + (type.Next() - '0'); return total; } // as an extension to the published grammar recent runtimes emit structs like // this: // "{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}" AppleObjCTypeEncodingParser::StructElement::StructElement() : name(""), type(clang::QualType()), bitfield(0) {} AppleObjCTypeEncodingParser::StructElement AppleObjCTypeEncodingParser::ReadStructElement(clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression) { StructElement retval; if (type.NextIf('"')) retval.name = ReadQuotedString(type); if (!type.NextIf('"')) return retval; uint32_t bitfield_size = 0; retval.type = BuildType(ast_ctx, type, for_expression, &bitfield_size); retval.bitfield = bitfield_size; return retval; } clang::QualType AppleObjCTypeEncodingParser::BuildStruct(clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression) { return BuildAggregate(ast_ctx, type, for_expression, '{', '}', clang::TTK_Struct); } clang::QualType AppleObjCTypeEncodingParser::BuildUnion(clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression) { return BuildAggregate(ast_ctx, type, for_expression, '(', ')', clang::TTK_Union); } clang::QualType AppleObjCTypeEncodingParser::BuildAggregate( clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression, char opener, char closer, uint32_t kind) { if (!type.NextIf(opener)) return clang::QualType(); std::string name(ReadStructName(type)); // We do not handle templated classes/structs at the moment. // If the name has a < in it, we are going to abandon this. // We're still obliged to parse it, so we just set a flag that // means "Don't actually build anything." const bool is_templated = name.find('<') != std::string::npos; if (!type.NextIf('=')) return clang::QualType(); bool in_union = true; std::vector elements; while (in_union && type.HasAtLeast(1)) { if (type.NextIf(closer)) { in_union = false; break; } else { auto element = ReadStructElement(ast_ctx, type, for_expression); if (element.type.isNull()) break; else elements.push_back(element); } } if (in_union) return clang::QualType(); if (is_templated) return clang::QualType(); // This is where we bail out. Sorry! ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); if (!lldb_ctx) return clang::QualType(); CompilerType union_type(lldb_ctx->CreateRecordType( nullptr, lldb::eAccessPublic, name.c_str(), kind, lldb::eLanguageTypeC)); if (union_type) { ClangASTContext::StartTagDeclarationDefinition(union_type); unsigned int count = 0; for (auto element : elements) { if (element.name.empty()) { StreamString elem_name; elem_name.Printf("__unnamed_%u", count); element.name = elem_name.GetString(); } ClangASTContext::AddFieldToRecordType( union_type, element.name.c_str(), CompilerType(&ast_ctx, element.type), lldb::eAccessPublic, element.bitfield); ++count; } ClangASTContext::CompleteTagDeclarationDefinition(union_type); } return ClangUtil::GetQualType(union_type); } clang::QualType AppleObjCTypeEncodingParser::BuildArray(clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression) { if (!type.NextIf('[')) return clang::QualType(); uint32_t size = ReadNumber(type); clang::QualType element_type(BuildType(ast_ctx, type, for_expression)); if (!type.NextIf(']')) return clang::QualType(); ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); if (!lldb_ctx) return clang::QualType(); CompilerType array_type(lldb_ctx->CreateArrayType( CompilerType(&ast_ctx, element_type), size, false)); return ClangUtil::GetQualType(array_type); } // the runtime can emit these in the form of @"SomeType", giving more specifics // this would be interesting for expression parser interop, but since we // actually try // to avoid exposing the ivar info to the expression evaluator, consume but // ignore the type info // and always return an 'id'; if anything, dynamic typing will resolve things // for us anyway clang::QualType AppleObjCTypeEncodingParser::BuildObjCObjectPointerType( clang::ASTContext &ast_ctx, lldb_utility::StringLexer &type, bool for_expression) { if (!type.NextIf('@')) return clang::QualType(); std::string name; if (type.NextIf('"')) { // We have to be careful here. We're used to seeing // @"NSString" // but in records it is possible that the string following an @ is the name // of the next field and @ means "id". // This is the case if anything unquoted except for "}", the end of the // type, or another name follows the quoted string. // // E.g. // - @"NSString"@ means "id, followed by a field named NSString of type id" // - @"NSString"} means "a pointer to NSString and the end of the struct" // - @"NSString""nextField" means "a pointer to NSString and a field named // nextField" // - @"NSString" followed by the end of the string means "a pointer to // NSString" // // As a result, the rule is: If we see @ followed by a quoted string, we // peek. // - If we see }, ), ], the end of the string, or a quote ("), the quoted // string is a class name. // - If we see anything else, the quoted string is a field name and we push // it back onto type. name = ReadQuotedString(type); if (type.HasAtLeast(1)) { switch (type.Peek()) { default: // roll back type.PutBack(name.length() + 2); // undo our consumption of the string and of the quotes name.clear(); break; case '}': case ')': case ']': case '"': // the quoted string is a class name – see the rule break; } } else { // the quoted string is a class name – see the rule } } if (for_expression && !name.empty()) { size_t less_than_pos = name.find('<'); if (less_than_pos != std::string::npos) { if (less_than_pos == 0) return ast_ctx.getObjCIdType(); else name.erase(less_than_pos); } DeclVendor *decl_vendor = m_runtime.GetDeclVendor(); if (!decl_vendor) return clang::QualType(); const bool append = false; const uint32_t max_matches = 1; std::vector decls; uint32_t num_types = decl_vendor->FindDecls(ConstString(name), append, max_matches, decls); // The user can forward-declare something that has no definition. The runtime // doesn't prohibit this at all. // This is a rare and very weird case. We keep this assert in debug builds so // we catch other weird cases. #ifdef LLDB_CONFIGURATION_DEBUG assert(num_types); #else if (!num_types) return ast_ctx.getObjCIdType(); #endif return ClangUtil::GetQualType( ClangASTContext::GetTypeForDecl(decls[0]).GetPointerType()); } else { // We're going to resolve this dynamically anyway, so just smile and wave. return ast_ctx.getObjCIdType(); } } clang::QualType AppleObjCTypeEncodingParser::BuildType(clang::ASTContext &ast_ctx, StringLexer &type, bool for_expression, uint32_t *bitfield_bit_size) { if (!type.HasAtLeast(1)) return clang::QualType(); switch (type.Peek()) { default: break; case '{': return BuildStruct(ast_ctx, type, for_expression); case '[': return BuildArray(ast_ctx, type, for_expression); case '(': return BuildUnion(ast_ctx, type, for_expression); case '@': return BuildObjCObjectPointerType(ast_ctx, type, for_expression); } switch (type.Next()) { default: type.PutBack(1); return clang::QualType(); case 'c': return ast_ctx.CharTy; case 'i': return ast_ctx.IntTy; case 's': return ast_ctx.ShortTy; case 'l': return ast_ctx.getIntTypeForBitwidth(32, true); // this used to be done like this: // ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); // if (!lldb_ctx) // return clang::QualType(); // return lldb_ctx->GetIntTypeFromBitSize(32, true).GetQualType(); // which uses one of the constants if one is available, but we don't think all // this work is necessary. case 'q': return ast_ctx.LongLongTy; case 'C': return ast_ctx.UnsignedCharTy; case 'I': return ast_ctx.UnsignedIntTy; case 'S': return ast_ctx.UnsignedShortTy; case 'L': return ast_ctx.getIntTypeForBitwidth(32, false); // see note for 'l' case 'Q': return ast_ctx.UnsignedLongLongTy; case 'f': return ast_ctx.FloatTy; case 'd': return ast_ctx.DoubleTy; case 'B': return ast_ctx.BoolTy; case 'v': return ast_ctx.VoidTy; case '*': return ast_ctx.getPointerType(ast_ctx.CharTy); case '#': return ast_ctx.getObjCClassType(); case ':': return ast_ctx.getObjCSelType(); case 'b': { uint32_t size = ReadNumber(type); if (bitfield_bit_size) { *bitfield_bit_size = size; return ast_ctx.UnsignedIntTy; // FIXME: the spec is fairly vague here. } else return clang::QualType(); } case 'r': { clang::QualType target_type = BuildType(ast_ctx, type, for_expression); if (target_type.isNull()) return clang::QualType(); else if (target_type == ast_ctx.UnknownAnyTy) return ast_ctx.UnknownAnyTy; else return ast_ctx.getConstType(target_type); } case '^': { if (!for_expression && type.NextIf('?')) { // if we are not supporting the concept of unknownAny, but what is being // created here is an unknownAny*, then // we can just get away with a void* // this is theoretically wrong (in the same sense as 'theoretically // nothing exists') but is way better than outright failure // in many practical cases return ast_ctx.VoidPtrTy; } else { clang::QualType target_type = BuildType(ast_ctx, type, for_expression); if (target_type.isNull()) return clang::QualType(); else if (target_type == ast_ctx.UnknownAnyTy) return ast_ctx.UnknownAnyTy; else return ast_ctx.getPointerType(target_type); } } case '?': return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType(); } } CompilerType AppleObjCTypeEncodingParser::RealizeType( clang::ASTContext &ast_ctx, const char *name, bool for_expression) { if (name && name[0]) { StringLexer lexer(name); clang::QualType qual_type = BuildType(ast_ctx, lexer, for_expression); return CompilerType(&ast_ctx, qual_type); } return CompilerType(); }