Instantiator
Generate c++ template instantiations
Loading...
Searching...
No Matches
Instantiator.cpp
Go to the documentation of this file.
1#include <filesystem>
2#include <fstream>
3#include <iostream>
4#include <system_error>
5#include <unordered_set>
6
7#include "fmt/color.h"
8#include "fmt/ranges.h"
9#include "fmt/std.h"
10#include "spdlog/cfg/env.h"
11#include "spdlog/spdlog.h"
12
13#include "clang/AST/ASTImporter.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "clang/Rewrite/Core/Rewriter.h"
17#include "clang/Tooling/CommonOptionsParser.h"
18#include "clang/Tooling/Tooling.h"
19#include "llvm/Support/CommandLine.h"
20
21#include "ASTCreation.hpp"
25#include "IO/ProgressBar.hpp"
26#include "Injection.hpp"
27#include "Matcher/Matcher.hpp"
28
29//
30// Command line options
31//
32static llvm::cl::OptionCategory InstantiatorOptions("Instantiator options");
33
34static llvm::cl::opt<bool> Invasive("invasive",
35 llvm::cl::desc("Inject instantiations invasively. (Directly after function definition.)"),
36 llvm::cl::cat(InstantiatorOptions));
37
38static llvm::cl::opt<bool> Progress("progress", llvm::cl::desc("Print out a progress bar."), llvm::cl::cat(InstantiatorOptions));
39
40static llvm::cl::opt<bool> Clean("clean", llvm::cl::desc("Delete all explicit instantiations."), llvm::cl::cat(InstantiatorOptions));
41static llvm::cl::list<std::string> excludePatterns(
42 "exclude-namespace",
43 llvm::cl::desc("List of namespaces which should be excluded. Several namespaces can be added by multiple --exclude-namespace calls."),
44 llvm::cl::value_desc("namespace"),
45 llvm::cl::cat(InstantiatorOptions));
46static llvm::cl::alias excludePatternA("e", llvm::cl::desc("Alias for --exclude-namespace"), llvm::cl::aliasopt(excludePatterns));
47
48static llvm::cl::list<std::string> includePatterns(
49 "include-namespace",
50 llvm::cl::desc("List of namespaces which should be included. Several namespaces can be added by multiple --include-namespace calls."),
51 llvm::cl::value_desc("namespace"),
52 llvm::cl::cat(InstantiatorOptions));
53static llvm::cl::alias includePatternA("i", llvm::cl::desc("Alias for --include-namespace"), llvm::cl::aliasopt(includePatterns));
54
55static llvm::cl::extrahelp CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage);
56
57int main(int argc, const char** argv)
58{
59 spdlog::cfg::load_env_levels();
60
61 auto ExpectedParser = clang::tooling::CommonOptionsParser::create(argc, argv, InstantiatorOptions, llvm::cl::OneOrMore);
62 if(!ExpectedParser) {
63 // Fail gracefully for unsupported options.
64 llvm::errs() << ExpectedParser.takeError();
65 return 1;
66 }
67 clang::tooling::CommonOptionsParser& OptionsParser = ExpectedParser.get();
68
69 if(excludePatterns.size() == 0) { excludePatterns.push_back("std"); }
70 if(includePatterns.size() == 0) { includePatterns.push_back(".*"); }
71 auto excludedNameMatcher = clang::ast_matchers::matchesName("^::" + excludePatterns[0] + "::.*$");
72 for(auto it = excludePatterns.begin() + 1; it != excludePatterns.end(); it++) {
73 excludedNameMatcher = clang::ast_matchers::anyOf(excludedNameMatcher, clang::ast_matchers::matchesName("^::" + *it + "::.*$"));
74 }
75 auto includedNameMatcher = clang::ast_matchers::matchesName("^::" + includePatterns[0] + "::.*$");
76 for(auto it = includePatterns.begin() + 1; it != includePatterns.end(); it++) {
77 includedNameMatcher = clang::ast_matchers::anyOf(includedNameMatcher, clang::ast_matchers::matchesName("^::" + *it + "::.*$"));
78 }
79
80 clang::ast_matchers::DeclarationMatcher TemplateInstantiationMatcher = TemplInstWithoutDef(excludedNameMatcher, includedNameMatcher);
81
82 clang::ast_matchers::DeclarationMatcher FunctionDefMatcher = FuncWithDef(excludedNameMatcher, includedNameMatcher);
83
84 std::error_code tmp_create_error;
85 auto tmpdir = std::filesystem::temp_directory_path(tmp_create_error);
86 if(tmp_create_error) {
87 std::cerr << "Error (" << tmp_create_error.value() << ") while getting temporary directory:\n" << tmp_create_error.message() << std::endl;
88 return 1;
89 }
90
91 std::vector<std::filesystem::path> main_and_injection_files;
92 for(const auto& file : OptionsParser.getCompilations().getAllFiles()) { main_and_injection_files.push_back(std::filesystem::path(file)); }
93
94 spdlog::info("Source files from CompilationDatabase:\n{}", main_and_injection_files);
95 llvm::ArrayRef<std::filesystem::path> sources(main_and_injection_files.data(), main_and_injection_files.size());
96
97 if(Clean) {
98 ProgressBar deletion_bar(sources.size());
99 for(std::size_t i = 0; i < sources.size(); i++) {
100 // deletion_bar.message = sources[i].string();
101 bool HAS_INJECTED_INSTANTIATION = true;
102 while(HAS_INJECTED_INSTANTIATION) {
103 // deletion_bar.set_option(indicators::option::PostfixText{"Processing: " + sources[i].string()});
104 if(not Invasive) {
105 auto gen_file = sources[i];
106 gen_file.replace_extension("gen.cpp");
107 std::ofstream f(gen_file, std::ios::out | std::ios::trunc);
108 f.close();
109 HAS_INJECTED_INSTANTIATION = false;
110 } else {
111 std::unique_ptr<clang::ASTUnit> AST;
112 parseOrLoadAST(AST, OptionsParser.getCompilations(), sources[i], tmpdir);
113 clang::Rewriter rewriter(AST->getSourceManager(), AST->getLangOpts());
114 DeleteInstantiations Deleter;
115 Deleter.rewriter = &rewriter;
116 clang::ast_matchers::MatchFinder Inst_Finder;
117 Inst_Finder.addMatcher(/*Matcher*/ TemplInst(excludedNameMatcher, includedNameMatcher), /*Callback*/ &Deleter);
118 Inst_Finder.matchAST(AST->getASTContext());
119 rewriter.overwriteChangedFiles();
120 HAS_INJECTED_INSTANTIATION = rewriter.buffer_begin() != rewriter.buffer_end();
121 }
122 }
123 if(Progress) { deletion_bar.step(); }
124 }
125 fmt::print("\n");
126 return 0;
127 }
128
130
131 std::vector<Injection> toDoList;
132 Getter.toDoList = &toDoList;
133 clang::ast_matchers::MatchFinder Finder;
134 Finder.addMatcher(/*Matcher*/ TemplateInstantiationMatcher, /*Callback*/ &Getter);
135
136 // match in current main file.
137 std::unordered_set<std::string> workList;
138 workList.insert(OptionsParser.getSourcePathList()[0]);
139
140 while(workList.size() > 0) {
141 auto copyOf_workList = workList;
142 for(const auto& item : copyOf_workList) {
143 spdlog::info("Processing file {}", item);
144 workList.erase(item);
145 std::unique_ptr<clang::ASTUnit> source_AST;
146 parseOrLoadAST(source_AST, OptionsParser.getCompilations(), item, tmpdir);
147 spdlog::debug("Got AST for file {}", item);
148 Finder.matchAST(source_AST->getASTContext());
149 spdlog::info("Found {} todos for file {}", toDoList.size(), item);
150 for(const auto& toDo : toDoList) { spdlog::debug("\t{}", toDo); }
151 ProgressBar inner_bar(main_and_injection_files.size());
152
153 for(const auto& file_for_search : main_and_injection_files) {
154 if(toDoList.size() == 0) {
155 // inner_bar.set_option(indicators::option::PostfixText{"ToDoList became empty. "});
156 if(Progress) { inner_bar.update(inner_bar.numTasks()); }
157 break;
158 }
159 std::unique_ptr<clang::ASTUnit> target_AST;
160 parseOrLoadAST(target_AST, OptionsParser.getCompilations(), file_for_search, tmpdir);
161 spdlog::info("Search in AST of file {}", file_for_search);
162 clang::Rewriter rewriter(target_AST->getSourceManager(), target_AST->getLangOpts());
163 clang::ast_matchers::MatchFinder FuncFinder;
164 InjectInstantiation instantiator;
165 instantiator.toDoList = &toDoList;
166 instantiator.rewriter = &rewriter;
167 instantiator.invasive = Invasive;
168 FuncFinder.addMatcher(/*Matcher*/ FunctionDefMatcher, /*Callback*/ &instantiator);
169 FuncFinder.matchAST(target_AST->getASTContext());
170 spdlog::debug("Called matchAST()");
171 rewriter.overwriteChangedFiles();
172 spdlog::debug("Called rewriter");
173 bool HAS_INJECTED_INTANTIATION = rewriter.buffer_begin() != rewriter.buffer_end();
174 if(HAS_INJECTED_INTANTIATION) {
175 spdlog::info("HAS_INJECTED={}", fmt::format(fg(fmt::color::green), "{}", HAS_INJECTED_INTANTIATION));
176 workList.insert(file_for_search);
177 } else {
178 spdlog::info("HAS_INJECTED={}", fmt::format(fg(fmt::color::red), "{}", HAS_INJECTED_INTANTIATION));
179 }
180 if(Progress) { inner_bar.step(); }
181 }
182 }
183 }
184 spdlog::critical("#toDos that are left: {}", toDoList.size());
185 if(toDoList.size() > 0) { spdlog::critical("toDos left:"); }
186 std::size_t count = 0ul;
187 for(const auto& toDo : toDoList) { spdlog::critical("{}: {}", count++, toDo); }
188}
void parseOrLoadAST(std::unique_ptr< clang::ASTUnit > &AST, const clang::tooling::CompilationDatabase &db, const std::filesystem::path &filename, const std::filesystem::path &tmpdir)
int main(int argc, const char **argv)
static llvm::cl::alias excludePatternA("e", llvm::cl::desc("Alias for --exclude-namespace"), llvm::cl::aliasopt(excludePatterns))
static llvm::cl::opt< bool > Clean("clean", llvm::cl::desc("Delete all explicit instantiations."), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::OptionCategory InstantiatorOptions("Instantiator options")
static llvm::cl::extrahelp CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage)
static llvm::cl::list< std::string > excludePatterns("exclude-namespace", llvm::cl::desc("List of namespaces which should be excluded. Several namespaces can be added by multiple --exclude-namespace calls."), llvm::cl::value_desc("namespace"), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::opt< bool > Progress("progress", llvm::cl::desc("Print out a progress bar."), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::opt< bool > Invasive("invasive", llvm::cl::desc("Inject instantiations invasively. (Directly after function definition.)"), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::list< std::string > includePatterns("include-namespace", llvm::cl::desc("List of namespaces which should be included. Several namespaces can be added by multiple --include-namespace calls."), llvm::cl::value_desc("namespace"), llvm::cl::cat(InstantiatorOptions))
static llvm::cl::alias includePatternA("i", llvm::cl::desc("Alias for --include-namespace"), llvm::cl::aliasopt(includePatterns))
AST matcher expressions to filter the relevant nodes for template instantiations.
clang::ast_matchers::DeclarationMatcher FuncWithDef(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names, const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &included_names)
clang::ast_matchers::DeclarationMatcher TemplInstWithoutDef(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names, const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &included_names)
clang::ast_matchers::DeclarationMatcher TemplInst(const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &excluded_names, const clang::ast_matchers::internal::Matcher< clang::NamedDecl > &included_names)
MatchCallback for template instantiation deletions.
MatchCallback for template instantiation detections.
std::vector< Injection > * toDoList
MatchCallback for injection of explicit instantiations into the source files at appropriate places.
clang::Rewriter * rewriter
std::vector< Injection > * toDoList
void update(int curr, std::string message="")
void step(std::string message="")
int numTasks() const