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 
18 using namespace lldb_private;
19 using namespace clang;
20 
21 CxxModuleHandler::CxxModuleHandler(ASTImporter &importer, ASTContext *target)
22     : m_importer(&importer),
23       m_sema(TypeSystemClang::GetASTContext(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(supported_names.begin(), 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).
54 static 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, parent, result);
61 
62     Scope *scope =
63         new Scope(result.back(), Scope::DeclScope, sema.getDiagnostics());
64     scope->setEntity(ctxt);
65     result.push_back(scope);
66   } else
67     result.push_back(sema.TUScope);
68 }
69 
70 /// Uses the Sema to look up the given name in the given DeclContext.
71 static std::unique_ptr<LookupResult>
72 emulateLookupInCtxt(Sema &sema, llvm::StringRef name, DeclContext *ctxt) {
73   IdentifierInfo &ident = sema.getASTContext().Idents.get(name);
74 
75   std::unique_ptr<LookupResult> lookup_result;
76   lookup_result = std::make_unique<LookupResult>(sema, DeclarationName(&ident),
77                                                  SourceLocation(),
78                                                  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, scopes);
85 
86   // Now actually perform the lookup with the sema.
87   sema.LookupName(*lookup_result, 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.
99 struct 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("error when reconstructing context of kind {0}:{1}",
111                         m_context->getDeclKindName(), m_error);
112   }
113 
114   std::error_code convertToErrorCode() const override {
115     return llvm::inconvertibleErrorCode();
116   }
117 };
118 
119 char 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.
124 static llvm::Expected<DeclContext *>
125 getEqualLocalDeclContext(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->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>(foreign_ctxt);
144     llvm::StringRef ns_name = ns->getName();
145 
146     auto lookup_result = emulateLookupInCtxt(sema, ns_name, *parent);
147     for (NamedDecl *named_decl : *lookup_result) {
148       if (DeclContext *DC = llvm::dyn_cast<DeclContext>(named_decl))
149         return DC->getPrimaryContext();
150     }
151     return llvm::make_error<MissingDeclContext>(
152         foreign_ctxt,
153         "Couldn't find namespace " + ns->getQualifiedNameAsString());
154   }
155 
156   return llvm::make_error<MissingDeclContext>(foreign_ctxt, "Unknown context ");
157 }
158 
159 /// Returns true iff tryInstantiateStdTemplate supports instantiating a template
160 /// with the given template arguments.
161 static 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.
177 template <typename T, typename... Args>
178 T *createDecl(ASTImporter &importer, Decl *from_d, Args &&... args) {
179   T *to_d = T::Create(std::forward<Args>(args)...);
180   importer.RegisterImportedDecl(from_d, to_d);
181   return to_d;
182 }
183 
184 std::optional<Decl *> CxxModuleHandler::tryInstantiateStdTemplate(Decl *d) {
185   Log *log = GetLog(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>(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(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(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(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(TemplateArgument(*type));
243       break;
244     }
245     case TemplateArgument::Integral: {
246       llvm::APSInt integral = arg.getAsIntegral();
247       llvm::Expected<QualType> type =
248           m_importer->Import(arg.getIntegralType());
249       if (!type) {
250         LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");
251         return std::nullopt;
252       }
253       imported_args.push_back(
254           TemplateArgument(d->getASTContext(), integral, *type));
255       break;
256     }
257     default:
258       assert(false && "templateArgsAreSupported not updated?");
259     }
260   }
261 
262   // Find the class template specialization declaration that
263   // corresponds to these arguments.
264   void *InsertPos = nullptr;
265   ClassTemplateSpecializationDecl *result =
266       new_class_template->findSpecialization(imported_args, InsertPos);
267 
268   if (result) {
269     // We found an existing specialization in the module that fits our arguments
270     // so we can treat it as the result and register it with the ASTImporter.
271     m_importer->RegisterImportedDecl(d, result);
272     return result;
273   }
274 
275   // Instantiate the template.
276   result = createDecl<ClassTemplateSpecializationDecl>(
277       *m_importer, d, m_sema->getASTContext(),
278       new_class_template->getTemplatedDecl()->getTagKind(),
279       new_class_template->getDeclContext(),
280       new_class_template->getTemplatedDecl()->getLocation(),
281       new_class_template->getLocation(), new_class_template, imported_args,
282       nullptr);
283 
284   new_class_template->AddSpecialization(result, InsertPos);
285   if (new_class_template->isOutOfLine())
286     result->setLexicalDeclContext(
287         new_class_template->getLexicalDeclContext());
288   return result;
289 }
290 
291 std::optional<Decl *> CxxModuleHandler::Import(Decl *d) {
292   if (!isValid())
293     return {};
294 
295   return tryInstantiateStdTemplate(d);
296 }
297