You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.9 KiB
291 lines
9.9 KiB
4 months ago
|
//===-- CxxModuleHandler.cpp ----------------------------------------------===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "Plugins/ExpressionParser/Clang/CxxModuleHandler.h"
|
||
|
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
|
||
|
|
||
|
#include "lldb/Utility/Log.h"
|
||
|
#include "clang/Sema/Lookup.h"
|
||
|
#include "llvm/Support/Error.h"
|
||
|
|
||
|
using namespace lldb_private;
|
||
|
using namespace clang;
|
||
|
|
||
|
CxxModuleHandler::CxxModuleHandler(ASTImporter &importer, ASTContext *target)
|
||
|
: m_importer(&importer),
|
||
|
m_sema(TypeSystemClang::GetASTContext(target)->getSema()) {
|
||
|
|
||
|
std::initializer_list<const char *> supported_names = {
|
||
|
// containers
|
||
|
"deque",
|
||
|
"forward_list",
|
||
|
"list",
|
||
|
"queue",
|
||
|
"stack",
|
||
|
"vector",
|
||
|
// pointers
|
||
|
"shared_ptr",
|
||
|
"unique_ptr",
|
||
|
"weak_ptr",
|
||
|
// utility
|
||
|
"allocator",
|
||
|
"pair",
|
||
|
};
|
||
|
m_supported_templates.insert(supported_names.begin(), supported_names.end());
|
||
|
}
|
||
|
|
||
|
/// Builds a list of scopes that point into the given context.
|
||
|
///
|
||
|
/// \param sema The sema that will be using the scopes.
|
||
|
/// \param ctxt The context that the scope should look into.
|
||
|
/// \param result A list of scopes. The scopes need to be freed by the caller
|
||
|
/// (except the TUScope which is owned by the sema).
|
||
|
static void makeScopes(Sema &sema, DeclContext *ctxt,
|
||
|
std::vector<Scope *> &result) {
|
||
|
// FIXME: The result should be a list of unique_ptrs, but the TUScope makes
|
||
|
// this currently impossible as it's owned by the Sema.
|
||
|
|
||
|
if (auto parent = ctxt->getParent()) {
|
||
|
makeScopes(sema, parent, result);
|
||
|
|
||
|
Scope *scope =
|
||
|
new Scope(result.back(), Scope::DeclScope, sema.getDiagnostics());
|
||
|
scope->setEntity(ctxt);
|
||
|
result.push_back(scope);
|
||
|
} else
|
||
|
result.push_back(sema.TUScope);
|
||
|
}
|
||
|
|
||
|
/// Uses the Sema to look up the given name in the given DeclContext.
|
||
|
static std::unique_ptr<LookupResult>
|
||
|
emulateLookupInCtxt(Sema &sema, llvm::StringRef name, DeclContext *ctxt) {
|
||
|
IdentifierInfo &ident = sema.getASTContext().Idents.get(name);
|
||
|
|
||
|
std::unique_ptr<LookupResult> lookup_result;
|
||
|
lookup_result = std::make_unique<LookupResult>(sema, DeclarationName(&ident),
|
||
|
SourceLocation(),
|
||
|
Sema::LookupOrdinaryName);
|
||
|
|
||
|
// Usually during parsing we already encountered the scopes we would use. But
|
||
|
// here don't have these scopes so we have to emulate the behavior of the
|
||
|
// Sema during parsing.
|
||
|
std::vector<Scope *> scopes;
|
||
|
makeScopes(sema, ctxt, scopes);
|
||
|
|
||
|
// Now actually perform the lookup with the sema.
|
||
|
sema.LookupName(*lookup_result, scopes.back());
|
||
|
|
||
|
// Delete all the allocated scopes beside the translation unit scope (which
|
||
|
// has depth 0).
|
||
|
for (Scope *s : scopes)
|
||
|
if (s->getDepth() != 0)
|
||
|
delete s;
|
||
|
|
||
|
return lookup_result;
|
||
|
}
|
||
|
|
||
|
/// Error class for handling problems when finding a certain DeclContext.
|
||
|
struct MissingDeclContext : public llvm::ErrorInfo<MissingDeclContext> {
|
||
|
|
||
|
static char ID;
|
||
|
|
||
|
MissingDeclContext(DeclContext *context, std::string error)
|
||
|
: m_context(context), m_error(error) {}
|
||
|
|
||
|
DeclContext *m_context;
|
||
|
std::string m_error;
|
||
|
|
||
|
void log(llvm::raw_ostream &OS) const override {
|
||
|
OS << llvm::formatv("error when reconstructing context of kind {0}:{1}",
|
||
|
m_context->getDeclKindName(), m_error);
|
||
|
}
|
||
|
|
||
|
std::error_code convertToErrorCode() const override {
|
||
|
return llvm::inconvertibleErrorCode();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
char MissingDeclContext::ID = 0;
|
||
|
|
||
|
/// Given a foreign decl context, this function finds the equivalent local
|
||
|
/// decl context in the ASTContext of the given Sema. Potentially deserializes
|
||
|
/// decls from the 'std' module if necessary.
|
||
|
static llvm::Expected<DeclContext *>
|
||
|
getEqualLocalDeclContext(Sema &sema, DeclContext *foreign_ctxt) {
|
||
|
|
||
|
// Inline namespaces don't matter for lookups, so let's skip them.
|
||
|
while (foreign_ctxt && foreign_ctxt->isInlineNamespace())
|
||
|
foreign_ctxt = foreign_ctxt->getParent();
|
||
|
|
||
|
// If the foreign context is the TU, we just return the local TU.
|
||
|
if (foreign_ctxt->isTranslationUnit())
|
||
|
return sema.getASTContext().getTranslationUnitDecl();
|
||
|
|
||
|
// Recursively find/build the parent DeclContext.
|
||
|
llvm::Expected<DeclContext *> parent =
|
||
|
getEqualLocalDeclContext(sema, foreign_ctxt->getParent());
|
||
|
if (!parent)
|
||
|
return parent;
|
||
|
|
||
|
// We currently only support building namespaces.
|
||
|
if (foreign_ctxt->isNamespace()) {
|
||
|
NamedDecl *ns = llvm::dyn_cast<NamedDecl>(foreign_ctxt);
|
||
|
llvm::StringRef ns_name = ns->getName();
|
||
|
|
||
|
auto lookup_result = emulateLookupInCtxt(sema, ns_name, *parent);
|
||
|
for (NamedDecl *named_decl : *lookup_result) {
|
||
|
if (DeclContext *DC = llvm::dyn_cast<DeclContext>(named_decl))
|
||
|
return DC->getPrimaryContext();
|
||
|
}
|
||
|
return llvm::make_error<MissingDeclContext>(
|
||
|
foreign_ctxt,
|
||
|
"Couldn't find namespace " + ns->getQualifiedNameAsString());
|
||
|
}
|
||
|
|
||
|
return llvm::make_error<MissingDeclContext>(foreign_ctxt, "Unknown context ");
|
||
|
}
|
||
|
|
||
|
/// Returns true iff tryInstantiateStdTemplate supports instantiating a template
|
||
|
/// with the given template arguments.
|
||
|
static bool templateArgsAreSupported(ArrayRef<TemplateArgument> a) {
|
||
|
for (const TemplateArgument &arg : a) {
|
||
|
switch (arg.getKind()) {
|
||
|
case TemplateArgument::Type:
|
||
|
case TemplateArgument::Integral:
|
||
|
break;
|
||
|
default:
|
||
|
// TemplateArgument kind hasn't been handled yet.
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// Constructor function for Clang declarations. Ensures that the created
|
||
|
/// declaration is registered with the ASTImporter.
|
||
|
template <typename T, typename... Args>
|
||
|
T *createDecl(ASTImporter &importer, Decl *from_d, Args &&... args) {
|
||
|
T *to_d = T::Create(std::forward<Args>(args)...);
|
||
|
importer.RegisterImportedDecl(from_d, to_d);
|
||
|
return to_d;
|
||
|
}
|
||
|
|
||
|
llvm::Optional<Decl *> CxxModuleHandler::tryInstantiateStdTemplate(Decl *d) {
|
||
|
Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS);
|
||
|
|
||
|
// If we don't have a template to instiantiate, then there is nothing to do.
|
||
|
auto td = dyn_cast<ClassTemplateSpecializationDecl>(d);
|
||
|
if (!td)
|
||
|
return llvm::None;
|
||
|
|
||
|
// We only care about templates in the std namespace.
|
||
|
if (!td->getDeclContext()->isStdNamespace())
|
||
|
return llvm::None;
|
||
|
|
||
|
// We have a list of supported template names.
|
||
|
if (!m_supported_templates.contains(td->getName()))
|
||
|
return llvm::None;
|
||
|
|
||
|
// Early check if we even support instantiating this template. We do this
|
||
|
// before we import anything into the target AST.
|
||
|
auto &foreign_args = td->getTemplateInstantiationArgs();
|
||
|
if (!templateArgsAreSupported(foreign_args.asArray()))
|
||
|
return llvm::None;
|
||
|
|
||
|
// Find the local DeclContext that corresponds to the DeclContext of our
|
||
|
// decl we want to import.
|
||
|
llvm::Expected<DeclContext *> to_context =
|
||
|
getEqualLocalDeclContext(*m_sema, td->getDeclContext());
|
||
|
if (!to_context) {
|
||
|
LLDB_LOG_ERROR(log, to_context.takeError(),
|
||
|
"Got error while searching equal local DeclContext for decl "
|
||
|
"'{1}':\n{0}",
|
||
|
td->getName());
|
||
|
return llvm::None;
|
||
|
}
|
||
|
|
||
|
// Look up the template in our local context.
|
||
|
std::unique_ptr<LookupResult> lookup =
|
||
|
emulateLookupInCtxt(*m_sema, td->getName(), *to_context);
|
||
|
|
||
|
ClassTemplateDecl *new_class_template = nullptr;
|
||
|
for (auto LD : *lookup) {
|
||
|
if ((new_class_template = dyn_cast<ClassTemplateDecl>(LD)))
|
||
|
break;
|
||
|
}
|
||
|
if (!new_class_template)
|
||
|
return llvm::None;
|
||
|
|
||
|
// Import the foreign template arguments.
|
||
|
llvm::SmallVector<TemplateArgument, 4> imported_args;
|
||
|
|
||
|
// If this logic is changed, also update templateArgsAreSupported.
|
||
|
for (const TemplateArgument &arg : foreign_args.asArray()) {
|
||
|
switch (arg.getKind()) {
|
||
|
case TemplateArgument::Type: {
|
||
|
llvm::Expected<QualType> type = m_importer->Import(arg.getAsType());
|
||
|
if (!type) {
|
||
|
LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");
|
||
|
return llvm::None;
|
||
|
}
|
||
|
imported_args.push_back(TemplateArgument(*type));
|
||
|
break;
|
||
|
}
|
||
|
case TemplateArgument::Integral: {
|
||
|
llvm::APSInt integral = arg.getAsIntegral();
|
||
|
llvm::Expected<QualType> type =
|
||
|
m_importer->Import(arg.getIntegralType());
|
||
|
if (!type) {
|
||
|
LLDB_LOG_ERROR(log, type.takeError(), "Couldn't import type: {0}");
|
||
|
return llvm::None;
|
||
|
}
|
||
|
imported_args.push_back(
|
||
|
TemplateArgument(d->getASTContext(), integral, *type));
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
assert(false && "templateArgsAreSupported not updated?");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Find the class template specialization declaration that
|
||
|
// corresponds to these arguments.
|
||
|
void *InsertPos = nullptr;
|
||
|
ClassTemplateSpecializationDecl *result =
|
||
|
new_class_template->findSpecialization(imported_args, InsertPos);
|
||
|
|
||
|
if (result) {
|
||
|
// We found an existing specialization in the module that fits our arguments
|
||
|
// so we can treat it as the result and register it with the ASTImporter.
|
||
|
m_importer->RegisterImportedDecl(d, result);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// Instantiate the template.
|
||
|
result = createDecl<ClassTemplateSpecializationDecl>(
|
||
|
*m_importer, d, m_sema->getASTContext(),
|
||
|
new_class_template->getTemplatedDecl()->getTagKind(),
|
||
|
new_class_template->getDeclContext(),
|
||
|
new_class_template->getTemplatedDecl()->getLocation(),
|
||
|
new_class_template->getLocation(), new_class_template, imported_args,
|
||
|
nullptr);
|
||
|
|
||
|
new_class_template->AddSpecialization(result, InsertPos);
|
||
|
if (new_class_template->isOutOfLine())
|
||
|
result->setLexicalDeclContext(
|
||
|
new_class_template->getLexicalDeclContext());
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
llvm::Optional<Decl *> CxxModuleHandler::Import(Decl *d) {
|
||
|
if (!isValid())
|
||
|
return {};
|
||
|
|
||
|
return tryInstantiateStdTemplate(d);
|
||
|
}
|