使用 OCLint进行静态代码分析:完整的配置与 CMake 集成实例

文章目录

    • 0. 概述
    • 1. 安装 OCLint
    • 2. oclint配置文件
    • 3. 脚本详解
      • 3.1 禁用的规则列表
      • 3.2 需要启用的规则
        • 代码风格
        • 代码复杂性
        • 命名规范
        • 性能
        • 安全性
        • 其他
    • 4. 检测执行
      • 4.1. 使用 CMake 生成 `compile_commands.json`
      • 4.2. 手动运行 Oclint检测
      • 4.3. 与 CMake 集成自动检测
        • 4.3.1 CMake 脚本详解
        • 3.2 脚本细节解析
    • 5. 执行结果

0. 概述

本文介绍如何将 OCLint 静态代码分析工具集成到 CMake 构建系统中。通过自定义 CMake 脚本,自动运行 OCLint 检查并生成报告,确保代码在编译前经过静态分析,及时发现和解决潜在问题,提升代码质量和安全性。

  • 关于Clang-Tidy的使用请参考: 使用 Clang-Tidy 进行静态代码分析:完整的配置与 CMake 集成实例
  • 关于Cppcheck 的使用请参考: 使用 Cppcheck 进行静态代码分析:完整的 shell 脚本与 CMake 集成实例

1. 安装 OCLint

OCLint 是一个静态代码分析工具,通过词法分析和语法树生成来解析 C、C++ 和 Objective-C 代码。它应用预定义规则集,进行语义分析和静态检查,检测代码风格、复杂性、潜在错误、性能和安全问题。

下载链接:OCLint v0.13.1

解压并设置环境变量后即可运行:

tar -xzvf oclint-0.13.1-x86_64-linux-4.4.0-112-generic.tar.gz
export PATH=$PATH:/path/to/oclint/bin

2. oclint配置文件

---
# 禁用的规则列表
disable-rules:
  - LongLine
  - LongMethod
  - HighNPathComplexity
  - HighCyclomaticComplexity
  - DeepNestedBlock
  - HighNcssMethod
  - LongParameterList

# 需要启用的规则
rules:
  - RedundantVoidArgument
  - UseBoolLiteral
  - UseEqualsDefault
  - UseNullptr
  - MissingOverride
  - ExplicitConstructor
  - CppStyleCasts
  - BracesAroundStatements
  - ClassNamingConvention
  - StructNamingConvention
  - TypedefNamingConvention
  - EnumNamingConvention
  - NonConstParameter
  - CertDcl21Cpp
  - UndelegatedConstructor
  - MacroParentheses
  - MacroRepeatedSideEffects
  - ForwardDeclarationNamespace
  - BoolPointerImplicitConversion
  - MisplacedWideningCast
  - NarrowingConversion
  - ReinterpretCast
  - UnconventionalAssignOperator
  - AvoidPrivateStaticMembers
  - DeadCode
  - DeprecatedObjCImplementedProtocols
  - DuplicateMethodMatch
  - GotoStatement
  - InvertedLogic
  - LongVariableName
  - MagicNumber
  - MissingBreakInSwitchStatement
  - NestedBlockDepth
  - NilAssignedToNonPointer
  - ParameterReassignment
  - PreferEarlyExit
  - RedundantConditionalOperator
  - RedundantIfStatement
  - RedundantNilCheck
  - ReturnFromFinallyBlock
  - ShortVariableName
  - SwitchStatementsShouldHaveDefault
  - TooManyFields
  - TooManyMethods
  - TooManyParameters
  - UnreachableCode
  - UnusedMethodParameter
  - UnusedLocalVariable
  - UseContainerLiteral
  - UseEarlyExit
  - UselessParentheses

3. 脚本详解

以下是对给定OCLint配置文件的解读:

3.1 禁用的规则列表

这些规则被禁用,是为了避免在代码分析过程中被检查到:

  1. LongLine - 禁用对代码行长度的检查。
  2. LongMethod - 禁用对方法长度的检查。
  3. HighNPathComplexity - 禁用对N路径复杂度的检查。
  4. HighCyclomaticComplexity - 禁用对圈复杂度的检查。
  5. DeepNestedBlock - 禁用对深层嵌套块的检查。
  6. HighNcssMethod - 禁用对方法的NCSS(非注释源代码语句)数量的检查。
  7. LongParameterList - 禁用对长参数列表的检查。

