OCLint 一个静态代码分析工具
一、工具安装
OCLint
一个静态代码分析工具,通过 OCLint 的一些规则,让机器帮我们完成一部分代码质量的检测,规范代码,从而提高工作效率。还可以查找潜在的 bug,主要针对 C、C++、OC 的静态分析。
设置 brew 的第三方仓库
brew tap oclint/formulae
安装
brew install oclint
OCLint 升级
brew update
brew upgrade oclint
这是 0.13.0 版本,基于基于 LLVM 5。有个大神,基于 LLVM 7 重新编译了个版本
wget --no-check-certificate -O install-oclint https://github.com/yulingtianxia/oclint/releases/download/0.18.10/install-0.18.10
chmod +x install-oclint
./install-oclint
使用
OCLint 有三个指令:oclint、oclint-json-compilation-database、oclint-xcodebuild。
- oclint:基础指令。通过这个指令可以指定加载验证规则、编译代码、分析代码和生成报告。
- oclint-json-compilation-database:高级指令。从编译好的compile_commands.json文件中读取配置信息并执行oclint。
- oclint-xcodebuild:主要用于生成compile_commands.json文件,现在已经不进行维护了,使用xcpretty代替,来生成json文件。
oclint [options] <source> -- [compiler flags]
[options]为一些参数选项,可以是规则加载选项、报告形式选项等:
- -R<路径> :检测所用的规则的路径,可以是多个目录,用空格隔开。默认路径$(/path/to/bin/oclint)/../lib/oclint/rules
- -disable-rule <规则名> 通过规则名使某些验证规则失效
- -rc <参数>=<值> 修改某些阈值
民称 | 描述 | 默认值 |
---|---|---|
CYCLOMATIC_COMPLEXITY | 循环嵌套数限制 | 10 |
LONG_CLASS | 类行数限制 | 1000 |
LONG_LINE | 每行的字符限制 | 100 |
LONG_METHOD | 方法行数限制 | 50 |
LONG_VARIABLE_NAME | 参数名字符限制 | 20 |
MAXIMUM_IF_LENGTH | if 的行数限制 | 15 |
MINIMUM_CASES_IN_SWITCH | switch case 的最小数目 | 3 |
NPATH_COMPLEXITY | 通过该方法的非循环执行路径数量限制 | 200 |
NCSS_METHOD | 连续未注释行数限制 | 30 |
NESTED_BLOCK_DEPTH | block 嵌套层数限制 | 5 |
SHORT_VARIABLE_NAME | 变量名的最小字符数限制 | 3 |
TOO_MANY_FIELDS | 类成员限制 | 20 |
TOO_MANY_METHODS | 类方法数限制 | 30 |
TOO_MANY_PARAMETERS | 参数个数限制 | 10 |
示例
// 通过-rc改变检查规则的默认值,
oclint-json-compilation-database -- -rc=LONG_LINE=200 -o=report.html
// 通过 -disable-rule 可以禁止某一规则
oclint-json-compilation-database -disable-rule=LongLine
可以把这些阈值配置到项目的 .oclint文件下,比如:
rule-configurations:
- key: CYCLOMATIC_COMPLEXITY
value: 15
- key: LONG_LINE
value: 50
编译选项:通过 -- 的方式在指令的最后添加编译选项
编译数据库选项:-p <构建目录>:选择一个包含 compile_commands.json 文件的目录作为构建目录。如果没有指定构建目录,oclint指令会查找第一个输入文件的所有父目录来找到 compile_commands.json 文件。
生成报告选项
- -o <目录>:指定报告的输出目标
- -report-type <类型名>:指定报告输出的类型。默认是普通文本
报告输出的类型有如下几种:
- Plain Text Report(text)
- HTML Report(html)
- XML Report(xml)
- JSON Reporter (json)
- PMD Reporter (pmd):这种类型主要提供给 CI 系统使用,在 CI 系统的展示会更友好。
- Xcode Reporter (xcode):主要提供给 Xcode 内查看。
退出状态选项
- -max-priority-1 <阈值>
- -max-priority-2 <阈值>
- -max-priority-3 <阈值>
OCLint 会返回5种退出Code:
0 - SUCCESS:成功
- 1 - RULE_NOT_FOUND:没有找到验证规则。
- 2 - REPORTER_NOT_FOUND:没有指定报告输出地址。
- 3 - ERROR_WHILE_PROCESSING:验证过程中出错。
- 4 - ERROR_WHILE_REPORTING:生成报告时出错。
- 5 - VIOLATIONS_EXCEED_THRESHOLD:违反规则的次数超出阈值。
各个优先级默认的阈值:优先级 3 的阈值为 20;优先级 2 的阈值为 10;优先级 1 的阈值为 0。如果超过了其中任意一个阈值,就表示你的代码质量是不达标的,然后 OCLint 会返回 VIOLATIONS_EXCEED_THRESHOLD。
全局分析选项 :-enable-global-analysis
,开启这个选项可以得到更准确的分析结果,但是相对耗时比较长,一般不采用。
Clang 静态分析选项:-enable-clang-static-analyzer
。如果开启这个选项,OCLint会 hook Clang 的编译过程,然后收集编译信息然后添加到报告中。需要注意的是,这也会增加分析时间
Debug 选项: -debug 开启这个选项会给出更详细的信息。但是这个选项只有在OCLint的debug标示开启的时候才有效。
oclint-json-compilation-database 过滤选项:
- -i INCLUDES, -include INCLUDES, –include INCLUDES
- -e EXCLUDES, -exclude EXCLUDES, –exclude EXCLUDES
这两个选项是指在 compile_commands.json 文件配置的基础上添加一些文件/目录来执行 oclint,或者让一些文件或目录不执行oclint。
这两个选项支持正则匹配,这两个命令是用Python写的。oclint-json-compilation-database -e Pods
oclint 的选项:通过 -- 的方式在指令的最后 oclint 选项oclint-json-compilation-database -e Pods -- -o=report.html
。在最后添加了一个oclint的-o选项,表示将报告输出到当前的目录的 report.html 文件中
调试选项:-v
通过这个选项可以输出最终执行的oclint指令。-debug
开启这个选项会给出更详细的信息。但是同样这个选项只有在 OCLint 的debug标示开启的时候才有效。
oclint-xcodebuild :这个命令是给使用Xcode的用户提供的。主要用于分析 xcodebuild.log 文件,然后快速生成 compile_commands.json 文件。现在使用xcpretty代替。
控制OCLint的检查
在代码中控制OCLint忽略对代码的检查。
注解:可以使用注解的方法禁止OCLint的检查,比如我们知道一个参数没有使用,而又不想产生警告信息
__attribute__((annotate("oclint:suppress[unused method parameter]")))
- (IBAction)turnoverValueChanged: (id) __attribute__((annotate("oclint:suppress[unused method parameter]"))) sender
{
int i;// 这个参数不会被忽略,会产生OCLint警告
[self calculateTurnover];
}
!OCLint: 也可以通过//!OCLint
注释的方式,不让OCLint检查。注释要写在对应的行上面才能禁止对应的检查。
void a() {
int unusedLocalVariable; //!OCLINT
}
if (true) //!OCLint
{
// it is empty
}
xcodebuild
xcpretty
安装xcpretty 格式化编译日志。没有权限加sudo。用xcpretty是因为oclint-xcodebuild
不再维护了,在XCode 8之后采用xcpretty来生成
gem install xcpretty
xcpretty的使用帮助
短参数 | Center Aligned | 说明 |
---|---|---|
-t | --test | |
-s | --simple | |
-k | --knock | |
--tap | ||
-f | --formatter | |
-r | --report | 输出格式,可以是:junit、html、json-compilation-database |
-o | --output | 输出路径 |
-h | --help | 查看帮助 |
-v | --version | 查看版本号 |
--screenshots | 截图也输出到输出文件中 | |
--color | ||
--utf | ||
--no-color | ||
—no-utf |
遇到的错误
- code sign:项目要有证书
- invalid byte Sequence in US_ASCII :脚本里指定编码
export LC_ALL="zh_CN.UTF-8"
- /Library/Ruby/Site/2.3.0/rubygems.rb:289::多天尝试,发现命令已安装,且在命令行运行完好,但在Xcode的
Build Phases -> Run Script
中,老是报错。用which
命令查找,发现结果是/Users/zll/.rvm/gems/ruby-2.6.3/gems/xcpretty-0.3.0
,不是通常的/usr/local/bin/xcpretty
。想起自己的bash用的是zsh,应该要做些特殊配置。
export PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/bruce.wu/.rvm/bin"
# If you come from bash you might have to change your $PATH.
export PATH=$HOME/bin:/usr/local/bin:$PATH
打开注释两句的注释,重新安装路径就对了,未进行其他测试。就ok了。
二、oclint规则 索引
1. Basic 基本
- 条件位运算BitwiseOperatorInConditional:位运算是不利于后期维护和理解的,虽然计算速度快
void example(int a, int b)
{
if (a | b)
{
}
if (a & b)
{
}
}
- 错误的Null检查BrokenNullCheck:错误的空检查会让程序崩溃
void m(A *a, B *b)
{
if (a != NULL || a->bar(b))
{
}
if (a == NULL && a->bar(b))
{
}
}
- 错误的Nil检查BrokenNilCheck:在某些情况下,在OC中破零检查返回结果刚好相反
+ (void)compare:(A *)obj1 withOther:(A *)obj2
{
if (obj1 || [obj1 isEqualTo:obj2])
{
}
if (!obj1 && ![obj1 isEqualTo:obj2])
{
}
}
- BrokenOddnessCheck:如果x是负数,结果就是负数,不严谨。应该使用
x & 1 == 1
, orx % 2 != 0
void example()
{
if (x % 2 == 1) // violation
{
}
if (foo() % 2 == 1) // violation
{
}
}
- 可以合并的IF语句 CollapsibleIfStatements:可以合并的连续两个if,应该合并,提高代码的可读性
void example(bool x, bool y)
{
if (x) // these two if statements can be
{
if (y) // combined to if (x && y)
{
foo();
}
}
}
- 恒定条件预算 ConstantConditionalOperator:结果已经确定的条件运算
void example() { int a = 1 == 1 ? 1 : 0; // 1 == 1 is actually always true }
- IF表达式为确定ConstantIfExpression:if表达式的条件是已经确定的true或者else
void example()
{
if (true) // always true
{
foo();
}
if (1 == 0) // always false
{
bar();
}
}
- 无效代码DeadCode:在return、break、continue和throw之后的代码都是无效的。
void example(id collection)
{
for (id it in collection)
{
continue;
int i1; // dead code
}
return;
int i2; // dead code
}
- 双重否定 DoubleNegative:使用双重否定没有意义。(实际上主要用来把一个数值转化为布尔值,!!)
void example()
{
int b1 = !!1;
int b2 = ~~1;
}
- For应该转换为While ForLoopShouldBeWhileLoop:一些情况下For循环应该转换为While,使代码更简洁
void example(int a)
{
for (; a < 100;)
{
foo(a);
}
}
- Goto语句GotoStatement:Go To 被认为是有害的
void example()
{
A:
a();
goto A; // Considered Harmful
}
- 混乱的增量 JumbledIncrementer:混乱的增量计算,使代码很难阅读。
void aMethod(int a) {
for (int i = 0; i < a; i++) {
for (int j = 0; j < a; i++) { // references both 'i' and 'j'
}
}
}
- 错位的Null检查 MisplacedNullCheck:空检查应该再其他运算之前
void m(A *a, B *b)
{
if (a->bar(b) && a != NULL) // violation
{
}
if (a->bar(b) || !a) // violation
{
}
}
- 错位的Nil检查 MisplacedNilCheck:nil检查应该在其他运算之前
+ (void)compare:(A *)obj1 withOther:(A *)obj2
{
if ([obj1 isEqualTo:obj2] && obj1)
{
}
if (![obj1 isEqualTo:obj2] || obj1 == nil)
{
}
}
- 多余运算符 MultipleUnaryOperator:多余的运算符
void example()
{
int b = -(+(!(~1)));
}
- 从Finally 返回 ReturnFromFinallyBlock:不建议从Finally返回
void example()
{
@try
{
foo();
}
@catch(id ex)
{
bar();
}
@finally
{
return; // this can discard exceptions.
}
}
- Finally抛出异常 ThrowExceptionFromFinallyBlock:从Finally块抛出异常,可能掩盖其他的错误
void example()
{
@try {;}
@catch(id ex) {;}
@finally {
id ex1;
@throw ex1; // this throws an exception
NSException *ex2 = [NSException new];
[ex2 raise]; // this throws an exception, too
}
}
2.Cocoa
1、重写 isEqual 必须重写Hash MustOverrideHashWithIsEqual:当isEqual
方法被重写,hash
方法也应该被重写
@implementation BaseObject
- (BOOL)isEqual:(id)obj {
return YES;
}
/*
- (int)hash is missing; If you override isEqual you must override hash too.
*/
@end
2、必须调用超类 MustCallSuper:当一个类使用__attribute__((annotate("oclint:enforce[must call super]")))
注解的时候,他的所有实现(包括他自己和子类)都必须调用超类的实现。
@interface UIView (OCLintStaticChecks)
- (void)layoutSubviews __attribute__((annotate("oclint:enforce[must call super]")));
@end
@interface CustomView : UIView
@end
@implementation CustomView
- (void)layoutSubviews {
// [super layoutSubviews]; is enforced here
}
@end
3、验证禁止引用VerifyProhibitedCall:当一个方法标记 __attribute__((annotate("oclint:enforce[prohibited call]")))
注解,所有的引用都将被禁止。
@interface A : NSObject
- (void)foo __attribute__((annotate("oclint:enforce[prohibited call]")));
@end
@implementation A
- (void)foo {
}
- (void)bar {
[self foo]; // calling method `foo` is prohibited.
}
@end
4、验证 Protected 方法 VerifyProtectedMethod:protected
标记OC虽然没有语言级别的限制,但是从设计角度有时候希望他只能被自己以及子类访问。当违反时给一个警告
@interface A : NSObject
- (void)foo __attribute__((annotate("oclint:enforce[protected method]")));
@end
@interface B : NSObject
@property (strong, nonatomic) A* a;
@end
@implementation B
- (void)bar {
[self.a foo]; // calling protected method foo from outside A and its subclasses
}
@end
5、子类必须实现 SubclassMustImplement:这条用来验证抽象方法是被子类实现的。
@interface Parent
- (void)anAbstractMethod __attribute__((annotate("oclint:enforce[subclass must implement]")));
@end
@interface Child : Parent
@end
@implementation Child
/*
// Child, as a subclass of Parent, must implement anAbstractMethod
- (void)anAbstractMethod {}
*/
@end
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论