文章目录
- 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 禁用的规则列表
这些规则被禁用,是为了避免在代码分析过程中被检查到:
- LongLine – 禁用对代码行长度的检查。
- LongMethod – 禁用对方法长度的检查。
- HighNPathComplexity – 禁用对N路径复杂度的检查。
- HighCyclomaticComplexity – 禁用对圈复杂度的检查。
- DeepNestedBlock – 禁用对深层嵌套块的检查。
- HighNcssMethod – 禁用对方法的NCSS(非注释源代码语句)数量的检查。
- LongParameterList – 禁用对长参数列表的检查。
3.2 需要启用的规则
这些规则被启用,是为了在代码分析过程中进行检查:
代码风格
- RedundantVoidArgument – 检查冗余的void参数。
- UseBoolLiteral – 强制使用布尔字面量(true/false)。
- UseEqualsDefault – 使用=default指定默认构造函数。
- UseNullptr – 使用nullptr代替NULL。
- MissingOverride – 检查缺失的override关键字。
- ExplicitConstructor – 强制构造函数使用explicit关键字。
- CppStyleCasts – 使用C++风格的类型转换。
- BracesAroundStatements – 强制在语句周围使用大括号。
- SwitchStatementsShouldHaveDefault – 检查switch语句中是否有default分支。
- UseContainerLiteral – 使用容器字面量。
- UseEarlyExit – 优先使用提前返回。
代码复杂性
- NestedBlockDepth – 检查嵌套块的深度。
- LongVariableName – 检查变量名称是否过长。
- ShortVariableName – 检查变量名称是否过短。
- TooManyFields – 检查类中字段的数量是否过多。
- TooManyMethods – 检查类中方法的数量是否过多。
- TooManyParameters – 检查方法的参数数量是否过多。
命名规范
- ClassNamingConvention – 检查类的命名规范。
- StructNamingConvention – 检查结构体的命名规范。
- TypedefNamingConvention – 检查typedef的命名规范。
- EnumNamingConvention – 检查枚举的命名规范。
性能
- NonConstParameter – 检查非const参数。
- UndelegatedConstructor – 检查未委托的构造函数。
- AvoidPrivateStaticMembers – 避免私有静态成员。
安全性
- CertDcl21Cpp – CERT C++编程标准的规则。
- MacroParentheses – 检查宏定义中的括号。
- MacroRepeatedSideEffects – 检查宏定义中重复的副作用。
- ForwardDeclarationNamespace – 检查命名空间中的前向声明。
- BoolPointerImplicitConversion – 检查布尔指针的隐式转换。
- MisplacedWideningCast – 检查位置错误的扩宽类型转换。
- NarrowingConversion – 检查缩小类型转换。
- ReinterpretCast – 检查reinterpret_cast。
- UnconventionalAssignOperator – 检查非常规的赋值操作符。
其他
- DeadCode – 检查死代码。
- DeprecatedObjCImplementedProtocols – 检查废弃的ObjC实现的协议。
- DuplicateMethodMatch – 检查重复的方法匹配。
- GotoStatement – 检查goto语句。
- InvertedLogic – 检查反向逻辑。
- MagicNumber – 检查魔术数字。
- MissingBreakInSwitchStatement – 检查switch语句中缺失的break。
- NilAssignedToNonPointer – 检查将nil赋值给非指针。
- ParameterReassignment – 检查参数重新赋值。
- RedundantConditionalOperator – 检查冗余的条件操作符。
- RedundantIfStatement – 检查冗余的if语句。
- RedundantNilCheck – 检查冗余的nil检查。
- ReturnFromFinallyBlock – 检查finally块中的返回。
- UnreachableCode – 检查无法到达的代码。
- UnusedMethodParameter – 检查未使用的方法参数。
- UnusedLocalVariable – 检查未使用的局部变量。
- 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