首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Cppcheck exceptDeallocThrow 检查器解析:检测 delete 与异常抛出之间的悬垂指针(use-after-free)
📅 2026/10/4 10:06:32
✍️ 爱科研究院
👁 阅读 3,247
开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载本文深入解析 Cppcheck 中的exceptDeallocThrow检查器warning 级、仅适用于 C它以delete 释放指针 → 抛异常 → 指针重新赋值这一特定代码顺序为检测目标在全局/静态指针被释放后、异常传播期间仍保留旧地址这一场景中提前暴露极易被忽略的 use-after-free 风险。读完本文你将掌握该检查器的触发条件与修复范式、其源码级判定算法含 inconclusive 模式的差异以及如何在命令行中启用它并读懂输出。检查器速览exceptDeallocThrow的元信息在 man/checkers/exceptDeallocThrow.md 中有明确定义属性值消息MessageException thrown in invalid state, p points at deallocated memory.类别CategoryUndefined Behaviour未定义行为严重级别SeverityWarning适用语言C only报告位置引发问题的throw语句所在 tokenCWE398Indicator of Poor Code Quality见 lib/checkexceptionsafety.cpp底层函数CheckExceptionSafetyImpl::deallocThrow()见 lib/checkexceptionsafety.cpp该检查器在 lib/checkers.cpp 中以{CheckExceptionSafety::deallocThrow,warning}注册属于CheckExceptionSafety异常安全检查族与exceptThrowInDestructor析构函数抛异常、exceptRethrowCopy抛出异常副本等检查器并列。问题本质delete 与重新赋值之间的异常窗口文档对问题的定义非常精确A global/static pointer isdeleted and an exception can then be thrown before the pointer is given a new value, leaving a dangling pointer for any surviving code to use.即一个全局或静态指针先被delete释放随后在它被赋予新值之前抛出了异常。此时指针仍保留着已被释放内存的地址成为悬垂指针dangling pointer。此后任何触碰该指针的代码——无论是某个异常处理器还是在异常被捕获后继续执行的后续代码——都会对它解引用造成典型的 use-after-free。这类缺陷的隐蔽性在于从代码字面看作者确实做了清理delete p;后跟p nullptr;只是清理动作的顺序不安全——把可能抛异常的操作放在了释放与重置之间。静态分析的价值就在这里它不依赖运行时是否真的抛了异常而是基于控制流证明存在一条执行路径使得异常在指针失效期间被抛出。触发场景与完整示例文档给出的修复前示例Before正是这一模式的典型形态static int* p nullptr; void f(bool someCondition) { delete p; if (someCondition) throw 1; // - p is left dangling if this throws p nullptr; }逐行剖析这段代码的问题第 3 行delete p;指针p指向的内存被释放p成为悬垂指针第 4~5 行若someCondition为真则抛出异常异常展开stack unwinding会把控制权转移到外层异常处理器而此刻p仍是旧地址第 6 行p nullptr;本意是重置指针但它排在throw之后——只要异常真正抛出这行代码根本不会执行重置动作被跳过。结果就是异常处理器或 catch 块后续代码中任何对p的读写都发生在已释放的内存上属于未定义行为。修复方法把重置动作前移文档给出的修复后示例After只需调整一行代码的位置把p nullptr;从throw之后移到throw之前static int* p nullptr; void f(bool someCondition) { delete p; p nullptr; if (someCondition) throw 1; }修复要点在释放内存之后、任何可能抛出异常的操作之前立即将指针重置为nullptr。这样无论someCondition为何值、异常是否抛出p都不会保持悬垂状态——异常处理器看到的永远是一个值为nullptr的合法指针解引用空指针虽仍有风险但已不是 UAF且不会与已释放内存的地址混淆。文档选择nullptr作为重置值实践中还可以考虑更彻底的方案如 RAII 智能指针替代裸指针但核心原则不变不要让可能抛异常的控制流跨越释放与重置之间的窗口。源码级实现deallocThrow() 的判定算法该检查器的实现位于 lib/checkexceptionsafety.cpp 的CheckExceptionSafetyImpl::deallocThrow()它通过 Token 流扫描按如下步骤完成检测步骤 1启用条件判断第 93 行if (!mSettings.severity.isEnabled(Severity::warning) !mSettings.isPremiumEnabled(exceptDeallocThrow)) return;即warning 级别未启用、且未通过规则集如--cert-cpp显式启用该检查器时直接返回。步骤 2遍历所有函数作用域第 103 行。通过SymbolDatabase::functionScopes逐个函数体扫描对每个 token 查找delete关键字第 106~107 行。步骤 3识别delete[]第 111~112 行。若delete后紧跟[ ]跳过两个 token以同时覆盖delete p;与delete[] p;两种形态。随后用Token::Match(tok, %var% ;)限定为释放单个指针变量并以分号结尾的语句第 115 行。步骤 4限定变量类型第 118~121 行。只有var-isGlobal() || var-isStatic()的全局变量或静态变量含函数内static变量才会继续分析局部变量不在检查范围内。步骤 5在释放点之后向前扫描第 128~149 行直到当前函数体结束对每个 token 分三种情况处理遇到的情况处理逻辑语义throwinconclusive 模式下立即报告否则记录该 token 为throwToken存在释放之后抛出异常的路径p ...对同一变量的赋值若此前已记录throwToken则报告该 throw然后停止扫描赋值说明函数在异常之后还会恢复执行并重置指针这正是throw 打断清理流程的危险证据指针被作为参数传给函数(,后跟或变量名再跟,)直接停止扫描bail out假定被调函数会重新赋值指针不再继续分析步骤 6报告第 154~158 行。命中时调用deallocThrowError()在throwtoken 位置报告(warning) Exception thrown in invalid state, varname points at deallocated memory. [exceptDeallocThrow]消息中的变量名取自delete后跟随的变量标识符varname由代码tok-str()动态代入。inconclusive 模式的判定差异deallocThrow()第 98 行读取mSettings.certainty.isEnabled(Certainty::inconclusive)即命令行--inconclusive开关这直接改变判定激进程度非 inconclusive 模式默认仅当throw之后、函数结束前存在对同一指针的赋值时才报告。因为赋值意味着作者本打算在异常之后把指针重置——如果异常抛出重置被跳过任何存活代码都会看到死指针这是强证据。若函数在 throw 处直接结束、无后续赋值则默认以异常退出是安全的不报告。inconclusive 模式--inconclusive只要delete之后出现throw就立即报告不再等待赋值证据宁可误报也不放过潜在 UAF。这一差异被测试用例精确锁定见下节也提醒使用者--inconclusive会显著提高该检查器的召回率但也会带来更多需要人工核实的报告。测试用例验证该检查器的行为由 test/testexceptionsafety.cpp 中的三个测试用例严格约束deallocThrow1第 130~148 行——命中场景函数内static int* p与文件级全局指针int * p;均在delete p;与p 0;之间插入条件throw断言输出为[test.cpp:5:9]: (warning) Exception thrown in invalid state, p points at deallocated memory. [exceptDeallocThrow]注意报告位置是throw语句所在行列与文档中异常在指针失效期间被抛出的定位一致。deallocThrow2第 150~167 行——inconclusive 模式下的安全场景delete p;之后先有p new int;再 throw不报告delete p;之后调用reset(p);再 throw因指针传给函数假定会重新赋值而 bail out不报告。deallocThrow3第 169~183 行——同一段代码delete p; throw 1;后函数结束在非 inconclusive 模式下无输出在--inconclusive下输出[test.cpp:4:5]: (warning) ...精确印证了上一节描述的两种模式差异。这些测试同时揭示了本检查器的已知边界局部指针不查、delete后指针被传参即停止分析、非 inconclusive 下无赋值的裸 throw 默认放行。如何在项目中启用与解读输出命令行启用方式C 文件# 按 warning 级别启用该检查器注册为 warning cppcheck --enablewarning file.cpp # 更激进配合 inconclusive 模式 cppcheck --enablewarning --inconclusive file.cpp # 按规则集启用CERT C 模式映射规则 DCL57 cppcheck --cert-cpp file.cpp最后一种方式的依据exceptDeallocThrow出现在 lib/settings.cpp 的certCppCheckers集合中isPremiumEnabled()lib/settings.cpp会对--cert-cpp参数做匹配同时 lib/checkersidmapping.cpp 中将其映射到 CERT C 规则DCL57与exceptThrowInDestructor共用。因此在 CERT C 合规场景下即使未显式--enablewarning该检查器也会被启用。命中后的输出格式以测试断言为证[test.cpp:5:9]: (warning) Exception thrown in invalid state, p points at deallocated memory. [exceptDeallocThrow]方括号内为报告位置文件:行:列落在throw语句上(warning)为严重级别消息末尾的[exceptDeallocThrow]为检查器 ID可用于--suppressexceptDeallocThrow等抑制规则。与其他异常安全检查器的关系exceptDeallocThrow同属 lib/checkexceptionsafety.cpp 中的异常安全检查族使用时值得区分exceptThrowInDestructordestructors()第 43~79 行析构函数在noexcept语义下抛异常导致程序std::terminateexceptRethrowCopycheckRethrowCopy()第 166~205 行throw err;复制异常对象而非裸throw;重抛exceptDeallocThrowdeallocThrow()本文主题聚焦释放与重置之间抛异常造成的悬垂指针。三者共同覆盖了异常安全中抛出点相关的三类典型问题其中exceptDeallocThrow因直接关联内存释放风险等级最高UAF 属于未定义行为。总结exceptDeallocThrow是一个精准锁定delete 与指针重置之间异常窗口的 C 专用 warning 级检查器它通过扫描全局/静态指针的delete语句、跟踪其后的throw与赋值控制流lib/checkexceptionsafety.cpp识别出异常传播期间指针保持悬垂状态的路径并以--inconclusive提供更激进的模式选择。修复方法极为简单——在delete之后立即重置指针——但检测价值在于它把一种看起来像正确清理、实则顺序不安全的隐蔽 UAF 提前到编译期之外的分析阶段暴露出来可作为异常安全审查与 CERT CDCL57合规检查的有效补充。赞分享开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载相关推荐Skia 悬垂指针检测器Dangling Pointer Detector实战指南基于 raw_ptr 与 PartitionAlloc 的 Use-After-Free 防护Skia 悬垂指针检测器Dangling Pointer Detector实战指南基于 raw_ptr 与 PartitionAlloc 的 Use Af图形学cppcheck 检查器深度解析copyCtorPointerCopying —— 复制构造函数浅拷贝指针导致的悬垂与 double-free 风险cppcheck 检查器深度解析copyCtorPointerCopying —— 复制构造函数浅拷贝指针导致的悬垂与 double free 风险 本文是开发工具静态分析代码质量质量保障cppcheck invalidLifetime 检查器深度解析如何静态捕获悬垂指针与失效 Lambda 捕获cppcheck invalidLifetime 检查器深度解析如何静态捕获悬垂指针与失效 Lambda 捕获 导读 本文围绕 cppcheck 的 inva开发工具静态分析代码质量质量保障上一篇2025新范式Flowgram.ai如何用AI重构工作流开发下一篇ORB_SLAM2_ROS相机标定教程获取精准内外参数的完整流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/4 10:06:32
Angular 结构指令(Structural Directives)实战指南:从 `*ngIf` / `*ngFor` 到自定义结构指令
2026/10/4 10:06:32
质谱数据采集模式全解析:DDA与DIA的选择策略与实战
2026/10/4 10:06:32
Cadence Virtuoso高效操作技巧:快捷键、脚本与自动化实战
2026/10/4 10:51:34
MRAM与PIC18LF45K22:工业级非易失存储实战解析
2026/10/4 10:51:34
用命题逻辑重构Flutter响应式状态管理:鸿蒙适配实战
2026/10/4 10:51:34
Flutter三棵树生命周期详解:从Widget、Element到RenderObject的创建、更新与销毁
2026/10/4 10:51:34
SpringCloud电商项目数据库导入与配置:从拆分到避坑全指南
2026/10/4 10:51:34
AI芯片为何都绕不开脉动阵列?原理、建模与部署避坑指南
2026/10/4 10:46:34
张家界慢游指南:金鞭溪畔听水声,峰林间找回旅行松弛感
2026/10/4 0:00:57
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/4 0:00:57
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/4 0:00:57
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/4 0:00:57
Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化
2026/10/4 0:00:57
多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系
2026/10/4 0:00:57
hindsight:面向LLM应用的事后可观测性工程实践
2026/10/4 2:41:08
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/3 12:41:10
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/3 15:20:14
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)