CodeQL C/C++ 库 0.12.5 版本解读:PreprocBlock 预处理器块视图与 ThrowingFunction 异常建模抽象类 静态分析SAST应用安全漏洞扫描代码质量【免费下载链接】codeqlCodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security项目地址https://gitcode.com/gh_mirrors/co/codeql点击查看免费下载CodeQL 的 C/C 查询库cpp/ql/lib在 0.12.5 版本中引入了两项面向安全查询开发的基础设施改进新增PreprocBlock.qll库将#if/#elif/#else等预处理分支组织成可导航的父子树结构新增ThrowingFunction抽象类为建模可能抛出异常的外部函数提供统一入口。本文以该版本发布说明为骨架结合 PreprocBlock.qll 与 Throwing.qll 的源码实现深入讲解这两个 API 的设计动机、核心成员与典型用法帮助读者在自己的 CodeQL 查询中直接复用这两项能力。0.12.5 版本概览本次发布对应的变更记录位于 cpp/ql/lib/change-notes/released/0.12.5.md属于 C 查询库QL library层面的更新包含两条新特性新增PreprocBlock.qll库把#if、#elif、#else等预处理分支指令抽象为代码块并建立可导航的父-子关系树根节点为整个文件新增ThrowingFunction抽象类用于建模外部函数可能抛出异常这一行为供数据流与污点分析在跨函数传播异常路径时使用。下文分别对两者展开。PreprocBlock.qll以树结构视图预处理分支设计动机在 C/C 代码中条件编译指令#if、#ifdef、#ifndef、#elif、#elifdef、#elifndef、#else、#endif会把文件内容切分成大量互斥或嵌套的代码区间。传统做法是逐条处理PreprocessorBranchDirective但遇到深层嵌套时难以回答这段代码处于哪个条件分支内这个#include属于哪个#if块某宏定义在哪个分支生效这类结构性问题。PreprocBlock.qll正是为解决这类问题而设计它将一对开启/关闭指令之间的代码块chunk建模为PreprocessorBlock并通过getParent()/getAChild()形成一棵树树的根节点是源文件本身File。有了这棵树查询代码就可以沿分支层级自由上下导航而不再需要手工计算指令配对关系。核心类PreprocessorBlockPreprocessorBlock定义在 cpp/ql/lib/semmle/code/cpp/headers/PreprocBlock.qll#L41-L46其 characteristic predicate 表明一个块可以是以下三种元素之一File整个文件树的根PreprocessorBranch#if/#ifdef/#ifndef/#elif/#elifdef/#elifndef开启的分支PreprocessorElse#else分支。块的范围从开启指令所在行延伸到关闭指令#elif、#elifdef、#elifndef、#else或#endif的上一行见getEndLine()的实现PreprocBlock.qll#L83-L87。关键成员一览PreprocessorBlock提供的成员可直接用于查询编写成员说明getFile()返回该块所在的源文件getStartLine()/getEndLine()返回块的起始行号与结束行号结束行取关闭指令前一行文件根取总行数getParent()返回直接包围当前块的外层块若为文件本身则无结果顶层指令的父节点是文件getAChild()返回直接嵌套在当前块内部的子块result.getParent() thisgetAnInclude()返回直接位于该块内的#include指令不含子块中的getAMacro()返回直接位于该块内的宏定义不含子块中的hasLocationInfo(...)提供标准的位置信息谓词便于接入 CodeQL 的 Locations 机制toString()返回元素的文本表示值得注意的实现细节getAnInclude()/getAMacro()通过not result this.getAChild().getAnEnclosedInclude()PreprocBlock.qll#L142-L145、PreprocBlock.qll#L157-L160排除掉子块中的指令/宏保证结果严格限定在本层。父子关系的建立原理块的层级关系由私有谓词getParentInternal()计算PreprocBlock.qll#L89-L108通过getPreprocIndex找到当前块对应#if指令在文件全部PreprocessorBranchDirective中的序号ix指令按起始行排序见 PreprocBlock.qll#L14-L17取出排在该#if之前的上一条指令prev若prev是#endif说明当前块并不与#endif平级而是与#endif对应的#if拥有相同的父节点因此递归求解prev.getIf()的父块否则prev就是#if/#ifdef/#ifndef/#elif/#else中层级更高的一层直接作为父块。对外暴露的getParent()在此基础上补充了根处理非文件块若找不到更外层指令则父节点为整个文件PreprocBlock.qll#L114-L125。典型使用场景场景一找出被#if 0或未定义宏禁用的分支。遍历PreprocessorBranch分支块检查其分支条件在预处理后是否成立即可定位死代码或仅供特定平台编译的片段。场景二按条件编译粒度统计依赖。例如统计某个平台宏如_WIN32保护下的#include集合可利用getAnInclude()限定在指定块内import cpp import semmle.code.cpp.headers.PreprocBlock from PreprocessorBlock b where b.getFile().getBaseName() platform.h and b.toString().matches(%_WIN32%) select b, b.getAnInclude()场景三在数据流或污点分析中作为结构约束。当同一变量在不同#if分支中被赋予不同来源时块的父子关系可作为路径约束参与分析。ThrowingFunction外部函数异常行为建模抽象类设计动机安全分析中外部函数标准库、系统 API、第三方库函数的异常行为往往不可见——源码不在当前数据库中但其是否可能抛出异常直接影响调用点的控制流与数据流结论。0.12.5 引入了ThrowingFunction抽象类让库作者可以通过**子类化 特征谓词characteristic predicate**的方式声明性地描述哪些外部函数可能抛出异常。接口定义接口定义在 cpp/ql/lib/semmle/code/cpp/models/interfaces/Throwing.qll其文档注释明确了使用方式要使用该 QL 库请创建一个继承ThrowingFunction的 QL 类用特征谓词选中你要建模其异常流的函数或函数集合。该文件同时声明了AlwaysSehThrowingFunction——表示无条件抛出结构化异常处理SEH异常的函数Throwing.qll#L14-L16。与之配套的还有 NonThrowing.qll 中的NonCppThrowingFunction表示保证永不抛出 C 异常但仍可能触发 SEH 异常的函数。一正一反两个抽象类构成外部函数异常行为的完整建模面。使用方式假设要为某个封装库建模所有my_lib::*命名空间下的函数可能抛出std::exception可以这样写import cpp import semmle.code.cpp.models.interfaces.Throwing class MyLibThrowingFunction extends ThrowingFunction { MyLibThrowingFunction() { this.getName().matches(my_lib::%) } }之后凡是在数据流配置、污点追踪或异常可达性分析中引用ThrowingFunction的位置都会自动纳入这些外部函数。版本演进与弃用路线后续版本对该 API 进行了演进阅读时请注意版本差异5.2.0ThrowingFunction位于semmle.code.cpp.models.interfaces.Throwing被标记为弃用官方建议改用AlwaysSehThrowingFunction见 cpp/ql/lib/change-notes/released/5.2.0.md10.0.0弃用的ThrowingFunction类被移除请使用AlwaysSehThrowingFunction同时弃用的NonThrowingFunction被移除改用NonCppThrowingFunction见 cpp/ql/lib/change-notes/released/10.0.0.md。也就是说0.12.5 中新增的ThrowingFunction是当时版本的建模入口若你当前使用的是较新的 CodeQL 版本请直接使用AlwaysSehThrowingFunction。小结PreprocBlock.qllcpp/ql/lib/semmle/code/cpp/headers/PreprocBlock.qll把 C/C 条件编译指令转换为可导航的树结构PreprocessorBlock提供getParent()、getAChild()、getAnInclude()、getAMacro()等成员适合所有需要按分支粒度分析代码的安全查询ThrowingFunctioncpp/ql/lib/semmle/code/cpp/models/interfaces/Throwing.qll提供了外部函数异常行为的声明式建模入口注意它在 5.2.0 被弃用、10.0.0 被移除新代码应使用AlwaysSehThrowingFunction。这两个 API 虽然只是 0.12.5 变更记录中的两行条目却是支撑 C/C 数据流分析与条件编译相关查询的底层基础设施值得在编写自定义查询时优先复用。赞分享静态分析SAST应用安全漏洞扫描代码质量【免费下载链接】codeqlCodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security项目地址https://gitcode.com/gh_mirrors/co/codeql点击查看免费下载相关推荐CodeQL C 分析器 1.21 版本更新详解C 8 特性提取与 QL 库建模CodeQL C 分析器 1.21 版本更新详解C 8 特性提取与 QL 库建模 导读 本文基于 CodeQL 仓库 change notes/1.21/an静态分析SAST应用安全漏洞扫描代码质量CodeQL C 库 0.2.2 版本解析AnalyzedString 重命名与 C17 if/switch 初始化器 APICodeQL C 库 0.2.2 版本解析AnalyzedString 重命名与 C17 if/switch 初始化器 API 本文以 CodeQL静态分析SAST应用安全漏洞扫描代码质量CodeQL C 分析库中的 cstdint 标准类型建模FixedWidthIntegralType 与 FixedWidthEnumType 详解CodeQL C 分析库中的 cstdint 标准类型建模FixedWidthIntegralType 与 FixedWidthEnumType 详解 本静态分析SAST应用安全漏洞扫描代码质量上一篇make-sense.ai AI集成功能详解YOLOv5、SSD、PoseNet实战下一篇GitHub_Trending/sum/Summer2025-Internships开发环境本地搭建项目开发环境的步骤创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考