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