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