blob: fe89a43d7538b0a4b956a05816496b05289f09cc [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "lifetime_analysis/template_placeholder_support.h"
#include <functional>
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/CFG.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/Refactoring/AtomicChange.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/Transformer/RangeSelector.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/Stencil.h"
#include "clang/Tooling/Transformer/Transformer.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
namespace clang {
namespace tidy {
namespace lifetimes {
namespace {
using clang::ast_matchers::MatchFinder;
class TranslationUnitMatcherCallback : public MatchFinder::MatchCallback {
public:
explicit TranslationUnitMatcherCallback(
std::function<void(clang::ASTContext&)> operation)
: operation_{operation} {}
void run(const MatchFinder::MatchResult& Result) override {
const auto* tu = Result.Nodes.getNodeAs<clang::TranslationUnitDecl>("tu");
if (!tu) return;
operation_(tu->getASTContext());
}
std::function<void(clang::ASTContext&)> operation_;
};
} // namespace
llvm::Expected<GeneratedCode> GenerateTemplateInstantiationCode(
const clang::TranslationUnitDecl* tu,
const llvm::DenseMap<clang::FunctionTemplateDecl*,
const clang::FunctionDecl*>& templates) {
using clang::ast_matchers::asString;
using clang::ast_matchers::decl;
using clang::ast_matchers::equalsNode;
using clang::ast_matchers::functionDecl;
using clang::ast_matchers::functionTemplateDecl;
using clang::ast_matchers::hasBody;
using clang::ast_matchers::hasParent;
using clang::ast_matchers::loc;
using clang::ast_matchers::qualType;
using clang::ast_matchers::stmt;
using clang::ast_matchers::typeLoc;
using clang::tooling::Transformer;
using clang::transformer::cat;
using clang::transformer::charRange;
using clang::transformer::edit;
using clang::transformer::EditGenerator;
using clang::transformer::name;
using clang::transformer::node;
using clang::transformer::remove;
auto& context = tu->getASTContext();
auto file_id = tu->getASTContext().getSourceManager().getMainFileID();
auto& source_manager = context.getSourceManager();
auto source_filename =
source_manager.getFilename(source_manager.getLocForStartOfFile(file_id));
auto source_code = clang::Lexer::getSourceText(
clang::CharSourceRange::getTokenRange(
source_manager.getLocForStartOfFile(file_id),
source_manager.getLocForEndOfFile(file_id)),
source_manager, context.getLangOpts());
llvm::Error err = llvm::Error::success();
clang::tooling::AtomicChanges changes;
std::vector<std::unique_ptr<Transformer>> transformers;
auto consumer =
[&changes,
&err](llvm::Expected<llvm::MutableArrayRef<clang::tooling::AtomicChange>>
c) {
if (c) {
changes.insert(changes.end(), std::make_move_iterator(c->begin()),
std::make_move_iterator(c->end()));
} else {
err = c.takeError();
llvm::errs() << llvm::toString(c.takeError()) << "\n";
}
};
clang::TranslationUnitDecl* translation_unit =
context.getTranslationUnitDecl();
llvm::DenseSet<const clang::Decl*> toplevels(translation_unit->decls_begin(),
translation_unit->decls_end());
int placeholder_suffix_idx = 0;
std::vector<std::string> placeholder_classes;
for (const auto& [tmpl, func] : templates) {
toplevels.erase(tmpl);
auto* params = tmpl->getTemplateParameters();
std::vector<std::string> parameters;
llvm::SmallVector<EditGenerator, 2> edits;
std::string func_name = func->getNameAsString();
for (auto param : *params) {
// TODO(kinuko): check the template parameter types, this only assumes
// type parameters for now.
std::string placeholder_class = absl::StrCat(
func_name, "_type_placeholder_", placeholder_suffix_idx++);
placeholder_classes.push_back(placeholder_class);
parameters.push_back(placeholder_class);
auto change_type_rule =
makeRule(typeLoc(loc(qualType(asString(param->getNameAsString())))),
changeTo(cat(placeholder_class)));
edits.push_back(rewriteDescendants(func_name, change_type_rule));
}
edits.push_back(edit(changeTo(node("body"), cat(";"))));
edits.push_back(edit(
changeTo(name(func_name),
cat(absl::StrCat(func->getNameAsString(), "<",
absl::StrJoin(parameters, ", "), ">")))));
edits.push_back(edit(remove(charRange(clang::CharSourceRange::getCharRange(
params->getLAngleLoc(), params->getRAngleLoc().getLocWithOffset(1))))));
auto rule =
makeRule(functionDecl(equalsNode(func), hasBody(stmt().bind("body")),
hasParent(functionTemplateDecl()))
.bind(func_name),
flattenVector(edits));
transformers.push_back(std::make_unique<Transformer>(rule, consumer));
}
for (const auto* node_to_delete : toplevels) {
// Delete all other top-level nodes (we only need the instantiation code as
// original code is to be included separately)
auto rule = makeRule(decl(equalsNode(node_to_delete)), changeTo(cat("")));
transformers.push_back(std::make_unique<Transformer>(rule, consumer));
}
std::string instantiation_code;
MatchFinder match_finder;
for (const auto& transformer : transformers) {
transformer->registerMatchers(&match_finder);
}
match_finder.matchAST(context);
// `consumer` might have produced an error.
if (err) return std::move(err);
if ((err = clang::tooling::applyAtomicChanges(
source_filename, source_code, changes,
clang::tooling::ApplyChangesSpec())
.moveInto(instantiation_code))) {
return std::move(err);
}
// insertBefore or other transform edits don't work quite well, so simply
// concat and add the string.
std::vector<std::string> placeholder_definitions;
for (auto& c : placeholder_classes) {
placeholder_definitions.push_back("struct ");
placeholder_definitions.push_back(c);
placeholder_definitions.push_back(" {};\n");
}
GeneratedCode generated;
generated.filename = (source_filename + "-with-placeholders.cc").str();
generated.code = absl::StrCat("#include \"", source_filename.str(), "\"\n",
absl::StrJoin(placeholder_definitions, ""),
instantiation_code);
return generated;
}
void RunToolOnCodeWithOverlay(
clang::ASTContext& original_context, const std::string& filename,
const std::string& code,
const std::function<void(clang::ASTContext&)> operation) {
using clang::ast_matchers::MatchFinder;
using clang::ast_matchers::translationUnitDecl;
// Set up an overlay filesystem and add the `code` as a virtual file of it.
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs(
&original_context.getSourceManager()
.getFileManager()
.getVirtualFileSystem());
auto overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(fs);
auto memory_fs = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
overlay->pushOverlay(memory_fs);
memory_fs->addFile(filename, 0, llvm::MemoryBuffer::getMemBuffer(code));
clang::ast_matchers::MatchFinder match_finder;
TranslationUnitMatcherCallback callback(operation);
match_finder.addMatcher(translationUnitDecl().bind("tu"), &callback);
std::unique_ptr<clang::tooling::FrontendActionFactory> factory(
(clang::tooling::newFrontendActionFactory(&match_finder)));
// TODO(kinuko): get the args from the current ASTContext.
clang::tooling::runToolOnCodeWithArgs(factory->create(), code, overlay,
{"-fsyntax-only", "-std=c++17"},
filename, "lifetime-with-placedholder");
}
} // namespace lifetimes
} // namespace tidy
} // namespace clang