OCLint 一个静态代码分析工具 - 文章教程

OCLint 一个静态代码分析工具

发布于 2021-03-02 字数 16679 浏览 951 评论 0

一、工具安装

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。

  1. oclint:基础指令。通过这个指令可以指定加载验证规则、编译代码、分析代码和生成报告。
  2. oclint-json-compilation-database:高级指令。从编译好的compile_commands.json文件中读取配置信息并执行oclint。
  3. oclint-xcodebuild:主要用于生成compile_commands.json文件,现在已经不进行维护了,使用xcpretty代替,来生成json文件。

oclint [options] <source> -- [compiler flags]

[options]为一些参数选项,可以是规则加载选项、报告形式选项等:

  1. -R<路径> :检测所用的规则的路径,可以是多个目录,用空格隔开。默认路径$(/path/to/bin/oclint)/../lib/oclint/rules
  2. -disable-rule <规则名> 通过规则名使某些验证规则失效
  3. -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

遇到的错误

  1. code sign:项目要有证书
  2. invalid byte Sequence in US_ASCII :脚本里指定编码 export LC_ALL="zh_CN.UTF-8"
  3. /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 基本

  1. 条件位运算BitwiseOperatorInConditional:位运算是不利于后期维护和理解的,虽然计算速度快
void example(int a, int b)
{
    if (a | b)
    {
    }
    if (a & b)
    {
    }
}
  1. 错误的Null检查BrokenNullCheck:错误的空检查会让程序崩溃
void m(A *a, B *b)
{
    if (a != NULL || a->bar(b))
    {
    }

    if (a == NULL && a->bar(b))
    {
    }
}
  1. 错误的Nil检查BrokenNilCheck:在某些情况下,在OC中破零检查返回结果刚好相反
+ (void)compare:(A *)obj1 withOther:(A *)obj2
{
    if (obj1 || [obj1 isEqualTo:obj2])
    {
    }

    if (!obj1 && ![obj1 isEqualTo:obj2])
    {
    }
}
  1. BrokenOddnessCheck:如果x是负数,结果就是负数,不严谨。应该使用x & 1 == 1, or x % 2 != 0
void example()
{
    if (x % 2 == 1)         // violation
    {
    }

    if (foo() % 2 == 1)     // violation
    {
    }
}
  1. 可以合并的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();
        }
    }
}
  1. 恒定条件预算 ConstantConditionalOperator:结果已经确定的条件运算
    void example()
    {
        int a = 1 == 1 ? 1 : 0;     // 1 == 1 is actually always true
    }
    
  2. IF表达式为确定ConstantIfExpression:if表达式的条件是已经确定的true或者else
void example()
{
    if (true)       // always true
    {
        foo();
    }
    if (1 == 0)     // always false
    {
        bar();
    }
}
  1. 无效代码DeadCode:在return、break、continue和throw之后的代码都是无效的。
void example(id collection)
{
    for (id it in collection)
    {
        continue;
        int i1;                 // dead code
    }
    return;
    int i2;                     // dead code
}
  1. 双重否定 DoubleNegative:使用双重否定没有意义。(实际上主要用来把一个数值转化为布尔值,!!)
void example()
{
    int b1 = !!1;
    int b2 = ~~1;
}
  1. For应该转换为While ForLoopShouldBeWhileLoop:一些情况下For循环应该转换为While,使代码更简洁
void example(int a)
{
    for (; a < 100;)
    {
        foo(a);
    }
}
  1. Goto语句GotoStatement:Go To 被认为是有害的
void example()
{
    A:
        a();
    goto A;     // Considered Harmful
}
  1. 混乱的增量 JumbledIncrementer:混乱的增量计算,使代码很难阅读。
void aMethod(int a) {
    for (int i = 0; i < a; i++) {
        for (int j = 0; j < a; i++) { // references both 'i' and 'j'
        }
    }
}
  1. 错位的Null检查 MisplacedNullCheck:空检查应该再其他运算之前
void m(A *a, B *b)
{
    if (a->bar(b) && a != NULL) // violation
    {
    }

    if (a->bar(b) || !a)        // violation
    {
    }
}
  1. 错位的Nil检查 MisplacedNilCheck:nil检查应该在其他运算之前
+ (void)compare:(A *)obj1 withOther:(A *)obj2
{
    if ([obj1 isEqualTo:obj2] && obj1)
    {
    }

    if (![obj1 isEqualTo:obj2] || obj1 == nil)
    {
    }
}
  1. 多余运算符 MultipleUnaryOperator:多余的运算符
void example()
{
    int b = -(+(!(~1)));
}
  1. 从Finally 返回 ReturnFromFinallyBlock:不建议从Finally返回
void example()
{
    @try
    {
        foo();
    }
    @catch(id ex)
    {
        bar();
    }
    @finally
    {
        return;         // this can discard exceptions.
    }
}
  1. 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

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

目前还没有任何评论,快来抢沙发吧!

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

2583 文章
29 评论
84935 人气
更多

推荐作者

清风夜微凉

文章 1 评论 0

为你鎻心

文章 2 评论 0

xxhui

文章 0 评论 0

1PKOH46yx8j0x

文章 0 评论 0

Arthur

文章 0 评论 0