对OCLint浅浅的理解

什么是 OCLint?

OCLint是针对于 C,C++,Objective-C 代码的静态分析工具,目的是提高软件质量并且减少代码中存在的潜在问题,OCLint 旨于分析以下潜在问题:

  • 可能出现的 bug:if/else/try/catch 等条件语句空的声明
  • 未使用的代码: 未使用的局部变量以及参数
  • 复杂的代码逻辑:高循环复杂度、NP 复杂度(懵)、 高 NCSS(懵)
  • 冗余代码:冗余的条件表达式以及无效的括号
  • 代码嗅觉:方法代码行过长或者参数过多
  • 不好的代码习惯:颠倒的逻辑和参数的错误分配

OCLint安装

有多种方式安装,选择homebrew安装更为方便
brew tap oclint/formulae (oclint所依赖的三方库)
brew install oclint

XCPretty安装

gem install xcpretty

OCLint 工具的组成

  • oclint
  • oclint-json-compilation-database
  • xcpretty

1.oclint 是 OCLint 工具集最主要的指令,主要作用是规则加载、编译分析选项以及生成分析报告

  1. oclint-json-compilation-database 的作用是在 JSON Compilation Database format 类型的编译文件 compile_commands.json 中提取必要的信息。
  2. xcpretty 用于将 xcodebuild 生成的 log 文件 xcodebuild.log 转换为 JSON Compilation Database format 类型的compile_commands.json

可以使用时序图来概括我们使用这几个指令的场景:


image.png

oclint一些简单的指令

USAGE: oclint [options] <source0> [... <sourceN>]

OPTIONS:

Generic Options:

 -help                         - Display available options (-help-hidden for more)
 -help-list                    - Display list of available options (-help-list-hidden for more)
 -version                      - Display the version of this program

OCLint options:

 -R=<directory>                - Add directory to rule loading path
 -allow-duplicated-violations  - Allow duplicated violations in the OCLint report
 -disable-rule=<rule name>     - Disable rules
 -enable-clang-static-analyzer - Enable Clang Static Analyzer, and integrate results into OCLint report
 -enable-global-analysis       - Compile every source, and analyze across global contexts (depends on number of source files, could results in high memory load)
 -extra-arg=<string>           - Additional argument to append to the compiler command line
 -extra-arg-before=<string>    - Additional argument to prepend to the compiler command line
 -list-enabled-rules           - List enabled rules
 -max-priority-1=<threshold>   - The max allowed number of priority 1 violations
 -max-priority-2=<threshold>   - The max allowed number of priority 2 violations
 -max-priority-3=<threshold>   - The max allowed number of priority 3 violations
 -no-analytics                 - Disable the anonymous analytics
 -o=<path>                     - Write output to <path>
 -p=<string>                   - Build path
 -rc=<parameter>=<value>       - Override the default behavior of rules
 -report-type=<name>           - Change output report type
 -rule=<rule name>             - Explicitly pick rules

-p <build-path> is used to read a compile command database.

   For example, it can be a CMake build directory in which a file named
   compile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
   CMake option to get this output). When no build path is specified,
   a search for compile_commands.json will be attempted through all
   parent paths of the first input file . See:
   http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an
   example of setting up Clang Tooling on a source tree.

<source0> ... specify the paths of source files. These paths are
   looked up in the compile command database. If the path of a file is
   absolute, it needs to point into CMake's source tree. If the path is
   relative, the current working directory needs to be in the CMake
   source tree and the file must be in a subdirectory of the current
   working directory. "./" prefixes in the relative files will be
   automatically removed, but the rest of a relative path must be a
   suffix of a path in the compile command database.

For more information, please visit http://oclint.org

oclint-json-compilation-database一些简单的指令

usage: oclint-json-compilation-database [-h] [-v] [-debug] [-I INCLUDES]
                                        [-e EXCLUDES] [-p build-path]
                                        [oclint_args [oclint_args ...]]

OCLint for JSON Compilation Database (compile_commands.json)

positional arguments:
  oclint_args           arguments that are passed to OCLint invocation

optional arguments:
  -h, --help            show this help message and exit
  -v                    show invocation command with arguments
  -debug, --debug       invoke OCLint in debug mode
  -i INCLUDES, -include INCLUDES, --include INCLUDES
                        extract files matching pattern
  -e EXCLUDES, -exclude EXCLUDES, --exclude EXCLUDES
                        remove files matching pattern
  -p build-path         specify the directory containing compile_commands.json

