3. 静态分析技术原理
静态分析并不是单一算法,而是一条由浅入深、逐层逼近程序真实行为的分析流水线。理解这条流水线中每一层“能看见什么、看不见什么”,是判断某类航空软件缺陷能否被工具检出、以及为什么会出现漏报与误报的关键。本节按分析深度从低到高展开:词法分析、语法分析、语义分析、数据流分析、控制流分析、抽象解释,并说明它们各自在 DO-178C 语境下的适用边界。
一、从字符到结构:词法分析与语法分析
词法分析把源文件的字符流切分为记号(token)序列,识别标识符、关键字、常量、运算符与注释,并维护行号列号。它只关心“词”的形态,不理解含义。因此词法层能检出的是:非法字符、未闭合的字符串或注释、超长标识符、编码不规范(如混入非 ASCII 字符)、以及 MISRA 类规则中“禁止使用八进制常量”“禁止三字符组”等纯书写约束。它无法判断一个变量是否被初始化。
语法分析依据文法把 token 序列组织为抽象语法树(AST),确定语句与表达式的嵌套结构。这一层能检出括号不匹配、缺少分号、if/else 悬挂、声明语法错误等。对航空软件而言,语法层还支撑结构化覆盖类检查,例如“禁止 goto”“每个函数只能有一个出口”“禁止递归调用”等规则,本质上是在 AST 上做模式匹配。
词法与语法分析都是局部且线性的,复杂度接近 O(n),因此可以全量、快速执行,适合作为提交门禁的第一道关卡。但它们的检出能力止步于“形式是否合法”,对逻辑正确性无能为力。
二、语义分析:建立符号与类型约束
语义分析在 AST 之上构建符号表,完成名称解析、类型检查、作用域与可见性判定、函数签名匹配。它回答的是“这个名字指向哪个实体、这个表达式类型是否相容”。典型可检缺陷包括:使用未声明标识符、类型不匹配的赋值、实参与形参类型或个数不符、隐式类型转换导致精度损失、数组下标类型非法、返回类型缺失。
在航空嵌入式代码中,语义分析对 int 与 unsigned int 混用、枚举与整型互转、位域宽度越界等问题的识别尤为有价值。但语义分析仍是路径不敏感的:它逐条语句检查,不追踪执行顺序,因此无法发现“变量在某个分支上未初始化”这类只在特定路径上出现的问题。
三、控制流分析:把程序变成图
控制流分析把函数体转换为控制流图(CFG):节点是基本块,边是可能的跳转。基于 CFG 可以计算圈复杂度、识别不可达代码、检测死循环、分析函数出口是否唯一、判断是否存在无终止路径。对航空软件,控制流分析还是结构覆盖(语句覆盖、判定覆盖、MC/DC)度量与测试用例设计的基础。
需要强调:CFG 只描述“可能怎么走”,不描述“值是多少”。因此它能发现“这段代码永远到不了”,却不能发现“这段代码到了但算错了”。
四、数据流分析:追踪定义与使用
数据流分析在 CFG 上传播“变量的定义(def)与使用(use)”信息,典型框架是到达定值分析、活跃变量分析与可用表达式分析。它能检出的缺陷非常贴近航空软件实际:变量未初始化即使用、赋值后从未被读取(死存储)、同一路径上重复赋值、可能为空的指针被解引用、资源申请后未在所有路径释放。
数据流分析通常采用迭代求解,在分支汇合点做并集或交集运算。由于需要枚举路径,其精度与代价直接相关:路径敏感、上下文敏感的分析更准,但状态空间可能爆炸。工程上常通过设置分析深度、函数摘要、指针指向集上限来控制。
五、抽象解释:用格与不动点逼近真实语义
抽象解释是上述分析的统一数学框架。它把具体域(如所有整数值)映射到抽象域(如区间 [lo, hi]、符号、多面体),在抽象域上定义单调的转移函数,并通过不动点迭代求程序不变式。若抽象域中某条断言被证明恒成立,则具体执行中该断言必然成立——这就是可靠性(soundness)的来源。
以区间分析为例,对 x = a + b;,若 a ∈ [0,10]、b ∈ [0,5],则 x ∈ [0,15]。若后续存在数组访问 buf[x] 且 buf 长度为 12,工具即可报告“可能越界”,并给出 x 的抽象取值作为证据。抽象解释天然支持浮点范围分析、除零检测、溢出检测,这对飞控与航电软件中的数值安全至关重要。
其代价是保守性:抽象域越粗,越容易把不可能的情况也算进来,产生误报;抽象域越细,计算越昂贵。因此抽象解释工具必须允许按模块、按函数配置精度,并对不可判定路径给出“未知”而非强行结论。
六、各层能力与适用边界对照
下表汇总了六类技术在航空软件缺陷检测中的典型检出对象与固有局限,可作为选择工具与解读报告的依据。
| 分析层次 | 典型可检缺陷 | 固有边界 |
|---|---|---|
| 词法分析 | 非法字符、编码不规范、禁用记号 | 不理解语义,无法判断逻辑 |
| 语法分析 | 结构错误、goto/递归等结构约束 | 不追踪执行顺序与取值 |
| 语义分析 | 类型不匹配、未声明、精度损失 | 路径不敏感,漏掉分支相关问题 |
| 控制流分析 | 不可达代码、死循环、出口不唯一 | 不涉及数据取值 |
| 数据流分析 | 未初始化、死存储、资源泄漏 | 路径/上下文敏感度受限,可能漏报 |
| 抽象解释 | 越界、溢出、除零、范围违例 | 保守导致误报,需人工确认 |
七、一个可操作的判定流程
面对一条静态分析告警,可按以下顺序判断其性质,避免把误报当成缺陷、也避免把真缺陷当成噪声:
- 确认告警来自哪一层分析,明确该层的理论能力边界。
- 若为抽象解释告警,检查工具给出的抽象取值区间与路径条件,判断该区间是否包含真实可达值。
- 若为数据流告警,检查是否存在工具未建模的中断、DMA 或硬件寄存器写入路径。
- 对确认的缺陷,补充测试用例或修改代码;对确认的误报,记录抑制理由与责任人。
- 将抑制记录纳入配置管理,确保后续版本可追溯。
下面的信号链图展示了从源代码到缺陷结论的完整分析路径,以及各层之间的输入输出关系与反馈回路。
理解这条链路的层次关系后,就能对工具报告做出专业判断:词法/语法/语义层的告警通常可直接修复;控制流与数据流告警需结合中断与硬件访问上下文确认;抽象解释告警必须核对抽象取值与真实可达路径。把每一层的边界写进项目静态分析计划,才能既满足 DO-178C 对验证证据的要求,又避免在误报上耗费过多评审资源。