1#include "clang/AST/ASTConsumer.h"
2#include "clang/AST/ASTContext.h"
3#include "clang/AST/ASTTypeTraits.h"
4#include "clang/AST/Attr.h"
5#include "clang/AST/ParentMapContext.h"
6#include "clang/AST/RecursiveASTVisitor.h"
7#include "clang/Frontend/CompilerInstance.h"
8#include "clang/Lex/Lexer.h"
9#include "clang/Rewrite/Core/Rewriter.h"
10#include "clang/Tooling/ArgumentsAdjusters.h"
11#include "clang/Tooling/CommonOptionsParser.h"
12#include "clang/Tooling/Tooling.h"
13#include "llvm/ADT/RewriteBuffer.h"
14#include "llvm/ADT/StringRef.h"
15#include "llvm/Support/CommandLine.h"
16#include "llvm/Support/Error.h"
17#include "llvm/Support/FileSystem.h"
18#include "llvm/Support/InitLLVM.h"
19#include "llvm/Support/raw_ostream.h"
28#include <unordered_set>
36 static std::mutex mutex;
41 static std::unordered_set<std::string> registry;
48 auto [_, inserted] = registry.insert(path);
59namespace fs = std::filesystem;
61static cl::OptionCategory ToolCategory(
"ascii-chat instrumentation options");
63static cl::extrahelp CommonHelp(tooling::CommonOptionsParser::HelpMessage);
64static cl::extrahelp MoreHelp(
"\nInstrumentation tool for ascii-chat debugging\n");
66static cl::opt<std::string> OutputDirectoryOption(
"output-dir",
67 cl::desc(
"Directory where instrumented sources will be written"),
68 cl::value_desc(
"path"), cl::Required, cl::cat(ToolCategory));
70static cl::opt<std::string>
71 InputRootOption(
"input-root", cl::desc(
"Root directory of original sources (used to compute relative paths)"),
72 cl::value_desc(
"path"), cl::init(
""), cl::cat(ToolCategory));
74static cl::opt<std::string> BuildPath(
"p", cl::desc(
"Build path (directory containing compile_commands.json)"),
75 cl::Optional, cl::cat(ToolCategory));
76static cl::opt<bool> LogMacroExpansionsOption(
"log-macro-expansions",
77 cl::desc(
"Instrument statements originating from macro expansions"),
78 cl::init(
false), cl::cat(ToolCategory));
80static cl::opt<bool> LogMacroInvocationsOption(
81 "log-macro-invocations",
82 cl::desc(
"Emit a synthetic record for the macro invocation site when expansions are instrumented"), cl::init(
false),
83 cl::cat(ToolCategory));
85static cl::opt<bool> LegacyIncludeMacroExpansionsOption(
86 "include-macro-expansions",
87 cl::desc(
"Deprecated alias for --log-macro-expansions (kept for backward compatibility)"), cl::init(
false),
88 cl::cat(ToolCategory), cl::Hidden);
90static cl::list<std::string>
91 FileIncludeFilters(
"filter-file", cl::desc(
"Only instrument files whose path contains the given substring"),
92 cl::value_desc(
"substring"), cl::cat(ToolCategory));
94static cl::list<std::string>
95 FunctionIncludeFilters(
"filter-function",
96 cl::desc(
"Only instrument functions whose name matches the given substring"),
97 cl::value_desc(
"substring"), cl::cat(ToolCategory));
99static cl::opt<std::string>
100 FileListOption(
"file-list", cl::desc(
"Path to file containing newline-delimited translation units to instrument"),
101 cl::value_desc(
"path"), cl::init(
""), cl::cat(ToolCategory));
103static cl::list<std::string> SourcePaths(cl::Positional, cl::desc(
"<source0> [... <sourceN>]"), cl::cat(ToolCategory));
105static cl::opt<std::string> SignalHandlerAnnotation(
106 "signal-handler-annotation",
108 "Annotation string used to mark functions that should be skipped (default: ASCII_INSTR_PANIC_SIGNAL_HANDLER)"),
109 cl::value_desc(
"annotation"), cl::init(
"ASCII_INSTR_PANIC_SIGNAL_HANDLER"), cl::cat(ToolCategory));
117class InstrumentationVisitor :
public clang::RecursiveASTVisitor<InstrumentationVisitor> {
119 InstrumentationVisitor(clang::ASTContext &context, clang::Rewriter &rewriter,
const fs::path &outputDir,
120 const fs::path &inputRoot,
bool enableFileFilters,
bool enableFunctionFilters,
121 bool logMacroInvocations,
bool logMacroExpansions)
122 : context_(context), rewriter_(rewriter), outputDir_(outputDir), inputRoot_(inputRoot),
123 enableFileFilters_(enableFileFilters), enableFunctionFilters_(enableFunctionFilters),
124 logMacroInvocations_(logMacroInvocations), logMacroExpansions_(logMacroExpansions) {}
126 bool TraverseFunctionDecl(clang::FunctionDecl *funcDecl) {
127 const bool previousSkipState = skipCurrentFunction_;
128 currentFunction_ = funcDecl;
129 skipCurrentFunction_ = shouldSkipFunction(funcDecl);
131 const bool result = clang::RecursiveASTVisitor<InstrumentationVisitor>::TraverseFunctionDecl(funcDecl);
133 skipCurrentFunction_ = previousSkipState;
134 currentFunction_ =
nullptr;
138 bool VisitStmt(clang::Stmt *statement) {
139 if (!statement || !currentFunction_) {
143 if (!isDirectChildOfCompound(*statement)) {
147 if (llvm::isa<clang::CompoundStmt>(statement) || llvm::isa<clang::NullStmt>(statement)) {
151 if (skipCurrentFunction_) {
155 if (!shouldInstrumentStatement(*statement)) {
159 clang::SourceManager &sourceManager = context_.getSourceManager();
160 const clang::LangOptions &langOptions = context_.getLangOpts();
162 clang::SourceLocation beginLocation = statement->getBeginLoc();
163 if (beginLocation.isInvalid()) {
167 clang::SourceLocation expansionLocation = sourceManager.getExpansionLoc(beginLocation);
168 if (!expansionLocation.isValid()) {
172 if (!sourceManager.isWrittenInMainFile(expansionLocation)) {
176 const clang::FileID fileId = sourceManager.getFileID(expansionLocation);
177 const clang::FileEntry *fileEntry = sourceManager.getFileEntryForID(fileId);
178 if (fileEntry ==
nullptr) {
182 const llvm::StringRef absoluteFilePath = fileEntry->tryGetRealPathName();
183 if (absoluteFilePath.empty()) {
187 const fs::path filePath = fs::path(absoluteFilePath.str());
188 if (enableFileFilters_ && !matchesFileFilters(filePath)) {
192 const std::string relativePath = makeRelativePath(filePath);
193 const std::string uniqueKey = buildUniqueKey(filePath, expansionLocation, sourceManager);
194 if (!instrumentedLocations_.insert(uniqueKey).second) {
198 const bool isMacroExpansion =
199 sourceManager.isMacroBodyExpansion(beginLocation) || sourceManager.isMacroArgExpansion(beginLocation);
201 std::string instrumentationBlock;
203 if (isMacroExpansion) {
204 if (logMacroInvocations_) {
205 const std::optional<MacroInvocationMetadata> invocationMetadata =
206 buildMacroInvocationMetadata(*statement, sourceManager, langOptions);
207 if (invocationMetadata.has_value()) {
208 if (macroInvocationLocations_.insert(invocationMetadata->uniqueKey).second) {
209 instrumentationBlock.append(buildInstrumentationLine(invocationMetadata->relativePath,
210 invocationMetadata->lineNumber,
216 if (logMacroExpansions_) {
217 const unsigned lineNumber = sourceManager.getSpellingLineNumber(expansionLocation);
218 const std::optional<std::string> snippetOpt = extractSnippet(*statement, sourceManager, langOptions);
219 const llvm::StringRef snippetRef = snippetOpt ? llvm::StringRef(*snippetOpt) : llvm::StringRef(
"<unavailable>");
220 instrumentationBlock.append(
224 if (instrumentationBlock.empty()) {
228 const unsigned lineNumber = sourceManager.getSpellingLineNumber(expansionLocation);
229 const std::optional<std::string> snippetOpt = extractSnippet(*statement, sourceManager, langOptions);
230 const llvm::StringRef snippetRef = snippetOpt ? llvm::StringRef(*snippetOpt) : llvm::StringRef(
"<unavailable>");
231 instrumentationBlock.append(buildInstrumentationLine(relativePath, lineNumber, snippetRef,
kMacroFlagNone));
234 if (instrumentationBlock.empty()) {
238 rewriter_.InsertText(expansionLocation, instrumentationBlock,
true,
true);
239 includeNeeded_ =
true;
243 bool includeNeeded()
const {
244 return includeNeeded_;
247 std::string buildUniqueKey(
const fs::path &filePath, clang::SourceLocation location,
248 const clang::SourceManager &sourceManager)
const {
249 const unsigned offset = sourceManager.getFileOffset(location);
250 return (filePath.string() +
":" + std::to_string(offset));
253 std::string buildInstrumentationLine(
const std::string &relativePath,
unsigned lineNumber, llvm::StringRef snippet,
254 unsigned macroFlag)
const {
255 std::string escapedSnippet = escapeSnippet(snippet);
256 std::string instrumentationLine;
257 instrumentationLine.reserve(escapedSnippet.size() + 256);
258 instrumentationLine.append(
"ascii_instr_log_line(\"");
259 instrumentationLine.append(relativePath);
260 instrumentationLine.append(
"\", ");
261 instrumentationLine.append(std::to_string(lineNumber));
262 instrumentationLine.append(
", __func__, \"");
263 instrumentationLine.append(escapedSnippet);
264 instrumentationLine.append(
"\", ");
265 instrumentationLine.append(std::to_string(macroFlag));
266 instrumentationLine.append(
");\n");
267 return instrumentationLine;
270 struct MacroInvocationMetadata {
271 std::string relativePath;
272 unsigned lineNumber = 0;
274 std::string uniqueKey;
277 std::optional<MacroInvocationMetadata> buildMacroInvocationMetadata(
const clang::Stmt &statement,
278 clang::SourceManager &sourceManager,
279 const clang::LangOptions &langOptions)
const {
280 clang::SourceLocation beginLocation = statement.getBeginLoc();
281 if (!(sourceManager.isMacroBodyExpansion(beginLocation) || sourceManager.isMacroArgExpansion(beginLocation))) {
285 clang::SourceLocation callerLocation = sourceManager.getImmediateMacroCallerLoc(beginLocation);
286 if (!callerLocation.isValid()) {
290 callerLocation = sourceManager.getExpansionLoc(callerLocation);
291 if (!callerLocation.isValid() || !sourceManager.isWrittenInMainFile(callerLocation)) {
295 const clang::FileID callerFileId = sourceManager.getFileID(callerLocation);
296 const clang::FileEntry *callerFileEntry = sourceManager.getFileEntryForID(callerFileId);
297 if (callerFileEntry ==
nullptr) {
301 const llvm::StringRef callerPathRef = callerFileEntry->tryGetRealPathName();
302 if (callerPathRef.empty()) {
306 MacroInvocationMetadata metadata;
307 const fs::path callerPath = fs::path(callerPathRef.str());
308 metadata.relativePath = makeRelativePath(callerPath);
309 metadata.lineNumber = sourceManager.getSpellingLineNumber(callerLocation);
310 metadata.uniqueKey = buildUniqueKey(callerPath, callerLocation, sourceManager);
312 clang::CharSourceRange expansionRange = sourceManager.getImmediateExpansionRange(beginLocation);
313 if (expansionRange.isValid()) {
314 bool invalid =
false;
315 llvm::StringRef invocationSource =
316 clang::Lexer::getSourceText(expansionRange, sourceManager, langOptions, &invalid);
317 if (!invalid && !invocationSource.empty()) {
318 metadata.snippet = invocationSource.str();
322 if (metadata.snippet.empty()) {
323 bool invalid =
false;
324 clang::CharSourceRange tokenRange = clang::CharSourceRange::getTokenRange(callerLocation, callerLocation);
325 llvm::StringRef fallback = clang::Lexer::getSourceText(tokenRange, sourceManager, langOptions, &invalid);
326 if (!invalid && !fallback.empty()) {
327 metadata.snippet = fallback.str();
329 metadata.snippet =
"<macro invocation>";
336 bool matchesFileFilters(
const fs::path &filePath)
const {
337 if (FileIncludeFilters.empty()) {
340 const std::string filePathString = filePath.generic_string();
341 for (
const std::string &token : FileIncludeFilters) {
342 if (filePathString.find(token) != std::string::npos) {
349 bool matchesFunctionFilters(
const clang::FunctionDecl *functionDecl)
const {
350 if (FunctionIncludeFilters.empty()) {
353 if (functionDecl ==
nullptr) {
356 const std::string functionName = functionDecl->getNameInfo().getAsString();
357 for (
const std::string &token : FunctionIncludeFilters) {
358 if (functionName.find(token) != std::string::npos) {
365 bool shouldInstrumentStatement(
const clang::Stmt &statement)
const {
366 if (llvm::isa<clang::NullStmt>(&statement)) {
369 if (llvm::isa<clang::ImplicitCastExpr>(&statement)) {
372 if (llvm::isa<clang::ParenExpr>(&statement)) {
376 if (enableFunctionFilters_ && !matchesFunctionFilters(currentFunction_)) {
383 bool shouldSkipFunction(
const clang::FunctionDecl *functionDecl)
const {
384 if (functionDecl ==
nullptr) {
388 if (functionDecl->isImplicit()) {
392 clang::SourceManager &sourceManager = context_.getSourceManager();
393 clang::SourceLocation location = functionDecl->getLocation();
394 location = sourceManager.getExpansionLoc(location);
395 if (!location.isValid() || !sourceManager.isWrittenInMainFile(location)) {
399 for (
const clang::AnnotateAttr *annotation : functionDecl->specific_attrs<clang::AnnotateAttr>()) {
403 if (annotation->getAnnotation() == SignalHandlerAnnotation.getValue()) {
411 std::string makeRelativePath(
const fs::path &absolutePath)
const {
412 if (inputRoot_.empty()) {
413 return absolutePath.generic_string();
416 std::error_code errorCode;
417 fs::path relative = fs::relative(absolutePath, inputRoot_, errorCode);
419 return absolutePath.generic_string();
421 return relative.generic_string();
424 static std::string escapeSnippet(llvm::StringRef snippet) {
426 result.reserve(snippet.size());
427 constexpr std::size_t kMaxSnippetLength = 1024;
428 std::size_t processed = 0;
429 for (
char ch : snippet) {
430 if (processed >= kMaxSnippetLength) {
431 result.append(
"<truncated>");
435 const unsigned char uch =
static_cast<unsigned char>(ch);
438 result.append(
"\\\\");
441 result.append(
"\\\"");
444 result.append(
"\\n");
447 result.append(
"\\r");
450 result.append(
"\\t");
453 if (std::isprint(uch) != 0 || uch >= 0x80) {
454 result.push_back(
static_cast<char>(uch));
456 result.append(
"\\\\x");
457 constexpr char hexDigits[] =
"0123456789ABCDEF";
458 result.push_back(hexDigits[(uch >> 4) & 0xF]);
459 result.push_back(hexDigits[uch & 0xF]);
467 static std::optional<std::string> extractSnippet(
const clang::Stmt &statement,
468 const clang::SourceManager &sourceManager,
469 const clang::LangOptions &langOptions) {
470 clang::SourceLocation begin = statement.getBeginLoc();
471 clang::SourceLocation end = statement.getEndLoc();
473 if (begin.isInvalid() || end.isInvalid()) {
477 clang::SourceLocation expansionBegin = sourceManager.getExpansionLoc(begin);
478 clang::SourceLocation expansionEnd = sourceManager.getExpansionLoc(end);
479 if (!expansionBegin.isValid() || !expansionEnd.isValid()) {
483 clang::CharSourceRange range = clang::CharSourceRange::getTokenRange(expansionBegin, expansionEnd);
484 bool invalid =
false;
485 llvm::StringRef text = clang::Lexer::getSourceText(range, sourceManager, langOptions, &invalid);
486 if (invalid || text.empty()) {
493 clang::ASTContext &context_;
494 clang::Rewriter &rewriter_;
497 bool enableFileFilters_;
498 bool enableFunctionFilters_;
499 bool logMacroInvocations_;
500 bool logMacroExpansions_;
501 const clang::FunctionDecl *currentFunction_ =
nullptr;
502 bool skipCurrentFunction_ =
false;
503 bool includeNeeded_ =
false;
504 std::unordered_set<std::string> instrumentedLocations_;
505 std::unordered_set<std::string> macroInvocationLocations_;
507 bool isDirectChildOfCompound(
const clang::Stmt &statement)
const {
508 clang::DynTypedNodeList parents = context_.getParents(statement);
509 for (
const auto &parent : parents) {
510 if (parent.get<clang::CompoundStmt>()) {
518class InstrumentationASTConsumer :
public clang::ASTConsumer {
520 explicit InstrumentationASTConsumer(InstrumentationVisitor &visitor) : visitor_(visitor) {}
522 void HandleTranslationUnit(clang::ASTContext &context)
override {
523 visitor_.TraverseDecl(context.getTranslationUnitDecl());
527 InstrumentationVisitor &visitor_;
530class InstrumentationFrontendAction :
public clang::ASTFrontendAction {
532 explicit InstrumentationFrontendAction(
const fs::path &outputDir,
const fs::path &inputRoot,
bool enableFileFilters,
533 bool enableFunctionFilters,
bool logMacroInvocations,
bool logMacroExpansions)
534 : outputDir_(outputDir), inputRoot_(inputRoot), enableFileFilters_(enableFileFilters),
535 enableFunctionFilters_(enableFunctionFilters), logMacroInvocations_(logMacroInvocations),
536 logMacroExpansions_(logMacroExpansions) {}
538 void EndSourceFileAction()
override {
539 clang::SourceManager &sourceManager = rewriter_.getSourceMgr();
540 const clang::FileEntry *fileEntry = sourceManager.getFileEntryForID(sourceManager.getMainFileID());
546 llvm::errs() <<
"Instrumentation visitor not initialized; skipping file output" <<
"\n";
547 hadWriteError_ =
true;
551 const llvm::StringRef filePathRef = fileEntry->tryGetRealPathName();
552 if (filePathRef.empty()) {
553 llvm::errs() <<
"Unable to resolve file path for instrumented output\n";
557 const fs::path originalPath = fs::path(filePathRef.str());
558 const std::string relativePath = visitor_->makeRelativePath(originalPath);
559 const fs::path destinationPath = outputDir_ / relativePath;
560 const std::string destinationString = destinationPath.string();
566 if (fs::exists(destinationPath)) {
567 llvm::errs() <<
"Refusing to overwrite existing file: " << destinationPath.c_str() <<
"\n";
569 hadWriteError_ =
true;
573 const fs::path parent = destinationPath.parent_path();
574 std::error_code directoryError;
575 fs::create_directories(parent, directoryError);
576 if (directoryError) {
577 llvm::errs() <<
"Failed to create output directory: " << parent.c_str() <<
" - " << directoryError.message()
580 hadWriteError_ =
true;
584 ensureIncludeInserted(originalPath);
586 std::string rewrittenContents;
587 if (
const llvm::RewriteBuffer *buffer = rewriter_.getRewriteBufferFor(sourceManager.getMainFileID())) {
588 rewrittenContents.assign(buffer->begin(), buffer->end());
590 rewrittenContents = sourceManager.getBufferData(sourceManager.getMainFileID()).str();
593 std::error_code fileError;
594 llvm::raw_fd_ostream outputStream(destinationPath.string(), fileError, llvm::sys::fs::OF_Text);
596 llvm::errs() <<
"Failed to open output file: " << destinationPath.c_str() <<
" - " << fileError.message() <<
"\n";
598 hadWriteError_ =
true;
601 outputStream << rewrittenContents;
602 outputStream.close();
603 if (outputStream.has_error()) {
604 llvm::errs() <<
"Error while writing instrumented file: " << destinationPath.c_str() <<
"\n";
606 hadWriteError_ =
true;
610 bool hadWriteError()
const {
611 return hadWriteError_;
615 std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &compiler, llvm::StringRef)
override {
616 rewriter_.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts());
617 visitor_ = std::make_unique<InstrumentationVisitor>(compiler.getASTContext(), rewriter_, outputDir_, inputRoot_,
618 enableFileFilters_, enableFunctionFilters_,
619 logMacroInvocations_, logMacroExpansions_);
620 return std::make_unique<InstrumentationASTConsumer>(*visitor_);
624 void ensureIncludeInserted(
const fs::path &originalPath) {
626 if (!visitor_ || !visitor_->includeNeeded()) {
630 clang::SourceManager &sourceManager = rewriter_.getSourceMgr();
631 const clang::FileID fileId = sourceManager.getMainFileID();
632 const llvm::StringRef bufferData = sourceManager.getBufferData(fileId);
633 if (bufferData.contains(
"#include \"tooling/panic/instrument_log.h\"")) {
637 clang::SourceLocation insertionLocation = sourceManager.getLocForStartOfFile(fileId);
638 rewriter_.InsertText(insertionLocation,
"#include \"tooling/panic/instrument_log.h\"\n",
false,
true);
641 clang::Rewriter rewriter_;
644 bool enableFileFilters_;
645 bool enableFunctionFilters_;
646 bool logMacroInvocations_;
647 bool logMacroExpansions_;
648 std::unique_ptr<InstrumentationVisitor> visitor_;
649 bool hadWriteError_ =
false;
652class InstrumentationActionFactory :
public clang::tooling::FrontendActionFactory {
654 InstrumentationActionFactory(
const fs::path &outputDir,
const fs::path &inputRoot,
bool enableFileFilters,
655 bool enableFunctionFilters,
bool logMacroInvocations,
bool logMacroExpansions)
656 : outputDir_(outputDir), inputRoot_(inputRoot), enableFileFilters_(enableFileFilters),
657 enableFunctionFilters_(enableFunctionFilters), logMacroInvocations_(logMacroInvocations),
658 logMacroExpansions_(logMacroExpansions) {}
660 std::unique_ptr<clang::FrontendAction> create() {
661 return std::make_unique<InstrumentationFrontendAction>(
662 outputDir_, inputRoot_, enableFileFilters_, enableFunctionFilters_, logMacroInvocations_, logMacroExpansions_);
668 bool enableFileFilters_;
669 bool enableFunctionFilters_;
670 bool logMacroInvocations_;
671 bool logMacroExpansions_;
676int main(
int argc,
const char **argv) {
678 llvm::InitLLVM InitLLVM(argc, argv);
681 cl::ParseCommandLineOptions(argc, argv,
"ascii-chat instrumentation tool\n");
683 const fs::path outputDir = fs::path(OutputDirectoryOption.getValue());
685 if (!InputRootOption.getValue().empty()) {
686 inputRoot = fs::path(InputRootOption.getValue());
688 inputRoot = fs::current_path();
691 std::vector<std::string> rawSourceArgs;
692 rawSourceArgs.reserve(SourcePaths.size());
693 for (
const auto &path : SourcePaths) {
694 rawSourceArgs.push_back(path);
696 std::vector<std::string> sourcePaths;
697 std::vector<std::string> extraCompilerArgs;
698 for (std::size_t i = 0; i < rawSourceArgs.size(); ++i) {
699 const std::string &entry = rawSourceArgs[i];
706 if (entry[0] ==
'-') {
707 extraCompilerArgs.push_back(entry);
708 const bool consumesNext = (entry ==
"-I" || entry ==
"-isystem" || entry ==
"-include" ||
709 entry ==
"-include-pch" || entry ==
"-imacros" || entry ==
"-idirafter" ||
710 entry ==
"-iprefix" || entry ==
"-iwithprefix" || entry ==
"-iwithprefixbefore" ||
711 entry ==
"-resource-dir" || entry ==
"-Xclang" || entry ==
"-Xpreprocessor");
712 if (consumesNext && (i + 1) < rawSourceArgs.size()) {
713 extraCompilerArgs.push_back(rawSourceArgs[i + 1]);
718 sourcePaths.push_back(entry);
720 if (!FileListOption.getValue().empty()) {
721 std::ifstream listStream(FileListOption.getValue());
722 if (!listStream.is_open()) {
723 llvm::errs() <<
"Failed to open file list: " << FileListOption.getValue() <<
"\n";
728 while (std::getline(listStream, line)) {
729 llvm::StringRef trimmed(line);
730 trimmed = trimmed.trim();
731 if (trimmed.empty()) {
734 sourcePaths.emplace_back(trimmed.str());
738 if (sourcePaths.empty()) {
740 <<
"No translation units specified for instrumentation. Provide positional source paths or --file-list."
745 if (fs::exists(outputDir)) {
746 if (!fs::is_directory(outputDir)) {
747 llvm::errs() <<
"Output path exists and is not a directory: " << outputDir.c_str() <<
"\n";
751 std::error_code errorCode;
752 fs::create_directories(outputDir, errorCode);
754 llvm::errs() <<
"Failed to create output directory: " << outputDir.c_str() <<
" - " << errorCode.message()
760 const bool logMacroExpansions = LogMacroExpansionsOption.getValue() || LegacyIncludeMacroExpansionsOption.getValue();
761 const bool logMacroInvocations = LogMacroInvocationsOption.getValue();
763 if (!LogMacroExpansionsOption.getValue() && LegacyIncludeMacroExpansionsOption.getValue()) {
764 llvm::errs() <<
"warning: --include-macro-expansions is deprecated; use --log-macro-expansions instead\n";
768 std::string buildPath = BuildPath.getValue();
769 if (buildPath.empty()) {
772 std::string errorMessage;
773 std::unique_ptr<tooling::CompilationDatabase> compilations =
774 tooling::CompilationDatabase::loadFromDirectory(buildPath, errorMessage);
776 llvm::errs() <<
"Error loading compilation database from '" << buildPath <<
"': " << errorMessage <<
"\n";
780 clang::tooling::ClangTool tool(*compilations, sourcePaths);
782 if (!extraCompilerArgs.empty()) {
783 const std::vector<std::string> extraArgsCopy(extraCompilerArgs.begin(), extraCompilerArgs.end());
784 tool.appendArgumentsAdjuster([extraArgsCopy](
const clang::tooling::CommandLineArguments &args, llvm::StringRef) {
785 clang::tooling::CommandLineArguments adjusted = args;
786 adjusted.insert(adjusted.end(), extraArgsCopy.begin(), extraArgsCopy.end());
791 auto stripPchAdjuster = [](
const clang::tooling::CommandLineArguments &args, llvm::StringRef) {
792 clang::tooling::CommandLineArguments result;
793 const auto containsCMakePch = [](llvm::StringRef value) {
return value.contains(
"cmake_pch"); };
795 for (std::size_t i = 0; i < args.size(); ++i) {
796 const std::string &arg = args[i];
797 llvm::StringRef argRef(arg);
799 if ((argRef ==
"-include" || argRef ==
"--include" || argRef ==
"-include-pch" || argRef ==
"--include-pch") &&
800 (i + 1) < args.size()) {
801 if (containsCMakePch(args[i + 1])) {
807 if ((argRef.starts_with(
"-include=") || argRef.starts_with(
"--include=") || argRef.starts_with(
"-include-pch=") ||
808 argRef.starts_with(
"--include-pch=")) &&
809 containsCMakePch(argRef)) {
813 result.push_back(arg);
818 tool.appendArgumentsAdjuster(stripPchAdjuster);
820 auto ensureSourceIncludeAdjuster = [&inputRoot](
const clang::tooling::CommandLineArguments &args, llvm::StringRef) {
821 clang::tooling::CommandLineArguments result = args;
823 const auto hasInclude = [&result](
const std::string &dir) {
824 const std::string combined =
"-I" + dir;
825 if (std::find(result.begin(), result.end(), combined) != result.end()) {
828 for (std::size_t i = 0; i + 1 < result.size(); ++i) {
829 if (result[i] ==
"-I" && result[i + 1] == dir) {
836 const auto appendInclude = [&result](
const std::string &dir) {
837 result.push_back(
"-I");
838 result.push_back(dir);
841 const std::string libDir = (inputRoot /
"lib").generic_string();
842 const std::string srcDir = (inputRoot /
"src").generic_string();
844 if (!hasInclude(libDir)) {
845 appendInclude(libDir);
847 if (!hasInclude(srcDir)) {
848 appendInclude(srcDir);
853 tool.appendArgumentsAdjuster(ensureSourceIncludeAdjuster);
863 auto stripUnnecessaryFlags = [](
const clang::tooling::CommandLineArguments &args, llvm::StringRef) {
864 clang::tooling::CommandLineArguments result;
866 for (
size_t i = 0; i < args.size(); ++i) {
867 const std::string &arg = args[i];
870 if (arg.find(
"-fsanitize") != std::string::npos)
872 if (arg.find(
"-fno-sanitize") != std::string::npos)
874 if (arg.find(
"sanitize") != std::string::npos)
878 if (arg ==
"-g" || arg ==
"-g2" || arg ==
"-g3")
880 if (arg ==
"-gcolumn-info")
882 if (arg ==
"-fstandalone-debug")
884 if (arg.find(
"-gcodeview") != std::string::npos)
886 if (arg.find(
"-gdwarf") != std::string::npos)
890 if (arg.find(
"-fstack-protector") != std::string::npos)
894 if (arg.find(
"-fno-omit-frame-pointer") != std::string::npos)
896 if (arg.find(
"-fomit-frame-pointer") != std::string::npos)
900 if (arg ==
"-fno-inline")
902 if (arg ==
"-fno-eliminate-unused-debug-types")
905 result.push_back(arg);
913 tool.appendArgumentsAdjuster(stripUnnecessaryFlags);
915 InstrumentationActionFactory actionFactory(outputDir, inputRoot, !FileIncludeFilters.empty(),
916 !FunctionIncludeFilters.empty(), logMacroInvocations, logMacroExpansions);
917 const int executionResult = tool.run(&actionFactory);
918 if (executionResult != 0) {
919 llvm::errs() <<
"Instrumenter failed with code " << executionResult <<
"\n";
921 return executionResult;