- 静态分析
- 代码质量
- 开发工具
- 质量保障
【免费下载链接】TscanCode
A static code analyzer for C++, C#, Lua
TscanCode 是腾讯开源的静态代码分析工具,面向 C/C++、C# 和 Lua 三类语言,目标是帮助程序员在编码阶段尽早发现代码缺陷。本文基于项目根目录的 README.md 展开,完整覆盖其功能定位、编译方式与一行命令上手流程,并结合 trunk 源码进一步剖析命令参数的底层实现、README 中示例错误的检测原理以及cfg.xml规则配置体系,帮助读者既能开箱即用,也能读懂 TscanCode 的检测机制与可定制点。
一、项目定位与核心特性
TscanCode 的自我定位是一句直白的话:"A fast and accurate static analysis solution for C/C++, C#, Lua codes",即帮助开发者在缺陷刚出现时就把它找出来(help programmers to find out code defects at the very beginning)。README 中列出了四条核心特性:
| 特性 | 说明 |
|---|---|
| 多语言 | 同时支持C/C++、C#、Lua三种语言的代码分析 |
| 快速且准确 | 官方给出的性能指标为约 200K 行代码/分钟,准确率约 90% |
| 易用 | 不需要严格的编译环境,单条命令即可完成检查 |
| 可扩展 | 支持用户自定义 check 规则 |
从仓库的组织结构看,README 描述的语言分布是清晰的:
- C/C++ 分析引擎:完整开源,位于 trunk,包含
lib/(核心分析库)、cli/(命令行入口)、common/(路径与文件枚举等公共模块)、externals/tinyxml/(XML 输出依赖); - C# 与 Lua 版本:README 明确说明"codes under trunk are only for TscanCode CPP version, C# and Lua version are in the internal review process",即这两种语言的引擎源码未随仓库开放,但
release/目录下提供了各平台的可执行程序(如 TscSharp、tsclua),且 samples 下配有完整的三语言示例。
版本演进方面,README 记录了几个关键节点:
- v2.15.02(2022-04-28):修复 Lua 崩溃问题,开放更多 checker;
- v2.15.01(2022-01-19):支持 Lua 5.4,并带来一批 bug 修复与新特性;
- v2.14.24(2018-02-24):GUI 发布 Rule Package 便于规则定制,GUI 支持标记误报。
更完整的变更历史可查阅 CHANGELOG.md,例如其中还记录了 2.11.27 版本新增的lua_VarSpellWrongError(Lua 变量拼写错误检查)、CS_UnityMessgeSpellWrong(Unity 消息函数拼写检查,如把OnDestroy误拼成OnDestory)等细节。
二、编译构建:一条make命令的完整工程拆解
2.1 编译器要求
README 给出的编译前提是:
- 任意支持 C++11 的编译器均可编译;部分支持 C++11 的编译器也可能工作,最低参考线是 Visual Studio 2015 所具备的 C++11 特性;
- 若编译器不支持
nullptr,可以通过 lib/cxx11emu.h 头文件做模拟兼容; - 平台选项:Windows 用 Visual Studio 2015+,Linux 用 g++ 4.6+,macOS 用 clang++。
2.2 Visual Studio 构建
Windows 下使用解决方案文件 trunk/tscancode.sln:该文件默认配置为 Visual Studio 2015,但可以方便地切换到新旧版本的平台工具集;解决方案同时包含 x86 与 x64 两个平台目标,选择Release配置即可产出发布版本。
2.3 Linux/macOS:make构建细节
在 trunk 目录下执行:
make这条"零依赖"命令背后的构建规则定义在 trunk/Makefile 中,值得逐条看一下:
ifndef CXX CXX=g++ endif ifndef CXXFLAGS CXXFLAGS=-std=c++0x -O2 -include lib/cxx11emu.h -DNDEBUG -Wall -Wno-sign-compare -Wno-unknown-pragmas -Wno-reorder endif # Force including libpthread LIBS+=-lpthread几个关键点:
-include lib/cxx11emu.h:Makefile 默认把cxx11emu.h强制包含进每个编译单元,这正是 README 中"不支持 nullptr 时用它模拟"的落地方式;-lpthread强制链接:分析引擎内置多线程执行器(对应 cli/tscthreadexecutor.cpp),因此 Linux 构建必须链接 pthread;- 产物构成:最终目标
tscancode由四组对象文件链接而成——LIBOBJ(trunk/lib 下约 50 个分析核心源文件,如check*.o、tokenize.o、valueflow.o等)、CLIOBJ(trunk/cli 下的main.o、cmdlineparser.o、tscexecutor.o、tscthreadexecutor.o)、COMMONOBJ(trunk/common 下的路径与文件枚举模块)以及外置的externals/tinyxml/tinyxml2.o; - 支持通过
CXX、CXXFLAGS环境变量覆盖默认编译器与编译选项。
三、一分钟上手:从空指针示例到 XML 报告
README 用了一个经典空指针缺陷作为"Usage at a glance"示例。下面这段代码的问题在于:对p做判空只说明p可能为空,因此在if作用域之外解引用*p是不安全的:
// func.cpp void func(int* p) { if(p == NULL) { printf("p is null!"); } printf("p is %d", *p); }执行检查:
./tscancode --xml func.cpp 2>result.xml注意--xml会把结果以 XML 格式写到错误流(stderr),因此用2>result.xml重定向。报告内容如下:
<?xml version="1.0" encoding="UTF-8"?> <results> <error file="func.cpp" line="7" id="nullpointer" subid="dereferenceAfterCheck" severity="error" msg="Comparing [p] to null at line 3 implies [p] might be null. Dereferencing null pointer [p]." /> </results>报告字段含义:file/line定位到第 7 行的解引用;id为检查类别nullpointer;subid为具体缺陷子类dereferenceAfterCheck(先判空、后在判空作用域外解引用);severity为严重级别;msg是可读的诊断说明。
3.1 这个错误是谁报出来的?——源码级溯源
dereferenceAfterCheck这个 subid 的报错实现位于 trunk/lib/checktscnullpointer2.cpp 的nullPointerError函数中。该函数根据表达式取值状态(CExprValue)分派到不同 subid:
else if (ev.IsCondCheckNull()) { ss << "Comparing [" << sExprLoc.Expr.ExprStr << "] to null at line " << ... // 若表达式含 "(" 归类为 funcDereferenceAfterCheck // 若表达式含 "[" 归类为 arrayDereferenceAfterCheck // 否则(普通指针/变量)走下面分支: reportError(sExprLoc.Begin, Severity::error, ErrorType::NullPointer, "dereferenceAfterCheck", ss.str().c_str(), ...); }也就是说,README 示例中那句Comparing [p] to null at line 3 implies [p] might be null...的完整错误文本,正是由这段拼接逻辑生成。同一函数还区分了其他变体:invalidDereferenceIterator(迭代器失效解引用)、funcRetNull(函数返回值判空缺陷)、explicitNullDereference(显式空指针解引用)等。
3.2 subid 规则表:cfg.xml 中的规则开关与中文释义
每个 subid 的启用状态(value)、严重级别(severity)、规则中文名与说明集中在 trunk/cfg/cfg.xml 的nullpointer节点中,例如:
<id name="nullpointer" value="1"> <subid name="dereferenceAfterCheck" value="1" severity="Serious" rule_name="先判空后解引用" desc="指针先判空,然后在判空作用域外解引用……"/> <subid name="funcRetNull" value="0" severity="Warning" rule_name="函数返回值未判空解引用" desc="……"/> <subid name="dereferenceIfNull" value="1" severity="Critical" rule_name="指针为空解应用" desc="if条件判断指针等于NULL成立时对指针解引用,通常是判空条件写反了导致的"/> ... </id>value="1"表示该规则默认启用,value="0"表示默认关闭;severity分Critical/Serious/Warning/Information四级。同文件中还以同样的粒度管理bufoverrun(缓冲区越界)、memleak(内存泄漏)等类别,每类都带有中文rule_name与desc。发布包 release/linux/TscanCodeV2.14.2395.linux/cfg 下除cfg.xml外,还附带std.cfg、windows.cfg、posix.cfg、qt.cfg、gtk.cfg、sdl.cfg、avr.cfg、gnu.cfg、microsoft_sal.cfg等平台/标准库配置,供不同代码库按需加载(C# 侧对应 TscSharp 的 cfg_cs.ini 与 rule.xml,Lua 侧对应 TscLua/cfg 下的lua_cfg.xml、lua_std.cfg等)。
四、命令行参数全景:从帮助文本到解析实现
./tscancode --help(或-h)输出的帮助文本定义在 trunk/cli/cmdlineparser.cpp 的PrintHelp()中。README 只演示了--xml一个参数,下面把ParseFromArgs()(cmdlineparser.cpp)实际支持的参数完整整理如下,均以当前仓库源码为准:
4.1 基础与输出控制
| 参数 | 作用 |
|---|---|
-h, --help | 打印帮助后退出 |
-q, --quiet | 不打印进度报告 |
--xml | 将结果以 XML 格式写入 stderr(README 示例所用) |
--xml-version=<n> | 指定 XML 版本,仅支持 1 或 2,同时隐含启用--xml |
-v, --verbose | 输出更详细的配置信息 |
--errorlist | 打印所有可能的错误消息后退出(隐含启用 XML) |
--template=<fmt> | 自定义输出模板,支持{file}、{line}、{severity}、{message}等占位符;内置快捷值gcc({file}:{line}: {severity}: {message})、vs({file}({line}): ...)、edit |
-rp, --relative-paths[=base1;base2] | 输出相对路径;可用;分隔指定多个基准路径 |
--error-exitcode=<n> | 检查发现错误时以指定整数码退出,便于 CI 集成 |
--report-progress | 报告检查进度 |
--showtime=file\|summary\|top5 | 显示耗时统计 |
4.2 预处理与包含路径
| 参数 | 作用 |
|---|---|
-D<ID>/-D<ID=VAL> | 定义预处理宏;不带=时自动补=1。源码中有一处实用逻辑:只要使用了-D且未加--force/--max-configs,就会把--max-configs设为 1,即只检查给定宏配置的那一种#ifdef分支,避免配置爆炸 |
-U<ID> | 取消宏定义,显式隐藏某些#ifdef分支 |
-I <dir>/-I<dir> | 给出头文件搜索路径,可多次指定,先给出的路径优先 |
--includes-file=<file> | 从文件逐行读取包含路径 |
--include=<file> | 指定必须包含的文件 |
--config-exclude=<path>/--config-excludes-file= | 配置排除路径 |
4.3 抑制与过滤
| 参数 | 作用 |
|---|---|
--suppress=<id> | 按 id 抑制指定错误 |
--suppressions-list=<file> | 从文件读取抑制规则(一个参数只能带一个文件,多文件需多次传参) |
--inline-suppr | 启用源码内联抑制注释 |
--exitcode-suppressions=<file> | 指定不影响退出码的抑制规则 |
4.4 性能、平台与语言控制
| 参数 | 作用 |
|---|---|
-j <jobs>/-j<n> | 多线程检查(上限 10000);源码中有保护逻辑:unusedFunction检查与-j不兼容,会提示并禁用该检查 |
-l <n> | 按系统负载控制并发 |
-f, --force | 强制检查所有#ifdef配置(_maxConfigs = ~0U) |
--max-configs=<n> | 限制检查的最大#ifdef配置数,指定后会重置--force |
--std=c89\|c99\|c11\|c++03\|c++11\|posix | 指定语言标准,影响内置函数库的语义理解 |
--platform=win32A\|win32W\|win64\|unix32\|unix64\|native | 指定目标平台,影响平台相关的类型宽度分析 |
-x c\|c++/--language= | 强制指定待检语言 |
-i <path> | 忽略指定目录(如生成代码目录) |
--file-list=<file> | 从文件逐行读取待检文件列表(文件名为-时读 stdin) |
--enable=<id> | 帮助文本列出了all/warning/style/performance/portability/information/unusedFunction/missingInclude等 id;但源码解析处将其标注为"for compatibility only"直接跳过,因为ParseFromArgs末尾默认执行addEnabled("all")——当前版本默认启用全部检查 |
--library=<name> | 加载库定义(配合 cfg 目录下的平台/标准配置) |
--lua <filePath> | 指定 Lua 项目路径,启用 Lua 检查模式 |
--check-config/--check-library | 检查包含配置 / 检查库定义文件的正确性 |
--append=<file> | 向待检代码尾部追加用户自定义代码 |
--rule=<pattern>/--rule-file=<file> | 自定义规则(--rule-file读取含<rule>/<tokenlist>/<pattern>/<message>节点的 XML,解析逻辑见 cmdlineparser.cpp),对应 README 宣称的"可自定义 check"能力 |
--no-analyze/--no-check | 关闭分析/检查阶段(调试用) |
--debug、--debug-normal、--debug-warnings、--debug-fp、--dump*系列 | 各类调试输出开关 |
-rf, --recordfuncinfo/--cfg-merge/-e2o | 记录函数信息 / 合并两个 cfg.xml / 内部开关 |
用法示例(摘自帮助文本):
# 递归检查当前目录,进度打到屏幕,错误写入文件 tscancode . 2> err.txt # 递归检查项目且不打印进度 tscancode --quiet ../myproject/ # 检查单个文件并启用全部检查 tscancode --enable=all test.cpp # 检查 f.cpp 并从 inc1/、inc2/ 搜索头文件 tscancode -I inc1/ -I inc2/ f.cpp另外,参数以-开头但无法识别时会报unrecognized command line option并退出;命令行未给任何源文件/目录时报No C or C++ source files found。
五、示例集与目录结构:每个规则都能找到对应样本
README 指向三个示例目录,仓库中确实一一对应,且样本文件名与 subid 同名,可直接用于回归验证某条规则:
- C/C++ 样本:samples/cpp,共 80+ 个样本,如 dereferenceAfterCheck.cpp(README 同类场景:
if(obj != NULL)保护之外的obj->dosth2())、arrayindexoutofbounds.cpp、memleak.cpp、unsignedlessthanzero.cpp、recursivefunc.cpp、stlsortcheck.cpp等,覆盖空指针、越界、内存泄漏、逻辑缺陷、STL 误用等 cfg.xml 中的主要类别; - C# 样本:samples/csharp,如
cs_arrayindexoutofbound.cs、cs_dereferenceifnull.cs、cs_unreleasedlock.cs、cs_inherit/update系列(cs_inefficientupdate.cs、cs_foreachinupdate.cs等 Unity 相关检查); - Lua 样本:samples/lua,如
lua_Uninitvar.lua、lua_undeclaredvar.lua、lua_UseVarIfNil.lua、lua_keywordspellwrongerror.lua(对应 CHANGELOG 提到的拼写类检查)。
每个样本文件通常只含十几行最小缺陷代码,适合作为规则行为的最小复现样例。
六、发布包与平台适配
release 目录按平台组织,可直接下载使用预编译版本:
| 平台 | 内容 |
|---|---|
| release/linux | TscanCodeV2.14.2395.linux/(tscancode 可执行文件 + cfg/ 全套规则配置)、TscSharpV1.14.667.linux/(C# 分析器 + cfg_cs.ini + rule.xml)、TscLua/(Lua 分析器 + cfg),附 README |
| release/mac | tscancode、TscSharp、tsclua.mac与全套 cfg(含filter.ini、rule.xml) |
| release/windows/lua | tsclua.exe与核心库tsclua-core.dll、cfg 配置 |
发布包命名即版本信息(如 V2.14.2395、V1.14.667);而源码 trunk/lib/version.h 中当前版本宏为2.14.2397,README 徽标则标注 2.15.02,三者分别对应发布包、源码修订与对外发布的口径,实际使用请以目标二进制自带的--version输出为准。
七、适用边界与注意事项
- 开源范围:
trunk/仅包含 C/C++ 版本的分析引擎源码;C# 与 Lua 引擎源码按 README 说明仍在内部评审流程中未开放,但两类语言的发布二进制、配置文件与 samples 均已随仓库提供; - 性能与准确率:200K 行/分钟与约 90% 的准确率是 README 给出的官方指标,实际吞吐与误报率会随代码库复杂度、
#ifdef配置数量和-D/--force用法而变化; - 静态分析的固有限制:TscanCode 不要求完整编译环境(这正是其易用性的来源),但也因此对跨文件语义的理解依赖
cfg配置与内置库定义;对未定义行为的判定部分依赖统计推断(如 cfg.xml 中默认关闭的funcRetNullStatistic、OutOfBoundsStatistic等"基于统计推断"类规则); - 若需了解各版本具体改动,请查阅 CHANGELOG.md;若需要扩展自定义规则,可参考
--rule/--rule-file的 XML 格式与 cfg.xml 中的rule_name/desc组织方式。
- 静态分析
- 代码质量
- 开发工具
- 质量保障
【免费下载链接】TscanCode
A static code analyzer for C++, C#, Lua
相关推荐
Infer 静态分析器完全指南:Java/C/C++/Objective-C 跨文件深层缺陷检测的原理与工程实践
Infer 静态分析器完全指南:Java/C/C++/Objective C 跨文件深层缺陷检测的原理与工程实践 导读:本文以 Infer 官方文档 02 ab
静态分析代码质量开发工具代码零缺陷:C++静态分析工具精选指南
代码零缺陷:C++静态分析工具精选指南 你还在为调试C++内存泄漏焦头烂额?还在担心上线前遗漏安全漏洞?本文将带你掌握静态代码分析(Static Code An
文档教程LittleD编译时配置详解:如何定制嵌入式数据库功能以满足应用需求
LittleD编译时配置详解:如何定制嵌入式数据库功能以满足应用需求 LittleD是一款专为嵌入式设备和传感器节点设计的关系型数据库,通过灵活的编译时配置,开
数据库嵌入式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考