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
58static llvm::cl::opt<bool> gStdinMode("stdin",
59 llvm::cl::desc("Read the input from <stdin>."sv),
60 llvm::cl::init(false),
61 llvm::cl::cat(gInsightCategory));
62//-----------------------------------------------------------------------------
63
64static llvm::cl::opt<bool>
65 gUseLibCpp("use-libc++", llvm::cl::desc("Use libc++."sv), llvm::cl::init(false), llvm::cl::cat(gInsightCategory));
66//-----------------------------------------------------------------------------
67
68#define INSIGHTS_OPT(option, name, deflt, description, category) \
69 static llvm::cl::opt<bool, true> g##name(option, \
70 llvm::cl::desc(std::string_view{description}), \
71 llvm::cl::NotHidden, \
72 llvm::cl::location(gInsightsOptions.name), \
73 llvm::cl::init(deflt), \
74 llvm::cl::cat(category));
75//-----------------------------------------------------------------------------
76
77#include "InsightsOptions.def"
78//-----------------------------------------------------------------------------
79
80static const ASTContext* gAST{};
81const ASTContext& GetGlobalAST()
82{
83 return *gAST;
84}
85//-----------------------------------------------------------------------------
86
87static const CompilerInstance* gCI{};
88const CompilerInstance& GetGlobalCI()
89{
90 return *gCI;
91}
92//-----------------------------------------------------------------------------
93
94namespace clang::insights {
95std::string EmitGlobalVariableCtors();
96
97using GlobalInsertMap = std::pair<bool, std::string_view>;
98
99static constinit std::array<GlobalInsertMap, static_cast<size_t>(GlobalInserts::MAX)> gGlobalInserts{};
100
101void AddGLobalInsertMapEntry(GlobalInserts idx, std::string_view value)
102{
103 gGlobalInserts[static_cast<size_t>(idx)] = {false, value};
104}
105
107{
108 gGlobalInserts[static_cast<size_t>(idx)].first = true;
109}
110
111} // namespace clang::insights
112
113using IncludeData = std::pair<const SourceLocation, std::string>;
114
116{
117 SourceManager& mSm;
118 Preprocessor& mPP;
119 std::vector<IncludeData>& mIncludes;
120
121public:
122 FindIncludes(SourceManager& sm, Preprocessor& pp, std::vector<IncludeData>& incData)
123 : PPCallbacks{}
124 , mSm{sm}
125 , mPP{pp}
126 , mIncludes{incData}
127 {
128 }
129
130 void InclusionDirective(SourceLocation hashLoc,
131 const Token& /*IncludeTok*/,
132 StringRef fileName,
133 bool isAngled,
134 CharSourceRange /*FilenameRange*/,
135 OptionalFileEntryRef /*file*/,
136 StringRef /*SearchPath*/,
137 StringRef /*RelativePath*/,
138 const Module* /*Imported*/,
139 bool /*ModuleImported*/,
140 SrcMgr::CharacteristicKind /*FileType*/) override
141 {
142 auto expansionLoc = mSm.getExpansionLoc(hashLoc);
143
144 if(expansionLoc.isInvalid() or mSm.isInSystemHeader(expansionLoc)) {
145 return;
146 }
147
148 // XXX: distinguish between include and import via the IncludeTok
149 if(isAngled) {
150 mIncludes.emplace_back(expansionLoc, StrCat("#include <"sv, fileName, ">\n"sv));
151
152 } else {
153 mIncludes.emplace_back(expansionLoc, StrCat("#include \""sv, fileName, "\"\n"sv));
154 }
155 }
156
157 void MacroDefined(const Token& macroNameTok, const MacroDirective* md) override
158 {
159 const auto loc = md->getLocation();
160 if(not mSm.isWrittenInMainFile(loc)) {
161 return;
162 }
163
164 auto name = mPP.getSpelling(macroNameTok);
165
166 if(not name.starts_with("INSIGHTS_"sv)) {
167 return;
168 }
169
170 mIncludes.emplace_back(loc, StrCat("#define "sv, name, "\n"sv));
171 }
172};
173
175{
176 Rewriter& mRewriter;
177 std::vector<IncludeData>& mIncludes;
178
179public:
180 explicit CppInsightASTConsumer(Rewriter& rewriter, std::vector<IncludeData>& includes)
181 : ASTConsumer{}
182 , mRewriter{rewriter}
183 , mIncludes{includes}
184 {
185 if(GetInsightsOptions().UseShow2C) {
186 if(GetInsightsOptions().ShowCoroutineTransformation) {
187 gInsightsOptions.UseShow2C = false;
188 } else {
189 gInsightsOptions.ShowLifetime = true;
190 }
191 }
192
193 if(GetInsightsOptions().ShowLifetime) {
194 gInsightsOptions.UseShowInitializerList = true;
195 }
196 }
197
198 void HandleTranslationUnit(ASTContext& context) override
199 {
200 gAST = &context;
201 auto& sm = context.getSourceManager();
202
203 auto isExpansionInSystemHeader = [&sm](const Decl* d) {
204 auto expansionLoc = sm.getExpansionLoc(d->getLocation());
205
206 return expansionLoc.isInvalid() or sm.isInSystemHeader(expansionLoc);
207 };
208
209 const auto& mainFileId = sm.getMainFileID();
210
211 mRewriter.ReplaceText({sm.getLocForStartOfFile(mainFileId), sm.getLocForEndOfFile(mainFileId)}, "");
212
213 OutputFormatHelper outputFormatHelper{};
214 CodeGeneratorVariant codeGenerator{outputFormatHelper};
215
216 auto include = mIncludes.begin();
217
218 auto insertBlankLineIfRequired = [&](std::optional<SourceLocation>& lastLoc, SourceLocation nextLoc) {
219 if(lastLoc.has_value() and
220 (2 <= (sm.getSpellingLineNumber(nextLoc) - sm.getSpellingLineNumber(lastLoc.value())))) {
221 outputFormatHelper.AppendNewLine();
222 }
223
224 lastLoc = nextLoc;
225 };
226
227 for(std::optional<SourceLocation> lastLoc{}; const auto* d : context.getTranslationUnitDecl()->decls()) {
228 if(isExpansionInSystemHeader(d)) {
229 continue;
230 }
231
232 // includes before this decl
233 for(; (mIncludes.end() != include) and (include->first < d->getLocation()); include = std::next(include)) {
234 insertBlankLineIfRequired(lastLoc, include->first);
235 outputFormatHelper.Append(include->second);
236 }
237
238 // ignore includes inside this decl
239 include = std::find_if_not(include, mIncludes.end(), [&](auto& inc) {
240 return ((inc.first >= d->getLocation()) and (inc.first <= d->getEndLoc()));
241 });
242
243 if(isa<LinkageSpecDecl>(d) and d->isImplicit()) {
244 continue;
245
246 // Only handle explicit specializations here. Implicit ones are handled by the `VarTemplateDecl`
247 // itself.
248 } else if(const auto* vdspec = dyn_cast_or_null<VarTemplateSpecializationDecl>(d);
249 vdspec and (TSK_ExplicitSpecialization != vdspec->getSpecializationKind())) {
250 continue;
251 }
252
253 insertBlankLineIfRequired(lastLoc, d->getLocation());
254
255 codeGenerator->InsertArg(d);
256 }
257
258 std::string insightsIncludes{};
259
260 if(GetInsightsOptions().ShowCoroutineTransformation) {
261 insightsIncludes.append(
262 R"(/*************************************************************************************
263 * NOTE: The coroutine transformation you've enabled is a hand coded transformation! *
264 * Most of it is _not_ present in the AST. What you see is an approximation. *
265 *************************************************************************************/
266)"sv);
267 } else if(GetInsightsOptions().UseShow2C or GetInsightsOptions().ShowLifetime) {
268 insightsIncludes.append(
269 R"(/*************************************************************************************
270 * NOTE: This an educational hand-rolled transformation. Things can be incorrect or *
271 * buggy. *
272 *************************************************************************************/
273)"sv);
274 }
275
276 // Check whether we had static local variables which we transformed. Then for the placement-new we need to
277 // include the header <new>.
278 std::string inserts{};
279 for(const auto& [active, value] : gGlobalInserts) {
280 if(not active) {
281 continue;
282 }
283
284 inserts.append(value);
285 inserts.append("\n"sv);
286 }
287
288 if(not inserts.empty()) {
289 insightsIncludes.append(inserts);
290 insightsIncludes.append("\n");
291 }
292
293 outputFormatHelper.InsertAt(0, insightsIncludes);
294
295 mRewriter.InsertText(sm.getLocForStartOfFile(mainFileId), outputFormatHelper.GetString());
296
297 if(GetInsightsOptions().UseShow2C) {
298 const auto& fileEntry = sm.getFileEntryForID(mainFileId);
300 const auto cxaLoc = sm.translateFileLineCol(fileEntry, fileEntry->getSize(), 1);
301
302 mRewriter.InsertText(cxaLoc, cxaStart);
303 }
304 }
305};
306//-----------------------------------------------------------------------------
307
309{
310 Rewriter mRewriter{};
311 std::vector<IncludeData> mIncludes{};
312
313public:
315 void EndSourceFileAction() override
316 {
317 mRewriter.getEditBuffer(mRewriter.getSourceMgr().getMainFileID()).write(llvm::outs());
318 }
319
320 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance& CI, StringRef /*file*/) override
321 {
322 gCI = &CI;
323
324 Preprocessor& pp = CI.getPreprocessor();
325 pp.addPPCallbacks(std::make_unique<FindIncludes>(CI.getSourceManager(), pp, mIncludes));
326
327 mRewriter.setSourceMgr(CI.getSourceManager(), CI.getLangOpts());
328 return std::make_unique<CppInsightASTConsumer>(mRewriter, mIncludes);
329 }
330};
331//-----------------------------------------------------------------------------
332
333#include "clang/Basic/Version.h"
334
335static void PrintVersion(raw_ostream& ostream)
336{
337 ostream << "cpp-insights " << INSIGHTS_VERSION << " https://cppinsights.io (" << GIT_REPO_URL << " "
338 << GIT_COMMIT_HASH << ")"
339 << "\n";
340
341#ifdef INSIGHTS_DEBUG
342 ostream << " Build with debug enabled\n";
343#endif
344 ostream << " LLVM Revision: " << clang::getLLVMRevision() << '\n';
345 ostream << " Clang Revision: " << clang::getClangFullCPPVersion() << '\n';
346}
347//-----------------------------------------------------------------------------
348
349int main(int argc, const char** argv)
350{
351 // Headers go first
352 using enum GlobalInserts;
354 "#include <new> // for thread-safe static's placement new\n#include <stdint.h> // for "
355 "uint64_t under Linux/GCC"sv);
356 AddGLobalInsertMapEntry(HeaderException, "#include <exception> // for noexcept transformation"sv);
357 AddGLobalInsertMapEntry(HeaderUtility, "#include <utility> // std::move"sv);
358 AddGLobalInsertMapEntry(HeaderStddef, "#include <stddef.h> // NULL and more"sv);
359 AddGLobalInsertMapEntry(HeaderAssert, "#include <assert.h> // _Static_assert"sv);
360 AddGLobalInsertMapEntry(HeaderStdlib, "#include <stdlib.h> // abort"sv);
361
362 // Now all the forward declared functions
363 AddGLobalInsertMapEntry(FuncCxaStart, "void __cxa_start(void);"sv);
364 AddGLobalInsertMapEntry(FuncCxaAtExit, "void __cxa_atexit(void);"sv);
365 AddGLobalInsertMapEntry(FuncMalloc, "void* malloc(unsigned int);"sv);
366 AddGLobalInsertMapEntry(FuncFree, R"(extern "C" void free(void*);)"sv);
367 AddGLobalInsertMapEntry(FuncMemset, R"(extern "C" void* memset(void*, int, unsigned int);)"sv);
368 AddGLobalInsertMapEntry(FuncMemcpy, R"(void* memcpy(void*, const void*, unsigned int);)"sv);
371 R"(extern "C" void* __cxa_vec_new(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
374 R"(extern "C" void* __cxa_vec_ctor(void*, unsigned int, unsigned int, unsigned int, void* (*)(void*), void* (*)(void*));)"sv);
377 R"(extern "C" void __cxa_vec_delete(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
380 R"(extern "C" void __cxa_vec_dtor(void *, unsigned int, unsigned int, void* (*destructor)(void *) );)"sv);
381 AddGLobalInsertMapEntry(FuncVtableStruct, R"(typedef int (*__vptp)();
382
383struct __mptr
384{
385 short d;
386 short i;
387 __vptp f;
388};
389
390extern struct __mptr* __vtbl_array[];
391)"sv);
392 AddGLobalInsertMapEntry(FuncCxaPureVirtual, R"(extern "C" void __cxa_pure_virtual() { abort(); })");
393
394 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
395 llvm::cl::SetVersionPrinter(&PrintVersion);
396
397 auto opExpected = CommonOptionsParser::create(argc, argv, gInsightCategory);
398
399 if(auto err = opExpected.takeError()) {
400 llvm::errs() << toString(std::move(err)) << "\n";
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//-----------------------------------------------------------------------------
static llvm::cl::opt< bool > gStdinMode("stdin", llvm::cl::desc("Read the input from <stdin>."sv), llvm::cl::init(false), llvm::cl::cat(gInsightCategory))
std::pair< const SourceLocation, std::string > IncludeData
Definition Insights.cpp:113
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:88
const ASTContext & GetGlobalAST()
Get access to the ASTContext.
Definition Insights.cpp:81
static const ASTContext * gAST
Definition Insights.cpp:80
static InsightsOptions gInsightsOptions
Definition Insights.cpp:34
static void PrintVersion(raw_ostream &ostream)
Definition Insights.cpp:335
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:87
static llvm::cl::opt< bool > gUseLibCpp("use-libc++", llvm::cl::desc("Use libc++."sv), llvm::cl::init(false), llvm::cl::cat(gInsightCategory))
constexpr std::string_view cxaStart
void HandleTranslationUnit(ASTContext &context) override
Definition Insights.cpp:198
CppInsightASTConsumer(Rewriter &rewriter, std::vector< IncludeData > &includes)
Definition Insights.cpp:180
std::unique_ptr< ASTConsumer > CreateASTConsumer(CompilerInstance &CI, StringRef) override
Definition Insights.cpp:320
CppInsightFrontendAction()=default
void EndSourceFileAction() override
Definition Insights.cpp:315
void MacroDefined(const Token &macroNameTok, const MacroDirective *md) override
Definition Insights.cpp:157
FindIncludes(SourceManager &sm, Preprocessor &pp, std::vector< IncludeData > &incData)
Definition Insights.cpp:122
void InclusionDirective(SourceLocation hashLoc, const Token &, StringRef fileName, bool isAngled, CharSourceRange, OptionalFileEntryRef, StringRef, StringRef, const Module *, bool, SrcMgr::CharacteristicKind) override
Definition Insights.cpp:130
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:99
std::pair< bool, std::string_view > GlobalInsertMap
Definition Insights.cpp:97
void AddGLobalInsertMapEntry(GlobalInserts idx, std::string_view value)
Definition Insights.cpp:101
void EnableGlobalInsert(GlobalInserts idx)
Definition Insights.cpp:106
std::string StrCat(const auto &... args)
Global C++ Insights command line options.
Definition Insights.h:20