1. Compile LLVM project

1.1 LLVM download

As a result of the domestic network limited, we need to use image download the source code of LLVM mirror.tuna.tsinghua.edu.cn/help/llvm/

Download the LLVM project:

git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/llvm.git
Copy the code

Download Clang from LLVM’s Tools directory:

cd llvm/tools
git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang.git
Copy the code

Download compiler-rt, libcxx, libcxxabi in the LLVM projects directory:

cd .. /projects git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/compiler-rt.git git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxx.git git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/libcxxabi.gitCopy the code

Install the extra tool under Clang tools:

cd .. /tools/clang/tools git clone https://mirrors.tuna.tsinghua.edu.cn/git/llvm/clang-tools-e xtra.gitCopy the code

1.2 install cmake

Since the latest LLVM only supports cmake to compile, we also need to install cmake.

  • To viewbrewWhether to installcmakeIf so, skip the following steps
    brew list
    Copy the code
  • throughbrewThe installationcmake 
    brew install cmake
    Copy the code

1.3 compile LLVM

1.3.1 Compiling LLVM using Xcode

Cmake compiles into Xcode projects

mkdir build_xcode cd build_xcode cmake -G Xcode .. /llvmCopy the code

useXcodecompileClang

It would take a long time to create Schemes automatically, so we choose manual management:

Click the plus sign in the lower left corner to add clang and clangTooling to Target:

Clang and clangTooling were selected to compile respectively:

1.3.2 Compiling LLVM using Ninja

  • Compiling with Ninja requires installationninja. use$ brew install ninjaCommand to install Ninja.
  • inllvmCreate a new one in the source directorybuild_ninjaDirectory, which will eventually be generated in the build_ninja directorybuild.ninja.
  • inllvmCreate a new one in the source directoryllvm_releaseDirectory, the final compiled file will be inllvm_releaseIn the folder path.
    $ cd llvm_build $ cmake -G Ninja .. / llvm-dcmake_install_prefix = Installation path (the local directory is /Users/ XXX/XXX /LLVM/ llVM_release. Note that DCMAKE_INSTALL_PREFIX cannot be followed by a spaceCopy the code
  • Execute compile and install instructions in sequence
      $ ninja
      $ ninja install
    Copy the code

2. Create the Clang plugin

/ LLVM /tools/clang/tools/HKPlugin:

Add add_clang_subdirectory(HKPlugin) to cmakelists. TXT in/LLVM /tools/clang/tools:

Create a new file named hkplugi. CPP and cmakelists.txt under HKPlugin:

In cmakelists.txt write the following:

add_llvm_library( HKPlugin MODULE BUILDTREE_ONLY
  HKPlugin.cpp
)
Copy the code

Cmake -g Xcode in build_xcode. / LLVM command.

Finally, LLVM’s Xcode project has its own Plugin directory under Loadable Modules, where we can write Plugin code:

3. Write plug-in code

3.1 Analysis of top-level nodes

In hkplugin.cpp, write the following 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 the namespace using namespace clang; using namespace std; using namespace llvm; // define namespace namespace HKPlugin {// define HKConsumer class HKConsumer:public ASTConsumer{public: Bool HandleTopLevelDecl(DeclGroupRef D) {cout<<" parsing..." <<endl; return true; } // Call back void HandleTranslationUnit(ASTContext &Ctx) {cout<<" File parsing done..." <<endl; }}; Class HKASTAction:public PluginASTAction{public: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { return unique_ptr<HKConsumer> (new HKConsumer); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) { return true; }}; } / / registered plug-in static FrontendPluginRegistry: : Add < HKPlugin: : HKASTAction > X (" HKPlugin." "This is the description of the plugin");Copy the code

Compile the HKPlugin project, go to clang under the Products directory of the project, and Show In Finder to view the executable:

Find the hkplugin.dylib executable in the same way:

Create a hello.m file anywhere and write the following code:

int sum(int a);
int a;

int sum(int a){
    int b = 10;
    return 10 + b;
}

int sum2(int a,int b){
    int c = 10;
    return a + b + c;
}
Copy the code

Run the following command to test the plug-in:

// Self-compiled clang path -isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.5 SDK / -xclang-load-xclang plugin (.dylib) path -xclang-add-plugin -xclang plugin name -c source code path example [: ](url) /Users/lcy/study/build_xcode/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -Xclang  -load -Xclang /Users/lcy/study/build_xcode/Debug/lib/HKPlugin.dylib -Xclang -add-plugin -Xclang HKPlugin -c ./hello.m ------------------------- // The following output is displayed: Parsing... Parsing... Parsing... Parsing... File parsing completed...Copy the code

3.2 Analyzing the OC code

Create your App project and write the following code in viewController.m:

#import "ViewController.h"

@interface ViewController ()

@property(nonatomic, strong) NSString* name;
@property(nonatomic, strong) NSArray* arrs;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

@end
Copy the code

Clang – isysroot execution /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -fmodules-fsyntax-only -xclang -ast-dump viewcontroller. m

  • inObjCPropertyDeclNode,nameandarrsThe type, modifier, position, and so on of the

3.3 MatchFinder filters AST nodes

Add matchfinder-related code to hkplugin.cpp:

#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 the namespace using namespace clang; using namespace std; using namespace llvm; using namespace clang::ast_matchers; / / define the namespace namespace HKPlugin {class HKMatchCallBack: public MatchFinder: : MatchCallback {public: Void run(const MatchFinder::MatchResult &Result) {// Get node information from Result const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl"); If (propertyDecl) {string typeStr = propertyDecl -> getType().getAsString(); Cout <<"-------- get: "<<typeStr<<endl; }}}; // public ASTConsumer{private: // AST node filter MatchFinder matcher; HKMatchCallBack callback; public: HKConsumer() {// Add a MatchFinder to match -objcpropertyDecl node // Find the desired node, call back, AddMatcher (objcPropertyDecl().bind("objcPropertyDecl"), &callback); Bool HandleTopLevelDecl(DeclGroupRef D) {cout<<" parsing..." <<endl; return true; } // Call back void HandleTranslationUnit(ASTContext &Ctx) {cout<<" File parsing done..." <<endl; matcher.matchAST(Ctx); }}; Class HKASTAction:public PluginASTAction{public: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { return unique_ptr<HKConsumer> (new HKConsumer); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) { return true; }}; } / / registered plug-in static FrontendPluginRegistry: : Add < HKPlugin: : HKASTAction > X (" HKPlugin." "This is the description of the plugin");Copy the code

Test plugins for viewController.m:

  • nameandarrsType is printed out.
  • Because of the expansion of the header file, the properties of the system file are also printed out, and there are a large number of system files that we need to filter.

3.4 Filtering System Files

System files are stored in the Xcode package, which starts with /Applications/ xcode. app.

Add the filter system file code to hkplugin.cpp:

#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 the namespace using namespace clang; using namespace std; using namespace llvm; using namespace clang::ast_matchers; / / define the namespace namespace HKPlugin {class HKMatchCallBack: public MatchFinder: : MatchCallback {private: CompilerInstance &CI; Bool isUserSourceCode(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: HKMatchCallBack(CompilerInstance &CI):CI(CI){} void run(const MatchFinder::MatchResult &Result) {// Fetch node information const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl"); String fileName = ci.getSourcemanager ().getFilename(propertyDecl->getSourceRange().getbegin ()).str(); If (propertyDecl && isUserSourceCode(fileName)) {// If the node has a value && is not a system file string typeStr = propertyDecl -> getType().getAsString(); Cout < < "-- -- -- -- -- -- -- -- received:" < < < < typeStr "it belongs to the file: < < fileName" < < endl; }}}; // public ASTConsumer{private: // AST node filter MatchFinder matcher; HKMatchCallBack callback; public: HKConsumer(CompilerInstance &CI):callback(CI) {// Add a MatchFinder to match -objCPropertyDecl AddMatcher (objcPropertyDecl().bind("objcPropertyDecl"), &callback); Bool HandleTopLevelDecl(DeclGroupRef D) {cout<<" parsing..." <<endl; return true; } // Call back void HandleTranslationUnit(ASTContext &Ctx) {cout<<" File parsing done..." <<endl; matcher.matchAST(Ctx); }}; Class HKASTAction:public PluginASTAction{public: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { return unique_ptr<HKConsumer> (new HKConsumer(CI)); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) { return true; }}; } / / registered plug-in static FrontendPluginRegistry: : Add < HKPlugin: : HKASTAction > X (" HKPlugin." "This is the description of the plugin");Copy the code

Test plugins for viewController.m:

  • At this point, the system files are removed.

3.5 Copy Modifier Verification A warning message is displayed

We are checking property modifiers, should use copy, but do not use copy, warning.

Add the relevant code to hkplugin.cpp:

#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 the namespace using namespace clang; using namespace std; using namespace llvm; using namespace clang::ast_matchers; / / define the namespace namespace HKPlugin {class HKMatchCallBack: public MatchFinder: : MatchCallback {private: CompilerInstance &CI; Bool isUserSourceCode(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; } 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) {// Fetch node information const ObjCPropertyDecl *propertyDecl = Result.Nodes.getNodeAs<ObjCPropertyDecl>("objcPropertyDecl"); String fileName = ci.getSourcemanager ().getFilename(propertyDecl->getSourceRange().getbegin ()).str(); If (propertyDecl && isUserSourceCode(fileName)) {// If the node has a value && is not a system file // The type of the node string typeStr = propertyDecl -> getType().getAsString(); / / get the description of the node information ObjCPropertyDecl: : PropertyAttributeKind attrKind = propertyDecl - > getPropertyAttributes (); if (isShouldUseCopy(typeStr) && ! AttrKind & ObjCPropertyDecl::OBJC_PR_copy) {// Copy should be used, but copy is not used // DiagnosticsEngine &diag = CI.getDiagnostics(); / / diag report report. The report (propertyDecl - > getLocation (), diag. GetCustomDiagID (DiagnosticsEngine: : Warning, "this place should use copy")); }}}}; // public ASTConsumer{private: // AST node filter MatchFinder matcher; HKMatchCallBack callback; public: HKConsumer(CompilerInstance &CI):callback(CI) {// Add a MatchFinder to match -objCPropertyDecl AddMatcher (objcPropertyDecl().bind("objcPropertyDecl"), &callback); Bool HandleTopLevelDecl(DeclGroupRef D) {cout<<" parsing..." <<endl; return true; } // Call back void HandleTranslationUnit(ASTContext &Ctx) {cout<<" File parsing done..." <<endl; matcher.matchAST(Ctx); }}; Class HKASTAction:public PluginASTAction{public: std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { return unique_ptr<HKConsumer> (new HKConsumer(CI)); } bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) { return true; }}; } / / registered plug-in static FrontendPluginRegistry: : Add < HKPlugin: : HKASTAction > X (" HKPlugin." "This is the description of the plugin");Copy the code

Test plugins for viewController.m:

  • rightnameandarrsProperty reports warnings and indicates their location information.

3.6 Xcode integration compiler plug-in

3.6.1 Loading a Plug-in

To open the test project, go to Build Settings -> Other C Flags and add the following:

- xclang-load-xclang (.dylib) dynamic library path - xclang-add-plugin -Xclang HKPluginCopy the code

3.6.2 Setting the Compiler

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

error: unable to load plugin '/Users/lcy/study/build_xcode/Debug/lib/HKPlugin.dylib': 'dlopen(/Users/lcy/study/build_xcode/Debug/lib/HKPlugin.dylib, 9): Symbol not found: __ZN5clang12ast_matchers16objcPropertyDeclE Referenced from: /Users/lcy/study/build_xcode/Debug/lib/HKPlugin.dylib Expected in: flat namespace in /Users/lcy/study/build_xcode/Debug/lib/HKPlugin.dylib' /Users/lichunyang/Library/Developer/Xcode/DerivedData/MyDemo-asmouncvgpgfyzccmbpymrxoyjha/Build/Intermediates.noindex/My Demo.build/Debug-iphonesimulator/MyDemo.build/Objects-normal/x86_64/main.dia:1:1: warning: Could not read serialized diagnostics file: error("Failed to open diagnostics file") (in target 'MyDemo' from project 'MyDemo') Command CompileC failed with a nonzero exit codeCopy the code

Add two user-defined Settings to the Build Settings TAB:

  • , respectively,CCandCXX.CCThe corresponding one is compiled by itselfclangThe absolute path of,CXXThe corresponding one is compiled by itselfclang++The absolute path to.

The error message is not the same for different clang versions:

Next, search for index in Build Settings and change Enable index-wihle-building Functionality to Default to NO:

After compiling the project, we finally see what we want, and give ⚠️ prompt for the properties: