这个不少人都作过,文章也挺多的,我也是参考别人文章的,不过直到真正实现仍是踩了许多坑,因此记录下来,或许对其余人有帮助。其实LLVM和Clang我尚未好好研究过,以前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,因此此次选择OC的代码检查做为开始,经过实践找找感受和兴趣,以后再一点一点精进。ios
代开终端c++
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
。git
若是没有安装cmake
须要安装一下,后面须要用。github
brew update
brew install cmake
复制代码
cd到llvm/llvm/tools/clang/examples
xcode
1.打开这个目录下的CMakeLists.txt
文件,而后添加add_subdirectory(CodeChecker)
bash
2.在当前目录建立新的文件夹CodeChecker
,并cd到CodeCheckerapp
mkdir CodeChecker
cd CodeChecker
复制代码
3.在新建的CodeChecker
目录下建立三个文件ide
touch CMakeLists.txt
touch CodeChecker.cpp
touch CodeChecker.exports
复制代码
在新建立的CMakeLists.txt
中添加ui
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
文件中加入this
#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");
复制代码
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
3.编译 clang,CodeChecker,libclang
4.在llvm_build/Debug/lib
目录下能够找到咱们的插件,以下图:
建立须要加载插件的项目,在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
复制代码
而后你build项目,可能有unable to load plugin '/Users/roy.cao/Desktop/CodeChecker.dylib'
的error,这是因为Clang插件须要对应的Clang版原本加载,若是版本不一致会致使编译错误。
在Build Settings栏目中新增两项用户定义的设置,分别为CC和CXX CC对应的是本身编译的clang的绝对路径,CXX对应的是本身编译的clang++的绝对路径
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang++
复制代码
再build,会有如下错误:
在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality的Default改成NO.
再build可能会出现一大堆系统库的 symbol not found
错误
这个时候须要在刚刚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
,每一个人的可能不一样,须要到目录下看看本身的版本。 那么完整的就是下图这样:
再build就能作代码风格检查啦,庆祝一下吧~~~~
参考文章:
LLVM & Clang 入门
使用Xcode开发iOS语法检查的Clang插件
Clang 之旅--使用 Xcode 开发 Clang 插件