基于LLVM开发Clang插件进行代码风格检查

栏目: IOS · 发布时间: 5年前

内容简介:这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。代开终端下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的

这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。

下载源码

代开终端

sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`

git clone -b release_60 https://github.com/llvm-mirror/llvm.git llvm  
git clone -b release_60 https://github.com/llvm-mirror/clang.git llvm/tools/clang  
git clone -b release_60 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra  
git clone -b release_60 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
复制代码

下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的 release_60

安装cmake

如果没有安装 cmake 需要安装一下,后面需要用。

brew update
brew install cmake

复制代码

开始编写插件

cd到 llvm/llvm/tools/clang/examples

1.打开这个目录下的 CMakeLists.txt 文件,然后添加 add_subdirectory(CodeChecker)

基于LLVM开发Clang插件进行代码风格检查

2.在当前目录创建新的文件夹 CodeChecker ,并cd到CodeChecker

mkdir CodeChecker
cd CodeChecker
复制代码

3.在新建的 CodeChecker 目录下创建三个文件

touch  CMakeLists.txt
touch  CodeChecker.cpp
touch  CodeChecker.exports
复制代码
基于LLVM开发Clang插件进行代码风格检查

在新创建的 CMakeLists.txt 中添加

if( NOT MSVC ) # MSVC mangles symbols differently
  if( NOT LLVM_REQUIRES_RTTI )
    if( NOT LLVM_REQUIRES_EH )
      set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports)
    endif()
  endif()
endif()

add_llvm_loadable_module(CodeChecker CodeChecker.cpp)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE}
    clangAST
    clangBasic
    clangFrontend
    LLVMSupport
    )
endif()
复制代码

CodeChecker.cpp 文件中加入

#include <iostream>
#include <stdio.h>
#include <string>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <functional>
#include <vector>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Sema/Sema.h"

using namespace clang;
using namespace std;

namespace
{
    static vector<string> split(const string &s, char delim)
    {
        vector<string> elems;
        stringstream ss;
        ss.str(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

    class CodeVisitor : public RecursiveASTVisitor<CodeVisitor>
    {
    private:
        CompilerInstance &Instance;
        ASTContext *Context;

    public:

        void setASTContext (ASTContext &context)
        {
            this -> Context = &context;
        }

    private:

        /**
         判断是否为用户源码

         @param decl 声明
         @return true 为用户源码,false 非用户源码
         */
        bool isUserSourceCode (Decl *decl)
        {
            string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();

            if (filename.empty())
                return false;

            //非XCode中的源码都认为是用户源码
            if(filename.find("/Applications/Xcode.app/") == 0)
                return false;

            return true;
        }

        /**
         检测类名是否存在小写开头

         @param decl 类声明
         */
        void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isLowercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toUppercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }

        /**
         检测类名是否包含下划线

         @param decl 类声明
         */
        void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名不能包含下划线
            size_t underscorePos = className.find('_');
            if (underscorePos != StringRef::npos)
            {
                //修正提示
                std::string tempName = className;
                std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_');
                tempName.erase(end_pos, tempName.end());
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden");
                SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
                diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测方法名是否存在大写开头

         @param decl 方法声明
         */
        void checkMethodNameForUppercaseName(ObjCMethodDecl *decl)
        {
            //检查名称的每部分,都不允许以大写字母开头
            Selector sel = decl -> getSelector();
            int selectorPartCount = decl -> getNumSelectorLocs();
            for (int i = 0; i < selectorPartCount; i++)
            {
                StringRef selName = sel.getNameForSlot(i);
                char c = selName[0];
                if (isUppercase(c))
                {
                    //修正提示
                    std::string tempName = selName;
                    tempName[0] = toLowercase(c);
                    StringRef replacement(tempName);
                    SourceLocation nameStart = decl -> getSelectorLoc(i);
                    SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1);
                    FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                    //报告警告
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter");
                    SourceLocation location = decl->getLocation();
                    D.Report(location, diagID).AddFixItHint(fixItHint);
                }
            }
        }

        /**
         检测方法中定义的参数名称是否存在大写开头

         @param decl 方法声明
         */
        void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl)
        {
            for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++)
            {
                ParmVarDecl *parmVarDecl = *it;
                StringRef name = parmVarDecl -> getName();
                char c = name[0];
                if (isUppercase(c))
                {
                    //修正提示
                    std::string tempName = name;
                    tempName[0] = toLowercase(c);
                    StringRef replacement(tempName);
                    SourceLocation nameStart = parmVarDecl -> getLocation();
                    SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                    FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                    //报告警告
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter");
                    SourceLocation location = decl->getLocation();
                    D.Report(location, diagID).AddFixItHint(fixItHint);
                }
            }
        }

        /**
         检测方法实现是否超过500行代码

         @param decl 方法声明
         */
        void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl)
        {
            if (decl -> hasBody())
            {
                //存在方法体
                Stmt *methodBody = decl -> getBody();

                string srcCode;
                srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()),
                               methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1);
                vector<string> lines = split(srcCode, '\n');
                if(lines.size() > 500)
                {
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines");
                    D.Report(decl -> getSourceRange().getBegin(), diagID);
                }
            }
        }

        /**
         检测属性名是否存在大写开头

         @param decl 属性声明
         */
        void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl)
        {
            bool checkUppercaseNameIndex = 0;

            StringRef name = decl -> getName();

            if (name.find('_') == 0)
            {
                //表示以下划线开头
                checkUppercaseNameIndex = 1;
            }

            //名称必须以小写字母开头
            char c = name[checkUppercaseNameIndex];
            if (isUppercase(c))
            {
                //修正提示
                std::string tempName = name;
                tempName[checkUppercaseNameIndex] = toLowercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }

        /**
         检测属性名是否包含下划线

         @param decl 属性声明
         */
        void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl)
        {
            StringRef name = decl -> getName();

            if (name.size() == 1)
            {
                //不需要检测
                return;
            }

            //类名不能包含下划线
            size_t underscorePos = name.find('_', 1);
            if (underscorePos != StringRef::npos)
            {
                //修正提示
                std::string tempName = name;
                std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_');
                tempName.erase(end_pos, tempName.end());
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden");
                SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
                diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测委托属性是否有使用weak修饰

         @param decl 属性声明
         */
        void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl)
        {
            QualType type = decl -> getType();
            StringRef typeStr = type.getAsString();

            //Delegate
            if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos)
            {
                ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes();

                string typeSrcCode;
                typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()),
                                   decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding());

                if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak))
                {
                    DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                    unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak.");
                    diagEngine.Report(decl -> getLocation(), diagID);
                }
            }
        }


        /**
         检测常量名称是否存在小写开头

         @param decl 常量声明
         */
        void checkConstantNameForLowercaseName (VarDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isLowercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toUppercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测变量名称是否存在大写开头

         @param decl 变量声明
         */
        void checkVarNameForUppercaseName (VarDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isUppercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toLowercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测变量名称

         @param decl 变量声明
         */
        void checkVarName(VarDecl *decl)
        {
            if (decl -> isStaticLocal())
            {
                //静态变量

                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }

            }
            else if (decl -> isLocalVarDecl())
            {
                //本地变量
                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }
            }
            else if (decl -> isFileVarDecl())
            {
                //文件定义变量
                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }
            }
        }

    public:

        CodeVisitor (CompilerInstance &Instance)
        :Instance(Instance)
        {

        }

        /**
         观察ObjC的类声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkClassNameForLowercaseName(declaration);
                checkClassNameForUnderscoreInName(declaration);
            }

            return true;
        }


        /**
         观察类方法声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCMethodDecl(ObjCMethodDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkMethodNameForUppercaseName(declaration);
                checkMethodParamsNameForUppercaseName(declaration);
                checkMethodBodyForOver500Lines(declaration);
            }

            return true;
        }


        /**
         观察类属性声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkPropertyNameForUppercaseName(declaration);
                checkPropertyNameForUnderscoreInName(declaration);
                checkDelegatePropertyForUsageWeak(declaration);
            }

            return true;
        }

        /**
         观察变量声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitVarDecl(VarDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkVarName(declaration);
            }

            return true;
        }

        /**
         观察枚举常量声明

         @param declaration 声明对象
         @return 返回
         */
        //        bool VisitEnumConstantDecl (EnumConstantDecl *declaration)
        //        {
        //            return true;
        //        }
    };

    class CodeConsumer : public ASTConsumer
    {
        CompilerInstance &Instance;
        std::set<std::string> ParsedTemplates;
    public:
        CodeConsumer(CompilerInstance &Instance,
                     std::set<std::string> ParsedTemplates)
        : Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance)
        {

        }

        bool HandleTopLevelDecl(DeclGroupRef DG) override
        {
            return true;
        }

        void HandleTranslationUnit(ASTContext& context) override
        {
            visitor.setASTContext(context);
            visitor.TraverseDecl(context.getTranslationUnitDecl());
        }

    private:
        CodeVisitor visitor;
    };

    class CodeASTAction : public PluginASTAction
    {
        std::set<std::string> ParsedTemplates;
    protected:
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                       llvm::StringRef) override
        {
            return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates);
        }

        bool ParseArgs(const CompilerInstance &CI,
                       const std::vector<std::string> &args) override
        {
            //            DiagnosticsEngine &D = CI.getDiagnostics();
            //            D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
            //                                       "My plugin Started..."));

            return true;
        }
    };
}

static clang::FrontendPluginRegistry::Add<CodeASTAction>
X("CodeChecker", "Code Checker");

复制代码

使用cmake编译源代码

1.cd到 llvm ,注意不是 llvm/llvm ,执行

mkdir llvm_build && cd llvm_build
cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel

复制代码

2.然后会在llvm_build文件目录中看到LLVM.xcodeproj,用xcode打开,选择 Automatically Create Schemes

基于LLVM开发Clang插件进行代码风格检查

3.编译 clang,CodeChecker,libclang

基于LLVM开发Clang插件进行代码风格检查
基于LLVM开发Clang插件进行代码风格检查
基于LLVM开发Clang插件进行代码风格检查

4.在 llvm_build/Debug/lib 目录下可以找到我们的插件,如下图:

基于LLVM开发Clang插件进行代码风格检查

Xcode集成Plugin

创建需要加载插件的项目,在Build Settings栏目中的OTHER_CFLAGS添加上如下内容:

-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字

复制代码

我把插件拷贝的桌面了,所以我的是:

-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker

复制代码
基于LLVM开发Clang插件进行代码风格检查

然后你build项目,可能有 unable to load plugin '/Users/roy.cao/Desktop/CodeChecker.dylib' 的error,这是由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误。

基于LLVM开发Clang插件进行代码风格检查

在Build Settings栏目中新增两项用户定义的设置,分别为CC和CXX CC对应的是自己编译的clang的绝对路径,CXX对应的是自己编译的clang++的绝对路径

基于LLVM开发Clang插件进行代码风格检查
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang++

复制代码

再build,会有以下错误:

基于LLVM开发Clang插件进行代码风格检查

在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality的Default改为NO.

基于LLVM开发Clang插件进行代码风格检查

再build可能会出现一大堆系统库的 symbol not found 错误

基于LLVM开发Clang插件进行代码风格检查

这个时候需要在刚刚OTHER_CFLAGS的

-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker

复制代码

后面再加上 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.0.sdk

注意 iPhoneSimulator12.0.sdk ,每个人的可能不同,需要到目录下看看自己的版本。 那么完整的就是下图这样:

基于LLVM开发Clang插件进行代码风格检查

再build就能做代码风格检查啦,庆祝一下吧~~~~

基于LLVM开发Clang插件进行代码风格检查

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Game Programming Patterns

Game Programming Patterns

Robert Nystrom / Genever Benning / 2014-11-2 / USD 39.95

The biggest challenge facing many game programmers is completing their game. Most game projects fizzle out, overwhelmed by the complexity of their own code. Game Programming Patterns tackles that exac......一起来看看 《Game Programming Patterns》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试