299 lines · cpp
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-exception6//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 18using namespace lldb_private;19using namespace clang;20 21CxxModuleHandler::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 // containers27 "array",28 "deque",29 "forward_list",30 "list",31 "queue",32 "stack",33 "vector",34 // pointers35 "shared_ptr",36 "unique_ptr",37 "weak_ptr",38 // iterator39 "move_iterator",40 "__wrap_iter",41 // utility42 "allocator",43 "pair",44 };45 m_supported_templates.insert_range(supported_names);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 caller53/// (except the TUScope which is owned by the sema).54static 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 makes57 // 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 } else67 result.push_back(sema.TUScope);68}69 70/// Uses the Sema to look up the given name in the given DeclContext.71static std::unique_ptr<LookupResult>72emulateLookupInCtxt(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. But81 // here don't have these scopes so we have to emulate the behavior of the82 // 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 (which90 // 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.99struct 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 119char MissingDeclContext::ID = 0;120 121/// Given a foreign decl context, this function finds the equivalent local122/// decl context in the ASTContext of the given Sema. Potentially deserializes123/// decls from the 'std' module if necessary.124static llvm::Expected<DeclContext *>125getEqualLocalDeclContext(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 template160/// with the given template arguments.161static 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 created176/// declaration is registered with the ASTImporter.177template <typename T, typename... Args>178T *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 184std::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 this201 // 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 our207 // 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(243 TemplateArgument(*type, /*isNullPtr*/ false, arg.getIsDefaulted()));244 break;245 }246 case TemplateArgument::Integral: {247 llvm::APSInt integral = arg.getAsIntegral();248 llvm::Expected<QualType> type =249 m_importer->Import(arg.getIntegralType());250 if (!type) {251 LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");252 return std::nullopt;253 }254 imported_args.push_back(TemplateArgument(d->getASTContext(), integral,255 *type, arg.getIsDefaulted()));256 break;257 }258 default:259 assert(false && "templateArgsAreSupported not updated?");260 }261 }262 263 // Find the class template specialization declaration that264 // corresponds to these arguments.265 void *InsertPos = nullptr;266 ClassTemplateSpecializationDecl *result =267 new_class_template->findSpecialization(imported_args, InsertPos);268 269 if (result) {270 // We found an existing specialization in the module that fits our arguments271 // so we can treat it as the result and register it with the ASTImporter.272 m_importer->RegisterImportedDecl(d, result);273 return result;274 }275 276 // Instantiate the template.277 result = createDecl<ClassTemplateSpecializationDecl>(278 *m_importer, d, m_sema->getASTContext(),279 new_class_template->getTemplatedDecl()->getTagKind(),280 new_class_template->getDeclContext(),281 new_class_template->getTemplatedDecl()->getLocation(),282 new_class_template->getLocation(), new_class_template, imported_args,283 td->hasStrictPackMatch(),284 /*PrevDecl=*/nullptr);285 286 new_class_template->AddSpecialization(result, InsertPos);287 if (new_class_template->isOutOfLine())288 result->setLexicalDeclContext(289 new_class_template->getLexicalDeclContext());290 return result;291}292 293std::optional<Decl *> CxxModuleHandler::Import(Decl *d) {294 if (!isValid())295 return {};296 297 return tryInstantiateStdTemplate(d);298}299