使用

1.新建工程,添加Aggregate


image.png

2.点击next输入ProductName


image.png

3.创建运行的脚本


image.png

4.配置运行脚本路径


image.png

5.脚本

export LANG="zh_CN.UTF-8"
export LC_COLLATE="zh_CN.UTF-8"
export LC_CTYPE="zh_CN.UTF-8"
export LC_MESSAGES="zh_CN.UTF-8"
export LC_MONETARY="zh_CN.UTF-8"
export LC_NUMERIC="zh_CN.UTF-8"
export LC_TIME="zh_CN.UTF-8"
export LC_ALL=

function checkDepend () {
    command -v xcpretty >/dev/null 2>&1 || { 
        echo >&2 "I require xcpretty but it's not installed.  Install:gem install xcpretty"; 
        exit
        }
    command -v oclint-json-compilation-database >/dev/null 2>&1 || { 
        echo >&2 "I require oclint-json-compilation-database but it's not installed.  Install:brew install oclint"; 
        exit
        }
}

function oclintForProject () {

    # 检测依赖
    checkDepend

    projectName=$1
    scheme=$2
    reportType=$3

    REPORT_PMD="pmd"
    REPORT_XCODE="Xcode"
    
    myworkspace=${projectName}
    myscheme=${scheme} 
    echo "myworkspace是:${myworkspace}"
    echo "myscheme是:${myscheme}"
    echo "reportType为:${reportType}"

    # 清除上次编译数据
    if [ -d ./build/derivedData ]; then
        echo '-----清除上次编译数据derivedData-----'
        rm -rf ./build/derivedData
    fi

    # xcodebuild -workspace $myworkspace -scheme $myscheme clean
    xcodebuild clean

    echo '-----开始编译-----'

    # 生成编译数据
    # 使用 xcpretty 直接将编译结果输出为 compile_commands.json
    xcodebuild -workspace ${myworkspace} -scheme ${myscheme} -sdk iphonesimulator -derivedDataPath ./build/derivedData -configuration Debug COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json
    

    if [ -f ./compile_commands.json ]
        then
        echo '-----编译数据生成完毕-----'
    else
        echo "-----生成编译数据失败-----"
        return -1
    fi

    echo '-----分析中-----'

    # 自定义排除警告的目录,将目录字符串加到数组里面
    # 转化为:-e Debug.m -e Port.m -e Test
    exclude_files=("Pods" "./ABCTime/ABCtimeFoundation/ATBase/SDK/")

    exclude=""
    for i in ${exclude_files[@]}; do
        exclude=${exclude}"-e "${i}" "
    done
    echo "排除目录:${exclude}"

#    # 分析reportType
#    if [[ ${reportType} =~ ${REPORT_PMD} ]]
#        then
#        nowReportType="-report-type pmd -o pmd.xml"
#    else
#        nowReportType="-report-type Xcode"
#    fi
    # 自定义report 如:
     nowReportType="-report-type html -o ./oclint/ABC_oclint_result.html"

    # 生成报表
    oclint-json-compilation-database ${exclude} -- \
    ${nowReportType} \
    -rc LONG_LINE=200 \
    # --命名
    # 变量名字最长字节
    -rc=LONG_VARIABLE_NAME=20 \
    # 变量名字最短字节
    -disable-rule ShortVariableName \
    # --size # 圈复杂度 #
    -re=CYCLOMATIC_COMPLEXITY=10 \
    # 每个类最行数 #
    -rc=LONG_CLASS=700 \
    # 每行字节数量 #
    -rc=LONG_LINE=200 \
    # 每个方法行数 #
    -rc=LONG_METHOD=80 \
    # 忽略注释后括号后的有效代码行数 #
    -rc=NCSS_METHOD=40 \
    # 嵌套深度 #
    -rc=NESTED_BLOCK_DEPTH=5 \
    # 字段数量 #
    -rc=TOO_MANY_FIELDS=20 \
    # 方法数量 #
    -rc=TOO_MANY_METHODS=50 \
    # 方法参数 #
    -rc=TOO_MANY_PARAMETERS=6
    -disable-rule ShortVariableName \
    -disable-rule ObjCAssignIvarOutsideAccessors \
    -disable-rule AssignIvarOutsideAccessors \
    -max-priority-1=100000 \
    -max-priority-2=100000 \
    -max-priority-3=100000

    rm compile_commands.json
    if [[ ${reportType} =~ ${REPORT_PMD} ]] && [ ! -f ./pmd.xml ]
    then
        echo "-----分析失败-----"
        return -1
    else
        echo '-----分析完毕-----'
        return 0
    fi
}

