Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

1. Write at the front

LLVM download and LLVM compilation were described in my previous blog post. Clang and clangTooling were also compiled for LLVM project.

LLVM for iOS (part 2) — Custom Clang plugin (Part 1)

IOS Low-level exploration LLVM(I) — the first experience of LLVM

This blog will teach you how to code and customize a Clang plug-in. The final function is to report an error for incorrect use of attribute modifiers and suggest the correct word. The final effect is as follows.

2. Prepare

2.1 Creating a Plug-in

Create JPPlugins in/LLVM /tools/clang/tools.

2.2 modify CMakeLists. TXT

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

  • inJPPluginsCreate a new directory namedJPPlugins.cppThe documents andCMakeLists.txtThe document, inCMakeLists.txtWrite the following code in

add_llvm_library( JPPlugins MODULE BUILDTREE_ONLY
JPPlugins.cpp
)

Copy the code

2.3 Compiling Plug-ins

  • And then use it againcmakeThe command is regeneratedxcodeProject, or atllvm_buildUse in directorycmake -G Xcode .. /llvmCommand.
  • Finally, you canLLVMtheXcodeYou can see that in the projectLoadable modulesWe have our own under the directoryPluginCatalog, I cheerfully open the project a look…

What? It failed! There was nothing! Is this some kind of joke? CPP file with the same name as JPPlugins. I changed the CPP name and removed the “S” from the plugin name, and it worked! It’s got me scratching my head. It’s weird!

  • My stubborn temper, I do not believe in this evil, I changed back to the original name,JPPlugins.cppFiles andJPPluginsThe plugin still has the same name. I compiled it again and it failed again.
  • I again toJP.cppFile and plug-in namesJPPluginsThis time it’s different. It’s a success! It failed.
  • I tried a third time,JPPlugin.cppFile and plug-in namesJPPluginThis time I willsIt’s gone. That’s how weird it is. It worked.

I do not go to any conclusion, anyway, the same name can be compiled successfully, specific is not caused by the SUFFIX S, I do not know, anyway, my two success is related to this, more time old iron can go to verify, here will not bother to verify.

So we can now go to CPP to write code for the Clang plug-in.

3. Write plug-in code

No more nonsense to write, directly on the code, the steps are not a list out, are written in the 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"

	// Declare a namespace
using namespace clang;
using namespace std;
using namespace llvm;
using namespace clang::ast_matchers;

	// Plug-in namespace
namespace JPPlugin {

 // Step 3: callback after scan
  // Create a custom callback class from MatchCallback
 class JPMatchCallback : public MatchFinder::MatchCallback {

	   private:
	       // CI pass path: the CreateASTConsumer method parameter in the JPASTAction class -> JPASTConsumer's constructor -> JPMatchCallback's private property, obtained from the JPASTConsumer constructor via the constructor
	        CompilerInstance &CI;

	        // Check if it is your own file
	        bool isUserSourceCode(const string fileName) {
		            // The file name is not empty
		            if (fileName.empty()) return false;
		            // Code that is not in Xcode is assumed to belong to the user
		            if (0 == fileName.find("/Applications/Xcode.app/")) return false;
		            return true;
		        }

	        // Check whether copy should be used
	        bool isShouldUseCopy(const string typeStr) {
		            // Check whether the type is NSString/NSArray/NSDictionary
		            if (typeStr.find("NSString") != string::npos ||
							                 typeStr.find("NSArray") != string::npos ||
							                 typeStr.find("NSDictionary") != string::npos) {
			                return true;
			            }
		            return false;
		        }

	    public:
		// constructor
	        JPMatchCallback(CompilerInstance &CI):CI(CI) {}

