├── .gitignore ├── .clang-format ├── src ├── MatchPrinter.cpp ├── RewritePrinter.cpp └── main.cpp ├── include ├── MatchPrinter.h └── RewritePrinter.h ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── CMakeLists.txt ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM -------------------------------------------------------------------------------- /src/MatchPrinter.cpp: -------------------------------------------------------------------------------- 1 | #include "MatchPrinter.h" 2 | 3 | namespace nus::test { 4 | void MatchPrinter::run(const MatchResult &Result) { 5 | for (auto [id, node] : Result.Nodes.getMap()) { 6 | llvm::outs() << "Found " << id << ":\n"; 7 | node.dump(llvm::outs(), *Result.Context); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /include/MatchPrinter.h: -------------------------------------------------------------------------------- 1 | #ifndef MATCHPRINTER_H_ 2 | #define MATCHPRINTER_H_ 3 | 4 | #include "clang/ASTMatchers/ASTMatchFinder.h" 5 | 6 | namespace nus::test { 7 | class MatchPrinter : public clang::ast_matchers::MatchFinder::MatchCallback { 8 | public: 9 | using MatchResult = clang::ast_matchers::MatchFinder::MatchResult; 10 | 11 | void run(const MatchResult &Result) override; 12 | }; 13 | 14 | } 15 | 16 | #endif /* MATCHPRINTER_H_ */ 17 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "dockerfile": "./Dockerfile", 4 | "context": "." 5 | }, 6 | "customizations": { 7 | // Configure properties specific to VS Code. 8 | "vscode": { 9 | // Add the IDs of extensions you want installed when the container is created. 10 | "extensions": [ 11 | "ms-vscode.cpptools", 12 | "ms-vscode.cmake-tools" 13 | ] 14 | } 15 | }, 16 | 17 | // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 18 | "remoteUser": "vscode" 19 | } -------------------------------------------------------------------------------- /include/RewritePrinter.h: -------------------------------------------------------------------------------- 1 | #ifndef REWRITEPRINTER_H_ 2 | #define REWRITEPRINTER_H_ 3 | 4 | #include "clang/Tooling/Refactoring.h" 5 | #include "clang/Tooling/Transformer/RewriteRule.h" 6 | #include "clang/Tooling/Transformer/Transformer.h" 7 | 8 | namespace nus::test { 9 | class RewritePrinter : public clang::tooling::Transformer { 10 | public: 11 | using RewriteRule = clang::transformer::RewriteRule; 12 | explicit RewritePrinter(const RewriteRule& rule); 13 | void onEndOfTranslationUnit() override; 14 | private: 15 | clang::tooling::AtomicChanges Changes; 16 | }; 17 | } // namespace nus::test 18 | 19 | #endif /* REWRITEPRINTER_H_ */ 20 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # [Choice] Debian / Ubuntu version (use Debian 12, Debian 11, Ubuntu 22.04 on local arm64/Apple Silicon): debian-12, debian-11, debian-10, ubuntu-22.04, ubuntu-20.04 2 | ARG VARIANT=debian-12 3 | FROM mcr.microsoft.com/devcontainers/base:${VARIANT} 4 | USER root 5 | 6 | # Install needed packages. Use a separate RUN statement to add your own dependencies. 7 | RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 8 | && apt-get -y install build-essential cmake cppcheck valgrind clang lldb llvm gdb \ 9 | && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* 10 | 11 | RUN apt-get update && apt-get -y install libclang-dev && rm -rf /var/lib/apt/lists/* 12 | 13 | ARG USERNAME=vscode -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | set (TOOL_NAME my-tool) 3 | project(${TOOL_NAME} LANGUAGES C CXX) 4 | 5 | find_package(LLVM REQUIRED) 6 | find_package(Clang REQUIRED) 7 | 8 | include_directories(include) 9 | include_directories(${LLVM_INCLUDE_DIRS}) 10 | include_directories(${CLANG_INCLUDE_DIRS}) 11 | 12 | add_definitions(${LLVM_DEFINITIONS}) 13 | add_definitions(${CLANG_DEFINITIONS}) 14 | 15 | add_executable(${TOOL_NAME} 16 | src/main.cpp 17 | src/MatchPrinter.cpp 18 | src/RewritePrinter.cpp 19 | ) 20 | target_link_libraries(${TOOL_NAME} clangBasic clangTooling clangTransformer) 21 | target_compile_features(${TOOL_NAME} PRIVATE cxx_std_20) 22 | 23 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") 24 | target_compile_options(${TOOL_NAME} PRIVATE "-Wno-deprecated-enum-enum-conversion") 25 | endif() -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2023, National University of Singapore 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/RewritePrinter.cpp: -------------------------------------------------------------------------------- 1 | #include "RewritePrinter.h" 2 | #include "clang/Tooling/Refactoring.h" 3 | #include "llvm/Support/MemoryBuffer.h" 4 | #include // std::move 5 | #include // std::all_of 6 | 7 | namespace nus::test { 8 | 9 | namespace detail { 10 | bool isCompilableFilepath(std::string_view path) { 11 | return path.find(".c") != std::string_view::npos || 12 | path.find(".cc") != std::string_view::npos || 13 | path.find(".cpp") != std::string_view::npos; 14 | } 15 | } // namespace detail 16 | 17 | RewritePrinter::RewritePrinter(const RewriteRule &rule) 18 | : Transformer(rule, 19 | [this](llvm::Expected ac) { 20 | if (ac) 21 | Changes.push_back(std::move(*ac)); 22 | }) {} 23 | 24 | void RewritePrinter::onEndOfTranslationUnit() { 25 | if (Changes.size() > 0) { 26 | auto filepath = Changes.front().getFilePath(); 27 | bool SameFilePath = std::ranges::all_of(Changes, [&filepath](decltype(Changes)::const_reference ac) -> bool { 28 | return ac.getFilePath() == filepath; 29 | }); 30 | 31 | if (detail::isCompilableFilepath(filepath) && SameFilePath) { 32 | // open the file 33 | if (auto buffer = llvm::MemoryBuffer::getFile(filepath, true)) { 34 | if (auto patched_code = clang::tooling::applyAtomicChanges( 35 | filepath, (*buffer)->getBuffer(), Changes, {})) 36 | llvm::outs() << *patched_code; // print the code out to stdout 37 | } 38 | } 39 | } 40 | } 41 | } // namespace nus::test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clang Tool Template 2 | 3 | This template project is meant to be a tutorial and a quick-setup for anyone who wants to write a tool to generate/modify C/C++ code. LLVM and Clang are huge projects with huge pipelines, and many helper classes and tools exist to simplify tool development. However, that also means there is more than one way to do a single task. 4 | 5 | This template project uses the bare minimum amount of LLVM/Clang code to get your tool up-and-running in as idiomatic of a way as possible. 6 | 7 | Some tutorial code is also available in `main.cpp` to help you understand how to write an AST matcher, or a transformer. 8 | 9 | ## Setup 10 | 11 | Requirements: 12 | - CMake 13 | - LLVM 14 or higher 14 | - Clang 14 developer libraries 15 | 16 | In Ubuntu 22.04, you just have to install the following packages: 17 | 18 | ```bash 19 | sudo apt install -y cmake llvm libclang-dev 20 | ``` 21 | 22 | After this, the tool can be built with the following: 23 | ```bash 24 | mkdir build 25 | cd build 26 | cmake .. 27 | cmake --build . 28 | ``` 29 | 30 | The name of the tool can be changed in CMakeLists.txt:2. 31 | 32 | In Visual Studio Code, the CMake extension should be automatically able to configure this project, and all integration should work out-of-the-box. 33 | 34 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/nus-test/clang-tool-template?quickstart=1) 35 | 36 | ⚠️ *There is currently no support for Windows-based compilation with CMake. Please [create an issue](https://github.com/nus-test/clang-tool-template/issues/new/choose) if you require it, and it should be resolved within a day.* 37 | 38 | ## Resources 39 | The LLVM tutorials are always a good place to start, albeit sometimes out of date. 40 | - [LibTooling Setup](https://clang.llvm.org/docs/LibTooling.html) 41 | - [Clang Transformer Tutorial](https://clang.llvm.org/docs/ClangTransformerTutorial.html) 42 | - [AST Matchers Tutorial](https://clang.llvm.org/docs/LibASTMatchersTutorial.html) -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "clang/Frontend/FrontendActions.h" 2 | #include "clang/Tooling/CommonOptionsParser.h" 3 | #include "clang/Tooling/Tooling.h" 4 | #include "llvm/Support/CommandLine.h" 5 | 6 | #include "clang/ASTMatchers/ASTMatchFinder.h" // MatchFinder 7 | #include "clang/Tooling/Transformer/RangeSelector.h" // RangeSelector 8 | #include "clang/Tooling/Transformer/Stencil.h" // cat 9 | 10 | #include "MatchPrinter.h" 11 | #include "RewritePrinter.h" 12 | 13 | // Apply a custom category to all command-line options so that they are the 14 | // only ones displayed. 15 | static llvm::cl::OptionCategory MyToolCategory("my-tool options"); 16 | static llvm::cl::extrahelp 17 | CommonHelp(clang::tooling::CommonOptionsParser::HelpMessage); 18 | 19 | int main(int argc, const char **argv) { 20 | using namespace clang::tooling; 21 | 22 | // CommonOptionsParser constructor will parse arguments and create a 23 | // CompilationDatabase. In case of error it will terminate the program. 24 | auto OptionsParser = CommonOptionsParser::create(argc, argv, MyToolCategory); 25 | 26 | if (!OptionsParser) { 27 | llvm::errs() << OptionsParser.takeError() << "\n"; 28 | return 1; 29 | } 30 | 31 | ClangTool Tool(OptionsParser->getCompilations(), 32 | OptionsParser->getSourcePathList()); 33 | 34 | enum class Mode { 35 | Match, 36 | Rewrite, 37 | }; 38 | 39 | /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 40 | Change this variable to determine what kind of tool you want to generate! 41 | */ 42 | auto ToolMode = Mode::Match; 43 | 44 | switch (ToolMode) { 45 | case Mode::Match: { 46 | using namespace clang::ast_matchers; 47 | 48 | clang::ast_matchers::MatchFinder Finder; 49 | nus::test::MatchPrinter Printer; 50 | 51 | /* 52 | If you are only looking out for patterns, the only thing you need to 53 | change is this rule. 54 | 55 | By default, this definition tries to find a main function that 56 | takes in two arguments. 57 | 58 | For more information on how to make rules, see the ASTMatchers documentation 59 | at: https://clang.llvm.org/docs/LibASTMatchersReference.html 60 | */ 61 | auto MatchRule = 62 | functionDecl(allOf(hasName("main"), parameterCountIs(2))).bind("mainFunc"); 63 | 64 | Finder.addMatcher(MatchRule, &Printer); 65 | 66 | return Tool.run(newFrontendActionFactory(&Finder).get()); 67 | } 68 | 69 | case Mode::Rewrite: { 70 | using namespace clang::ast_matchers; 71 | using namespace clang::transformer; 72 | 73 | clang::ast_matchers::MatchFinder Finder; 74 | 75 | /* 76 | For a rewriter, there are four objects you need to define. 77 | 78 | \var MatchRule 79 | The MatchRule is the AST matcher as in Match 80 | 81 | \var RangeSelector 82 | The RangeSelector is the region of code to rewrite. 83 | 84 | \var RewriteRule 85 | The RewriteRule uses the MatchRule and the selector, as well as a Stencil, 86 | to define the transformation of the code. 87 | 88 | For more information, see the following documentation: 89 | - ASTMatchers: https://clang.llvm.org/docs/LibASTMatchersReference.html 90 | - Transformer: https://clang.llvm.org/docs/ClangTransformerTutorial.html 91 | */ 92 | DeclarationMatcher MatchRule = functionDecl( 93 | allOf(hasName("main"), hasDescendant(compoundStmt().bind("mainFn")))); 94 | 95 | RangeSelector Selector = statements("mainFn"); 96 | 97 | // This surrounds the main function in a true block. 98 | // Note that we cannot insert AST nodes, only source code. 99 | Stencil Transformed = cat("if (1) {", Selector, "}"); 100 | 101 | RewriteRule Rewrite = makeRule(MatchRule, changeTo(Selector, Transformed)); 102 | 103 | nus::test::RewritePrinter Printer(Rewrite); 104 | Finder.addMatcher(MatchRule, &Printer); 105 | 106 | return Tool.run(newFrontendActionFactory(&Finder).get()); 107 | } 108 | } 109 | } --------------------------------------------------------------------------------