The target

When accessing code, you need to prompt or warn if you find non-standard embellishments

Write plug-in code

In the last article we created a plug-in module that needed to write.cpp code

#include <iostream>
#include "clang/AST/AST.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"

// Use this namespace!
using namespace clang;
using namespace std;
using namespace llvm;
using namespace ast_matchers;
Copy the code

If we want to write a plugin, we can check out the clang official plugin development API. Clang itself is the front end of the compiler, and we need to do some things as it reads code, either in the callback methods it provides us or in the methods we override ourselves. So it’s a process of secondary development.

PluginASTAction Abstract syntax tree

  1. Define a class that inherits PluginASTAction to customize the processing since it reads from the abstract syntax tree
namespace HKPlugin {
    class HKASTAction:public PluginASTAction {}
}
Copy the code
  1. Register plug-ins:
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> 
X("HKPlugin"."this is the description"); // The left side is the name of the plug-in, and the right side is the description of the plug-in
Copy the code
  1. Two functions need to be overloaded to read the AST, so theHKASTActionOverride the following two functions
  std::unique_ptr<ASTConsumer> CreateASTConsumer( CompilerInstance &CI, StringRef InFileCopy the code
bool ParseArgs(const CompilerInstance &CI, const vector<string> &arg)
Copy the code
  1. 3 in theASTConsumerIs a base class that we need to implement ourselves, which is the key to reading the AST. Create a classHKConsumerInheritance inASTConsumer
class HKConsumer:public ASTConsumer{}
Copy the code
  1. The class to focus on isHKConsumerAnd this has two functions to implement
bool HandleTopLevelDecl(DeclGroupRef D) 
// Call back a top-level declaration, such as global variables, function definitions, and attributes
Copy the code
void HandleTranslationUnit(ASTContext &Ctx) // Call back when the entire file is parsed
Copy the code

After BULID, weshow in finderFind this file Debug -> bin -> clang: compiled binary Debug -> lib -> HKPlugin: path to the custom plug-inNow we use this to compile our own code

To test the plug-in

The path to the self-compiled Clang file is the orange path above -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.sdk/ - xclang-load-xclang plug-in path is the path marked in pink. Xclang-add-plugin -Xclang plug-in name -c source path

Thought analysis

Purpose: You need to know which node is in the lexical analysis if you want to use copy to modify the NSString attribute with Strong. So we need to locate the compilation phase of lexical parsing, we write two properties in VC and then use the system’s own CLang to look at the compilation process

@interface ViewController(a)
@property(nonatomic.strong) NSString* name;
@property(nonatomic.strong) NSArray* arrs;
@end
Copy the code

clang -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.5. The SDK -fmodules -fsyntax-only -Xclang -ast-dump ViewController.m We found that the properties are associated withObjCPropertyDeclThis key word is relevant.

Write plug-in code below

MatchFinder AST Syntax tree node lookup MatchCallback finds the callback

  1. The AST node looks for filters
    class HKConsumer:public ASTConsumer{
    private:
        // Filter for the MatchFinder AST node
        MatchFinder matcher;
    }
Copy the code
  1. rewriteHKConsumerThe construction method of
HKConsumer() {// Add a MatchFinder to match the ObjCPropertyDecl node
/ / callback!
	matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
}
Copy the code

The first argument is the node to match, and the second argument is the callback to find that node. This callback is a base class

class HKMatchCallback: public  MatchFinder::MatchCallback
Copy the code
  1. Override the run method
void run(const MatchFinder::MatchResult &Result)
Copy the code
  1. The result of the run is to get the node object throughgetType().getAsString()Get the string of the property node
  2. However, because there are too many node strings, we need to filter out the system. So we need to know the paths of all the current nodes
CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str(a)Copy the code

// Check if it is your own file
 bool isUserSourceCode(const string fileName){
      if (fileName.empty()) return false;
      // Code that is not in Xcode is assumed to belong to the user
      if (fileName.find("/Applications/Xcode.app/") = =0) return false;
      return  true;
}
Copy the code
 // Check whether copy should be used
bool isShouldUseCopy(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
  1. Obtain the description of the current node and compare it with the Copy

You should use copy but the judgment if you don’t use copy is,

isShouldUseCopy(typeStr) && ! (attrKind & ObjCPropertyDecl::OBJC_PR_copy)Copy the code

This situation calls for a warning

  1. Get the compiler’s diagnostic engine and issue a warning
DiagnosticsEngine &diag = CI.getDiagnostics(a); diag.Report(propertyDecl->getLocation(),diag.getCustomDiagID(DiagnosticsEngine::Error, "This place should use Copy."));
Copy the code

Integrated into the Xcode

inBuild SettingssearchOther C Flagsadd

-Xclang -load -Xclang xxx.dylib -Xclang -add-plugin -Xclang xxx
Copy the code

Xxx.dylib: dynamic library path

The Clang plug-in needs to be loaded using the corresponding version. If the version is inconsistent, a compilation error will occur. The following message is Expected in: Flat Namespace

CC: the absolute path to your own compiled clang

CXX: absolute path to clang++ corresponding to its own compilation

At the same time to searchindexThat will beEnable index-while-buliding-FunctionalityChange to NO

Run, the final result is as follows.

The complete code

#include <iostream>
#include "clang/AST/AST.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"

// Use this namespace!
using namespace clang;
using namespace std;
using namespace llvm;
using namespace ast_matchers;


namespace HKPlugin {
    class HKMatchCallback: public  MatchFinder::MatchCallback{
    private:
        CompilerInstance &CI;
        // Check if it is your own file
        bool isUserSourceCode(const string fileName){
            if (fileName.empty()) return false;
            // Code that is not in Xcode is assumed to belong to the user
            if (fileName.find("/Applications/Xcode.app/") = =0) return false;
            return  true;
        }
        
        // Check whether copy should be used
        bool isShouldUseCopy(const string typeStr){
            if(typeStr.find("NSString") != string::npos ||
               typeStr.find("NSArray") != string::npos ||
               typeStr.find("NSDictionary") != string::npos){
                return true;
            }
            return false;
        }
        
    public:
        HKMatchCallback(CompilerInstance &CI):CI(CI){}
        void run(const MatchFinder::MatchResult &Result) {
            // Get the node object from the result
           const ObjCPropertyDecl * propertyDecl =  Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl");
            
            // Get the file name (including the path)
            string fileName = CI.getSourceManager().getFilename(propertyDecl->getSourceRange().getBegin()).str(a);if (propertyDecl && isUserSourceCode(fileName)) {// If the node has a value && is not a system file!
                // Node type
                string typeStr = propertyDecl->getType().getAsString(a);// Get the description of the node
                ObjCPropertyDecl::PropertyAttributeKind attrKind = propertyDecl->getPropertyAttributes(a);if (isShouldUseCopy(typeStr) && ! (attrKind & ObjCPropertyDecl::OBJC_PR_copy)) {// Copy should be used but copy is not used
                    // Diagnostic engine
                    DiagnosticsEngine &diag = CI.getDiagnostics(a);/ / the Report Report
                    diag.Report(propertyDecl->getLocation(),diag.getCustomDiagID(DiagnosticsEngine::Error, "This place should use Copy."));
// cout<}}}};// Custom HKConsumer
    class HKConsumer:public ASTConsumer{
    private:
        // Filter for the MatchFinder AST node
        MatchFinder matcher;
        HKMatchCallback callback;
    public:
        HKConsumer(CompilerInstance &CI):callback(CI){
            // Add a MatchFinder to match the ObjCPropertyDecl node
            / / callback!
            matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
        }
        
        // Once a top-level declaration is parsed, it is called back
        bool HandleTopLevelDecl(DeclGroupRef D){
            return true;
        }
        
        // Call back when the entire file is parsed!!
        void HandleTranslationUnit(ASTContext &Ctx) {
            cout<<"File parsing complete!!"<<endl;
            matcher.matchAST(Ctx); }};// Define a class PluginASTAction
    class HKASTAction:public PluginASTAction{
    public:
        bool ParseArgs(const CompilerInstance &CI, const vector<string> &arg) {
            return  true;
        }
        
        
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
            return unique_ptr<HKConsumer> (new HKConsumer(CI)); }}; }// Register the plugin!
static FrontendPluginRegistry::Add<HKPlugin::HKASTAction> X("HKPlugin"."this is the description");

Copy the code