aboutsummaryrefslogtreecommitdiffstats
path: root/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp
blob: 6402e80d6f9808b98dc9a30a19a6d313aa4875e6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
//===-- AppleObjCTypeEncodingParser.cpp -------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#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 <vector>

using namespace lldb_private;

AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser(
    ObjCLanguageRuntime &runtime)
    : ObjCLanguageRuntime::EncodingToType(), m_runtime(runtime) {
  if (!m_scratch_ast_ctx_up)
    m_scratch_ast_ctx_up.reset(new ClangASTContext(runtime.GetProcess()
                                                       ->GetTarget()
                                                       .GetArchitecture()
                                                       .GetTriple()
                                                       .str()
                                                       .c_str()));
}

std::string AppleObjCTypeEncodingParser::ReadStructName(StringLexer &type) {
  StreamString buffer;
  while (type.HasAtLeast(1) && type.Peek() != '=')
    buffer.Printf("%c", type.Next());
  return buffer.GetString();
}

std::string AppleObjCTypeEncodingParser::ReadQuotedString(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(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,
                                               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, StringLexer &type, bool for_expression) {
  return BuildAggregate(ast_ctx, type, for_expression, '{', '}',
                        clang::TTK_Struct);
}

clang::QualType AppleObjCTypeEncodingParser::BuildUnion(
    clang::ASTContext &ast_ctx, StringLexer &type, bool for_expression) {
  return BuildAggregate(ast_ctx, type, for_expression, '(', ')',
                        clang::TTK_Union);
}

clang::QualType AppleObjCTypeEncodingParser::BuildAggregate(
    clang::ASTContext &ast_ctx, 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<StructElement> 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(ClangASTContext::GetASTContext(&ast_ctx),
                       element.type.getAsOpaquePtr()),
          lldb::eAccessPublic, element.bitfield);
      ++count;
    }
    ClangASTContext::CompleteTagDeclarationDefinition(union_type);
  }
  return ClangUtil::GetQualType(union_type);
}

clang::QualType AppleObjCTypeEncodingParser::BuildArray(
    clang::ASTContext &ast_ctx, 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(ClangASTContext::GetASTContext(&ast_ctx),
                   element_type.getAsOpaquePtr()),
      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, 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();

    auto types = decl_vendor->FindTypes(ConstString(name), /*max_matches*/ 1);

// 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(!types.empty());
#else
    if (types.empty())
      return ast_ctx.getObjCIdType();
#endif

    return ClangUtil::GetQualType(types.front().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(ClangASTContext::GetASTContext(&ast_ctx),
                        qual_type.getAsOpaquePtr());
  }
  return CompilerType();
}