3.2 需要启用的规则

这些规则被启用,是为了在代码分析过程中进行检查:

代码风格
  1. RedundantVoidArgument - 检查冗余的void参数。
  2. UseBoolLiteral - 强制使用布尔字面量(true/false)。
  3. UseEqualsDefault - 使用=default指定默认构造函数。
  4. UseNullptr - 使用nullptr代替NULL。
  5. MissingOverride - 检查缺失的override关键字。
  6. ExplicitConstructor - 强制构造函数使用explicit关键字。
  7. CppStyleCasts - 使用C++风格的类型转换。
  8. BracesAroundStatements - 强制在语句周围使用大括号。
  9. SwitchStatementsShouldHaveDefault - 检查switch语句中是否有default分支。
  10. UseContainerLiteral - 使用容器字面量。
  11. UseEarlyExit - 优先使用提前返回。
代码复杂性
  1. NestedBlockDepth - 检查嵌套块的深度。
  2. LongVariableName - 检查变量名称是否过长。
  3. ShortVariableName - 检查变量名称是否过短。
  4. TooManyFields - 检查类中字段的数量是否过多。
  5. TooManyMethods - 检查类中方法的数量是否过多。
  6. TooManyParameters - 检查方法的参数数量是否过多。
命名规范
  1. ClassNamingConvention - 检查类的命名规范。
  2. StructNamingConvention - 检查结构体的命名规范。
  3. TypedefNamingConvention - 检查typedef的命名规范。
  4. EnumNamingConvention - 检查枚举的命名规范。
性能
  1. NonConstParameter - 检查非const参数。
  2. UndelegatedConstructor - 检查未委托的构造函数。
  3. AvoidPrivateStaticMembers - 避免私有静态成员。
安全性
  1. CertDcl21Cpp - CERT C++编程标准的规则。
  2. MacroParentheses - 检查宏定义中的括号。
  3. MacroRepeatedSideEffects - 检查宏定义中重复的副作用。
  4. ForwardDeclarationNamespace - 检查命名空间中的前向声明。
  5. BoolPointerImplicitConversion - 检查布尔指针的隐式转换。
  6. MisplacedWideningCast - 检查位置错误的扩宽类型转换。
  7. NarrowingConversion - 检查缩小类型转换。
  8. ReinterpretCast - 检查reinterpret_cast。
  9. UnconventionalAssignOperator - 检查非常规的赋值操作符。
其他
  1. DeadCode - 检查死代码。
  2. DeprecatedObjCImplementedProtocols - 检查废弃的ObjC实现的协议。
  3. DuplicateMethodMatch - 检查重复的方法匹配。
  4. GotoStatement - 检查goto语句。
  5. InvertedLogic - 检查反向逻辑。
  6. MagicNumber - 检查魔术数字。
  7. MissingBreakInSwitchStatement - 检查switch语句中缺失的break。
  8. NilAssignedToNonPointer - 检查将nil赋值给非指针。
  9. ParameterReassignment - 检查参数重新赋值。
  10. RedundantConditionalOperator - 检查冗余的条件操作符。
  11. RedundantIfStatement - 检查冗余的if语句。
  12. RedundantNilCheck - 检查冗余的nil检查。
  13. ReturnFromFinallyBlock - 检查finally块中的返回。
  14. UnreachableCode - 检查无法到达的代码。
  15. UnusedMethodParameter - 检查未使用的方法参数。
  16. UnusedLocalVariable - 检查未使用的局部变量。
  17. UselessParentheses - 检查无用的括号。

4. 检测执行

4.1. 使用 CMake 生成 compile_commands.json

为了使用 Cppcheck 的 --project 选项,你需要一个 compile_commands.json 文件。这个文件是一个编译数据库,包含项目中所有源文件的编译信息。
如果你的项目使用 CMake 构建,可以通过以下命令生成 compile_commands.json 文件:

cd /path/to/your/project
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .

运行上述命令后,会在项目的构建目录中生成一个 compile_commands.json 文件。

4.2. 手动运行 Oclint检测

  • .oclint配置文件放在代码工程根目录
  • 接着执行:oclint-json-compilation-database -p . -- -extra-arg=-std=c++14 -report-type text -o oclint_report.text

4.3. 与 CMake 集成自动检测

通过将 OCLint 与 CMake 集成,可以在编译阶段自动运行代码分析,早期发现潜在问题。本节将介绍如何在 CMake 构建系统中集成 OCLint,并解释相关脚本的工作原理。

4.3.1 CMake 脚本详解
cmake_minimum_required(VERSION 3.10)

# Project name
project(ExampleProject)

# Generate compile_commands.json to enable OCLint checks
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# example target
add_executable(example example.cpp)

# Find all .cpp files in the project source directory, excluding the build directory
file(GLOB_RECURSE ALL_CPP_FILES ${CMAKE_SOURCE_DIR}/*.cpp)
list(FILTER ALL_CPP_FILES EXCLUDE REGEX "${CMAKE_BINARY_DIR}/.*")

# OCLint custom targets for running and checking results
add_custom_target(run_oclint
    COMMAND oclint-json-compilation-database -p ${CMAKE_BINARY_DIR} -- -extra-arg=-std=c++14 -report-type text -o ${CMAKE_BINARY_DIR}/oclint_report.txt ${ALL_CPP_FILES}
    COMMAND /bin/bash -c "if ! grep -q 'FilesWithViolations=0' ${CMAKE_BINARY_DIR}/oclint_report.txt; then touch ${CMAKE_BINARY_DIR}/oclint_failed; fi"
    COMMENT "Running OCLint"
    VERBATIM)

add_custom_target(check_oclint
    COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/oclint_failed
    COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target run_oclint
    COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_BINARY_DIR}/oclint_report.txt
    COMMAND /bin/bash -c "if [ -f ${CMAKE_BINARY_DIR}/oclint_failed ]; then echo 'OCLint errors found.'; exit 1; else echo 'No OCLint issues found.'; fi"
    COMMENT "Checking OCLint results"
    VERBATIM)

# Ensure run_oclint and check_oclint are run before building any target
add_dependencies(example check_oclint)

# Function to add OCLint pre-build check
function(add_oclint_pre_build target_name)
    add_custom_command(TARGET ${target_name} PRE_BUILD
        COMMAND ${CMAKE_COMMAND} -E echo "Running OCLint pre-build check for ${target_name}"
        COMMAND /bin/bash -c "if [ -f ${CMAKE_BINARY_DIR}/oclint_failed ]; then echo 'Stopping build due to OCLint errors.'; exit 1; else echo 'No OCLint issues found. Continuing build.'; fi"
        COMMENT "Checking for OCLint issues before building ${target_name}"
        VERBATIM)
endfunction()

# Add OCLint pre-build check for targets
add_oclint_pre_build(example)
3.2 脚本细节解析
  • CMake 基本设置

    • set(CMAKE_EXPORT_COMPILE_COMMANDS ON): 生成 compile_commands.json 文件,提供编译数据库,方便 OCLint 使用。
  • 目标文件和源文件

    • file(GLOB_RECURSE ALL_CPP_FILES ${CMAKE_SOURCE_DIR}/*.cpp): 递归查找项目源目录下的所有 .cpp 文件,并将其存储在 ALL_CPP_FILES 变量中。这一步确保所有的源文件都包含在 OCLint 的检查范围内。
    • list(FILTER ALL_CPP_FILES EXCLUDE REGEX "${CMAKE_BINARY_DIR}/.*"): 排除构建目录中的文件,避免 OCLint 检查 CMake 自身生成的文件。
  • 定义 OCLint 自定义目标

    • add_custom_target(run_oclint ...):
      • COMMAND oclint-json-compilation-database -p ${CMAKE_BINARY_DIR} -- -extra-arg=-std=c++14 -report-type text -o ${CMAKE_BINARY_DIR}/oclint_report.txt ${ALL_CPP_FILES}:运行 OCLint,检查所有的 .cpp 文件,并将结果输出到 oclint_report.txt 文件中。
      • COMMAND /bin/bash -c "if ! grep -q 'FilesWithViolations=0' ${CMAKE_BINARY_DIR}/oclint_report.txt; then touch ${CMAKE_BINARY_DIR}/oclint_failed; fi":检查 oclint_report.txt 中是否包含 FilesWithViolations=0。如果不包含,则创建 oclint_failed 文件作为错误标志。
      • COMMENT "Running OCLint":提供执行过程中显示的注释。
      • VERBATIM:确保命令字符串的精确性,不对其进行解释或修改。
  • 检查 OCLint 结果并终止构建

    • add_custom_target(check_oclint ...)
      • COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/oclint_failed:删除 oclint_failed 文件,确保每次检查前都是干净的状态。
      • COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target run_oclint:构建 run_oclint 目标,实际执行 OCLint 检查。
      • COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_BINARY_DIR}/oclint_report.txt:显示 oclint_report.txt 的内容。
      • COMMAND /bin/bash -c "if [ -f ${CMAKE_BINARY_DIR}/oclint_failed ]; then echo 'OCLint errors found.'; exit 1; else echo 'No OCLint issues found.'; fi":检查是否存在 oclint_failed 文件。如果存在,则输出错误信息并终止构建;否则,继续构建过程。
      • COMMENT "Checking OCLint results":提供执行过程中显示的注释。
      • VERBATIM:确保命令字符串的精确性。
  • 确保依赖关系正确

    • add_dependencies(example check_oclint):定义 example 目标依赖于 check_oclint 目标。这确保在构建 example 之前,先运行 OCLint 检查。
  • 添加预构建检查

    • function(add_oclint_pre_build target_name):定义一个函数,用于为指定的目标添加 OCLint 预构建检查。
      • add_custom_command(TARGET ${target_name} PRE_BUILD ...)
        • COMMAND ${CMAKE_COMMAND} -E echo "Running OCLint pre-build check for ${target_name}":在构建前输出一条消息,表示正在进行 OCLint 预构建检查。
        • COMMAND /bin/bash -c "if [ -f ${CMAKE_BINARY_DIR}/oclint_failed ]; then echo 'Stopping build due to OCLint errors.'; exit 1; else echo 'No OCLint issues found. Continuing build.'; fi":运行一个 Bash 脚本,检查 oclint_failed 文件是否存在。如果存在,则终止构建并输出错误消息;如果不存在,则继续构建。
        • COMMENT "Checking for OCLint issues before building ${target_name}":提供执行过程中显示的注释。
        • VERBATIM:确保命令字符串的精确性。
  • 为目标添加预构建检查

    • add_oclint_pre_build(example):为 example 目标添加 OCLint 预构建检查,确保在每次构建之前进行代码分析。

这样配置可以确保在构建过程中,如果有任何 OCLint 检查失败,会在终端中直接显示相关的错误和警告信息,并且不会继续构建过程。

5. 执行结果

  • 有错误时的结果

    $ make example
    [ 33%] Checking OCLint results
    [100%] Running OCLint
    [100%] Built target run_oclint
    
    
    OCLint Report
    
    Summary: TotalFiles=2 FilesWithViolations=1 P1=0 P2=0 P3=1 
    
    /home/test/Desktop/oclint_example/example.cpp:47:3: unused local variable [unused|P3] The local variable 'unusedVariable' is unused.
    
    [OCLint (http://oclint.org) v0.13.1]
    OCLint errors found.
    CMakeFiles/check_oclint.dir/build.make:70: recipe for target 'CMakeFiles/check_oclint' failed
    make[3]: *** [CMakeFiles/check_oclint] Error 1
    CMakeFiles/Makefile2:136: recipe for target 'CMakeFiles/check_oclint.dir/all' failed
    make[2]: *** [CMakeFiles/check_oclint.dir/all] Error 2
    CMakeFiles/Makefile2:91: recipe for target 'CMakeFiles/example.dir/rule' failed
    make[1]: *** [CMakeFiles/example.dir/rule] Error 2
    Makefile:124: recipe for target 'example' failed
    make: *** [example] Error 2
    
  • 无错误时的结果

    $ make example
    [ 33%] Checking OCLint results
    [100%] Running OCLint
    [100%] Built target run_oclint
    
    
    OCLint Report
    
    Summary: TotalFiles=2 FilesWithViolations=0 P1=0 P2=0 P3=0 
    
    
    [OCLint (http://oclint.org) v0.13.1]
    No OCLint issues found.
    Built target check_oclint
    Built target example
    

你可能感兴趣的:(oclint,静态检测,cppcheck,cmake,代码质量)