# workspace的名字
myworkspace="test.xcworkspace"
# scheme的名字
myscheme="test"
# 输出方式 Xcode/pmd
reportType="html"

oclintForProject ${myworkspace} ${myscheme} ${reportType}

遇到的error

  1. 权限不够


    权限错误.jpg

解决办法:设置chmod -R 777 $SRCROOT/oclint,-R 是以递归的方式遍历oclint下的所有文件夹下的文件有读写权限

OCLint自己的规则

基于OClint官方文档进行翻译

Basic

1.Bitwise operator in conditional
说明:对于按位与或者按位或的情况,OClint认为这不便于理解。

2.Broken Nil Check
说明:nil检查,在某些情况会返回相反的结果

3.Broken Null Check
说明:NULL检查会导致程序crash

4.Broken Oddness Check
说明:X%2==1对负数不起作用,需要使用X&1 ==1或者X%2!=0来代替

5.collapsible if statements
说明:判断两个相连的if是否能够合并

6.Constant Conditional Operator
说明:条件是否永远为true或者永远false?

7. constant if expression
 说明:if判断中的值是恒定的,比如if(true),其中的值永远为真。

8.dead code
说明:表示标示位置有永远不会被执行的代码。

9.Double Negative
说明:表示不需要使用双重否定,形如!!1,~~1等,因为这并没有用处。

10.ForLoop Should Be WhileLoop
说明:表示此处不应该用for循环而应该使用while循环。

11.Goto Statement
说明:提示谨慎使用Goto语句。

12.Jumbled Incrementer
说明:混乱的增量,多数出现在嵌套for循环中,如果不是因为笔误写错了,那么这种混乱的写法容易造成代码阅读的问题。

13.Misplaced Null Check
说明:Null Check被放错地方,在OC中向一个空指针发送消息并不会发生什么,但是在C或者C++中可能会引起crash,同时可能会造成代码阅读的困惑。

14.Multiple Unary Operator
说明:多重一元操作很难被理解。形如:int b = -(+(!(~1)));)

15.Return From Finally Block
 说明:在最后一个block中return的做法是不推荐的。

16.Throw Exception From Finally Block
说明:不要在最后一个BLOCK中抛出异常,因为这样的情况可能会掩盖其他异常或者代码缺陷。

Cocoa(OC)

1.Must Override Hash With IsEqual
说明:如果重写isEqual,一定也要重写hash,原名为ObjCVerifyIsEqualHash。

2.Must Call Super
说明:提示需要增加call super的情况,例如layoutSubviews方法,需要添加[super layoutSubviews].

3.Verify Prohibited Call
说明:当一个方法声明时含有attribute((annotate("oclint:enforce[prohibited call]"))),它的所有调用都是被禁止的。

4.Verify Protected Method
说明:当一个方法声明含有attribute((annotate("oclint:enforce[protected method]")))时,只有它和它的子类可以调用,因为OC中没有Protected,可以用这种方式达到Protected效果。

5.Subclass Must Implement
说明:子类必须实现,哪怕是一个抽象方法。

Convention

1.Avoid Branching Statement As Last InLoop
说明:表示不要在for循环或者其他循环语句的末尾使用break.

2.Base Class Destructor Should Be Virtual Or Protected
说明:基类的析构函数要么是公共的虚函数,要么是保护性的非虚函数。

3.covered switch statements dont need default
说明:因为已经覆盖了所有的switch分支,所以不需要写default break部分。

4.Default Label Not Last In Switch Statement
说明:DeFault没有放在Switch的最后一行。

5.Destructor Of VirtualClass
说明:本规则强制要求虚类的析构函数必须也是虚函数。

6.Inverted Logic
说明:反转逻辑让人难以理解。一般是指用(!a)而非(a)来判断让人难以理解。

