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_sp)
28 return;
29
30 m_scratch_ast_ctx_sp = std::make_shared<TypeSystemClang>(
31 "AppleObjCTypeEncodingParser ASTContext",
32 runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple());
33 }
34
ReadStructName(StringLexer & type)35 std::string AppleObjCTypeEncodingParser::ReadStructName(StringLexer &type) {
36 StreamString buffer;
37 while (type.HasAtLeast(1) && type.Peek() != '=')
38 buffer.Printf("%c", type.Next());
39 return std::string(buffer.GetString());
40 }
41
ReadQuotedString(StringLexer & type)42 std::string AppleObjCTypeEncodingParser::ReadQuotedString(StringLexer &type) {
43 StreamString buffer;
44 while (type.HasAtLeast(1) && type.Peek() != '"')
45 buffer.Printf("%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
ReadNumber(StringLexer & type)52 uint32_t AppleObjCTypeEncodingParser::ReadNumber(StringLexer &type) {
53 uint32_t total = 0;
54 while (type.HasAtLeast(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
StructElement()63 AppleObjCTypeEncodingParser::StructElement::StructElement()
64 : type(clang::QualType()) {}
65
66 AppleObjCTypeEncodingParser::StructElement
ReadStructElement(TypeSystemClang & ast_ctx,StringLexer & type,bool for_expression)67 AppleObjCTypeEncodingParser::ReadStructElement(TypeSystemClang &ast_ctx,
68 StringLexer &type,
69 bool for_expression) {
70 StructElement retval;
71 if (type.NextIf('"'))
72 retval.name = ReadQuotedString(type);
73 if (!type.NextIf('"'))
74 return retval;
75 uint32_t bitfield_size = 0;
76 retval.type = BuildType(ast_ctx, type, for_expression, &bitfield_size);
77 retval.bitfield = bitfield_size;
78 return retval;
79 }
80
BuildStruct(TypeSystemClang & ast_ctx,StringLexer & type,bool for_expression)81 clang::QualType AppleObjCTypeEncodingParser::BuildStruct(
82 TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) {
83 return BuildAggregate(ast_ctx, type, for_expression, _C_STRUCT_B, _C_STRUCT_E,
84 clang::TTK_Struct);
85 }
86
BuildUnion(TypeSystemClang & ast_ctx,StringLexer & type,bool for_expression)87 clang::QualType AppleObjCTypeEncodingParser::BuildUnion(
88 TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) {
89 return BuildAggregate(ast_ctx, type, for_expression, _C_UNION_B, _C_UNION_E,
90 clang::TTK_Union);
91 }
92
BuildAggregate(TypeSystemClang & ast_ctx,StringLexer & type,bool for_expression,char opener,char closer,uint32_t kind)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(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('<') != std::string::npos;
105
106 if (!type.NextIf('='))
107 return clang::QualType();
108 bool in_union = true;
109 std::vector<StructElement> elements;
110 while (in_union && type.HasAtLeast(1)) {
111 if (type.NextIf(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(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 nullptr, OptionalClangModuleID(), lldb::eAccessPublic, name, kind,
130 lldb::eLanguageTypeC));
131 if (union_type) {
132 TypeSystemClang::StartTagDeclarationDefinition(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("__unnamed_%u", count);
139 element.name = std::string(elem_name.GetString());
140 }
141 TypeSystemClang::AddFieldToRecordType(
142 union_type, element.name.c_str(), ast_ctx.GetType(element.type),
143 lldb::eAccessPublic, element.bitfield);
144 ++count;
145 }
146 TypeSystemClang::CompleteTagDeclarationDefinition(union_type);
147 }
148 return ClangUtil::GetQualType(union_type);
149 }
150
BuildArray(TypeSystemClang & ast_ctx,StringLexer & type,bool for_expression)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(ast_ctx, type, for_expression));
157 if (!type.NextIf(_C_ARY_E))
158 return clang::QualType();
159 CompilerType array_type(ast_ctx.CreateArrayType(
160 CompilerType(ast_ctx.weak_from_this(), element_type.getAsOpaquePtr()),
161 size, false));
162 return ClangUtil::GetQualType(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
BuildObjCObjectPointerType(TypeSystemClang & clang_ast_ctx,StringLexer & type,bool for_expression)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('"')) {
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(1)) {
202 switch (type.Peek()) {
203 default:
204 // roll back
205 type.PutBack(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('<');
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(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(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 #ifdef LLDB_CONFIGURATION_DEBUG
241 assert(!types.empty());
242 #else
243 if (types.empty())
244 return ast_ctx.getObjCIdType();
245 #endif
246
247 return ClangUtil::GetQualType(types.front().GetPointerType());
248 } else {
249 // We're going to resolve this dynamically anyway, so just smile and wave.
250 return ast_ctx.getObjCIdType();
251 }
252 }
253
254 clang::QualType
BuildType(TypeSystemClang & clang_ast_ctx,StringLexer & type,bool for_expression,uint32_t * bitfield_bit_size)255 AppleObjCTypeEncodingParser::BuildType(TypeSystemClang &clang_ast_ctx,
256 StringLexer &type, bool for_expression,
257 uint32_t *bitfield_bit_size) {
258 if (!type.HasAtLeast(1))
259 return clang::QualType();
260
261 clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext();
262
263 switch (type.Peek()) {
264 default:
265 break;
266 case _C_STRUCT_B:
267 return BuildStruct(clang_ast_ctx, type, for_expression);
268 case _C_ARY_B:
269 return BuildArray(clang_ast_ctx, type, for_expression);
270 case _C_UNION_B:
271 return BuildUnion(clang_ast_ctx, type, for_expression);
272 case _C_ID:
273 return BuildObjCObjectPointerType(clang_ast_ctx, type, for_expression);
274 }
275
276 switch (type.Next()) {
277 default:
278 type.PutBack(1);
279 return clang::QualType();
280 case _C_CHR:
281 return ast_ctx.CharTy;
282 case _C_INT:
283 return ast_ctx.IntTy;
284 case _C_SHT:
285 return ast_ctx.ShortTy;
286 case _C_LNG:
287 return ast_ctx.getIntTypeForBitwidth(32, true);
288 // this used to be done like this:
289 // return clang_ast_ctx->GetIntTypeFromBitSize(32, true).GetQualType();
290 // which uses one of the constants if one is available, but we don't think
291 // all this work is necessary.
292 case _C_LNG_LNG:
293 return ast_ctx.LongLongTy;
294 case _C_UCHR:
295 return ast_ctx.UnsignedCharTy;
296 case _C_UINT:
297 return ast_ctx.UnsignedIntTy;
298 case _C_USHT:
299 return ast_ctx.UnsignedShortTy;
300 case _C_ULNG:
301 return ast_ctx.getIntTypeForBitwidth(32, false);
302 // see note for _C_LNG
303 case _C_ULNG_LNG:
304 return ast_ctx.UnsignedLongLongTy;
305 case _C_FLT:
306 return ast_ctx.FloatTy;
307 case _C_DBL:
308 return ast_ctx.DoubleTy;
309 case _C_BOOL:
310 return ast_ctx.BoolTy;
311 case _C_VOID:
312 return ast_ctx.VoidTy;
313 case _C_CHARPTR:
314 return ast_ctx.getPointerType(ast_ctx.CharTy);
315 case _C_CLASS:
316 return ast_ctx.getObjCClassType();
317 case _C_SEL:
318 return ast_ctx.getObjCSelType();
319 case _C_BFLD: {
320 uint32_t size = ReadNumber(type);
321 if (bitfield_bit_size) {
322 *bitfield_bit_size = size;
323 return ast_ctx.UnsignedIntTy; // FIXME: the spec is fairly vague here.
324 } else
325 return clang::QualType();
326 }
327 case _C_CONST: {
328 clang::QualType target_type =
329 BuildType(clang_ast_ctx, type, for_expression);
330 if (target_type.isNull())
331 return clang::QualType();
332 else if (target_type == ast_ctx.UnknownAnyTy)
333 return ast_ctx.UnknownAnyTy;
334 else
335 return ast_ctx.getConstType(target_type);
336 }
337 case _C_PTR: {
338 if (!for_expression && type.NextIf(_C_UNDEF)) {
339 // if we are not supporting the concept of unknownAny, but what is being
340 // created here is an unknownAny*, then we can just get away with a void*
341 // this is theoretically wrong (in the same sense as 'theoretically
342 // nothing exists') but is way better than outright failure in many
343 // practical cases
344 return ast_ctx.VoidPtrTy;
345 } else {
346 clang::QualType target_type =
347 BuildType(clang_ast_ctx, type, for_expression);
348 if (target_type.isNull())
349 return clang::QualType();
350 else if (target_type == ast_ctx.UnknownAnyTy)
351 return ast_ctx.UnknownAnyTy;
352 else
353 return ast_ctx.getPointerType(target_type);
354 }
355 }
356 case _C_UNDEF:
357 return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType();
358 }
359 }
360
RealizeType(TypeSystemClang & ast_ctx,const char * name,bool for_expression)361 CompilerType AppleObjCTypeEncodingParser::RealizeType(TypeSystemClang &ast_ctx,
362 const char *name,
363 bool for_expression) {
364 if (name && name[0]) {
365 StringLexer lexer(name);
366 clang::QualType qual_type = BuildType(ast_ctx, lexer, for_expression);
367 return ast_ctx.GetType(qual_type);
368 }
369 return CompilerType();
370 }
371