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
22using namespace lldb_private;
23
24AppleObjCTypeEncodingParser::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
35std::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
42std::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
52uint32_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
63AppleObjCTypeEncodingParser::StructElement::StructElement()
64 : type(clang::QualType()) {}
65
66AppleObjCTypeEncodingParser::StructElement
67AppleObjCTypeEncodingParser::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
81clang::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
87clang::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
93clang::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
151clang::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
170clang::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
251clang::QualType
252AppleObjCTypeEncodingParser::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
358CompilerType 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

source code of lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp