blob: fe89a43d7538b0a4b956a05816496b05289f09cc [file] [log] [blame]
Luca Versari99fddff2022-05-25 10:22:32 -07001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5#include "lifetime_analysis/template_placeholder_support.h"
6
7#include <functional>
Dmitri Gribenkoa087d232023-07-10 08:03:46 -07008#include <iterator>
Luca Versari99fddff2022-05-25 10:22:32 -07009#include <memory>
10#include <string>
Luca Versari99fddff2022-05-25 10:22:32 -070011#include <utility>
12#include <vector>
13
14#include "absl/strings/str_cat.h"
Luca Versari99fddff2022-05-25 10:22:32 -070015#include "absl/strings/str_join.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070016#include "clang/AST/Decl.h"
Luca Versari99fddff2022-05-25 10:22:32 -070017#include "clang/AST/DeclTemplate.h"
18#include "clang/ASTMatchers/ASTMatchFinder.h"
19#include "clang/ASTMatchers/ASTMatchers.h"
20#include "clang/Analysis/CFG.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070021#include "clang/Basic/SourceLocation.h"
Luca Versari99fddff2022-05-25 10:22:32 -070022#include "clang/Lex/Lexer.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070023#include "clang/Tooling/Refactoring/AtomicChange.h"
Luca Versari99fddff2022-05-25 10:22:32 -070024#include "clang/Tooling/Tooling.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070025#include "clang/Tooling/Transformer/RangeSelector.h"
26#include "clang/Tooling/Transformer/RewriteRule.h"
Luca Versari99fddff2022-05-25 10:22:32 -070027#include "clang/Tooling/Transformer/Stencil.h"
28#include "clang/Tooling/Transformer/Transformer.h"
29#include "llvm/ADT/ArrayRef.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070030#include "llvm/ADT/DenseMap.h"
31#include "llvm/ADT/DenseSet.h"
32#include "llvm/ADT/IntrusiveRefCntPtr.h"
33#include "llvm/ADT/SmallVector.h"
Luca Versari99fddff2022-05-25 10:22:32 -070034#include "llvm/Support/Error.h"
35#include "llvm/Support/VirtualFileSystem.h"
Dmitri Gribenkoa087d232023-07-10 08:03:46 -070036#include "llvm/Support/raw_ostream.h"
Luca Versari99fddff2022-05-25 10:22:32 -070037
38namespace clang {
39namespace tidy {
40namespace lifetimes {
41
42namespace {
43
44using clang::ast_matchers::MatchFinder;
45
46class TranslationUnitMatcherCallback : public MatchFinder::MatchCallback {
47 public:
48 explicit TranslationUnitMatcherCallback(
49 std::function<void(clang::ASTContext&)> operation)
50 : operation_{operation} {}
51
52 void run(const MatchFinder::MatchResult& Result) override {
53 const auto* tu = Result.Nodes.getNodeAs<clang::TranslationUnitDecl>("tu");
54 if (!tu) return;
55 operation_(tu->getASTContext());
56 }
57
58 std::function<void(clang::ASTContext&)> operation_;
59};
60
61} // namespace
62
63llvm::Expected<GeneratedCode> GenerateTemplateInstantiationCode(
64 const clang::TranslationUnitDecl* tu,
65 const llvm::DenseMap<clang::FunctionTemplateDecl*,
66 const clang::FunctionDecl*>& templates) {
67 using clang::ast_matchers::asString;
68 using clang::ast_matchers::decl;
69 using clang::ast_matchers::equalsNode;
70 using clang::ast_matchers::functionDecl;
71 using clang::ast_matchers::functionTemplateDecl;
72 using clang::ast_matchers::hasBody;
73 using clang::ast_matchers::hasParent;
74 using clang::ast_matchers::loc;
75 using clang::ast_matchers::qualType;
76 using clang::ast_matchers::stmt;
77 using clang::ast_matchers::typeLoc;
78 using clang::tooling::Transformer;
79 using clang::transformer::cat;
80 using clang::transformer::charRange;
81 using clang::transformer::edit;
82 using clang::transformer::EditGenerator;
83 using clang::transformer::name;
84 using clang::transformer::node;
85 using clang::transformer::remove;
86
87 auto& context = tu->getASTContext();
88 auto file_id = tu->getASTContext().getSourceManager().getMainFileID();
89 auto& source_manager = context.getSourceManager();
90 auto source_filename =
91 source_manager.getFilename(source_manager.getLocForStartOfFile(file_id));
92
93 auto source_code = clang::Lexer::getSourceText(
94 clang::CharSourceRange::getTokenRange(
95 source_manager.getLocForStartOfFile(file_id),
96 source_manager.getLocForEndOfFile(file_id)),
97 source_manager, context.getLangOpts());
98
99 llvm::Error err = llvm::Error::success();
100 clang::tooling::AtomicChanges changes;
101 std::vector<std::unique_ptr<Transformer>> transformers;
102
103 auto consumer =
104 [&changes,
105 &err](llvm::Expected<llvm::MutableArrayRef<clang::tooling::AtomicChange>>
106 c) {
107 if (c) {
108 changes.insert(changes.end(), std::make_move_iterator(c->begin()),
109 std::make_move_iterator(c->end()));
110 } else {
111 err = c.takeError();
112 llvm::errs() << llvm::toString(c.takeError()) << "\n";
113 }
114 };
115
116 clang::TranslationUnitDecl* translation_unit =
117 context.getTranslationUnitDecl();
118 llvm::DenseSet<const clang::Decl*> toplevels(translation_unit->decls_begin(),
119 translation_unit->decls_end());
120
121 int placeholder_suffix_idx = 0;
122 std::vector<std::string> placeholder_classes;
123 for (const auto& [tmpl, func] : templates) {
124 toplevels.erase(tmpl);
125 auto* params = tmpl->getTemplateParameters();
126 std::vector<std::string> parameters;
127 llvm::SmallVector<EditGenerator, 2> edits;
128 std::string func_name = func->getNameAsString();
129
130 for (auto param : *params) {
131 // TODO(kinuko): check the template parameter types, this only assumes
132 // type parameters for now.
133 std::string placeholder_class = absl::StrCat(
134 func_name, "_type_placeholder_", placeholder_suffix_idx++);
135
136 placeholder_classes.push_back(placeholder_class);
137 parameters.push_back(placeholder_class);
138
139 auto change_type_rule =
140 makeRule(typeLoc(loc(qualType(asString(param->getNameAsString())))),
141 changeTo(cat(placeholder_class)));
142 edits.push_back(rewriteDescendants(func_name, change_type_rule));
143 }
144
145 edits.push_back(edit(changeTo(node("body"), cat(";"))));
146 edits.push_back(edit(
147 changeTo(name(func_name),
148 cat(absl::StrCat(func->getNameAsString(), "<",
149 absl::StrJoin(parameters, ", "), ">")))));
150 edits.push_back(edit(remove(charRange(clang::CharSourceRange::getCharRange(
151 params->getLAngleLoc(), params->getRAngleLoc().getLocWithOffset(1))))));
152
153 auto rule =
154 makeRule(functionDecl(equalsNode(func), hasBody(stmt().bind("body")),
155 hasParent(functionTemplateDecl()))
156 .bind(func_name),
157 flattenVector(edits));
158 transformers.push_back(std::make_unique<Transformer>(rule, consumer));
159 }
160
161 for (const auto* node_to_delete : toplevels) {
162 // Delete all other top-level nodes (we only need the instantiation code as
163 // original code is to be included separately)
164 auto rule = makeRule(decl(equalsNode(node_to_delete)), changeTo(cat("")));
165 transformers.push_back(std::make_unique<Transformer>(rule, consumer));
166 }
167
168 std::string instantiation_code;
169 MatchFinder match_finder;
170 for (const auto& transformer : transformers) {
171 transformer->registerMatchers(&match_finder);
172 }
173 match_finder.matchAST(context);
174
175 // `consumer` might have produced an error.
176 if (err) return std::move(err);
177
178 if ((err = clang::tooling::applyAtomicChanges(
179 source_filename, source_code, changes,
180 clang::tooling::ApplyChangesSpec())
181 .moveInto(instantiation_code))) {
182 return std::move(err);
183 }
184
185 // insertBefore or other transform edits don't work quite well, so simply
186 // concat and add the string.
187 std::vector<std::string> placeholder_definitions;
188 for (auto& c : placeholder_classes) {
189 placeholder_definitions.push_back("struct ");
190 placeholder_definitions.push_back(c);
191 placeholder_definitions.push_back(" {};\n");
192 }
193
194 GeneratedCode generated;
195 generated.filename = (source_filename + "-with-placeholders.cc").str();
196 generated.code = absl::StrCat("#include \"", source_filename.str(), "\"\n",
197 absl::StrJoin(placeholder_definitions, ""),
198 instantiation_code);
199 return generated;
200}
201
202void RunToolOnCodeWithOverlay(
203 clang::ASTContext& original_context, const std::string& filename,
204 const std::string& code,
205 const std::function<void(clang::ASTContext&)> operation) {
206 using clang::ast_matchers::MatchFinder;
207 using clang::ast_matchers::translationUnitDecl;
208
209 // Set up an overlay filesystem and add the `code` as a virtual file of it.
210 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs(
211 &original_context.getSourceManager()
212 .getFileManager()
213 .getVirtualFileSystem());
214 auto overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(fs);
215 auto memory_fs = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
216 overlay->pushOverlay(memory_fs);
217 memory_fs->addFile(filename, 0, llvm::MemoryBuffer::getMemBuffer(code));
218
219 clang::ast_matchers::MatchFinder match_finder;
220 TranslationUnitMatcherCallback callback(operation);
221
222 match_finder.addMatcher(translationUnitDecl().bind("tu"), &callback);
223 std::unique_ptr<clang::tooling::FrontendActionFactory> factory(
224 (clang::tooling::newFrontendActionFactory(&match_finder)));
225
226 // TODO(kinuko): get the args from the current ASTContext.
227 clang::tooling::runToolOnCodeWithArgs(factory->create(), code, overlay,
228 {"-fsyntax-only", "-std=c++17"},
229 filename, "lifetime-with-placedholder");
230}
231
232} // namespace lifetimes
233} // namespace tidy
234} // namespace clang