	        // Override the run method
	        void run(const MatchFinder::MatchResult &Result) {
		            // Get the node object from Result, based on the node ID ("objcPropertyDecl") (this id needs to be the same as the ID of bind in the JPASTConsumer constructor)
		            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 the node has a value && is a user file
		            if (propertyDecl && isUserSourceCode(fileName)) {
			                // Get the node type and convert it to a string
			                string typeStr = propertyDecl->getType().getAsString(a);// Node description
			                ObjCPropertyAttribute::Kind attrKind = propertyDecl->getPropertyAttributes(a);// Copy should have been used, but it was not
			                if (isShouldUseCopy(typeStr) && ! (attrKind & ObjCPropertyAttribute::kind_copy)) {// Get the diagnostic engine from CI
				                    DiagnosticsEngine &diag = CI.getDiagnostics(a);/ / the Report Report
				                    Error: getCustomDiagID (level, hint) */
				                    diag.Report(propertyDecl->getLocation(), diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0 - Copy is recommended for this property!!"))<< typeStr; }}}};// Step 2: The scan configuration is complete
    // 3, custom JPASTConsumer, derived from the abstract class ASTConsumer, used to listen for information on AST nodes -- filters
    class JPASTConsumer : public ASTConsumer {
	    private:
	        // AST node finder (filter)
	        MatchFinder matcher;
	        // Callback object
	        JPMatchCallback callback;

	    public:
	        // The MatchFinder object is created in the constructor
	        JPASTConsumer(CompilerInstance &CI):callback(CI) { // Construct passes CI to callback
		            // Add a MatchFinder with each objcPropertyDecl node bound to an objcPropertyDecl identifier (to match the objcPropertyDecl node)
		            // Callback is a callback that overwrites the run method in CJLMatchCallback.
		matcher.addMatcher(objcPropertyDecl().bind("objcPropertyDecl"), &callback);
		        }

	        Override the two methods HandleTopLevelDecl and HandleTranslationUnit

	        // Call back once after parsing a top-level declaration (top-level nodes, i.e. global variables, attributes, functions, etc.)
	        bool HandleTopLevelDecl(DeclGroupRef D) {
			// cout<<" parsing..." <
		            return true;
		        }

	        // Call back when the entire file has been parsed
	        void HandleTranslationUnit(ASTContext &Ctx) {
			// cout<<" File parsed!!" <
		            // Give matcher the context (AST syntax tree) of the parsed file
		            matcher.matchAST(Ctx); }};//2, inherit PluginASTAction to implement our custom JPASTAction, i.e. custom AST syntax tree behavior
    class JPASTAction : public PluginASTAction {
	    public:

	        // Override the ParseArgs and CreateASTConsumer methods

	        /* Parse the given plug-in command line argument - param CI compiler instance for reporting diagnostics. - return True if the parsing succeeds. Otherwise, the plug-in is destroyed and no action is taken. This plug-in is responsible for reporting errors using the Diagnostic object CompilerInstance. * /
	        bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string> &arg) {
		            return true;
		        }

	        // Returns a custom JPASTConsumer object, a subclass of the abstract class ASTConsumer
	        unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
		            /** Pass CI CI for: - Determine if the file is the user's - throw a warning */
		            return unique_ptr<JPASTConsumer>(new JPASTConsumer(CI)); }}; }// Step 1: Register the plug-in and customize the JPASTAction class
	// 1
static FrontendPluginRegistry::Add<JPPlugin::JPASTAction> X("JPPlugin"."this is JPPlugin");

Copy the code

3.1 Terminal Test Plug-in

Create a new project and write the following code in viewController.m

@interface ViewController(a)

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

@end
Copy the code
  • The test command is as follows

“Self-compiled clang file path -isysroot simulator file path -xclang-load-Xclang plug-in path (.dylib) -xclang-add-plugin -xclang plug-in name -c source file path”

  • self-compiledclangThe file path is:llvm-project/llvm_build/Debug/bin/clang

  • The emulator file path is: / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator. The SDK, Use your computer to judge.

  • Plugin name: this is the name of the plugin you created, in this case JPPlugin

  • Source file path: the path to the file that you need the plugin to recognize

  • For example, 👇

/Users/RENO/Desktop/TEST/JPDemo/llvm-project/llvm_build/Debug/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -Xclang -load -Xclang /Users/RENO/Desktop/TEST/JPDemo/llvm-project/llvm_build/Debug/lib/JPPlugin.dylib -Xclang -add-plugin -Xclang JPPlugin -c /Users/RENO/Desktop/TEST/JPDemo/PluginTestDemo/PluginTestDemo/ViewController.m

  • The test results are as follows

3.2 Xcode integration plug-in

  • Load the plug-in

Open your test project and add the following to Build Settings -> Other C Flags in the code project:

- xclang-load-xclang plug-in path (.dylib) - xclang-add-plugin - The name of the Xclang plug-in

  • Set the coder

Since the Clang plug-in needs to be loaded with the corresponding version, if the version is inconsistent, a compilation error will occur, as shown in the following figure:

  • inBuild SettingsAdd two user-defined Settings in the column

CC and CXX, respectively

  • CCThe corresponding one is compiled by itselfclangAbsolute path of
  • CXXThe corresponding one is compiled by itselfclang++Absolute path of

  • Set up theCCCXX

  • The next inBuild SettingsSearch in columnindexThat will beEnable Index-Wihle-Building FunctionalitywillDefaultInstead ofNO, myXcode Toxic, search, I was hard to find, directly down, good search, finally found.

3.3 Compiling test Plug-ins

  • The lastcommand + BCompile the

As you can see from the results, clang’s plug-in works perfectly.

4. To summarize

  • The process is tortuous, the result is beautiful! 😁
  • myXcodeVersion isVersion 12.5
  • MacOS 11.4 Big Sur
  • The above content is for reference only, everyone’s computer environment is not the same, there may be differences.
  • Be careful not to write wrong paths during configuration.
  • This is just to record my own exploration process, the most important is some ideas and methods, stepped on the pit, I hope to help you avoid lightning!

5. Write in the back

Pay attention to me, more content continues to output!

Stay tuned!

  • CSDN
  • The Denver nuggets
  • Jane’s book

🌹 if you like, give it a thumbs up 👍🌹

🌹 feel harvest, can come to a wave of collection + attention, so as not to find you next time I 😁🌹

🌹 welcome everyone to leave a message exchange, criticism and correction, forwarding please indicate the source, thank you for your support! 🌹