Develop clang plug-ins, most commonly the property inspector
The final effect of this paper:
llvm-project
, it is directly
This paper uses CPP to develop plug-ins
1. Project preparation
1.1 installationcmake
brew install cmake
Cmake can help us automatically create projects from scripts
Check if it’s been installed before
You can use
brew list | grep ^cmake
1.2 Obtaining LLVM projects
1.2.1 downloadllvm-project
git clone https://github.com/llvm/llvm-project
Generate LLVM projects using Xcode
Enter the
cd /Users/jzd/Movies/A_B/llvm-project
Is equivalent to
> cd /yourPath/llvm-project
production
cmake -S llvm -B build -G Xcode -DLLVM_ENABLE_PROJECTS="clang; libcxx; libcxxabi"
You need to add these three libraries, Clang, libcxx, and libcxxabi
(This may take a little longer)
Get the engineering documents we need
1.2.2, compile
Run the
(This step may take a long time)
2. Plug-in development
2.1 Preparation before development
2.1.1 Template Configuration
Go to this folder
/yourPath/llvm-project/clang/tools
Modify the file cmakelists.txt
Cmakelists. TXT is the configuration file for cmake
Add the last sentence
Add_clang_subdirectory (libclang) add_clang_subdirectory(amdgpu-arch) add_clang_subdirectory(propPlugIn)Copy the code
Create /yourPath/llvm-project/clang/tools
Folder propPlugIn folder
Corresponds to the statement added in the cmake configuration file
The effect is shown in figure
As you can see, on the far right, there are two files
Cmakelists. TXT:
add_llvm_library( propPlugIn MODULE BUILDTREE_ONLY propertyPlugIn.cpp)
Add the LLVM plug-in
The CPP file mentioned above is empty
2.1.2 The Configuration is complete and the result is displayed
Run again
cd /yourPath/llvm-project cmake -S llvm -B build -G Xcode -DLLVM_ENABLE_PROJECTS="clang; libcxx; libcxxabi"Copy the code
At this point to see
/yourPath/ llVM-project looks like this
Stage summary: At this time cmake temporarily put on hold, use Xcode operation
2.2 Plug-in development
2.2.1 In the first stage, find the abstract syntax tree AST
There’s always preparation
Import header file
#include <iostream> #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/ASTConsumer.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Frontend/FrontendPluginRegistry.h"Copy the code
Using namespaces
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;
Copy the code
Add the CPP namespace
namespace PropertyPlugIn {
}
Copy the code
The main job of writing a clang plug-in is to override the functions of the clang compilation process
Just for the PluginASTAction class,
-
The first method, ParseArgs, is routines
-
The second method, CreateASTConsumer, does things
For now, use the abstract class ASTConsumer
By overloading these two methods, the plug-in can be registered
Namespace PropertyPlugIn {// Implement PluginASTAction class PropASTAction:public PluginASTAction{public: bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) override{ return true; Unique_ptr <ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override{ return unique_ptr<ASTConsumer>(new ASTConsumer); }}; } / / registered plug-in static FrontendPluginRegistry: : Add < PropertyPlugIn: : PropASTAction > PropertyPlugInName (" the name of PropertyPlugIn","description of PropertyPlugIn");Copy the code
Compile and the plug-in is ready
The path is a
/yourPath/llvm-project/build/Debug/lib/propPlugIn.dylib
PropASTAction works through a subclass of ASTConsumer
A subclass of ASTConsumer provides an opportunity
The entire file has been parsed
Namespace PropertyPlugIn {// Custom PropConsumer class PropConsumer: public ASTConsumer{public: Void HandleTranslationUnit(ASTContext &Ctx) override {cout<<" The file is parsed!" <<endl; }}; // See github repo at the end of this article to modify the class PropASTAction.Copy the code
ASTConsumer, add filtering
Once you get the AST, you get a lot of information,
We’re just going to focus on properties here
Add a filter and, in the PropConsumer initialization method, create a binding
// Custom PropConsumer class PropConsumer: public ASTConsumer{private: //AST node lookup filter! MatchFinder filter; FilterCallback callback; public: PropConsumer(){// Add a filter and specify the node type of interest // call back the run method in FilterCallback // bind, which marks the node, // objcPropertyDecl, OC property declaration, Bind filter.addmatcher (objcPropertyDecl().bind("propObjC"), &callback); } // The entire file is parsed to complete the callback! Void HandleTranslationUnit(ASTContext &Ctx) override {void HandleTranslationUnit(ASTContext &Ctx) override { }};Copy the code
MatchFinder filters the callback class of the class that does things
The callback class works by overloading the run method
class FilterCallback: Public MatchFinder: : MatchCallback {void run (const MatchFinder: : MatchResult & Result) override {/ / the Result obtained by the tag to the node const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("propObjC"); If (propertyDecl) {string propType = propertyDecl->getType().getasString (); Cout << "The property is of type" << propType << endl; }}};Copy the code
Second filter, get the attribute node, filter out the system attributes
- First get the path of the file,
With getSourceManager, the CompilerInstance CompilerInstance,
Determine the position of each node
// 修改后,
// PropConsumer 和 PropASTAction ,两个类的变化,见 github repo
class FilterCallback: public MatchFinder::MatchCallback{
private:
CompilerInstance &CI;
public:
FilterCallback(CompilerInstance &CI):CI(CI){ }
void run(const MatchFinder::MatchResult &Result) override {
// Result 通过 tag 获取到节点
const ObjCPropertyDecl * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("propObjC");
// 文件路径
string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str();
// 判断节点有值
if (propertyDecl) {
// 拿到节点的类型
string propType = propertyDecl->getType().getAsString();
cout << fileName << " 的属性的类型是 " << propType << endl;
}
}
};
Copy the code
- Filter out the system
class FilterCallback: public MatchFinder::MatchCallback{ private: CompilerInstance &CI; bool isCustom(const string fileName){ if (fileName.empty()) return false; If (filename.find ("/Applications/ xcode.app /") == 0) return false; if (filename.find ("/Applications/ xcode.app /") == 0) return false; return true; } public: FilterCallback(CompilerInstance &CI):CI(CI){} void run(const MatchFinder::MatchResult &Result) Override {// Result passes Const ObjCPropertyDecl * propertyDecl = result.nodes.getNodeas <ObjCPropertyDecl>("propObjC"); string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str(); // Check that the node has a value. String propType = propertyDecl->getType().getAsString(); if (propertyDecl && isCustom(fileName)) { The cout << fileName << "attribute is of type" << propType << endl; }}};Copy the code
2.2.2 The first stage is plug-in verification
Command line input
Notice the prefix of the path, replace it with its own
/Users/jzd/Movies/A_B/llvm-project/build/Debug/bin/clang -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.5. The SDK -Xclang -load -Xclang /Users/jzd/Movies/A_B/llvm-project/build/Debug/lib/propPlugIn.dylib -Xclang -add-plugin -Xclang "name of PropertyPlugIn" -c /Users/jzd/Movies/A_B/ViewController.m
Clang using this path
/yourPath/llvm-project/build/Debug/bin/clang
Tip: Find Xcode on your MaciPhoneSimulator
sdk
open /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/
You can just, you know, check it out
You can also compare it to the system clang
clang -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.5. The SDK -fmodules -fsyntax-only -Xclang -ast-dump /Users/jzd/Movies/A_B/ViewController.m
Summary: From the AST, we filter out the properties, then filter out the system properties, leaving the properties written by the developer
Next, filter out the properties that are correctly written and leave out the properties that are incorrectly written
2.2.3 Application of plug-ins, for examplecopy
[NSString, NSArray, NSDictionary] as an attribute requires the copy modifier
Identify possible problems first
Bool copyApplys(const string typeStr){if (typestr.find ("NSString")! = string::npos || typeStr.find("NSArray") ! = string::npos || typeStr.find("NSDictionary") ! = string::npos) { return true; } return false; }Copy the code
Look for possible problems
class FilterCallback: public MatchFinder::MatchCallback{ // ... // For the rest, See github repo void run(const MatchFinder::MatchResult &Result) override {// Result gets the node const ObjCPropertyDecl by tag * propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("propObjC"); string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str(); // Check that the node has a value. String propType = propertyDecl->getType().getAsString(); if (propertyDecl && isCustom(fileName)) { / / get the description of the node information ObjCPropertyAttribute: : Kind attrKind = propertyDecl - > getPropertyAttributes (); if (copyApplys(propType) && ! (attrKind & ObjCPropertyAttribute: : kind_copy)) {/ / find out the possible errors cout < < propType < < "do not copy, the consequences... " << endl; }}}};Copy the code
2.2.4 Plug-in error Reported
DiagnosticsEngine is used
In the FilterCallback class’s run method,
if (copyApplys(propType) && ! (attrKind & ObjCPropertyAttribute: : kind_copy)) {/ / diagnosis DiagnosticsEngine & diag = CI. GetDiagnostics (); // Report, Report // The first argument, where is the warning placed // the second argument, Warning the identity of the diag. Report (propertyDecl - > getBeginLoc (), diag. GetCustomDiagID (DiagnosticsEngine: : Error, "% 0 this property, do not copy, consequences..." ))<<propType; }Copy the code
It looks like this:
➜ A_B/Users/JZD/Movies/A_B/LLVM – project/build/Debug/bin/clang – isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.5. The SDK -Xclang -load -Xclang /Users/jzd/Movies/A_B/llvm-project/build/Debug/lib/propPlugIn.dylib -Xclang -add-plugin -Xclang “name of PropertyPlugIn” -c /Users/jzd/Movies/A_B/ViewController.m
/Users/ JZD /Movies/A_B/ viewController.m :14:1: error: NSArray *
@property(nonatomic, strong) NSArray* arrs;
^
1 error generated.
2.2.5 Integrate the plug-in into Xcode
Load custom plug-in code
Need to get the current Xcode project, go custom compiled out of the plug-in, with the corresponding Clang
- Other C Flags
Fill in the input from the command line
The more informative line is the compiled Clang plug-in dynamic library
- Change the Clang path participating in the compilation
Create two options
CC corresponding clang
The corresponding clang++ CXX
- Compile option, index No
3. Principle supplement
LLVM is divided into a compiler front end and a back end
- Compiler front end, we mainly use Clang and Swift, generate abstract syntax tree AST, generate IR
Clang is for C, CPP, and Objective-C
- Compiler back end, do code generation work, including IR -> assembly -> binary