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