“符号执行将这一接口展开为位置、变量表达式和路径条件三元状态,手算三条终端路径并重放输入(1,2)。混合执行再从(0,0)的实际运行出发,翻转路径前缀而丢弃旧后缀,逐轮生成覆盖另外两条路径的输…”
形式陈述
一次运行,两份状态
混合执行把 concrete 与 symbolic 合在一起:程序按一个真实输入
在实际状态
赋值同时计算具体值和符号右侧。遇到判断时,实际值决定本轮方向;符号一侧保存判断表达式及所取方向。设本轮有
要改变第
其中
一个明确的调度版本
保持输入队列、已实际覆盖的判断前缀集合、已完成求解的翻转目标集合。每次运行后,登记本次路径全部前缀,再从最深判断向前考虑翻转;已覆盖或已完成的目标跳过,其余求解。SAT 输入放入队列,UNSAT 目标结束,unknown 单独保留,不能登记成不可行。
这里的前缀身份包含有序的节点与分支方向;含循环的实现还需区分第几次出现。本页无环语言中,每个节点每轮至多出现一次。缓存也必须绑定程序版本、输入域和语义,不能把上次版本的 UNSAT 用在新程序上。
在精确符号模型、完备判定器和穷尽此调度的条件下,无环程序的有限路径树最终全部覆盖:若仍有可行叶未覆盖,在全部已覆盖路径中选一条与它共享前缀最长的路径。两者下一分支不同,对应翻转目标可满足,应已产生一轮覆盖这个更长共同前缀的新执行,与最长选择矛盾。删掉任一条件后,通常只能报告已覆盖部分。
直觉
沿已走过的路找岔口
随机测试可能长期碰不到 y==x+1。混合执行先用普通输入走一遍,记下“为什么走了这一边”;下一轮有针对性地求一个走另一边的输入。具体运行提供真实控制轨迹,符号条件提供改变输入的方向。
它不需要每次把两条分支的完整程序状态都同时留在内存中,但仍可能探索指数多条路径。减少同时存活状态与消除路径爆炸是不同事情。
例子与边界
从零输入走到断言失败
使用前页返回值程序,输入域先固定为
| 轮次 | 输入 | 实际分支 | 结果与下一目标 |
|---|---|---|---|
| 1 | 4F | 返回0;翻转为 |
|
| 2 | 4T、5F | 返回2;翻转为 |
|
| 3 | 4T、5T | 返回1,违反 return != 1 |
第二轮具体 a=2,符号 a=X+1;二者在
三轮分别到达三条可行路径,最终每个未走过的翻转都已被覆盖或判不可行。这里恰好覆盖全部路径,不能从“通常运行三轮”推广出其他程序的覆盖界。
为什么必须删掉旧后缀
设另一程序先判断 x==0;真分支令 a:=1,假分支令 a:=2;汇合后再次判断 x==0。从
正确目标只约束 a=2 并走第二判断的假边。若后续判断还读取分支中被改写的变量,旧后缀也可能带有旧执行的表达式版本;改变前缀后更不能原样沿用。
具体化近似可能漏掉路径
真实工具可能遇到后端不能处理的表达式,便把某个符号量临时换成当前具体值。[1] 这有助于生成测试,但全称覆盖保证随之丢失。例如从 X*Y==1,若把
另一些近似会产生满足近似公式、却走不到目标分支的输入,所以新输入重放必须核对前缀。重放失败应记录为模型或搜索偏差,不能登记目标已覆盖。当前下载实现保持精确表达式并枚举有限域,没有这种具体化优化。
找到错误与证明没有错误
一个成功重放的错误输入只依赖那一次运行,证据很短;“没有错误”还要求覆盖全部允许输入路径。超时、只翻最后一个条件、跳过求解失败和截断循环,都可能留下空白。报告应分列已执行测试、已覆盖前缀、不可行目标、unknown 和尚未探索目标。
对并发、时间、随机性或外部服务,重跑同一输入未必重复旧前缀。要么把这些选择纳入输入和记录,要么把保证缩到实际测试轨迹,不能套用确定性证明。
推论与应用
成本按实际运行与查询分别计算
设完成
使用持久前缀节点时,前缀本体最多按新增分支记录量存储;如果像下载原型那样把每个长度为
迁移练习:把输入域收紧为
参考资料
[1] Koushik Sen、Darko Marinov、Gul Agha,CUTE: A Concolic Unit Testing Engine for C,ESEC/FSE 2005,263–272页;链接为作者技术稿,§§3.4–3.5及Figure 8路径约束求解算法说明具体/符号伴随、分支翻转和受限求解器的具体化。本文不实现论文的指针图模型,另给纯整数有限域的精确版本。
[2] Patrice Godefroid、Nils Klarlund、Koushik Sen,DART: Directed Automated Random Testing,PLDI 2005,213–223页;作者实验室页面说明静态接口提取、随机初始测试与动态引导输入生成三个组成部分。