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