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 | |
24 | AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser( |
25 | ObjCLanguageRuntime &runtime) |
26 | : ObjCLanguageRuntime::EncodingToType(), m_runtime(runtime) { |
27 | if (m_scratch_ast_ctx_sp) |
28 | return; |
29 | |
30 | m_scratch_ast_ctx_sp = std::make_shared<TypeSystemClang>( |
31 | args: "AppleObjCTypeEncodingParser ASTContext" , |
32 | args: runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple()); |
33 | } |
34 | |
35 | std::string AppleObjCTypeEncodingParser::ReadStructName(StringLexer &type) { |
36 | StreamString buffer; |
37 | while (type.HasAtLeast(s: 1) && type.Peek() != '=') |
38 | buffer.Printf(format: "%c" , type.Next()); |
39 | return std::string(buffer.GetString()); |
40 | } |
41 | |
42 | std::string AppleObjCTypeEncodingParser::ReadQuotedString(StringLexer &type) { |
43 | StreamString buffer; |
44 | while (type.HasAtLeast(s: 1) && type.Peek() != '"') |
45 | buffer.Printf(format: "%c" , type.Next()); |
46 | StringLexer::Character next = type.Next(); |
47 | UNUSED_IF_ASSERT_DISABLED(next); |
48 | assert(next == '"'); |
49 | return std::string(buffer.GetString()); |
50 | } |
51 | |
52 | uint32_t AppleObjCTypeEncodingParser::ReadNumber(StringLexer &type) { |
53 | uint32_t total = 0; |
54 | while (type.HasAtLeast(s: 1) && isdigit(type.Peek())) |
55 | total = 10 * total + (type.Next() - '0'); |
56 | return total; |
57 | } |
58 | |
59 | // as an extension to the published grammar recent runtimes emit structs like |
60 | // this: |
61 | // "{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}" |
62 | |
63 | AppleObjCTypeEncodingParser::StructElement::StructElement() |
64 | : type(clang::QualType()) {} |
65 | |
66 | AppleObjCTypeEncodingParser::StructElement |
67 | AppleObjCTypeEncodingParser::ReadStructElement(TypeSystemClang &ast_ctx, |
68 | StringLexer &type, |
69 | bool for_expression) { |
70 | StructElement retval; |
71 | if (type.NextIf(c: '"')) |
72 | retval.name = ReadQuotedString(type); |
73 | if (!type.NextIf(c: '"')) |
74 | return retval; |
75 | uint32_t bitfield_size = 0; |
76 | retval.type = BuildType(clang_ast_ctx&: ast_ctx, type, for_expression, bitfield_bit_size: &bitfield_size); |
77 | retval.bitfield = bitfield_size; |
78 | return retval; |
79 | } |
80 | |
81 | clang::QualType AppleObjCTypeEncodingParser::BuildStruct( |
82 | TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { |
83 | return BuildAggregate(clang_ast_ctx&: ast_ctx, type, for_expression, _C_STRUCT_B, _C_STRUCT_E, |
84 | kind: llvm::to_underlying(E: clang::TagTypeKind::Struct)); |
85 | } |
86 | |
87 | clang::QualType AppleObjCTypeEncodingParser::BuildUnion( |
88 | TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { |
89 | return BuildAggregate(clang_ast_ctx&: ast_ctx, type, for_expression, _C_UNION_B, _C_UNION_E, |
90 | kind: llvm::to_underlying(E: clang::TagTypeKind::Union)); |
91 | } |
92 | |
93 | clang::QualType AppleObjCTypeEncodingParser::BuildAggregate( |
94 | TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression, |
95 | char opener, char closer, uint32_t kind) { |
96 | if (!type.NextIf(c: opener)) |
97 | return clang::QualType(); |
98 | std::string name(ReadStructName(type)); |
99 | |
100 | // We do not handle templated classes/structs at the moment. If the name has |
101 | // a < in it, we are going to abandon this. We're still obliged to parse it, |
102 | // so we just set a flag that means "Don't actually build anything." |
103 | |
104 | const bool is_templated = name.find(c: '<') != std::string::npos; |
105 | |
106 | if (!type.NextIf(c: '=')) |
107 | return clang::QualType(); |
108 | bool in_union = true; |
109 | std::vector<StructElement> elements; |
110 | while (in_union && type.HasAtLeast(s: 1)) { |
111 | if (type.NextIf(c: closer)) { |
112 | in_union = false; |
113 | break; |
114 | } else { |
115 | auto element = ReadStructElement(ast_ctx, type, for_expression); |
116 | if (element.type.isNull()) |
117 | break; |
118 | else |
119 | elements.push_back(x: element); |
120 | } |
121 | } |
122 | if (in_union) |
123 | return clang::QualType(); |
124 | |
125 | if (is_templated) |
126 | return clang::QualType(); // This is where we bail out. Sorry! |
127 | |
128 | CompilerType union_type(ast_ctx.CreateRecordType( |
129 | decl_ctx: nullptr, owning_module: OptionalClangModuleID(), access_type: lldb::eAccessPublic, name, kind, |
130 | language: lldb::eLanguageTypeC)); |
131 | if (union_type) { |
132 | TypeSystemClang::StartTagDeclarationDefinition(type: union_type); |
133 | |
134 | unsigned int count = 0; |
135 | for (auto element : elements) { |
136 | if (element.name.empty()) { |
137 | StreamString elem_name; |
138 | elem_name.Printf(format: "__unnamed_%u" , count); |
139 | element.name = std::string(elem_name.GetString()); |
140 | } |
141 | TypeSystemClang::AddFieldToRecordType( |
142 | type: union_type, name: element.name.c_str(), field_type: ast_ctx.GetType(qt: element.type), |
143 | access: lldb::eAccessPublic, bitfield_bit_size: element.bitfield); |
144 | ++count; |
145 | } |
146 | TypeSystemClang::CompleteTagDeclarationDefinition(type: union_type); |
147 | } |
148 | return ClangUtil::GetQualType(ct: union_type); |
149 | } |
150 | |
151 | clang::QualType AppleObjCTypeEncodingParser::BuildArray( |
152 | TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { |
153 | if (!type.NextIf(_C_ARY_B)) |
154 | return clang::QualType(); |
155 | uint32_t size = ReadNumber(type); |
156 | clang::QualType element_type(BuildType(clang_ast_ctx&: ast_ctx, type, for_expression)); |
157 | if (!type.NextIf(_C_ARY_E)) |
158 | return clang::QualType(); |
159 | CompilerType array_type(ast_ctx.CreateArrayType( |
160 | element_type: CompilerType(ast_ctx.weak_from_this(), element_type.getAsOpaquePtr()), |
161 | element_count: size, is_vector: false)); |
162 | return ClangUtil::GetQualType(ct: array_type); |
163 | } |
164 | |
165 | // the runtime can emit these in the form of @"SomeType", giving more specifics |
166 | // this would be interesting for expression parser interop, but since we |
167 | // actually try to avoid exposing the ivar info to the expression evaluator, |
168 | // consume but ignore the type info and always return an 'id'; if anything, |
169 | // dynamic typing will resolve things for us anyway |
170 | clang::QualType AppleObjCTypeEncodingParser::BuildObjCObjectPointerType( |
171 | TypeSystemClang &clang_ast_ctx, StringLexer &type, bool for_expression) { |
172 | if (!type.NextIf(_C_ID)) |
173 | return clang::QualType(); |
174 | |
175 | clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext(); |
176 | |
177 | std::string name; |
178 | |
179 | if (type.NextIf(c: '"')) { |
180 | // We have to be careful here. We're used to seeing |
181 | // @"NSString" |
182 | // but in records it is possible that the string following an @ is the name |
183 | // of the next field and @ means "id". This is the case if anything |
184 | // unquoted except for "}", the end of the type, or another name follows |
185 | // the quoted string. |
186 | // |
187 | // E.g. |
188 | // - @"NSString"@ means "id, followed by a field named NSString of type id" |
189 | // - @"NSString"} means "a pointer to NSString and the end of the struct" - |
190 | // @"NSString""nextField" means "a pointer to NSString and a field named |
191 | // nextField" - @"NSString" followed by the end of the string means "a |
192 | // pointer to NSString" |
193 | // |
194 | // As a result, the rule is: If we see @ followed by a quoted string, we |
195 | // peek. - If we see }, ), ], the end of the string, or a quote ("), the |
196 | // quoted string is a class name. - If we see anything else, the quoted |
197 | // string is a field name and we push it back onto type. |
198 | |
199 | name = ReadQuotedString(type); |
200 | |
201 | if (type.HasAtLeast(s: 1)) { |
202 | switch (type.Peek()) { |
203 | default: |
204 | // roll back |
205 | type.PutBack(s: name.length() + |
206 | 2); // undo our consumption of the string and of the quotes |
207 | name.clear(); |
208 | break; |
209 | case _C_STRUCT_E: |
210 | case _C_UNION_E: |
211 | case _C_ARY_E: |
212 | case '"': |
213 | // the quoted string is a class name – see the rule |
214 | break; |
215 | } |
216 | } else { |
217 | // the quoted string is a class name – see the rule |
218 | } |
219 | } |
220 | |
221 | if (for_expression && !name.empty()) { |
222 | size_t less_than_pos = name.find(c: '<'); |
223 | |
224 | if (less_than_pos != std::string::npos) { |
225 | if (less_than_pos == 0) |
226 | return ast_ctx.getObjCIdType(); |
227 | else |
228 | name.erase(pos: less_than_pos); |
229 | } |
230 | |
231 | DeclVendor *decl_vendor = m_runtime.GetDeclVendor(); |
232 | if (!decl_vendor) |
233 | return clang::QualType(); |
234 | |
235 | auto types = decl_vendor->FindTypes(name: ConstString(name), /*max_matches*/ 1); |
236 | |
237 | // The user can forward-declare something that has no definition. The runtime |
238 | // doesn't prohibit this at all. This is a rare and very weird case. We keep |
239 | // this assert in debug builds so we catch other weird cases. |
240 | lldbassert(!types.empty()); |
241 | if (types.empty()) |
242 | return ast_ctx.getObjCIdType(); |
243 | |
244 | return ClangUtil::GetQualType(ct: 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 |
252 | AppleObjCTypeEncodingParser::BuildType(TypeSystemClang &clang_ast_ctx, |
253 | StringLexer &type, bool for_expression, |
254 | uint32_t *bitfield_bit_size) { |
255 | if (!type.HasAtLeast(s: 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 _C_STRUCT_B: |
264 | return BuildStruct(ast_ctx&: clang_ast_ctx, type, for_expression); |
265 | case _C_ARY_B: |
266 | return BuildArray(ast_ctx&: clang_ast_ctx, type, for_expression); |
267 | case _C_UNION_B: |
268 | return BuildUnion(ast_ctx&: clang_ast_ctx, type, for_expression); |
269 | case _C_ID: |
270 | return BuildObjCObjectPointerType(clang_ast_ctx, type, for_expression); |
271 | } |
272 | |
273 | switch (type.Next()) { |
274 | default: |
275 | type.PutBack(s: 1); |
276 | return clang::QualType(); |
277 | case _C_CHR: |
278 | return ast_ctx.CharTy; |
279 | case _C_INT: |
280 | return ast_ctx.IntTy; |
281 | case _C_SHT: |
282 | return ast_ctx.ShortTy; |
283 | case _C_LNG: |
284 | return ast_ctx.getIntTypeForBitwidth(DestWidth: 32, Signed: 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 _C_LNG_LNG: |
290 | return ast_ctx.LongLongTy; |
291 | case _C_UCHR: |
292 | return ast_ctx.UnsignedCharTy; |
293 | case _C_UINT: |
294 | return ast_ctx.UnsignedIntTy; |
295 | case _C_USHT: |
296 | return ast_ctx.UnsignedShortTy; |
297 | case _C_ULNG: |
298 | return ast_ctx.getIntTypeForBitwidth(DestWidth: 32, Signed: false); |
299 | // see note for _C_LNG |
300 | case _C_ULNG_LNG: |
301 | return ast_ctx.UnsignedLongLongTy; |
302 | case _C_FLT: |
303 | return ast_ctx.FloatTy; |
304 | case _C_DBL: |
305 | return ast_ctx.DoubleTy; |
306 | case _C_BOOL: |
307 | return ast_ctx.BoolTy; |
308 | case _C_VOID: |
309 | return ast_ctx.VoidTy; |
310 | case _C_CHARPTR: |
311 | return ast_ctx.getPointerType(ast_ctx.CharTy); |
312 | case _C_CLASS: |
313 | return ast_ctx.getObjCClassType(); |
314 | case _C_SEL: |
315 | return ast_ctx.getObjCSelType(); |
316 | case _C_BFLD: { |
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 _C_CONST: { |
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(T: target_type); |
333 | } |
334 | case _C_PTR: { |
335 | if (!for_expression && type.NextIf(_C_UNDEF)) { |
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(T: target_type); |
351 | } |
352 | } |
353 | case _C_UNDEF: |
354 | return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType(); |
355 | } |
356 | } |
357 | |
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(clang_ast_ctx&: ast_ctx, type&: lexer, for_expression); |
364 | return ast_ctx.GetType(qt: qual_type); |
365 | } |
366 | return CompilerType(); |
367 | } |
368 | |