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