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