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#if IS_CLANG_NEWER_THAN(22)
237 // An explicit instantiation is not transformed, it must not influence the blank line handling.
238 } else if(isa<ExplicitInstantiationDecl>(d)) {
239 continue;
240#endif
241 // Only handle explicit specializations here. Implicit ones are handled by the `VarTemplateDecl`
242 // itself.
243 } else if(const auto* vdspec = dyn_cast_or_null<VarTemplateSpecializationDecl>(d);
244 vdspec and (TSK_ExplicitSpecialization != vdspec->getSpecializationKind())) {
245 continue;
246 }
247
248 insertBlankLineIfRequired(lastLoc, d->getLocation());
249
250 codeGenerator->InsertArg(d);
251 }
252
253 std::string insightsIncludes{};
254
255 if(GetInsightsOptions().ShowCoroutineTransformation) {
256 insightsIncludes.append(
257 R"(/*************************************************************************************
258 * NOTE: The coroutine transformation you've enabled is a hand coded transformation! *
259 * Most of it is _not_ present in the AST. What you see is an approximation. *
260 *************************************************************************************/
261)"sv);
262 } else if(GetInsightsOptions().UseShow2C or GetInsightsOptions().ShowLifetime) {
263 insightsIncludes.append(
264 R"(/*************************************************************************************
265 * NOTE: This an educational hand-rolled transformation. Things can be incorrect or *
266 * buggy. *
267 *************************************************************************************/
268)"sv);
269 }
270
271 // Check whether we had static local variables which we transformed. Then for the placement-new we need to
272 // include the header <new>.
273 std::string inserts{};
274 for(const auto& [active, value] : gGlobalInserts) {
275 if(not active) {
276 continue;
277 }
278
279 inserts.append(value);
280 inserts.append("\n"sv);
281 }
282
283 if(not inserts.empty()) {
284 insightsIncludes.append(inserts);
285 insightsIncludes.append("\n");
286 }
287
288 outputFormatHelper.InsertAt(0, insightsIncludes);
289
290 mRewriter.InsertText(sm.getLocForStartOfFile(mainFileId), outputFormatHelper.GetString());
291
292 if(GetInsightsOptions().UseShow2C) {
293 const auto& fileEntry = sm.getFileEntryForID(mainFileId);
295 const auto cxaLoc = sm.translateFileLineCol(fileEntry, fileEntry->getSize(), 1);
296
297 mRewriter.InsertText(cxaLoc, cxaStart);
298 }
299 }
300};
301//-----------------------------------------------------------------------------
302
304{
305 Rewriter mRewriter{};
306 std::vector<IncludeData> mIncludes{};
307
308public:
310 void EndSourceFileAction() override
311 {
312 mRewriter.getEditBuffer(mRewriter.getSourceMgr().getMainFileID()).write(llvm::outs());
313 }
314
315 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef /*file*/) override
316 {
317 gCI = &CI;
318
319 Preprocessor& pp = CI.getPreprocessor();
320 pp.addPPCallbacks(std::make_unique<FindIncludes>(CI.getSourceManager(), pp, mIncludes));
321
322 mRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
323 return std::make_unique<CppInsightASTConsumer>(mRewriter, mIncludes);
324 }
325};
326//-----------------------------------------------------------------------------
327
328#include "clang/Basic/Version.h"
329
330static void PrintVersion(raw_ostream& ostream)
331{
332 ostream << "cpp-insights " << INSIGHTS_VERSION << " https://cppinsights.io (" << GIT_REPO_URL << " "
333 << GIT_COMMIT_HASH << ")"
334 << "\n";
335
336#ifdef INSIGHTS_DEBUG
337 ostream << " Build with debug enabled\n";
338#endif
339 ostream << " LLVM Revision: " << clang::getLLVMRevision() << '\n';
340 ostream << " Clang Revision: " << clang::getClangFullCPPVersion() << '\n';
341}
342//-----------------------------------------------------------------------------
343
344int main(int argc, const char** argv)
345{
346 // Headers go first
347 using enum GlobalInserts;
349 "#include <new> // for thread-safe static's placement new\n#include <stdint.h> // for "
350 "uint64_t under Linux/GCC"sv);
351 AddGLobalInsertMapEntry(HeaderException, "#include <exception> // for noexcept transformation"sv);
352 AddGLobalInsertMapEntry(HeaderUtility, "#include <utility> // std::move"sv);
353 AddGLobalInsertMapEntry(HeaderStddef, "#include <stddef.h> // NULL and more"sv);
354 AddGLobalInsertMapEntry(HeaderAssert, "#include <assert.h> // _Static_assert"sv);
355 AddGLobalInsertMapEntry(HeaderStdlib, "#include <stdlib.h> // abort"sv);
356
357 // Now all the forward declared functions
358 AddGLobalInsertMapEntry(FuncCxaStart, "void __cxa_start(void);"sv);
359 AddGLobalInsertMapEntry(FuncCxaAtExit, "void __cxa_atexit(void);"sv);
360 AddGLobalInsertMapEntry(FuncMalloc, "void* malloc(unsigned int);"sv);
361 AddGLobalInsertMapEntry(FuncFree, R"(extern "C" void free(void*);)"sv);
362 AddGLobalInsertMapEntry(FuncMemset, R"(extern "C" void* memset(void*, int, unsigned int);)"sv);
363 AddGLobalInsertMapEntry(FuncMemcpy, R"(void* memcpy(void*, const void*, unsigned int);)"sv);
366 R"(extern "C" void* __cxa_vec_new(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
369 R"(extern "C" void* __cxa_vec_ctor(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
372 R"(extern "C" void __cxa_vec_delete(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
375 R"(extern "C" void __cxa_vec_dtor(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
376 AddGLobalInsertMapEntry(FuncVtableStruct, R"(typedef int (*__vptp)();
377
378struct __mptr
379{
380 short d;
381 short i;
382 __vptp f;
383};
384
385extern struct __mptr* __vtbl_array[];
386)"sv);
387 AddGLobalInsertMapEntry(FuncCxaPureVirtual, R"(extern "C" void __cxa_pure_virtual() { abort(); })");
388
389 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
390 llvm::cl::SetVersionPrinter(&PrintVersion);
391
392 auto opExpected = CommonOptionsParser::create(argc, argv, gInsightCategory);
393
394 if(auto err = opExpected.takeError()) {
395 if(gAutoComplete) {
396// Emit options in the same format clang uses for its own --autocomplete, i.e.
397// "option\tdescription" (a single TAB separates the option from its
398// description). This lets every shell completion script reuse clang's parsing
399// and show the description alongside the option. The leading "--" is added here
400// so the bare option name from InsightsOptions.def can be reused directly.
401#define INSIGHTS_OPT(option, name, deflt, description, category) \
402 llvm::outs() << "--" << option << "\t" << description << "\n";
403
404#include "InsightsOptions.def"
405
406 return 0;
407 } else {
408
409 llvm::errs() << toString(std::move(err)) << "\n";
410 }
411
412 return 1;
413 }
414
415 // In STDINMode, we override the file content with the <stdin> input.
416 // Since `tool.mapVirtualFile` takes `StringRef`, we define `Code` outside of
417 // the if-block so that `Code` is not released after the if-block.
418 std::unique_ptr<llvm::MemoryBuffer> inMemoryCode{};
419
420 CommonOptionsParser& op{opExpected.get()};
421 ClangTool tool(op.getCompilations(), op.getSourcePathList());
422
423 if(gStdinMode) {
424 if(op.getSourcePathList().size() != 1) {
425 llvm::errs() << "Expect exactly one file path in STDINMode.\n"sv;
426 return 1;
427 }
428
429 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> codeOrErr = llvm::MemoryBuffer::getSTDIN();
430
431 if(const std::error_code errorCode = codeOrErr.getError()) {
432 llvm::errs() << errorCode.message() << "\n";
433 return 1;
434 }
435
436 inMemoryCode = std::move(codeOrErr.get());
437
438 if(inMemoryCode->getBufferSize() == 0) {
439 Error("empty file\n");
440 return 1; // Skip empty files.
441 }
442
443 llvm::StringRef sourceFilePath = op.getSourcePathList().front();
444 tool.mapVirtualFile(sourceFilePath, inMemoryCode->getBuffer());
445 }
446
447 auto prependArgument = [&](auto arg) {
448 tool.appendArgumentsAdjuster(getInsertArgumentAdjuster(arg, ArgumentInsertPosition::BEGIN));
449 };
450
451 // Special handling to spare users to figure out what include paths to add.
452
453 // For some reason, Clang on Apple seems to require an additional hint for the C++ headers.
454#ifdef __APPLE__
455 gUseLibCpp = true;
456#endif /* __APPLE__ */
457
458 if(gUseLibCpp) {
459 prependArgument(INSIGHTS_LLVM_INCLUDE_DIR);
460 prependArgument("-stdlib=libc++");
461 prependArgument("-fexperimental-library");
462
463#ifdef __APPLE__
464 prependArgument("-nostdinc++"); // macos Monterey
465#endif /* __APPLE__ */
466 }
467
468 prependArgument(INSIGHTS_CLANG_RESOURCE_INCLUDE_DIR);
469 prependArgument(INSIGHTS_CLANG_RESOURCE_DIR);
470
471 if(GetInsightsOptions().UseShow2C) {
474 }
475
476 return tool.run(newFrontendActionFactory<CppInsightFrontendAction>().get());
477}
478//-----------------------------------------------------------------------------
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:330
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:315
CppInsightFrontendAction()=default
void EndSourceFileAction() override
Definition Insights.cpp:310
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