7.Missing Break In Switch Statement
说明:在Switch中如果缺少break很容易引入bug.

8.Non Case Label In Switch Statement
说明:不要把label放到switch中。

9.ivar assignment outside accessors or init
说明:意思是某些成员的初始化不在getter/setter或者init中,而在其他位置。

10.parameter reassignment
说明:通常认为是赋值问题,具体问题需要具体判断,有的时候是因为对传来的参数进行了重新赋值,大多数时候不能修改。

11.use early exits and continue
说明:意思是,有其他出口时,先执行其他出口,再继续代码。

12.Switch Statements Should Have Default
说明:如果并不是每一个分支都有一条路径,那么switch重必须含有default.

13.Too Few Branches In Switch Statement
说明:Switch中分支很少,可以用if代替。

Design

1.Avoid Default Arguments On Virtual Methods
说明:避免给虚函数设置默认参数,给虚函数设置默认参数会破坏多样性和引起不必要的层次结构发杂性。

2.Avoid Private Static Members
说明:避免使用私有静态成员,静态成员很容易破换封装性。

Empty

1.Empty Catch Statement
说明:主要发生在一个异常exception被catch到了,但是却没有对此做任何操作。

2.Empty Do While Statement
说明:在do-while循环中没有做任何事情。

3.Empty Else Block
说明:在if中有操作,else中却是空的,此时else可以被安全移除。

4.Empty Finally Statemen
说明:在@finally中没有进行任何操作。

5.Empty For Statement
说明:for循环为空。

6.Empty If Statement
说明:if中代码为空。

7.Empty Switch Statement
说明:Switch中代码为空。

8.Empty Try Statement
说明:try中代码为空。

9.Empty While Statement
说明:While循环中没有代码。

Migration

1.Replace With Boxed Expression
说明:用来说明umberWithInt和stringWithUTF8String:getenv的简写形式。

2.Replace With Container Literal
说明:用来说明arrayWithObjects和dictionaryWithObjects的简写形式。

3.replace with number literal
说明:用来说明numberWithInt和numberWithBOOL的简写形式。

4.replace with object subscripting
说明:用来说明objectAtIndex和objectForKey的简写形式。

Naming

1.Long Variable Name
说明:命名太长。

2.Short Variable Name
说明:命名过短。

Redundant

1.Redundant Conditional Operator
说明:冗余的条件运算符,可以一步到位的判断,不需要进行多余比较。

2.Redundant If Statement
说明:if判断是多余的。

3.Redundant local variable
说明:return中的参数可以使用一个表达式来表示。

4.RedundantNilCheck
说明:在C或者C++中适用的判空检查在OC中是多余的。因为在OC中向空对象发送消息会返回false值。

5.UnnecessaryElseStatement
说明:如果if中已经带有return,则不需要写else语句。

6.Unnecessary Null Check For Dealloc
说明:在Dealloc中不需要判空,就能Delete元素。

7.useless parentheses
说明:用来表示有些括号是不必要的。

Size

1.High Cyclomatic Complexity
说明:圈复杂度过高。V(G)=e-n+2。其中,e表示控制流图中边的数量,n表示控制流图中节点的数量,或者V(G)=区域数=判定节点数+1。

2.Long Class
说明:类行数太多。

3.Long Line
说明:一行字数过多。

4.Long Method
 说明:在一个方法中试图完成很多事情,在一个方法中应该完成一个功能并把它做好。

5.High Ncss Method
说明:其实是指某个代码块中代码行数过多,查看代码块中代码是否能拆分,公共功能能否提供一个公共借口。

6.Deep Nested Block
说明:层次太深,多层嵌套。

7.High NPath Complexity
说明:NPath复杂度是一个方法中各种可能的执行路径总和,一般把200作为考虑降低复杂度的临界点,这里提示NPath复杂度过高。

8.Too Many Fields
说明:一个类中有太多的域表示它的东西太多了,需要进行适当的抽象。

9.Too Many Methods
说明:一个类中方法过多。

10.Too Many Parameters
说明:一个方法中参数过多。

Unused

1.Unused Local Variable
说明:没有被使用的局部变量。

2.Unused Method Parameter
说明:没有被使用的方法参数,请谨慎修改

官方文档地址

推荐阅读更多精彩内容