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

github repo