Insights.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * C++ Insights, copyright (C) by Andreas Fertig
4 * Distributed under an MIT license. See LICENSE for details
5 *
6 ****************************************************************************/
7
8#include <array>
9#include "clang/AST/ASTContext.h"
10#include "clang/Frontend/CompilerInstance.h"
11#include "clang/Frontend/FrontendActions.h"
12#include "clang/Rewrite/Core/Rewriter.h"
13#include "clang/Tooling/CommonOptionsParser.h"
14#include "clang/Tooling/Tooling.h"
15#include "llvm/Support/MemoryBuffer.h"
16#include "llvm/Support/Path.h"
17#include "llvm/Support/Signals.h"
18#include "llvm/Support/raw_ostream.h"
19
20#include <vector>
21
22#include "CodeGenerator.h"
23#include "DPrint.h"
24#include "Insights.h"
25#include "version.h"
26//-----------------------------------------------------------------------------
27
28using namespace clang;
29using namespace clang::driver;
30using namespace clang::tooling;
31using namespace clang::insights;
32//-----------------------------------------------------------------------------
33
35//-----------------------------------------------------------------------------
36
41//-----------------------------------------------------------------------------
42
47//-----------------------------------------------------------------------------
48
49static llvm::cl::OptionCategory gInsightCategory("Insights"sv);
50//-----------------------------------------------------------------------------
51
52//-----------------------------------------------------------------------------
53static llvm::cl::OptionCategory gInsightEduCategory(
54 "Insights-Educational"sv,
55 "This transformations are only for education purposes. The resulting code most likely does not compile."sv);
56//-----------------------------------------------------------------------------
57
58#define INSIGHTS_OPT(option, name, deflt, description, category) \
59 static llvm::cl::opt<bool, true> g##name(option, \
60 llvm::cl::desc(std::string_view{description}), \
61 llvm::cl::NotHidden, \
62 llvm::cl::location(gInsightsOptions.name), \
63 llvm::cl::init(deflt), \
64 llvm::cl::cat(category));
65//-----------------------------------------------------------------------------
66
67#include "InsightsOptions.def"
68//-----------------------------------------------------------------------------
69
70static const ASTContext* gAST{};
71const ASTContext& GetGlobalAST()
72{
73 return *gAST;
74}
75//-----------------------------------------------------------------------------
76
77static const CompilerInstance* gCI{};
78const CompilerInstance& GetGlobalCI()
79{
80 return *gCI;
81}
82//-----------------------------------------------------------------------------
83
84namespace clang::insights {
85std::string EmitGlobalVariableCtors();
86
87using GlobalInsertMap = std::pair<bool, std::string_view>;
88
89static constinit std::array<GlobalInsertMap, static_cast<size_t>(GlobalInserts::MAX)> gGlobalInserts{};
90
91void AddGLobalInsertMapEntry(GlobalInserts idx, std::string_view value)
92{
93 gGlobalInserts[static_cast<size_t>(idx)] = {false, value};
94}
95
97{
98 gGlobalInserts[static_cast<size_t>(idx)].first = true;
99}
100
101} // namespace clang::insights
102
103using IncludeData = std::pair<const SourceLocation, std::string>;
104
106{
107 SourceManager& mSm;
108 Preprocessor& mPP;
109 std::vector<IncludeData>& mIncludes;
110
111public:
112 FindIncludes(SourceManager& sm, Preprocessor& pp, std::vector<IncludeData>& incData)
113 : PPCallbacks{}
114 , mSm{sm}
115 , mPP{pp}
116 , mIncludes{incData}
117 {
118 }
119
120 void InclusionDirective(SourceLocation hashLoc,
121 const Token& /*IncludeTok*/,
122 StringRef fileName,
123 bool isAngled,
124 CharSourceRange /*FilenameRange*/,
125 OptionalFileEntryRef /*file*/,
126 StringRef /*SearchPath*/,
127 StringRef /*RelativePath*/,
128 const Module* /*Imported*/,
129 bool /*ModuleImported*/,
130 SrcMgr::CharacteristicKind /*FileType*/) override
131 {
132 auto expansionLoc = mSm.getExpansionLoc(hashLoc);
133
134 if(expansionLoc.isInvalid() or mSm.isInSystemHeader(expansionLoc)) {
135 return;
136 }
137
138 // XXX: distinguish between include and import via the IncludeTok
139 if(isAngled) {
140 mIncludes.emplace_back(expansionLoc, StrCat("#include <"sv, fileName, ">\n"sv));
141
142 } else {
143 mIncludes.emplace_back(expansionLoc, StrCat("#include \""sv, fileName, "\"\n"sv));
144 }
145 }
146
147 void MacroDefined(const Token& macroNameTok, const MacroDirective* md) override
148 {
149 const auto loc = md->getLocation();
150 if(not mSm.isWrittenInMainFile(loc)) {
151 return;
152 }
153
154 auto name = mPP.getSpelling(macroNameTok);
155
156 if(not name.starts_with("INSIGHTS_"sv)) {
157 return;
158 }
159
160 mIncludes.emplace_back(loc, StrCat("#define "sv, name, "\n"sv));
161 }
162};
163
165{
166 Rewriter& mRewriter;
167 std::vector<IncludeData>& mIncludes;
168
169public:
170 explicit CppInsightASTConsumer(Rewriter& rewriter, std::vector<IncludeData>& includes)
171 : ASTConsumer{}
172 , mRewriter{rewriter}
173 , mIncludes{includes}
174 {
175 if(GetInsightsOptions().UseShow2C) {
176 if(GetInsightsOptions().ShowCoroutineTransformation) {
177 gInsightsOptions.UseShow2C = false;
178 } else {
179 gInsightsOptions.ShowLifetime = true;
180 }
181 }
182
183 if(GetInsightsOptions().ShowLifetime) {
184 gInsightsOptions.UseShowInitializerList = true;
185 }
186 }
187
188 void HandleTranslationUnit(ASTContext& context) override
189 {
190 gAST = &context;
191 auto& sm = context.getSourceManager();
192
193 auto isExpansionInSystemHeader = [&sm](const Decl* d) {
194 auto expansionLoc = sm.getExpansionLoc(d->getLocation());
195
196 return expansionLoc.isInvalid() or sm.isInSystemHeader(expansionLoc);
197 };
198
199 const auto& mainFileId = sm.getMainFileID();
200
201 mRewriter.ReplaceText({sm.getLocForStartOfFile(mainFileId), sm.getLocForEndOfFile(mainFileId)}, "");
202
203 OutputFormatHelper outputFormatHelper{};
204 CodeGeneratorVariant codeGenerator{outputFormatHelper};
205
206 auto include = mIncludes.begin();
207
208 auto insertBlankLineIfRequired = [&](std::optional<SourceLocation>& lastLoc, SourceLocation nextLoc) {
209 if(lastLoc.has_value() and
210 (2 <= (sm.getSpellingLineNumber(nextLoc) - sm.getSpellingLineNumber(lastLoc.value())))) {
211 outputFormatHelper.AppendNewLine();
212 }
213
214 lastLoc = nextLoc;
215 };
216
217 for(std::optional<SourceLocation> lastLoc{}; const auto* d : context.getTranslationUnitDecl()->decls()) {
218 if(isExpansionInSystemHeader(d)) {
219 continue;
220 }
221
222 // includes before this decl
223 for(; (mIncludes.end() != include) and (include->first < d->getLocation()); include = std::next(include)) {
224 insertBlankLineIfRequired(lastLoc, include->first);
225 outputFormatHelper.Append(include->second);
226 }
227
228 // ignore includes inside this decl
229 include = std::find_if_not(include, mIncludes.end(), [&](auto& inc) {
230 return ((inc.first >= d->getLocation()) and (inc.first <= d->getEndLoc()));
231 });
232
233 if(isa<LinkageSpecDecl>(d) and d->isImplicit()) {
234 continue;
235
236 // Only handle explicit specializations here. Implicit ones are handled by the `VarTemplateDecl`
237 // itself.
238 } else if(const auto* vdspec = dyn_cast_or_null<VarTemplateSpecializationDecl>(d);
239 vdspec and (TSK_ExplicitSpecialization != vdspec->getSpecializationKind())) {
240 continue;
241 }
242
243 insertBlankLineIfRequired(lastLoc, d->getLocation());
244
245 codeGenerator->InsertArg(d);
246 }
247
248 std::string insightsIncludes{};
249
250 if(GetInsightsOptions().ShowCoroutineTransformation) {
251 insightsIncludes.append(
252 R"(/*************************************************************************************
253 * NOTE: The coroutine transformation you've enabled is a hand coded transformation! *
254 * Most of it is _not_ present in the AST. What you see is an approximation. *
255 *************************************************************************************/
256)"sv);
257 } else if(GetInsightsOptions().UseShow2C or GetInsightsOptions().ShowLifetime) {
258 insightsIncludes.append(
259 R"(/*************************************************************************************
260 * NOTE: This an educational hand-rolled transformation. Things can be incorrect or *
261 * buggy. *
262 *************************************************************************************/
263)"sv);
264 }
265
266 // Check whether we had static local variables which we transformed. Then for the placement-new we need to
267 // include the header <new>.
268 std::string inserts{};
269 for(const auto& [active, value] : gGlobalInserts) {
270 if(not active) {
271 continue;
272 }
273
274 inserts.append(value);
275 inserts.append("\n"sv);
276 }
277
278 if(not inserts.empty()) {
279 insightsIncludes.append(inserts);
280 insightsIncludes.append("\n");
281 }
282
283 outputFormatHelper.InsertAt(0, insightsIncludes);
284
285 mRewriter.InsertText(sm.getLocForStartOfFile(mainFileId), outputFormatHelper.GetString());
286
287 if(GetInsightsOptions().UseShow2C) {
288 const auto& fileEntry = sm.getFileEntryForID(mainFileId);
290 const auto cxaLoc = sm.translateFileLineCol(fileEntry, fileEntry->getSize(), 1);
291
292 mRewriter.InsertText(cxaLoc, cxaStart);
293 }
294 }
295};
296//-----------------------------------------------------------------------------
297
299{
300 Rewriter mRewriter{};
301 std::vector<IncludeData> mIncludes{};
302
303public:
305 void EndSourceFileAction() override
306 {
307 mRewriter.getEditBuffer(mRewriter.getSourceMgr().getMainFileID()).write(llvm::outs());
308 }
309
310 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef /*file*/) override
311 {
312 gCI = &CI;
313
314 Preprocessor& pp = CI.getPreprocessor();
315 pp.addPPCallbacks(std::make_unique<FindIncludes>(CI.getSourceManager(), pp, mIncludes));
316
317 mRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
318 return std::make_unique<CppInsightASTConsumer>(mRewriter, mIncludes);
319 }
320};
321//-----------------------------------------------------------------------------
322
323#include "clang/Basic/Version.h"
324
325static void PrintVersion(raw_ostream& ostream)
326{
327 ostream << "cpp-insights " << INSIGHTS_VERSION << " https://cppinsights.io (" << GIT_REPO_URL << " "
328 << GIT_COMMIT_HASH << ")"
329 << "\n";
330
331#ifdef INSIGHTS_DEBUG
332 ostream << " Build with debug enabled\n";
333#endif
334 ostream << " LLVM Revision: " << clang::getLLVMRevision() << '\n';
335 ostream << " Clang Revision: " << clang::getClangFullCPPVersion() << '\n';
336}
337//-----------------------------------------------------------------------------
338
339int main(int argc, const char** argv)
340{
341 // Headers go first
342 using enum GlobalInserts;
344 "#include <new> // for thread-safe static's placement new\n#include <stdint.h> // for "
345 "uint64_t under Linux/GCC"sv);
346 AddGLobalInsertMapEntry(HeaderException, "#include <exception> // for noexcept transformation"sv);
347 AddGLobalInsertMapEntry(HeaderUtility, "#include <utility> // std::move"sv);
348 AddGLobalInsertMapEntry(HeaderStddef, "#include <stddef.h> // NULL and more"sv);
349 AddGLobalInsertMapEntry(HeaderAssert, "#include <assert.h> // _Static_assert"sv);
350 AddGLobalInsertMapEntry(HeaderStdlib, "#include <stdlib.h> // abort"sv);
351
352 // Now all the forward declared functions
353 AddGLobalInsertMapEntry(FuncCxaStart, "void __cxa_start(void);"sv);
354 AddGLobalInsertMapEntry(FuncCxaAtExit, "void __cxa_atexit(void);"sv);
355 AddGLobalInsertMapEntry(FuncMalloc, "void* malloc(unsigned int);"sv);
356 AddGLobalInsertMapEntry(FuncFree, R"(extern "C" void free(void*);)"sv);
357 AddGLobalInsertMapEntry(FuncMemset, R"(extern "C" void* memset(void*, int, unsigned int);)"sv);
358 AddGLobalInsertMapEntry(FuncMemcpy, R"(void* memcpy(void*, const void*, unsigned int);)"sv);
361 R"(extern "C" void* __cxa_vec_new(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
364 R"(extern "C" void* __cxa_vec_ctor(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
367 R"(extern "C" void __cxa_vec_delete(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
370 R"(extern "C" void __cxa_vec_dtor(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
371 AddGLobalInsertMapEntry(FuncVtableStruct, R"(typedef int (*__vptp)();
372
373struct __mptr
374{
375 short d;
376 short i;
377 __vptp f;
378};
379
380extern struct __mptr* __vtbl_array[];
381)"sv);
382 AddGLobalInsertMapEntry(FuncCxaPureVirtual, R"(extern "C" void __cxa_pure_virtual() { abort(); })");
383
384 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
385 llvm::cl::SetVersionPrinter(&PrintVersion);
386
387 auto opExpected = CommonOptionsParser::create(argc, argv, gInsightCategory);
388
389 if(auto err = opExpected.takeError()) {
390 if(gAutoComplete) {
391#define INSIGHTS_OPT(option, name, deflt, description, category) llvm::outs() << "--" << option << " ";
392
393#include "InsightsOptions.def"
394
395 return 0;
396 } else {
397
398 llvm::errs() << toString(std::move(err)) << "\n";
399 }
400
401 return 1;
402 }
403
404 // In STDINMode, we override the file content with the <stdin> input.
405 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
406 // the if-block so that `Code` is not released after the if-block.
407 std::unique_ptr<llvm::MemoryBuffer> inMemoryCode{};
408
409 CommonOptionsParser& op{opExpected.get()};
410 ClangTool tool(op.getCompilations(), op.getSourcePathList());
411
412 if(gStdinMode) {
413 if(op.getSourcePathList().size() != 1) {
414 llvm::errs() << "Expect exactly one file path in STDINMode.\n"sv;
415 return 1;
416 }
417
418 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> codeOrErr = llvm::MemoryBuffer::getSTDIN();
419
420 if(const std::error_code errorCode = codeOrErr.getError()) {
421 llvm::errs() << errorCode.message() << "\n";
422 return 1;
423 }
424
425 inMemoryCode = std::move(codeOrErr.get());
426
427 if(inMemoryCode->getBufferSize() == 0) {
428 Error("empty file\n");
429 return 1; // Skip empty files.
430 }
431
432 llvm::StringRef sourceFilePath = op.getSourcePathList().front();
433 tool.mapVirtualFile(sourceFilePath, inMemoryCode->getBuffer());
434 }
435
436 auto prependArgument = [&](auto arg) {
437 tool.appendArgumentsAdjuster(getInsertArgumentAdjuster(arg, ArgumentInsertPosition::BEGIN));
438 };
439
440 // Special handling to spare users to figure out what include paths to add.
441
442 // For some reason, Clang on Apple seems to require an additional hint for the C++ headers.
443#ifdef __APPLE__
444 gUseLibCpp = true;
445#endif /* __APPLE__ */
446
447 if(gUseLibCpp) {
448 prependArgument(INSIGHTS_LLVM_INCLUDE_DIR);
449 prependArgument("-stdlib=libc++");
450 prependArgument("-fexperimental-library");
451
452#ifdef __APPLE__
453 prependArgument("-nostdinc++"); // macos Monterey
454#endif /* __APPLE__ */
455 }
456
457 prependArgument(INSIGHTS_CLANG_RESOURCE_INCLUDE_DIR);
458 prependArgument(INSIGHTS_CLANG_RESOURCE_DIR);
459
460 if(GetInsightsOptions().UseShow2C) {
463 }
464
465 return tool.run(newFrontendActionFactory<CppInsightFrontendAction>().get());
466}
467//-----------------------------------------------------------------------------
std::pair< const SourceLocation, std::string > IncludeData
Definition Insights.cpp:103
const InsightsOptions & GetInsightsOptions()
Get the global C++ Insights options.
Definition Insights.cpp:37
static llvm::cl::OptionCategory gInsightCategory("Insights"sv)
const CompilerInstance & GetGlobalCI()
Get access to the CompilerInstance.
Definition Insights.cpp:78
const ASTContext & GetGlobalAST()
Get access to the ASTContext.
Definition Insights.cpp:71
static const ASTContext * gAST
Definition Insights.cpp:70
static InsightsOptions gInsightsOptions
Definition Insights.cpp:34
static void PrintVersion(raw_ostream &ostream)
Definition Insights.cpp:325
static llvm::cl::OptionCategory gInsightEduCategory("Insights-Educational"sv, "This transformations are only for education purposes. The resulting code most likely does not compile."sv)
InsightsOptions & GetInsightsOptionsRW()
Definition Insights.cpp:43
static const CompilerInstance * gCI
Definition Insights.cpp:77
constexpr std::string_view cxaStart
void HandleTranslationUnit(ASTContext &context) override
Definition Insights.cpp:188
CppInsightASTConsumer(Rewriter &rewriter, std::vector< IncludeData > &includes)
Definition Insights.cpp:170
std::unique_ptr< ASTConsumer > CreateASTConsumer(CompilerInstance &CI, StringRef) override
Definition Insights.cpp:310
CppInsightFrontendAction()=default
void EndSourceFileAction() override
Definition Insights.cpp:305
void MacroDefined(const Token &macroNameTok, const MacroDirective *md) override
Definition Insights.cpp:147
FindIncludes(SourceManager &sm, Preprocessor &pp, std::vector< IncludeData > &incData)
Definition Insights.cpp:112
void InclusionDirective(SourceLocation hashLoc, const Token &, StringRef fileName, bool isAngled, CharSourceRange, OptionalFileEntryRef, StringRef, StringRef, const Module *, bool, SrcMgr::CharacteristicKind) override
Definition Insights.cpp:120
A special container which creates either a CodeGenerator or a CfrontCodeGenerator depending on the co...
@ HeaderStdlib
Track whether we need to insert <stdlib.h> in Cfront mode.
@ HeaderUtility
Track whether there was a std::move inserted.
@ HeaderAssert
Track whether we need to insert <assert.h> in Cfront mode.
@ HeaderException
Track whether there was a noexcept transformation requireing the exception header.
@ HeaderStddef
Track whether we need to insert <stddef.h> in Cfront mode.
void Error(const char *fmt, const auto &... args)
Log an error.
Definition DPrint.h:80
std::string EmitGlobalVariableCtors()
static constinit std::array< GlobalInsertMap, static_cast< size_t >(GlobalInserts::MAX)> gGlobalInserts
Definition Insights.cpp:89
std::pair< bool, std::string_view > GlobalInsertMap
Definition Insights.cpp:87
void AddGLobalInsertMapEntry(GlobalInserts idx, std::string_view value)
Definition Insights.cpp:91
void EnableGlobalInsert(GlobalInserts idx)
Definition Insights.cpp:96
std::string StrCat(const auto &... args)
Global C++ Insights command line options.
Definition Insights.h:20