8#include <unordered_map>
9#include <unordered_set>
12#include "clang/AST/ASTConsumer.h"
13#include "clang/AST/ASTContext.h"
14#include "clang/AST/RecursiveASTVisitor.h"
15#include "clang/Frontend/CompilerInstance.h"
16#include "clang/Lex/Lexer.h"
17#include "clang/Lex/Preprocessor.h"
18#include "clang/Rewrite/Core/Rewriter.h"
19#include "clang/Driver/Driver.h"
20#include "clang/Tooling/ArgumentsAdjusters.h"
21#include "clang/Tooling/CommonOptionsParser.h"
22#include "clang/Tooling/Tooling.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/FileSystem.h"
25#include "llvm/Support/InitLLVM.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/raw_ostream.h"
32namespace fs = std::filesystem;
35 static std::mutex mutex;
40 static std::unordered_set<std::string> registry;
47 bool inserted = registry.insert(path).second;
57static cl::OptionCategory ToolCategory(
"ascii-defer transformation options");
58static cl::extrahelp CommonHelp(tooling::CommonOptionsParser::HelpMessage);
59static cl::extrahelp MoreHelp(
"\nDefer transformation tool for ascii-chat\n");
61static cl::opt<std::string> OutputDirectoryOption(
"output-dir",
62 cl::desc(
"Directory where transformed sources will be written"),
63 cl::value_desc(
"path"), cl::Required, cl::cat(ToolCategory));
65static cl::opt<std::string>
66 InputRootOption(
"input-root", cl::desc(
"Root directory of original sources (used to compute relative paths)"),
67 cl::value_desc(
"path"), cl::init(
""), cl::cat(ToolCategory));
69static cl::opt<std::string> BuildPath(
"p", cl::desc(
"Build path (directory containing compile_commands.json)"),
70 cl::Optional, cl::cat(ToolCategory));
72static cl::opt<bool> QuietMode(
"quiet", cl::desc(
"Suppress verbose diagnostic output"), cl::init(
false),
73 cl::cat(ToolCategory));
75static cl::list<std::string> SourcePaths(cl::Positional, cl::desc(
"<source0> [... <sourceN>]"), cl::cat(ToolCategory));
81 CompoundStmt *stmt =
nullptr;
84 bool hasDefers =
false;
85 bool endsWithReturn =
false;
86 SourceLocation startLoc;
87 SourceLocation endLoc;
92 SourceLocation location;
93 SourceLocation endLocation;
95 std::string expression;
101 SourceLocation location;
103 std::vector<unsigned> activeScopeIds;
107struct FunctionTransformState {
108 FunctionDecl *funcDecl =
nullptr;
109 std::vector<DeferCall> deferCalls;
110 std::vector<ReturnInfo> returnInfos;
111 std::map<unsigned, BlockScope> blockScopes;
112 std::vector<unsigned> currentScopeStack;
113 bool needsTransformation =
false;
114 unsigned nextScopeId = 0;
117class DeferVisitor :
public RecursiveASTVisitor<DeferVisitor> {
119 DeferVisitor(ASTContext &context, Rewriter &rewriter,
const fs::path &outputDir,
const fs::path &inputRoot)
120 : context_(context), rewriter_(rewriter), outputDir_(outputDir), inputRoot_(inputRoot) {}
122 bool TraverseFunctionDecl(FunctionDecl *funcDecl) {
123 if (!funcDecl || funcDecl->isImplicit()) {
124 return RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
127 SourceManager &sourceManager = context_.getSourceManager();
128 SourceLocation location = funcDecl->getLocation();
129 location = sourceManager.getExpansionLoc(location);
130 if (!location.isValid() || !sourceManager.isWrittenInMainFile(location)) {
131 return RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
135 currentFunction_ = FunctionTransformState();
136 currentFunction_.funcDecl = funcDecl;
138 bool result = RecursiveASTVisitor<DeferVisitor>::TraverseFunctionDecl(funcDecl);
141 if (currentFunction_.needsTransformation && !currentFunction_.deferCalls.empty()) {
142 transformFunction(currentFunction_);
145 currentFunction_ = FunctionTransformState();
149 bool TraverseCompoundStmt(CompoundStmt *compoundStmt) {
150 if (!compoundStmt || !currentFunction_.funcDecl) {
151 return RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
154 SourceManager &sourceManager = context_.getSourceManager();
155 SourceLocation lbracLoc = compoundStmt->getLBracLoc();
156 if (!lbracLoc.isValid() || !sourceManager.isWrittenInMainFile(lbracLoc)) {
157 return RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
161 unsigned scopeId = currentFunction_.nextScopeId++;
162 unsigned depth = currentFunction_.currentScopeStack.size();
164 BlockScope blockScope;
165 blockScope.stmt = compoundStmt;
166 blockScope.scopeId = scopeId;
167 blockScope.depth = depth;
168 blockScope.hasDefers =
false;
169 blockScope.endsWithReturn =
false;
170 blockScope.startLoc = compoundStmt->getLBracLoc().getLocWithOffset(1);
171 blockScope.endLoc = compoundStmt->getRBracLoc();
174 if (!compoundStmt->body_empty()) {
175 Stmt *lastStmt = compoundStmt->body_back();
176 if (isa<ReturnStmt>(lastStmt)) {
177 blockScope.endsWithReturn =
true;
181 currentFunction_.blockScopes[scopeId] = blockScope;
182 currentFunction_.currentScopeStack.push_back(scopeId);
185 bool result = RecursiveASTVisitor<DeferVisitor>::TraverseCompoundStmt(compoundStmt);
188 currentFunction_.currentScopeStack.pop_back();
193 bool TraverseStmt(Stmt *stmt) {
194 if (!stmt || !currentFunction_.funcDecl) {
195 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
203 if (isa<CompoundStmt>(stmt) || isa<IfStmt>(stmt) || isa<ForStmt>(stmt) || isa<WhileStmt>(stmt) ||
204 isa<SwitchStmt>(stmt)) {
205 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
208 SourceManager &sourceManager = context_.getSourceManager();
209 SourceLocation stmtLoc = stmt->getBeginLoc();
212 SourceLocation checkLoc = stmtLoc;
213 if (stmtLoc.isMacroID()) {
214 checkLoc = sourceManager.getExpansionLoc(stmtLoc);
217 if (checkLoc.isValid() && sourceManager.isWrittenInMainFile(checkLoc)) {
218 CharSourceRange range;
219 bool isMacro = stmtLoc.isMacroID();
224 CharSourceRange macroRange = sourceManager.getImmediateExpansionRange(stmtLoc);
227 SourceLocation begin = stmt->getBeginLoc();
228 SourceLocation end = stmt->getEndLoc();
229 if (!begin.isValid() || !end.isValid()) {
230 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
232 range = CharSourceRange::getTokenRange(begin, end);
235 bool invalid =
false;
236 StringRef stmtText = Lexer::getSourceText(range, sourceManager, context_.getLangOpts(), &invalid);
239 SourceLocation begin = isMacro ? range.getBegin() : stmt->getBeginLoc();
243 bool shouldProcess = !isMacro || isa<DoStmt>(stmt);
245 if (shouldProcess && !invalid && containsDeferCall(stmtText)) {
247 size_t deferPos = findDeferCall(stmtText);
248 if (deferPos != StringRef::npos) {
250 size_t openParen = deferPos + 5;
251 size_t closeParen = findMatchingParen(stmtText, openParen);
253 if (closeParen != StringRef::npos) {
255 StringRef expression = stmtText.substr(openParen + 1, closeParen - openParen - 1);
258 SourceLocation deferLoc = begin.getLocWithOffset(deferPos);
261 SourceLocation deferEndLoc = begin.getLocWithOffset(closeParen + 1);
264 unsigned currentScopeId = 0;
265 if (!currentFunction_.currentScopeStack.empty()) {
266 currentScopeId = currentFunction_.currentScopeStack.back();
270 std::string exprStr = expression.str();
273 size_t firstNonSpace = exprStr.find_first_not_of(
" \t\n\r");
274 size_t lastNonSpace = exprStr.find_last_not_of(
" \t\n\r");
275 if (firstNonSpace != std::string::npos && lastNonSpace != std::string::npos) {
276 exprStr = exprStr.substr(firstNonSpace, lastNonSpace - firstNonSpace + 1);
280 deferCall.location = deferLoc;
281 deferCall.endLocation = deferEndLoc;
282 deferCall.fileOffset = sourceManager.getFileOffset(deferLoc);
283 deferCall.expression = exprStr;
284 deferCall.scopeId = currentScopeId;
287 if (currentFunction_.blockScopes.count(currentScopeId)) {
288 currentFunction_.blockScopes[currentScopeId].hasDefers =
true;
291 currentFunction_.deferCalls.push_back(deferCall);
292 currentFunction_.needsTransformation =
true;
298 return RecursiveASTVisitor<DeferVisitor>::TraverseStmt(stmt);
301 bool TraverseReturnStmt(ReturnStmt *returnStmt) {
302 if (returnStmt && currentFunction_.funcDecl) {
303 SourceManager &sourceManager = context_.getSourceManager();
304 SourceLocation location = returnStmt->getReturnLoc();
305 if (location.isValid()) {
306 SourceLocation expansionLocation = sourceManager.getExpansionLoc(location);
307 if (expansionLocation.isValid() && sourceManager.isWrittenInMainFile(expansionLocation)) {
309 ReturnInfo returnInfo;
310 returnInfo.location = expansionLocation;
311 returnInfo.fileOffset = sourceManager.getFileOffset(expansionLocation);
312 returnInfo.activeScopeIds = currentFunction_.currentScopeStack;
313 currentFunction_.returnInfos.push_back(returnInfo);
317 return RecursiveASTVisitor<DeferVisitor>::TraverseReturnStmt(returnStmt);
320 std::string makeRelativePath(
const fs::path &absolutePath)
const {
321 if (inputRoot_.empty()) {
322 return absolutePath.generic_string();
325 std::error_code errorCode;
326 fs::path relative = fs::relative(absolutePath, inputRoot_, errorCode);
328 return absolutePath.generic_string();
330 return relative.generic_string();
334 size_t findMatchingParen(StringRef text,
size_t openPos)
const {
335 if (openPos >= text.size() || text[openPos] !=
'(') {
336 return StringRef::npos;
340 for (
size_t i = openPos + 1; i < text.size(); i++) {
341 if (text[i] ==
'(') {
343 }
else if (text[i] ==
')') {
351 return StringRef::npos;
356 size_t findDeferCall(StringRef text,
size_t startPos = 0)
const {
357 size_t pos = startPos;
358 while (pos < text.size()) {
359 size_t found = text.find(
"defer(", pos);
360 if (found == StringRef::npos) {
361 return StringRef::npos;
364 if (found == 0 || (!std::isalnum(text[found - 1]) && text[found - 1] !=
'_')) {
370 return StringRef::npos;
374 bool containsDeferCall(StringRef text)
const {
375 return findDeferCall(text) != StringRef::npos;
379 std::vector<const DeferCall *> getDefersForScope(
unsigned scopeId,
const std::vector<DeferCall> &deferCalls)
const {
380 std::vector<const DeferCall *> result;
381 for (
const auto &dc : deferCalls) {
382 if (dc.scopeId == scopeId) {
383 result.push_back(&dc);
387 std::reverse(result.begin(), result.end());
392 std::string formatDeferExpression(
const std::string &expr)
const {
394 if (!expr.empty() && expr[0] ==
'{') {
396 return "do " + expr +
" while(0); ";
399 std::string result = expr;
401 while (!result.empty() && (result.back() ==
';' || result.back() ==
' ' || result.back() ==
'\t' ||
402 result.back() ==
'\n' || result.back() ==
'\r')) {
405 return result +
"; ";
411 std::string generateInlineCleanupForReturn(
const ReturnInfo &returnInfo,
const FunctionTransformState &state)
const {
414 for (
auto scopeIt = returnInfo.activeScopeIds.rbegin(); scopeIt != returnInfo.activeScopeIds.rend(); ++scopeIt) {
415 unsigned scopeId = *scopeIt;
416 auto blockIt = state.blockScopes.find(scopeId);
417 if (blockIt == state.blockScopes.end() || !blockIt->second.hasDefers) {
421 auto defers = getDefersForScopeBeforeOffset(scopeId, state.deferCalls, returnInfo.fileOffset);
422 for (
const auto *dc : defers) {
423 code += formatDeferExpression(dc->expression);
430 std::vector<const DeferCall *>
431 getDefersForScopeBeforeOffset(
unsigned scopeId,
const std::vector<DeferCall> &deferCalls,
unsigned maxOffset)
const {
432 std::vector<const DeferCall *> result;
433 for (
const auto &dc : deferCalls) {
434 if (dc.scopeId == scopeId && dc.fileOffset < maxOffset) {
435 result.push_back(&dc);
439 std::reverse(result.begin(), result.end());
444 std::string generateInlineCleanupAtBlockEnd(
unsigned scopeId,
const FunctionTransformState &state)
const {
446 auto defers = getDefersForScope(scopeId, state.deferCalls);
447 for (
const auto *dc : defers) {
448 code +=
" " + formatDeferExpression(dc->expression) +
"\n";
453 void transformFunction(FunctionTransformState &state) {
454 if (!state.funcDecl || state.deferCalls.empty()) {
458 Stmt *body = state.funcDecl->getBody();
463 CompoundStmt *compoundBody = dyn_cast<CompoundStmt>(body);
469 for (
const DeferCall &deferCall : state.deferCalls) {
470 removeDeferStatement(deferCall);
474 for (
const ReturnInfo &returnInfo : state.returnInfos) {
475 std::string cleanup = generateInlineCleanupForReturn(returnInfo, state);
476 if (!cleanup.empty()) {
477 rewriter_.InsertText(returnInfo.location, cleanup,
true,
true);
483 for (
const auto &pair : state.blockScopes) {
484 const BlockScope &blockScope = pair.second;
485 if (blockScope.hasDefers && blockScope.endLoc.isValid() && !blockScope.endsWithReturn) {
486 std::string cleanup = generateInlineCleanupAtBlockEnd(blockScope.scopeId, state);
487 if (!cleanup.empty()) {
488 rewriter_.InsertText(blockScope.endLoc, cleanup,
true,
true);
494 void removeDeferStatement(
const DeferCall &deferCall) {
495 SourceManager &sourceManager = context_.getSourceManager();
497 SourceLocation macroLoc = deferCall.location;
498 if (!macroLoc.isValid()) {
503 FileID fileId = sourceManager.getFileID(macroLoc);
504 bool invalid =
false;
505 StringRef fileData = sourceManager.getBufferData(fileId, &invalid);
510 unsigned offset = sourceManager.getFileOffset(macroLoc);
513 size_t deferStart = findDeferCall(fileData, offset);
514 if (deferStart == StringRef::npos || deferStart != offset) {
519 size_t openParen = deferStart + 5;
520 size_t closeParen = findMatchingParenInFile(fileData, openParen);
521 if (closeParen == StringRef::npos) {
526 size_t semicolonPos = closeParen + 1;
527 while (semicolonPos < fileData.size() && (fileData[semicolonPos] ==
' ' || fileData[semicolonPos] ==
'\t' ||
528 fileData[semicolonPos] ==
'\n' || fileData[semicolonPos] ==
'\r')) {
531 if (semicolonPos >= fileData.size() || fileData[semicolonPos] !=
';') {
535 SourceLocation semicolonLoc = macroLoc.getLocWithOffset(semicolonPos - offset);
536 CharSourceRange deferRange = CharSourceRange::getCharRange(macroLoc, semicolonLoc.getLocWithOffset(1));
540 std::string exprSummary = deferCall.expression;
541 bool isBlockDefer = !exprSummary.empty() && exprSummary[0] ==
'{';
543 exprSummary =
"{...}";
545 std::string comment =
"/* defer: " + exprSummary +
" (moved to scope exit) */";
546 rewriter_.ReplaceText(deferRange, comment);
549 size_t findMatchingParenInFile(StringRef fileData,
size_t openPos)
const {
550 if (openPos >= fileData.size() || fileData[openPos] !=
'(') {
551 return StringRef::npos;
555 size_t i = openPos + 1;
556 while (i < fileData.size()) {
557 char c = fileData[i];
561 }
else if (c ==
')') {
567 }
else if (c ==
'"') {
570 while (i < fileData.size() && fileData[i] !=
'"') {
571 if (fileData[i] ==
'\\' && i + 1 < fileData.size()) {
576 if (i < fileData.size()) {
579 }
else if (c ==
'\'') {
582 while (i < fileData.size() && fileData[i] !=
'\'') {
583 if (fileData[i] ==
'\\' && i + 1 < fileData.size()) {
588 if (i < fileData.size()) {
596 return StringRef::npos;
599 ASTContext &context_;
603 FunctionTransformState currentFunction_;
606class DeferASTConsumer :
public ASTConsumer {
608 explicit DeferASTConsumer(DeferVisitor &visitor) : visitor_(visitor) {}
610 void HandleTranslationUnit(ASTContext &context)
override {
611 visitor_.TraverseDecl(context.getTranslationUnitDecl());
615 DeferVisitor &visitor_;
618class DeferFrontendAction :
public ASTFrontendAction {
620 explicit DeferFrontendAction(
const fs::path &outputDir,
const fs::path &inputRoot)
621 : outputDir_(outputDir), inputRoot_(inputRoot) {
622 initializeProtectedDirectories();
625 void EndSourceFileAction()
override {
626 SourceManager &sourceManager = rewriter_.getSourceMgr();
627 const FileEntry *fileEntry = sourceManager.getFileEntryForID(sourceManager.getMainFileID());
633 llvm::errs() <<
"Defer visitor not initialized; skipping file output\n";
634 hadWriteError_ =
true;
638 const StringRef filePathRef = fileEntry->tryGetRealPathName();
639 if (filePathRef.empty()) {
640 llvm::errs() <<
"Unable to resolve file path for transformed output\n";
644 const fs::path originalPath = fs::path(filePathRef.str());
645 const std::string relativePath = visitor_->makeRelativePath(originalPath);
646 fs::path destinationPath = outputDir_ / relativePath;
650 fs::path canonicalOriginal = fs::canonical(originalPath, ec);
652 fs::path canonicalDest = fs::weakly_canonical(destinationPath, ec);
653 if (!ec && canonicalOriginal == canonicalDest) {
654 llvm::errs() <<
"ERROR: Output path is the same as source file! Refusing to overwrite source.\n";
655 llvm::errs() <<
" Source: " << canonicalOriginal.string() <<
"\n";
656 llvm::errs() <<
" Output: " << canonicalDest.string() <<
"\n";
657 hadWriteError_ =
true;
665 const std::string destinationString = destinationPath.generic_string();
672 bool fileExists = llvm::sys::fs::exists(llvm::Twine(destinationString));
674 if (fileExists && isInProtectedSourceTree(destinationPath)) {
675 llvm::errs() <<
"Refusing to overwrite existing file in protected source tree: " << destinationString <<
"\n";
677 hadWriteError_ =
true;
681 const fs::path parent = destinationPath.parent_path();
682 std::error_code directoryError;
683 fs::create_directories(parent, directoryError);
684 if (directoryError) {
685 llvm::errs() <<
"Failed to create output directory: " << parent.string() <<
" - " << directoryError.message()
688 hadWriteError_ =
true;
692 std::string rewrittenContents;
693 if (
const RewriteBuffer *buffer = rewriter_.getRewriteBufferFor(sourceManager.getMainFileID())) {
694 rewrittenContents.assign(buffer->begin(), buffer->end());
697 rewrittenContents = sourceManager.getBufferData(sourceManager.getMainFileID()).str();
700 std::error_code fileError;
701 llvm::raw_fd_ostream outputStream(destinationPath.string(), fileError, llvm::sys::fs::OF_Text);
703 llvm::errs() <<
"Failed to open output file: " << destinationString <<
" - " << fileError.message() <<
"\n";
705 hadWriteError_ =
true;
709 outputStream << rewrittenContents;
710 outputStream.close();
711 if (outputStream.has_error()) {
712 llvm::errs() <<
"Error while writing transformed file: " << destinationString <<
"\n";
714 hadWriteError_ =
true;
718 bool hadWriteError()
const {
719 return hadWriteError_;
723 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &compiler, StringRef)
override {
724 rewriter_.setSourceMgr(compiler.getSourceManager(), compiler.getLangOpts());
725 visitor_ = std::make_unique<DeferVisitor>(compiler.getASTContext(), rewriter_, outputDir_, inputRoot_);
726 return std::make_unique<DeferASTConsumer>(*visitor_);
733 fs::path inputRootCanonical_;
734 fs::path protectedSrcDir_;
735 fs::path protectedLibDir_;
736 std::unique_ptr<DeferVisitor> visitor_;
737 bool hadWriteError_ =
false;
739 void initializeProtectedDirectories() {
741 fs::path normalizedRoot = inputRoot_;
743 if (normalizedRoot.empty()) {
744 normalizedRoot = fs::current_path(ec);
748 if (!normalizedRoot.is_absolute()) {
749 normalizedRoot = fs::absolute(normalizedRoot, ec);
753 inputRootCanonical_ = fs::weakly_canonical(normalizedRoot, ec);
755 inputRootCanonical_.clear();
759 protectedSrcDir_ = inputRootCanonical_ /
"src";
760 protectedLibDir_ = inputRootCanonical_ /
"lib";
763 static bool pathStartsWith(
const fs::path &path,
const fs::path &prefix) {
764 if (prefix.empty()) {
768 auto pathIter = path.begin();
769 for (
auto prefixIter = prefix.begin(); prefixIter != prefix.end(); ++prefixIter) {
770 if (pathIter == path.end() || *pathIter != *prefixIter) {
779 bool isInProtectedSourceTree(
const fs::path &path)
const {
780 if (inputRootCanonical_.empty()) {
785 fs::path canonicalPath = fs::weakly_canonical(path, ec);
790 return pathStartsWith(canonicalPath, protectedSrcDir_) || pathStartsWith(canonicalPath, protectedLibDir_);
794class DeferActionFactory :
public tooling::FrontendActionFactory {
796 DeferActionFactory(
const fs::path &outputDir,
const fs::path &inputRoot)
797 : outputDir_(outputDir), inputRoot_(inputRoot) {}
799 std::unique_ptr<FrontendAction> create() {
800 return std::make_unique<DeferFrontendAction>(outputDir_, inputRoot_);
811static fs::path g_originalCwd;
813int main(
int argc,
const char **argv) {
814 InitLLVM InitLLVM(argc, argv);
817 g_originalCwd = fs::current_path();
820 cl::HideUnrelatedOptions(ToolCategory);
822 cl::ParseCommandLineOptions(argc, argv,
"ascii-defer transformation tool\n");
824 fs::path outputDir = fs::path(OutputDirectoryOption.getValue());
826 if (!outputDir.is_absolute()) {
827 outputDir = g_originalCwd / outputDir;
830 if (!InputRootOption.getValue().empty()) {
831 inputRoot = fs::path(InputRootOption.getValue());
833 inputRoot = fs::current_path();
837 if (!inputRoot.is_absolute()) {
839 inputRoot = fs::absolute(inputRoot, ec);
841 llvm::errs() <<
"Failed to resolve input root path: " << ec.message() <<
"\n";
846 std::vector<std::string> sourcePaths;
847 for (
const auto &path : SourcePaths) {
849 sourcePaths.push_back(path);
853 if (sourcePaths.empty()) {
854 llvm::errs() <<
"No translation units specified for transformation. Provide positional source paths.\n";
858 if (fs::exists(outputDir)) {
859 if (!fs::is_directory(outputDir)) {
860 llvm::errs() <<
"Output path exists and is not a directory: " << outputDir.c_str() <<
"\n";
864 std::error_code errorCode;
865 fs::create_directories(outputDir, errorCode);
867 llvm::errs() <<
"Failed to create output directory: " << outputDir.c_str() <<
" - " << errorCode.message()
874 std::string buildPath = BuildPath.getValue();
875 if (buildPath.empty()) {
878 std::string errorMessage;
879 std::unique_ptr<tooling::CompilationDatabase> compilations =
880 tooling::CompilationDatabase::loadFromDirectory(buildPath, errorMessage);
882 llvm::errs() <<
"Error loading compilation database from '" << buildPath <<
"': " << errorMessage <<
"\n";
886 tooling::ClangTool tool(*compilations, sourcePaths);
892 std::vector<std::string> prependArgs;
896 std::string resourceDir;
898#ifdef CLANG_RESOURCE_DIR
899 if (llvm::sys::fs::exists(CLANG_RESOURCE_DIR)) {
900 resourceDir = CLANG_RESOURCE_DIR;
904 llvm::errs() <<
"Embedded clang resource directory not found: " << CLANG_RESOURCE_DIR <<
"\n";
910 if (resourceDir.empty()) {
912 std::vector<std::string> searchPaths;
916 searchPaths.push_back(
"/opt/homebrew/opt/llvm/lib/clang");
918 searchPaths.push_back(
"/usr/local/opt/llvm/lib/clang");
920 searchPaths.push_back(
921 "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang");
923 searchPaths.push_back(
"/Library/Developer/CommandLineTools/usr/lib/clang");
927 searchPaths.push_back(
"/usr/lib/llvm-22/lib/clang");
928 searchPaths.push_back(
"/usr/lib/llvm-21/lib/clang");
929 searchPaths.push_back(
"/usr/lib/llvm-20/lib/clang");
930 searchPaths.push_back(
"/usr/lib/clang");
931 searchPaths.push_back(
"/usr/local/lib/clang");
934 searchPaths.push_back(
"/usr/local/lib/clang");
936 for (
const auto &basePath : searchPaths) {
937 if (!llvm::sys::fs::exists(basePath)) {
943 std::string bestVersion;
946 for (llvm::sys::fs::directory_iterator dir(basePath, ec), dirEnd; !ec && dir != dirEnd; dir.increment(ec)) {
947 std::string name = llvm::sys::path::filename(dir->path()).str();
950 if (sscanf(name.c_str(),
"%d", &major) == 1 && major > bestMajor) {
952 bestVersion = dir->path();
956 if (!bestVersion.empty()) {
957 resourceDir = bestVersion;
959 llvm::errs() <<
"Found clang resource directory at runtime: " << resourceDir <<
"\n";
965 if (resourceDir.empty() && !QuietMode) {
966 llvm::errs() <<
"Warning: Could not find clang resource directory\n";
970 if (!resourceDir.empty()) {
971 prependArgs.push_back(std::string(
"-resource-dir=") + resourceDir);
978 prependArgs.push_back(
"-target");
979 prependArgs.push_back(
"arm64-apple-darwin");
982 prependArgs.push_back(
"-target");
983 prependArgs.push_back(
"x86_64-apple-darwin");
986#elif defined(__linux__)
988 prependArgs.push_back(
"-target");
989 prependArgs.push_back(
"aarch64-linux-gnu");
992 prependArgs.push_back(
"-target");
993 prependArgs.push_back(
"x86_64-linux-gnu");
1001 std::string selectedSDK;
1004 const char *sdkPaths[] = {
1006 "/Applications/Xcode.app/Contents/Developer/Platforms/"
1007 "MacOSX.platform/Developer/SDKs/MacOSX.sdk",
1009 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk",
1012 for (
const char *sdk : sdkPaths) {
1013 if (llvm::sys::fs::exists(sdk)) {
1019 if (!selectedSDK.empty()) {
1020 prependArgs.push_back(
"-isysroot");
1021 prependArgs.push_back(selectedSDK);
1024 llvm::errs() <<
"Warning: No macOS SDK found, system headers may not be available\n";
1034 std::vector<std::string> appendArgs;
1035 if (!resourceDir.empty()) {
1036 std::string builtinInclude = resourceDir +
"/include";
1037 if (llvm::sys::fs::exists(builtinInclude)) {
1038 appendArgs.push_back(
"-isystem");
1039 appendArgs.push_back(builtinInclude);
1062 std::string inputRootStr = inputRoot.string();
1063 auto consolidatedAdjuster = [prependArgs, appendArgs, inputRootStr](
const tooling::CommandLineArguments &
args,
1066 auto isProjectPath = [&inputRootStr](
const std::string &path) ->
bool {
1067 if (inputRootStr.empty())
1071 auto normalizedPath = fs::canonical(path, ec);
1074 auto normalizedRoot = fs::canonical(inputRootStr, ec);
1078 std::string pathStr = normalizedPath.string();
1079 std::string rootStr = normalizedRoot.string();
1080 if (pathStr.find(rootStr) != 0)
1084 if (pathStr.find(
"/.deps-cache/") != std::string::npos)
1088 tooling::CommandLineArguments result;
1095 result.push_back(
args[0]);
1098 for (
const auto &arg : prependArgs) {
1099 result.push_back(arg);
1116 std::vector<std::string> collectedIsystemPaths;
1117 bool foundSeparator =
false;
1118 size_t separatorIndex = 0;
1119 for (
size_t i = 1; i <
args.size(); ++i) {
1120 const std::string &arg =
args[i];
1124 foundSeparator =
true;
1130 if (arg.find(
"-fsanitize") != std::string::npos)
1132 if (arg.find(
"-fno-sanitize") != std::string::npos)
1135 if (arg ==
"-g" || arg ==
"-g2" || arg ==
"-g3")
1137 if (arg ==
"-fno-eliminate-unused-debug-types")
1139 if (arg ==
"-fno-inline")
1142 if (arg ==
"-resource-dir") {
1146 if (arg.find(
"-resource-dir=") == 0)
1149 if (arg ==
"-isysroot") {
1153 if (arg.find(
"-isysroot=") == 0 || (arg.find(
"-isysroot") == 0 && arg.length() > 9))
1158 if (arg ==
"-isystem" && i + 1 <
args.size()) {
1159 collectedIsystemPaths.push_back(
args[++i]);
1162 if (arg.find(
"-isystem") == 0 && arg.length() > 8) {
1163 collectedIsystemPaths.push_back(arg.substr(8));
1173 if (arg ==
"-I" && i + 1 <
args.size()) {
1175 const std::string &includePath =
args[++i];
1176 if (isProjectPath(includePath) && includePath.find(
"/include") == std::string::npos) {
1178 result.push_back(
"-iquote");
1179 result.push_back(includePath);
1180 }
else if (!isProjectPath(includePath)) {
1182 collectedIsystemPaths.push_back(includePath);
1185 result.push_back(
"-I");
1186 result.push_back(includePath);
1190 if (arg.find(
"-I") == 0 && arg.length() > 2) {
1192 std::string includePath = arg.substr(2);
1193 if (isProjectPath(includePath) && includePath.find(
"/include") == std::string::npos) {
1195 result.push_back(
"-iquote");
1196 result.push_back(includePath);
1197 }
else if (!isProjectPath(includePath)) {
1199 collectedIsystemPaths.push_back(includePath);
1202 result.push_back(
"-I");
1203 result.push_back(includePath);
1208 result.push_back(arg);
1214 for (
const auto &arg : appendArgs) {
1215 result.push_back(arg);
1218 for (
const auto &path : collectedIsystemPaths) {
1219 result.push_back(
"-isystem");
1220 result.push_back(path);
1224 result.push_back(
"-DASCIICHAT_DEFER_TOOL_PARSING");
1225 if (foundSeparator) {
1226 result.push_back(
"--");
1228 for (
size_t i = separatorIndex + 1; i <
args.size(); ++i) {
1229 result.push_back(
args[i]);
1235 tool.appendArgumentsAdjuster(consolidatedAdjuster);
1237 DeferActionFactory actionFactory(outputDir, inputRoot);
1238 const int executionResult = tool.run(&actionFactory);
1239 if (executionResult != 0) {
1240 llvm::errs() <<
"Defer transformation failed with code " << executionResult <<
"\n";
1242 return executionResult;