1//===-- CxxModuleHandler.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 "Plugins/ExpressionParser/Clang/CxxModuleHandler.h"
10#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
11
12#include "lldb/Utility/LLDBLog.h"
13#include "lldb/Utility/Log.h"
14#include "clang/Sema/Lookup.h"
15#include "llvm/Support/Error.h"
16#include <optional>
17
18using namespace lldb_private;
19using namespace clang;
20
21CxxModuleHandler::CxxModuleHandler(ASTImporter &importer, ASTContext *target)
22 : m_importer(&importer),
23 m_sema(TypeSystemClang::GetASTContext(ast_ctx: target)->getSema()) {
24
25 std::initializer_list<const char *> supported_names = {
26 // containers
27 "array",
28 "deque",
29 "forward_list",
30 "list",
31 "queue",
32 "stack",
33 "vector",
34 // pointers
35 "shared_ptr",
36 "unique_ptr",
37 "weak_ptr",
38 // iterator
39 "move_iterator",
40 "__wrap_iter",
41 // utility
42 "allocator",
43 "pair",
44 };
45 m_supported_templates.insert(begin: supported_names.begin(), end: supported_names.end());
46}
47
48/// Builds a list of scopes that point into the given context.
49///
50/// \param sema The sema that will be using the scopes.
51/// \param ctxt The context that the scope should look into.
52/// \param result A list of scopes. The scopes need to be freed by the caller
53/// (except the TUScope which is owned by the sema).
54static void makeScopes(Sema &sema, DeclContext *ctxt,
55 std::vector<Scope *> &result) {
56 // FIXME: The result should be a list of unique_ptrs, but the TUScope makes
57 // this currently impossible as it's owned by the Sema.
58
59 if (auto parent = ctxt->getParent()) {
60 makeScopes(sema, ctxt: parent, result);
61
62 Scope *scope =
63 new Scope(result.back(), Scope::DeclScope, sema.getDiagnostics());
64 scope->setEntity(ctxt);
65 result.push_back(x: scope);
66 } else
67 result.push_back(x: sema.TUScope);
68}
69
70/// Uses the Sema to look up the given name in the given DeclContext.
71static std::unique_ptr<LookupResult>
72emulateLookupInCtxt(Sema &sema, llvm::StringRef name, DeclContext *ctxt) {
73 IdentifierInfo &ident = sema.getASTContext().Idents.get(Name: name);
74
75 std::unique_ptr<LookupResult> lookup_result;
76 lookup_result = std::make_unique<LookupResult>(args&: sema, args: DeclarationName(&ident),
77 args: SourceLocation(),
78 args: Sema::LookupOrdinaryName);
79
80 // Usually during parsing we already encountered the scopes we would use. But
81 // here don't have these scopes so we have to emulate the behavior of the
82 // Sema during parsing.
83 std::vector<Scope *> scopes;
84 makeScopes(sema, ctxt, result&: scopes);
85
86 // Now actually perform the lookup with the sema.
87 sema.LookupName(R&: *lookup_result, S: scopes.back());
88
89 // Delete all the allocated scopes beside the translation unit scope (which
90 // has depth 0).
91 for (Scope *s : scopes)
92 if (s->getDepth() != 0)
93 delete s;
94
95 return lookup_result;
96}
97
98/// Error class for handling problems when finding a certain DeclContext.
99struct MissingDeclContext : public llvm::ErrorInfo<MissingDeclContext> {
100
101 static char ID;
102
103 MissingDeclContext(DeclContext *context, std::string error)
104 : m_context(context), m_error(error) {}
105
106 DeclContext *m_context;
107 std::string m_error;
108
109 void log(llvm::raw_ostream &OS) const override {
110 OS << llvm::formatv(Fmt: "error when reconstructing context of kind {0}:{1}",
111 Vals: m_context->getDeclKindName(), Vals: m_error);
112 }
113
114 std::error_code convertToErrorCode() const override {
115 return llvm::inconvertibleErrorCode();
116 }
117};
118
119char MissingDeclContext::ID = 0;
120
121/// Given a foreign decl context, this function finds the equivalent local
122/// decl context in the ASTContext of the given Sema. Potentially deserializes
123/// decls from the 'std' module if necessary.
124static llvm::Expected<DeclContext *>
125getEqualLocalDeclContext(Sema &sema, DeclContext *foreign_ctxt) {
126
127 // Inline namespaces don't matter for lookups, so let's skip them.
128 while (foreign_ctxt && foreign_ctxt->isInlineNamespace())
129 foreign_ctxt = foreign_ctxt->getParent();
130
131 // If the foreign context is the TU, we just return the local TU.
132 if (foreign_ctxt->isTranslationUnit())
133 return sema.getASTContext().getTranslationUnitDecl();
134
135 // Recursively find/build the parent DeclContext.
136 llvm::Expected<DeclContext *> parent =
137 getEqualLocalDeclContext(sema, foreign_ctxt: foreign_ctxt->getParent());
138 if (!parent)
139 return parent;
140
141 // We currently only support building namespaces.
142 if (foreign_ctxt->isNamespace()) {
143 NamedDecl *ns = llvm::cast<NamedDecl>(Val: foreign_ctxt);
144 llvm::StringRef ns_name = ns->getName();
145
146 auto lookup_result = emulateLookupInCtxt(sema, name: ns_name, ctxt: *parent);
147 for (NamedDecl *named_decl : *lookup_result) {
148 if (DeclContext *DC = llvm::dyn_cast<DeclContext>(Val: named_decl))
149 return DC->getPrimaryContext();
150 }
151 return llvm::make_error<MissingDeclContext>(
152 Args&: foreign_ctxt,
153 Args: "Couldn't find namespace " + ns->getQualifiedNameAsString());
154 }
155
156 return llvm::make_error<MissingDeclContext>(Args&: foreign_ctxt, Args: "Unknown context ");
157}
158
159/// Returns true iff tryInstantiateStdTemplate supports instantiating a template
160/// with the given template arguments.
161static bool templateArgsAreSupported(ArrayRef<TemplateArgument> a) {
162 for (const TemplateArgument &arg : a) {
163 switch (arg.getKind()) {
164 case TemplateArgument::Type:
165 case TemplateArgument::Integral:
166 break;
167 default:
168 // TemplateArgument kind hasn't been handled yet.
169 return false;
170 }
171 }
172 return true;
173}
174
175/// Constructor function for Clang declarations. Ensures that the created
176/// declaration is registered with the ASTImporter.
177template <typename T, typename... Args>
178T *createDecl(ASTImporter &importer, Decl *from_d, Args &&... args) {
179 T *to_d = T::Create(std::forward<Args>(args)...);
180 importer.RegisterImportedDecl(FromD: from_d, ToD: to_d);
181 return to_d;
182}
183
184std::optional<Decl *> CxxModuleHandler::tryInstantiateStdTemplate(Decl *d) {
185 Log *log = GetLog(mask: LLDBLog::Expressions);
186
187 // If we don't have a template to instiantiate, then there is nothing to do.
188 auto td = dyn_cast<ClassTemplateSpecializationDecl>(Val: d);
189 if (!td)
190 return std::nullopt;
191
192 // We only care about templates in the std namespace.
193 if (!td->getDeclContext()->isStdNamespace())
194 return std::nullopt;
195
196 // We have a list of supported template names.
197 if (!m_supported_templates.contains(key: td->getName()))
198 return std::nullopt;
199
200 // Early check if we even support instantiating this template. We do this
201 // before we import anything into the target AST.
202 auto &foreign_args = td->getTemplateInstantiationArgs();
203 if (!templateArgsAreSupported(a: foreign_args.asArray()))
204 return std::nullopt;
205
206 // Find the local DeclContext that corresponds to the DeclContext of our
207 // decl we want to import.
208 llvm::Expected<DeclContext *> to_context =
209 getEqualLocalDeclContext(*m_sema, td->getDeclContext());
210 if (!to_context) {
211 LLDB_LOG_ERROR(log, to_context.takeError(),
212 "Got error while searching equal local DeclContext for decl "
213 "'{1}':\n{0}",
214 td->getName());
215 return std::nullopt;
216 }
217
218 // Look up the template in our local context.
219 std::unique_ptr<LookupResult> lookup =
220 emulateLookupInCtxt(*m_sema, td->getName(), *to_context);
221
222 ClassTemplateDecl *new_class_template = nullptr;
223 for (auto LD : *lookup) {
224 if ((new_class_template = dyn_cast<ClassTemplateDecl>(LD)))
225 break;
226 }
227 if (!new_class_template)
228 return std::nullopt;
229
230 // Import the foreign template arguments.
231 llvm::SmallVector<TemplateArgument, 4> imported_args;
232
233 // If this logic is changed, also update templateArgsAreSupported.
234 for (const TemplateArgument &arg : foreign_args.asArray()) {
235 switch (arg.getKind()) {
236 case TemplateArgument::Type: {
237 llvm::Expected<QualType> type = m_importer->Import(FromT: arg.getAsType());
238 if (!type) {
239 LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");
240 return std::nullopt;
241 }
242 imported_args.push_back(
243 Elt: TemplateArgument(*type, /*isNullPtr*/ false, arg.getIsDefaulted()));
244 break;
245 }
246 case TemplateArgument::Integral: {
247 llvm::APSInt integral = arg.getAsIntegral();
248 llvm::Expected<QualType> type =
249 m_importer->Import(FromT: arg.getIntegralType());
250 if (!type) {
251 LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");
252 return std::nullopt;
253 }
254 imported_args.push_back(Elt: TemplateArgument(d->getASTContext(), integral,
255 *type, arg.getIsDefaulted()));
256 break;
257 }
258 default:
259 assert(false && "templateArgsAreSupported not updated?");
260 }
261 }
262
263 // Find the class template specialization declaration that
264 // corresponds to these arguments.
265 void *InsertPos = nullptr;
266 ClassTemplateSpecializationDecl *result =
267 new_class_template->findSpecialization(Args: imported_args, InsertPos);
268
269 if (result) {
270 // We found an existing specialization in the module that fits our arguments
271 // so we can treat it as the result and register it with the ASTImporter.
272 m_importer->RegisterImportedDecl(d, result);
273 return result;
274 }
275
276 // Instantiate the template.
277 result = createDecl<ClassTemplateSpecializationDecl>(
278 *m_importer, d, m_sema->getASTContext(),
279 new_class_template->getTemplatedDecl()->getTagKind(),
280 new_class_template->getDeclContext(),
281 new_class_template->getTemplatedDecl()->getLocation(),
282 new_class_template->getLocation(), new_class_template, imported_args,
283 nullptr);
284
285 new_class_template->AddSpecialization(D: result, InsertPos);
286 if (new_class_template->isOutOfLine())
287 result->setLexicalDeclContext(
288 new_class_template->getLexicalDeclContext());
289 return result;
290}
291
292std::optional<Decl *> CxxModuleHandler::Import(Decl *d) {
293 if (!isValid())
294 return {};
295
296 return tryInstantiateStdTemplate(d);
297}
298

source code of lldb/source/Plugins/ExpressionParser/Clang/CxxModuleHandler.cpp