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 
28 using namespace clang;
29 using namespace clang::driver;
30 using namespace clang::tooling;
31 using namespace clang::insights;
32 //-----------------------------------------------------------------------------
33 
35 //-----------------------------------------------------------------------------
36 
38 {
39  return gInsightsOptions;
40 }
41 //-----------------------------------------------------------------------------
42 
44 {
45  return gInsightsOptions;
46 }
47 //-----------------------------------------------------------------------------
48 
49 static llvm::cl::OptionCategory gInsightCategory("Insights"sv);
50 //-----------------------------------------------------------------------------
51 
52 //-----------------------------------------------------------------------------
53 static 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 static 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 
64 static 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 
80 static const ASTContext* gAST{};
81 const ASTContext& GetGlobalAST()
82 {
83  return *gAST;
84 }
85 //-----------------------------------------------------------------------------
86 
87 static const CompilerInstance* gCI{};
88 const CompilerInstance& GetGlobalCI()
89 {
90  return *gCI;
91 }
92 //-----------------------------------------------------------------------------
93 
94 namespace clang::insights {
95 std::string EmitGlobalVariableCtors();
96 
97 using GlobalInsertMap = std::pair<bool, std::string_view>;
98 
99 static constinit std::array<GlobalInsertMap, static_cast<size_t>(GlobalInserts::MAX)> gGlobalInserts{};
100 
101 void 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 
113 using IncludeData = std::pair<const SourceLocation, std::string>;
114 
115 class FindIncludes : public PPCallbacks
116 {
117  SourceManager& mSm;
118  Preprocessor& mPP;
119  std::vector<IncludeData>& mIncludes;
120 
121 public:
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 
174 class CppInsightASTConsumer final : public ASTConsumer
175 {
176  Rewriter& mRewriter;
177  std::vector<IncludeData>& mIncludes;
178 
179 public:
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 
313 public:
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 
335 static 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 
349 int 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 
383 struct __mptr
384 {
385  short d;
386  short i;
387  __vptp f;
388 };
389 
390 extern 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 //-----------------------------------------------------------------------------
std::pair< const SourceLocation, std::string > IncludeData
Definition: Insights.cpp:113
int main(int argc, const char **argv)
Definition: Insights.cpp:349
static llvm::cl::OptionCategory gInsightCategory("Insights"sv)
InsightsOptions & GetInsightsOptionsRW()
Definition: Insights.cpp:43
static llvm::cl::opt< bool > gStdinMode("stdin", llvm::cl::desc("Read the input from <stdin>."sv), llvm::cl::init(false), llvm::cl::cat(gInsightCategory))
const CompilerInstance & GetGlobalCI()
Get access to the CompilerInstance.
Definition: Insights.cpp:88
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
const ASTContext & GetGlobalAST()
Get access to the ASTContext.
Definition: Insights.cpp:81
static llvm::cl::OptionCategory gInsightEduCategory("Insights-Educational"sv, "This transformations are only for education purposes. The resulting code most likely does not compile."sv)
const InsightsOptions & GetInsightsOptions()
Get the global C++ Insights options.
Definition: Insights.cpp:37
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
CppInsightFrontendAction()=default
std::unique_ptr< ASTConsumer > CreateASTConsumer(CompilerInstance &CI, StringRef) override
Definition: Insights.cpp:320
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...
The C++ Insights formatter.
void StrCat(std::string &ret, const auto &... args)
static constinit std::array< GlobalInsertMap, static_cast< size_t >GlobalInserts::MAX)> gGlobalInserts
Definition: Insights.cpp:99
@ 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()
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
Global C++ Insights command line options.
Definition: Insights.h:20