会议论文分享-FSE20-基于学习的状态修剪策略

在这篇paper中,作者提出了Homi 算法,在运行时通过修剪掉不必要(unpromising)的状态来增强符号执行工具。

1.引言

下图是符号执行算法的形式化描述,红框为作者添加的部分:

  • 每个状态可以用3元组 ( i n s t r , s t o r e , Φ ) (instr, store, \Phi) (instr,store,Φ) 描述:

    • i n s t r instr instr 为将要执行的指令。

    • s t o r e store store 为将程序变量映射为符号值的map。

    • Φ \Phi Φ 为当前状态对应的路径条件。

  • 每个测试用例用2元组 ( Φ , M o d e l ( Φ ) ) (\Phi, Model(\Phi)) (Φ,Model(Φ)) 描述, M o d e l ( Φ ) Model(\Phi) Model(Φ) 为对应输入。

请添加图片描述
在这篇paper中,作者认为有意义(promising)的状态是那些可以提高分支覆盖率的状态,因此需要修剪掉不必要的状态。那么需要解决的问题有:

  • 如何衡量每个状态的重要程度?计算状态分数

  • 要修剪掉多少状态? 计算修剪得数量

2.Homi算法

2.1.概率修剪策略

修剪策略大体上可分为2步:

  • sampling: 计算概率数据 P P P P P P 由3元组 ( F , P s t g y , P r a t i o ) (F, P_{stgy}, P_{ratio}) (F,Pstgy,Pratio) 构成。

    • F F F 表示一个n维特征集合,每一维表示一个core branch condition,用来计算一个状态的特征向量。

    • P s t g y P_{stgy} Pstgy 是n维向量 θ \theta θ 的概率分布, θ \theta θ 用于计算每个状态的分数。每次调用 P R U N E ( S , P , η t ) PRUNE(S, P, \eta_t) PRUNE(S,P,ηt) 都会根据 P s t g y P_{stgy} Pstgy 重新sample出一个 θ \theta θ 向量。

    • P r a t i o P_{ratio} Pratio 是修剪比例 r r r 的概率分布, r r r 用于决定要修剪的状态数量。每次调用 P R U N E ( S , P , η t ) PRUNE(S, P, \eta_t) PRUNE(S,P,ηt) 都会根据 P r a t i o P_{ratio} Pratio 重新sample出一个 r r r 值。

  • pruning:修剪函数如下面公式所示,说白了,就是选取bottom-k分数的状态修剪掉,k = |Sp * r|

P R U N E ( S , P , η t ) = { arg min ⁡ S p ⊆ S ⋀ ∣ S p ∣ = S ∗ r ∑ s ∈ S p s c o r e ( s , θ ) i f ( F    ≠    ∅ ) ∅ o t h e r w i s e PRUNE(S, P, \eta_t) = \left\{ \begin{array}{rcl} \argmin\limits_{S_p \subseteq S \bigwedge |S_p| = S * r} \sum\limits_{s \in S_p} score(s, \theta) & & {if (F \; \neq \; \empty)} \\ \empty & & {otherwise}\\ \end{array} \right. PRUNE(S,P,ηt)={SpSSp=SrargminsSpscore(s,θ)if(F=)otherwise

上述过程针对每个状态:

  • 首先将状态转换成1个特征向量,每个特征表示一个布尔谓词,用于检查状态 s 的路径条件 Φ \Phi Φ 是否包含特定的分支条件 ϕ \phi ϕ。形式化定义如下,即如果当前 s 的路径条件包括条件表达式 ϕ i \phi_i ϕi ϕ i \phi_i ϕi F F F 中第i个core branch condition),那么特征向量第i维为1,否则为0。其完整特征向量 f e a t ( s ) = < f e a t 1 ( s ) , . . . , f e a t n ( s ) > feat(s) = <feat_1(s), ..., feat_n(s)> feat(s)=<feat1(s),...,featn(s)>

f e a t i ( s ) = { 1 i f ( ϕ i    i n    Φ ) 0 o t h e r w i s e feat_i(s) = \left\{ \begin{array}{rcl} 1 & & {if (\phi_i \; in \; \Phi)} \\ 0 & & {otherwise}\\ \end{array} \right. feati(s)={10if(ϕiinΦ)otherwise

  • 之后通过概率分布 P s t g y P_{stgy} Pstgy 可sample出 θ \theta θ s c o r e ( s , θ ) = f e a t ( s ) . θ score(s, \theta) = feat(s) . \theta score(s,θ)=feat(s).θ。比如 θ = < 0.4 , − 0.82 , − 0.3 > , f e a t ( s ) = ⟨ 1 , 0 , 0 ⟩ \theta = <0.4, −0.82, −0.3>, feat(s) = ⟨1, 0, 0⟩ θ=<0.4,0.82,0.3>,feat(s)=1,0,0,那么 s c o r e = 0.4 score = 0.4 score=0.4

  • 然后从概率分布 P r a t i o P_{ratio} Pratio sample出修剪状态的比例 r r r,修剪掉 bottom-|Sp * r| 的状态。

2.2.Homi

在这里插入图片描述

  • Homi的关键点,算法2,是通过符号执行期间的online learning,不断更新特征和两个概率分布, P s t g y P_{stgy} Pstgy P r a t i o P_{ratio} Pratio。这一轮计算的概率数据 P s t g y P_{stgy} Pstgy P r a t i o P_{ratio} Pratio 会在下一轮用来进行状态修剪。

  • 在限定时间 N N N 内,标准符号执行只运行一次 RUN 函数,而HOMI将 N N N 分成多个时间片(不是平均分),每次运行 N ′ N^{'} N 个时间单位。

  • μ \mu μ 是均匀分布。

在HOMI的运行过程中:

  • N ′ , P s t g y , P r a t i o N^{'}, P_{stgy}, P_{ratio} N,Pstgy,Pratio 均采用均匀分布随机初始化, F F F 初始化为空集。因此第一轮 RUN,并不会进行状态修剪。

  • D D D 是由3元组 < Φ , t , B > <\Phi, t, B> <Φ,t,B> 构成的集合,第1个是路径条件,第2个是对应的测试用例,第3个是其覆盖的分支。

2.2.1.Collecting Promising Data

这一小节主要介绍算法2第10行 G o o d D ← E x t r a c t ( D ) GoodD \leftarrow Extract(D) GoodDExtract(D) 的过程。 G o o d D GoodD GoodD D D D 的一个子集,意味着最有意义的测试用例构成的集合,它们满足以下条件:

  • G o o d D GoodD GoodD 中所有用例的分支覆盖与 D D D 中相同。

  • G o o d D GoodD GoodD 最小化。

计算过程如下:

  • D ∗ = arg max ⁡ D ′ ⊆ D ∣ ∪ B ∣ ( _ , _ , B ) ∈ D ′ D^* = \argmax\limits_{D^{'} \subseteq D} \mathop{| \cup B|}\limits_{ (\_,\_,B) \in D^{'}} D=DDargmax(_,_,B)DB,找出所有能让分支覆盖率最大化的集合。

  • G o o d D = arg min ⁡ D ′ ∈ D ∣ D ′ ∣ GoodD = \argmin\limits_{D ^{'} \in D} |D^{'}| GoodD=DDargminD,选取最小的一个。

作者通过一个贪心算法实现上述过程。

2.2.2.Generating Features.

这部分对应算法2第11行 N e w F ← F G e n e r a t o r ( G o o d D ) NewF \leftarrow FGenerator(GoodD) NewFFGenerator(GoodD),也就是将 G o o d D GoodD GoodD 中的所有状态转换成对应特征向量。

作者首先定义一个core branch condition的概念,core branch condition ϕ \phi ϕ 可由下面语言推导出

L : : = c o n d    ∣    c o n d ⋀ c o n d    ∣    c o n d ⋁ c o n d L ::= cond \; | \; cond \bigwedge cond \; | \; cond \bigvee cond L::=condcondcondcondcond

c o n d : : = l v = n cond::= lv = n cond::=lv=n

l v : : = α    ∣    α [ i ] lv::= \alpha \; | \; \alpha[i] lv::=αα[i]

l − v a l u e ( l v ) l-value (lv) lvalue(lv) 表示一个符号值,或者符号数组的一个元素, c o n d cond cond 为判定符号值是否等于一个常量的表达式。core branch condition可以为单个表达式或多个表达式构成的合取范式或者析取范式。

为了生成特征向量作者

  • 首先从 G o o d D GoodD GoodD 中提取所有的路径条件组成集合 P C PC PC P C = { Φ    ∣    ( Φ , _ , _ ) ∈ G o o d D } PC = \{\Phi \; | \; (\Phi, \_, \_) \in GoodD\} PC={Φ(Φ,_,_)GoodD}

  • 然后从 P C PC PC 中提取所有的条件表达式组成集合 N e w F NewF NewF N e w F = { ϕ ∈ L    ∣    ϕ ∈ Φ , Φ ∈ P C } NewF = \{\phi \in L \; | \; \phi \in \Phi, \Phi \in PC\} NewF={ϕLϕΦ,ΦPC} L L L 指前面能推导出core branch condition的语言。

假设有 P C = { { ( α = = 3 ) , ( α > 1 ) } , { ( α [ 2 ] , 3 ) , ( α [ 2 ] = = 8 ) } } PC = \{\{(\alpha == 3), (\alpha > 1)\}, \{(\alpha[2] , 3), (\alpha[2] == 8)\}\} PC={{(α==3),(α>1)},{(α[2],3),(α[2]==8)}},那么提取的 N e w F = { ( α = = 3 ) , ( α [ 2 ] = = 8 ) } NewF = \{(\alpha == 3), (\alpha[2] == 8)\} NewF={(α==3),(α[2]==8)}

其中示例集合 N e w F NewF NewF 中的两个特征是确定每个路径条件的模型的最小条件;例如,第一路径条件的模型 α = = 3 ⋀ α > 1 \alpha == 3 \bigwedge \alpha > 1 α==3α>1 可以简化成 α = = 3 \alpha == 3 α==3。简而言之,第11行生成的特征集 N e w F NewF NewF 代表了最小测试用例的关键证据,这些测试用例有助于最大化分支覆盖,直到当前状态。

可以看到,在循环的每一轮 N e w F NewF NewF 的维度都可能不一样,因此状态对应的特征向量在每一轮可能都维度不同。

2.2.3.Learning Distribution

在算法2第12行, P s t g y , P r a t i o , N ′ ← P G e n e r a t o r ( G o o d D , N e w F ) P_{stgy}, P_{ratio}, N^{'} \leftarrow PGenerator(GoodD, NewF) Pstgy,Pratio,NPGenerator(GoodD,NewF) P G e n e r a t o r PGenerator PGenerator 会学习到向量 θ \theta θ 和标量 r r r 的概率分布 P s t g y , P r a t i o P_{stgy}, P_{ratio} Pstgy,Pratio,输出的 N ′ N^{'} N 会下个循环运行 RUN 的时间单位。

P s t g y = < P 1 , . . . , P n > P_{stgy} = <P_1, ..., P_n> Pstgy=<P1,...,Pn> P i P_i Pi N e w F NewF NewF 第i个特征对应的权重 θ i \theta^i θi 的概率。计算 P i P_i Pi

  • 首先从 G o o d D GoodD GoodD 中选取所有的testcase组成 G o o d T GoodT GoodT 集合, G o o d T = { t    ∣    ( _ , t , _ ) ∈ G o o d D } GoodT = \{t \; | \;(\_,t,\_) \in GoodD\} GoodT={t(_,t,_)GoodD}

  • 其中算法会为每个testcase维护对应的四元组信息 t = ( F , θ , r , N ′ ) t = (F, \theta, r, N^{'}) t=(F,θ,r,N)

    • F F F 是testcase对应状态集合的特征向量集合,推测testcase遍历过的每个状态的特征向量都被提取了

    • θ \theta θ 是对应权重向量

    • r r r 是修剪比例

    • N ′ N^{'} N 是时间分配

  • 收集所有状态的特征向量集合 G o o d F = ∪ ( F , _ , _ , _ ) ∈ G o o d T F GoodF = \mathop{\cup}\limits_{(F, \_, \_, \_) \in GoodT} F GoodF=(F,_,_,_)GoodTF

  • 接着,按如下方式计算 P i P_i Pi,方便之后sample出 θ \theta θ:

    • P i = { N ( μ ( W i ) , δ ( W i ) , − 1 , 1 ) i f ( ϕ i n e w ∈ G o o d F ) u ( [ − 1 , 1 ] ) o t h e r w i s e P_i = \left\{ \begin{array}{rcl} \mathop{N}(\mu(W_i), \delta(W_i), -1, 1) & & {if (\phi_i^{new} \in GoodF)} \\ u([-1, 1]) & & {otherwise}\\ \end{array} \right. Pi={N(μ(Wi),δ(Wi),1,1)u([1,1])if(ϕinewGoodF)otherwise ϕ i n e w \phi_i^{new} ϕinew N e w F NewF NewF 中第i个条件, N \mathop{N} N表示截断正态分布,平均值 μ ( W i ) \mu(W_i) μ(Wi),标准差 δ ( W i ) \delta(W_i) δ(Wi),最大值1,最小值-1。 u u u 为均匀分布。

    • W i = { θ k    ∣    ( ϕ i n e w = ϕ k ) ⋀ ( { ϕ 1 , . . . , ϕ n } , θ , _ , _ ) ∈ G o o d T } W_i = \{\theta^k \; | \; (\phi_i^{new} = \phi_k) \bigwedge (\{\phi_1, ..., \phi_n\}, \theta, \_, \_) \in GoodT\} Wi={θk(ϕinew=ϕk)({ϕ1,...,ϕn},θ,_,_)GoodT},这个公式有点难以理解,假设目前 G o o d T GoodT GoodT 中有3个testcase,目前 ϕ i n e w \phi_i^{new} ϕinew 在这3个testcase中分别为路径条件上第1,2,3个core branch condition,在生成testcase时,这3个示例可能对应3个不同的向量 θ \theta θ(因为每次状态修剪都会重新sample一个 θ \theta θ),记为 θ 1 , θ 2 , θ 3 \theta_1, \theta_2, \theta_3 θ1,θ2,θ3。那么此时 W i = { θ 1 1 , θ 2 2 , θ 3 3 } W_i = \{\theta^1_1, \theta^2_2, \theta^3_3\} Wi={θ11,θ22,θ33} θ j i \theta^i_j θji 表示向量 θ j \theta_j θj 第i维度的值)。

    • u ( W ) = ∑ w ∈ W w ∣ W ∣ u(W) = \sum\limits_{w \in W} \frac{w}{|W|} u(W)=wWWw, δ ( W ) = ∑ w ∈ W ( w − u ( W ) ) 2 ∣ W ∣ \delta(W) = \sqrt{\frac{\sum\limits_{w \in W}(w - u(W))^2}{|W|}} δ(W)=WwW(wu(W))2

  • 按上述步骤,可以求出 P s t g y P_{stgy} Pstgy 中每一项的概率分布,然后求 P r a t i o P_{ratio} Pratio 的分布:
    P r a t i o ( X = r ′ ) = ∣ { ( _ , _ , r , _ ) ∈ G o o d T ∣ r ′ = r } ∣ ∣ G o o d T ∣ P_{ratio}(X = r^{'}) = \frac{|\{(\_, \_, r, \_) \in GoodT | r^{'} = r\}|}{|GoodT|} Pratio(X=r)=GoodT{(_,_,r,_)GoodTr=r},这个概率分布会之后用来sample出 r r r

  • 再用同样的方式计算概率分布 P t i m e P_{time} Ptime,方便之后sample出 N ′ N^{'} N

2.2.4.Sampling Values

这一步是基于 P s t g y P_{stgy} Pstgy P r a t i o P_{ratio} Pratio sample出 θ \theta θ r r r。生成 θ \theta θ 的方法采用下面3个方法之一:

  • Exploitation,从 P s t g y P_{stgy} Pstgy 中先sample出exploit向量: S a m p l e e x p l o i t ( P 1 × . . . × P n ) = < θ 1 , . . . , θ n > Sample_{exploit}(P_1 \times ... \times P_n) = <\theta^1, ..., \theta^n> Sampleexploit(P1×...×Pn)=<θ1,...,θn>

  • Reverse Exploitation, 逆向sample出向量 θ r \theta_r θr

    • U = { r 1 , r 2 , . . . , r 100    ∣    r i ∼ u ( − 1 , 1 ) } U = \{r_1, r_2, ..., r_{100} \; | \; r_i \sim u(-1, 1)\} U={r1,r2,...,r100riu(1,1)},用均匀分布sample出100维向量 U U U

    • S a m p l e r e v e r s e ( P 1 × . . . × P n , U ) = < θ r 1 , . . . , θ r n > Sample_{reverse}(P_1 \times ... \times P_n, U) = <\theta_r^1, ..., \theta_r^n> Samplereverse(P1×...×Pn,U)=<θr1,...,θrn> θ r i = arg max ⁡ u ∈ U ( u − θ i ) \theta_r^i = \argmax\limits_{u \in U}(u - \theta^i) θri=uUargmax(uθi)

  • Exploration: 采用均匀分布采样的方法 u ( [ − 1 , 1 ] n ) u([-1, 1]^n) u([1,1]n) sample出 θ \theta θ

最后从概率分布 P r a t i o P_{ratio} Pratio P t i m e P_{time} Ptime 中sample出 r r r 和下次运行 RUN 的时间 N ′ N^{'} N N ′ N^{'} N 的值每次从 200, 400, 600, 800 中随机选择一个)。

2.2.5.总结

可以看出,HOMI的运行流程就是循环下面过程:

  • 基于上一轮(第1轮不进行状态修剪)计算的概率数据 ( F , P s t g y , P r a t i o ) (F, P_{stgy}, P_{ratio}) (F,Pstgy,Pratio) 运行 RUN 的时候进行状态修剪。

    • P s t g y P_{stgy} Pstgy 用来sample出n维向量(n是 F F F 中core branch condition数量) θ \theta θ

    • P r a t i o P_{ratio} Pratio 用来sample出修剪比例 r r r

    • F F F 用来给每一个状态 s s s 计算特征向量 f e a t ( s ) feat(s) feat(s), f e a t ( s ) . θ feat(s).\theta feat(s).θ s s s 的分数,选取分数最低的 k k k 个状态修剪掉。( k = ∣ S ∣ . r k = |S| . r k=S∣.r)

  • 运行完毕后会产生testcase集合 T ′ T^{'} T,从 T ′ T^{'} T 选出满足最小并且能达到和 T ′ T^{'} T 同样分支覆盖率的子集合 G o o d T ′ GoodT^{'} GoodT

  • G o o d T ′ GoodT^{'} GoodT 中提取core branch condition集合 F ′ F^{'} F

  • 基于 F ′ , G o o d T ′ F^{'}, GoodT^{'} F,GoodT 计算新的概率分布 P s t g y ′ , P r a t i o ′ P_{stgy}^{'}, P_{ratio}^{'} Pstgy,Pratio

  • ( F ′ , P s t g y ′ , P r a t i o ′ ) (F^{'}, P_{stgy}^{'}, P_{ratio}^{'}) (F,Pstgy,Pratio) 会在下一轮用来进行状态修剪。

总的来说,作者的目标是找出一组能不断提高测试用例分支覆盖率的参数。

3.实验

探究的问题包括:

  • 有效性:HOMI对提升分支覆盖率的效果如何?有多少bug只被HOMI发现了?

  • 状态数量:与普通符号执行工具相比,HOMI在运行时维护多少状态?

  • 与朴素方法的对比:与随机状态修剪相比,HOMI表现如何?

3.1.实验设置

使用了9个GNU开源程序,一些统计信息如下表所示:

程序名总行数分支数
gawk-3.1.46090411934
grep-2.6569317021
combine-0.4.0357562359
grep-2.6569317021
ginstall (8.31)222903652
ptx (8.31)221485262
vdir (8.31)193783830
pr (8.31)121561991
dd (8.31)105311547

选用的baseline包括:

  • 标准符号执行(不带状态修剪策略),分别采用9种不同的状态搜索策略:

    • CPICount (CallPath Instruction Count, nurs:cpicnt)

    • CovNew (nurs:covnew)

    • MinDistance (Minimal Distance to Uncovered, nurs: md2u)

    • InstrCount (Instruction Count, nurs:icnt)

    • QueryCost (nurs:qc)

    • RandomPath (random-path)

    • Depth (nurs:depth)

    • RandomState (random-state)

    • RoundRobin (klee默认)

其它设定包括(针对每个程序的运行)

  • 运行参数:--sym-args 0 1 10 --sym-args 0 2 2 --sym-files 1 8 --sym-stdin 8 --sym-stdout

  • 内存限制:2GB

  • 时间限制:5小时

3.2.有效性

下图反映了HOMI+covnew与标准符号执行+5个策略的对比结果

在这里插入图片描述
下表展示了仅由HOMI结合表现最佳的搜索策略(BestH)以及标准符号执行结合排名前5的搜索策略(xthH)覆盖到的分支的数量。

在这里插入图片描述
下表展示了表现最好的2种搜索策略结合与不结合HOMI的bug查找能力的对比

在这里插入图片描述

3.3.候选状态数量

下表展示了不同策略每一时刻状态列表的长度,紫线为MinDistance+HOMI,其它为标准符号执行结合不同搜索策略。

在这里插入图片描述

3.4.与朴素方法的对比

下表反映了HOMI+CPICNT与Random+CPICNT(随机状态修剪),CPICNT,CPICNT(divide)的对比

在这里插入图片描述

4.代码实操

Homi的github地址,作者提供了一个20G的vdi虚拟硬盘,用virtualbox打开,可以省去配环境的麻烦,并且里面klee和benchmark已经编译好了。作者的环境为:

  • klee-2.0

  • LLVM 6.0.0

整个Homi的文件夹组织为:

  • script: python脚本都存放在这个目录下

  • klee: 作者修改后的klee项目目录

  • experiments: 存放实验中间文件和各种输出文件的目录

  • benchmarks: 存放各种数据集

代码实操主要以 trueprint 这个程序为示例介绍,作者给出运行的命令如下:

  • cd ~/Homi/script

  • python3 Homi.py pgm_config/1trueprint.json 3600 homi nurs:md2u 1

    • pgm_config/1trueprint.json 包含各种文件路径,内容如下(当前路径为 ~/Homi/script):
    {
    "pgm_name": "trueprint", # 程序名
    "pgm_dir": "../benchmarks/trueprint-5.4/obj-llvm/", # llvm编译路径
    "exec_dir": "/src", # 可执行文件的子路径
    "gcov_path": "../benchmarks/trueprint-5.4/1obj-gcov/src/", # gcov存放路径
    "gcov_file": "../*/*.gcov", # gcov文件路径模式
    "gcda_file": "../*/*.gcda" # gcda文件路径模式
    } 
    
    • 3600 为符号执行该程序的时间限制,单位秒。

    • homi 表示运行homi,与之对应的参数是 klee,表示运行普通符号执行。

    • nurs:md2u 表示选择的搜索策略。

    • 1 表示实验次数,主要是用来构建中间文件名称用,其它并没有什么实际作用。

Homi.pymain 函数核心部分如下:

if tool=="homi":         
    # Homi performs the general symbolic execution without state-pruning on the first iteration.
    Run_KLEE(pgm_config, pgm, stgy, total_time, small_time, ith_trial, iters, tool, d_name, Space_time)  
        
    while iters<100:
        dir_name, Data = Run_gcov(load_config, pgm, stgy, iters, tool, ith_trial, Data, d_name)
        topk_testcases = SetCoverProblem(Data, iters)
        features = Feature_Extractor(pgm, stgy, dir_name, topk_testcases, ith_trial, iters)
        small_time= PruningStgy_Generator(load_config, pgm, stgy, ith_trial, features, dir_name, topk_testcases, iters, Space_time)
            
        iters=iters+1
        Run_KLEE(pgm_config, pgm, stgy, total_time, small_time, ith_trial, iters, tool, d_name, Space_time)
else:
    for num in range(1,100):
        Run_KLEE(pgm_config, pgm, stgy, total_time, small_time, ith_trial, iters, tool, d_name, Space_time) 
        iters=iters+1
  • Run_KLEE 对应paper算法2中第7行的 RUN 函数,不过代码里作者换了1种写法,在 while 循环外先 RUN 一次,然后循环体内第1句到倒数第2句都是收集 ( F ′ , P s t g y ′ , P r a t i o ′ ) (F^{'}, P_{stgy}^{'}, P_{ratio}^{'}) (F,Pstgy,Pratio) 为下一次状态修剪做准备。

  • Run_gcov 对应算法2 8-9行 D D D 的收集过程。

  • SetCoverProblem 对应算法2 第10行 G o o d D ← E x t r a c t ( D ) GoodD \leftarrow Extract(D) GoodDExtract(D)。算法输出的是一个 list,每个元素是一个testcase名称,str 类型,诸如 x_tc_dir/test000001.ktest 形式。

  • Feature_Extractor 对应算法2 第11行 N e w F ← F G e n e r a t o r ( G o o d D ) NewF \leftarrow FGenerator(GoodD) NewFFGenerator(GoodD)

  • PruningStgy_Generator 对应算法2 第12行 P s t g y , P r a t i o , N ′ ← P G e n e r a t o r ( G o o d D , N e w F ) P_{stgy}, P_{ratio}, N^{'} \leftarrow PGenerator(GoodD, NewF) Pstgy,Pratio,NPGenerator(GoodD,NewF)

在参数和返回值方面:

  • dir_name 为中间文件夹路径,在该示例中为 result_All/{x}homi_trueprint_nurs:md2u_tc_dir{x} 表示实验轮次。dir_name~/Homi/experiments 下的相对路径,用来存放各种输出文件。

  • Space_time 为硬编码的参数,为 [200, 400, 600, 800]

  • small_time 对应paper中的 N ′ N^{'} N,初始为 800

  • DataDict[str, Set[int]] 类型,对应paper中的 D D D

    • key为testcase的相对路径,比如 test000001.ktest 对应的key为 x_tc_dir/test000001.ktestx 为循环次数,如果这次是第1次运行 Run_gcov 那就是 1_tc_dir/test000001.ktest,value为该testcase覆盖的branch行数。

    • 在paper中, D D D 为一个 set,元素为 ( ϕ , t , B ) (\phi, t, B) (ϕ,t,B)(路径条件,测试用例,覆盖的branch)

  • topk_testcasesList[str],对应 G o o d D GoodD GoodD,不过只保存了名字。

  • features 对应 F ′ F^{'} F

4.1.代码运行

命令行运行 python3 Homi.py pgm_config/1trueprint.json 3600 homi nurs:md2u 1,运行结束后 experiments 下的目录结构如下:

../experiments
├── homi__nurs:md2u0
│   └── trueprint
├── homi__nurs:md2u1
│   └── trueprint
├── homi__nurs:md2u2
│   └── trueprint
├── homi__nurs:md2u3
│   └── trueprint
├── homi__nurs:md2u4
│   └── trueprint
├── homi__nurs:md2u5
│   └── trueprint
├── homi__nurs:md2u6
│   └── trueprint
└── result_All
    └── 1homi_trueprint_nurs:md2u_tc_dir
        ├── 0__tc_dirs
        ├── 1__tc_dirs
        ├── 2__tc_dirs
        ├── 3__tc_dirs
        ├── 4__tc_dirs
        ├── 5__tc_dirs  
        ├── 6__tc_dirs      

其中

  • 从文件夹数量来看,循环进行了7次(Feature_Generator)运行了7次。

  • homi__nurs:md2u{i}/trueprint 为空目录,这个目录主要是创建来把benchmark编译后的文件复制进来,每次运行 Run_KLEE 的时候都会生成一个 homi__nurs:md2u{i}/trueprint 目录并把benchmark复制进来,符号执行便在 homi__nurs:md2u{i}/trueprint 目录下进行,{i} 指第i次循环,执行完后会执行 cp -r klee-out-0 Homi/experiments/result_All/1homi_trueprint_nurs:md2u_tc_dir/{i}__tc_dirs 将生成的testcase等文件复制到 result_All 文件夹下并清空 homi__nurs:md2u{i}/trueprint 目录。

  • result_All/1homi_trueprint_nurs:md2u_tc_dir 下有1个 1homi_trueprint_nurs:md2u_pruning_ratio 文件,表示最新一轮 PruningStgy_Generator 后sample出的 r r r 值,但是里面保存了50个值,在运行1次 R U N RUN RUN 时会进行多次状态修剪,每次修剪都会用到不同的 r r r 值。在原paper算法1中, r r r 理论上每次执行 P r u n e Prune Prune 的时候sample出1个。

  • result_All/1homi_trueprint_nurs:md2u_tc_dir/weights/{i}trial 下保存了 50 个w文件,每一个对应1个 θ \theta θ 向量,每次进行状态修剪会用到不同的 θ \theta θ 向量。在原paper算法1中, θ \theta θ 理论上每次执行 P r u n e Prune Prune 的时候sample出1个。

  • result_All/1homi_trueprint_nurs:md2u_tc_dir 下有1个 1homi_trueprint_nurs:md2u_feature_data 文件,保存的应该是最新一轮 Feature_Extractor 之后生成的特征集合 F ′ F^{'} F,内容放在最下面了,每一行对应一个core branch condition,str 类型。

  • result_All/1homi_trueprint_nurs:md2u_tc_dir/weights 下有7个 feature_data 文件,对应每一轮 Feature_Extractor 生成的特征集合。

4.2.Run_KLEE

python脚本部分参考 Homi.py – Run_KLEERun_KLEE.py,其中 gen_run_cmd 函数拼凑出运行klee的命令,在示例中为: Homi/klee/build/bin/klee -trial=0 --max-memory=2000 --watchdog -max-time=800 -dirname=~/Homi/experiments/result_All -write-kqueries -only-output-states-covering-new --simplify-sym-indices --output-module=false --output-source=false --output-stats=false --disable-inlining --use-forked-solver --use-cex-cache --libc=uclibc --posix-runtime -env-file=~/Homi/klee/build/../test.env --max-sym-array-size=4096 --max-instruction-time=30 --switch-type=internal --use-batching-search --batch-instructions=10000 -ignore-solver-failures --search=nurs:md2u trueprint.bc --sym-args 0 1 10 --sym-args 0 2 2 --sym-files 1 8 --sym-stdin 8 --sym-stdout

接下来看 Executor.cpp run函数,首先读取上一轮计算好的 F ′ F^{'} F P r a t i o P_{ratio} Pratio

if(Homi){ 
     Generate a set of features. 
    feat_id_map_=read_feature(dirname, naming);
    pratio_vec_=read_pruningratio(dirname, naming);
}
  • feat_id_map_ 对应 F ′ F^{'} F,是一个 map<string, unsigned int>,key为core branch condition对应的字符串,value为该condition在特征集合中的索引好,最下面的 feature_data 文件对应的 feat_id_map_{"(Eq 46 (Read w8 1 arg02))": 0, "(Eq 49 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))": 1, ...}

  • pratio_vec_ 是一个 vector<unsigned>, 长度为50。前面说过,代码中为了方便不是严格意义上在 R U N RUN RUN 中用 P r a t i o P_{ratio} Pratio 采样,而是先采样50个 r r r 值,然后修剪状态时轮着用,因此 pratio_vec_ 对应50个不同的 r r r 值。

同时在代码中

  • 每秒(prev_time 变量就是为了这个)都会采样当前状态列表长度,打印到 state_data 文件中,并且运行的时候每30秒并且状态列表长度大于500时才进行修剪,而不是像paper 算法1中每次选择状态之前都修剪。

  • 修剪状态时,先从 weights/{i}trials 文件夹下读取 w 文件中的 θ \theta θ 向量(对应变量 wvector_vector<double> 类型),而不是读取 P s t g y P_{stgy} Pstgy 再采样,同样的,执行klee时已经从 P s t g y P_{stgy} Pstgy 时采样了50个 θ \theta θ 向量轮着用。

  • fv_vecs = extract_feature(states, feat_id_map_); 根据 θ \theta θ 和计算每个状态的特征向量,fv_vecsvector<vector<int>> 类型,一般第1维是状态数量,第2维是特征长度。

    • extract_feature 对应paper中计算 f e a t ( s ) feat(s) feat(s) 的部分,如果 feat_id_map_ 第i个condition出现在状态 s 的路径约束中,那么 f e a t i ( s ) = 1 feat_i(s) = 1 feati(s)=1,反之为 0 0 0

    • extract_feature 最后一行会记录一个冗余特征索引,如果1个特征对所有得状态都是0或1,那这个特征是冗余特征。因此 fv_vecs 的最后1个向量记录的是冗余特征索引。

  • prune_states(wv_name, fv_vecs, wvector_, pruning_ratio); 进行状态修剪。

    • prune_states 返回值是被修剪得状态数量,如果有效特征维度(core branch condition)数量太小(特征数-冗余特征数 <= 5)那就不进行状态修剪。

    • 修剪状态的方式就是调用 terminateStateEarly,对于 terminateStateEarly 生成的testcase,klee会输出一个 .early 文件,里面记录 message,被修剪状态的 messagelearning_data字符串,里面记录了冗余特征索引、修剪比率 r r r、采用的向量 θ \theta θ

剩下就是正常进行符号执行。

4.3.Run_Gcov

这一部分对应算法2 8-9行 D D D 的收集过程,返回值 DataDict[str, Set[int]] 型,key为testcase文件名,value为该testcase独自覆盖到的分支(分支用gcov文件行号标识,最下面有gcov文件示例)。

  • 首先,Run_Gcov 会计算一个 d_tc_data 变量,该变量为 dict,将每个testcase文件路径映射为一个四元组 [budget, pratio, l_rfeats, wvector],即该testcase生成时klee运行时间限制( N ′ N^{'} N)、修剪比率 r r r,冗余特征索引、向量 θ \theta θ 存储的文件路径。

  • 然后,Run_Gcov对每个testcase:

    • 运行klee-replay。生成gcda文件。

    • 运行gcov -b xxx.gcda,生成对应gcov文件。

    • 解析gcov文件,找出该testcase覆盖到的分支,具体过程可参考CalCoverage。6.3部分给出了一个gcov示例,在gcov文件中,branch 开头的行代表一条分支,never 表示对应条件判断语句没被执行过,taken 0% 表示条件语句执行过但是没覆盖过该分支。在计算分支覆盖率时用gcov文件行号来标识一个分支。

    • 将该testcase覆盖情况写入变量 Data

运行完 Run_Gcov 后接下来会通过 SetCoverProblem 生成 G o o d D GoodD GoodD,返回值 topk_testcasesList[str] 型,记录 G o o d D GoodD GoodD 每一个testcase对应的文件路径。

4.4.Feature_Extractor

在运行klee时,作者添加了 -write-kqueries 参数,因此每个testcase都会生成一个对应的 kquery 文件,保存对应的路径约束。这篇blog最底部有个 kquery 文件示例。这个部分作者在kquery输出部分修改了一些代码,因为看kquery文件默认长这样:

array a[4] : w32 -> w8 = symbolic
array b[4] : w32 -> w8 = symbolic
(query [(Eq 10
             (ReadLSB w32 0 a))
         (Eq false
             (Eq 5
                 (ReadLSB w32 0 b)))]
        false)

显然,这换行操作太多了,一个条件就占了n行,而当下任务需要一个条件只占1行,作者修改的代码分别在:

Feature_Extractor 则是通过遍历一个testcase的 kquery 文件生成 F ′ F^{'} F F ′ F^{'} F 对应代码中的 feat_set,为 Set[str] 类型),每一个core branch condition应该为 Eq ... arg ... 这样的 str。一次最多提取200个core branch condition。

最后,将 feat_set dump到一个 {i}homi_trueprint_nurs:md2u_feature_data 文件中(i 为实验轮次),这个文件每轮循环都会被覆盖。

4.5.PruningStgy_Generator

相当于 P G e n e r a t o r ( G o o d D , N e w F ) PGenerator(GoodD, NewF) PGenerator(GoodD,NewF)。这部分用到了一个全局变量 tried_wv,该变量将.w文件路径映射为该文件中的向量 θ \theta θ

在paper中,作者提到了3种 ( P s t g y , P r a t i o ) (P_{stgy}, P_{ratio}) (Pstgy,Pratio) 生成方案:exploit, reverse_exploit, explore。其中explore几乎等同于随机概率选择,并没有用到之前计算的特征值。对于生成方案的选择,作者选用以下策略:

  • 如果循环次数在10次以内,那么选择explore方案。

  • 其它情况从3种方案种随机选择一种方案。

对于explore方案,其 θ \theta θ 生成代码对应333行 weights = [str(random.uniform(lower, upper)) for _ in range(len(features))] ,即 θ \theta θ 每一维度都是在 -1, 1之间均匀随机选择。

对于剩下2种方案,首先根据 Run_Gcov 生成的 d_tc_data 计算变量 d_feat_wvs,该变量对应paper中3.2.3的 W W W,将没有成为冗余特征的core branch condition(str 类型,比如 (Eq 46 (Read w8 1 arg02)))映射为在 G o o d D GoodD GoodD 中出现过的所有权重。对于core branch condition i i i(即特征集合第 i i i 个特征),d_feat_wvs[i] 对应 W i W_i Wi。剩下的代码则是按照论文所说的方法直接sample出50个 θ \theta θ 向量。

5.参考文献

Cha S , Oh H . Making symbolic execution promising by learning aggressive state-pruning strategy[C]// ESEC/FSE '20: 28th ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering. ACM, 2020.

6.其它示例

6.1.kquery

代码

#include <klee/klee.h>

int main() {
  int a, b, c, res;
  klee_make_symbolic(&a, sizeof(a), "a");
  klee_make_symbolic(&b, sizeof(b), "b");
  klee_make_symbolic(&c, sizeof(c), "c");
  if (a <= 0)
      a = a + 10;
  else
      a = a - 10;
  
  if (a <= b)
      res = a - b;
  else
      res = a + b;
  
  if (res > c)
      res = 1;
  else
      res = 0;
  return 0;
}

kquery:

对应 false --> false --> true 分支的kquery文件内容如下,前3行为输入变量,下面则是kquery语句()

array a[4] : w32 -> w8 = symbolic
array b[4] : w32 -> w8 = symbolic
array c[4] : w32 -> w8 = symbolic
(query [
(Eq false (Sle N0:(ReadLSB w32 0 a) 0))
(Eq false (Sle (Add w32 4294967286 N0) N1:(ReadLSB w32 0 b))) 
(Slt (ReadLSB w32 0 c) (Add w32 4294967286 (Add w32 N0 N1)))
] false)

6.2.feature_data内容

(Eq 46 (Read w8 1 arg02))
(Eq 49 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 43 (Read w8 8 arg00))
(Eq 43 (Read w8 7 arg00))
(Eq 52 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 90 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 67 (Extract w8 0 (SExt w32 (Read w8 2 arg00))))
(Eq 46 (Read w8 2 arg00))
(Eq 1 (Read w8 4 arg00))
(Eq 116 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 0 (Read w8 2 arg00))
(Eq 0 (Read w8 7 arg00))
(Eq 101 (Read w8 3 arg00))
(Eq 118 (Read w8 9 arg00))
(Eq 64 (Read w8 6 arg00))
(Eq 97 (Read w8 8 arg00))
(Eq 64 (Read w8 7 arg00))
(Eq 111 (Read w8 7 arg00))
(Eq 103 (Read w8 6 arg00))
(Eq 61 (Read w8 9 arg00))
(Eq 114 (Extract w8 0 (SExt w32 (Read w8 1 arg02))))
(Eq 120 (Read w8 8 arg00))
(Eq 46 (Read w8 3 arg00))
(Eq 1 (Read w8 0 arg02))
(Eq 112 (Read w8 8 arg00))
(Eq 0 (Read w8 1 arg01))
(Eq 105 (Read w8 7 arg00))
(Eq 100 (Read w8 5 arg00))
(Eq 72 (Read w8 9 arg00))
(Eq 45 (Read w8 0 arg00))
(Eq 100 (Read w8 9 arg00))
(Eq 72 (Read w8 7 arg00))
(Eq 108 (Read w8 2 arg00))
(Eq 107 (Read w8 8 arg00))
(Eq 45 (Read w8 0 arg01))
(Eq 115 (Read w8 9 arg00))
(Eq 0 (Read w8 1 arg02))
(Eq 32 (Read w8 2 arg00))
(Eq 52 (Extract w8 0 (SExt w32 (Read w8 1 arg02))))
(Eq 74 (Extract w8 0 (SExt w32 (Read w8 3 arg00))))
(Eq 111 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 109 (Read w8 7 arg00))
(Eq 76 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 117 (Read w8 5 arg00))
(Eq 46 (Read w8 8 arg00))
(Eq 104 (Read w8 8 arg00))
(Eq 115 (Read w8 6 arg00))
(Eq 111 (Read w8 3 arg00))
(Eq 76 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 104 (Read w8 1 arg02))
(Eq 117 (Read w8 4 arg00))
(Eq 115 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 119 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 120 (Read w8 9 arg00))
(Eq 0 (Read w8 5 arg00))
(Eq 99 (Read w8 9 arg00))
(Eq 114 (Read w8 7 arg00))
(Eq 51 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 1 (Read w8 2 arg00))
(Eq 109 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 100 (Read w8 2 arg00))
(Eq 117 (Read w8 6 arg00))
(Eq 108 (Read w8 4 arg00))
(Eq 104 (Read w8 7 arg00))
(Eq 64 (Read w8 9 arg00))
(Eq 45 (Read w8 9 arg00))
(Eq 0 (Read w8 4 arg00))
(Eq 66 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 0 (Read w8 9 arg00))
(Eq 84 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 116 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 0 (Read w8 3 arg00))
(Eq 89 (Extract w8 0 (SExt w32 (Read w8 4 arg00))))
(Eq 103 (Read w8 8 arg00))
(Eq 61 (Read w8 8 arg00))
(Eq 45 (Read w8 1 arg00))
(Eq 87 (Extract w8 0 (SExt w32 (Read w8 2 arg00))))
(Eq 99 (Read w8 2 arg00))
(Eq 100 (Read w8 7 arg00))
(Eq 111 (Read w8 8 arg00))
(Eq 117 (Extract w8 0 (SExt w32 (Read w8 2 arg00))))
(Eq 46 (Read w8 0 arg00))
(Eq 32 (Read w8 5 arg00))
(Eq 43 (Read w8 3 arg00))
(Eq 118 (Read w8 0 arg02))
(Eq 61 (Read w8 4 arg00))
(Eq 98 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 102 (Read w8 7 arg00))
(Eq 103 (Read w8 5 arg00))
(Eq 108 (Read w8 7 arg00))
(Eq 110 (Extract w8 0 (SExt w32 (Read w8 4 arg00))))
(Eq 106 (Read w8 7 arg00))
(Eq 1 (Read w8 0 arg00))
(Eq 45 (Read w8 0 arg02))
(Eq 46 (Read w8 4 arg00))
(Eq 97 (Read w8 7 arg00))
(Eq 51 (Extract w8 0 (SExt w32 (Read w8 1 arg02))))
(Eq 99 (Read w8 8 arg00))
(Eq 114 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 98 (Read w8 5 arg00))
(Eq 101 (Read w8 8 arg00))
(Eq 116 (Read w8 6 arg00))
(Eq 88 (Extract w8 0 (SExt w32 (Read w8 3 arg00))))
(Eq 103 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 119 (Read w8 7 arg00))
(Eq 108 (Read w8 6 arg00))
(Eq 37 (Read w8 6 arg00))
(Eq 46 (Read w8 7 arg00))
(Eq 120 (Read w8 4 arg00))
(Eq 101 (Read w8 9 arg00))
(Eq 101 (Read w8 7 arg00))
(Eq 68 (Read w8 7 arg00))
(Eq 45 (Read w8 8 arg00))
(Eq 109 (Read w8 9 arg00))
(Eq 50 (Extract w8 0 (SExt w32 (Read w8 1 arg02))))
(Eq 112 (Read w8 9 arg00))
(Eq 87 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 112 (Read w8 6 arg00))
(Eq 99 (Read w8 7 arg00))
(Eq 106 (Read w8 6 arg00))
(Eq 72 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 68 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 104 (Read w8 9 arg00))
(Eq 97 (Read w8 9 arg00))
(Eq 46 (Read w8 5 arg00))
(Eq 45 (Read w8 7 arg00))
(Eq 67 (Read w8 7 arg00))
(Eq 61 (Read w8 5 arg00))
(Eq 84 (Read w8 7 arg00))
(Eq 115 (Read w8 8 arg00))
(Eq 115 (Read w8 0 arg02))
(Eq 71 (Extract w8 0 (SExt w32 (Read w8 1 arg00))))
(Eq 37 (Read w8 7 arg00))
(Eq 120 (Read w8 3 arg00))
(Eq 111 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 46 (Read w8 6 arg00))
(Eq 97 (Read w8 3 arg00))
(Eq 118 (Read w8 8 arg00))
(Eq 108 (Read w8 9 arg00))
(Eq 98 (Extract w8 0 (SExt w32 (Read w8 3 arg00))))
(Eq 37 (Read w8 9 arg00))
(Eq 47 (Read w8 4 arg00))
(Eq 46 (Read w8 1 arg00))
(Eq 46 (Read w8 1 arg01))
(Eq 115 (Read w8 5 arg00))
(Eq 112 (Read w8 7 arg00))
(Eq 46 (Read w8 0 arg01))
(Eq 110 (Read w8 4 arg00))
(Eq 97 (Read w8 4 arg00))
(Eq 98 (Read w8 4 arg00))
(Eq 120 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 50 (Extract w8 0 (SExt w32 (Read w8 1 arg01))))
(Eq 49 (Extract w8 0 (SExt w32 (Read w8 1 arg02))))
(Eq 67 (Read w8 9 arg00))

gcov示例

gcov -b 生成对应的gcov文件。

       80:  315:	      if (n == (size_t) -2)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
        -:  316:		{
        -:  317:# if SUPPORT_OLD_MBRTOWC
    #####:  318:		  state = backup_state;
        -:  319:# endif
    #####:  320:		  break;
        -:  321:		}
       80:  322:	      if (n == (size_t) -1)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
        -:  323:		{
        -:  324:		  /* Remember that we read a byte, but don't complain
        -:  325:		     about the error.  Because of the decoding error,
        -:  326:		     this is a considered to be byte but not a
        -:  327:		     character (that is, chars is not incremented).  */
    #####:  328:		  p++;
    #####:  329:		  bytes_read--;
        -:  330:		}
        -:  331:	      else
        -:  332:		{
       80:  333:		  if (n == 0)
branch  0 taken 1% (fallthrough)
branch  1 taken 99%
        -:  334:		    {
        1:  335:		      wide_char = 0;
        1:  336:		      n = 1;
        -:  337:		    }
       80:  338:		  p += n;
       80:  339:		  bytes_read -= n;
       80:  340:		  chars++;
       80:  341:		  switch (wide_char)
branch  0 taken 1%
branch  1 taken 0%
branch  2 taken 1%
branch  3 taken 95%
branch  4 taken 0%
branch  5 taken 3%
        -:  342:		    {
        1:  343:		    case '\n':
        1:  344:		      lines++;
        -:  345:		      /* Fall through. */
        1:  346:		    case '\r':
        -:  347:		    case '\f':
        1:  348:		      if (linepos > linelength)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
    #####:  349:			linelength = linepos;
        1:  350:		      linepos = 0;
        1:  351:		      goto mb_word_separator;
        1:  352:		    case '\t':
        1:  353:		      linepos += 8 - (linepos % 8);
        1:  354:		      goto mb_word_separator;
       76:  355:		    case ' ':
       76:  356:		      linepos++;
        -:  357:		      /* Fall through. */
        -:  358:		    case '\v':
       78:  359:		    mb_word_separator:
       78:  360:		      words += in_word;
       78:  361:		      in_word = false;
       78:  362:		      break;
        2:  363:		    default:
        2:  364:		      if (iswprint (wide_char))
branch  0 taken 50% (fallthrough)
branch  1 taken 50%
        -:  365:			{
        1:  366:			  int width = wcwidth (wide_char);
call    0 returned 100%
        1:  367:			  if (width > 0)
branch  0 taken 100% (fallthrough)
branch  1 taken 0%
        1:  368:			    linepos += width;
        1:  369:			  if (iswspace (wide_char))
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
    #####:  370:			    goto mb_word_separator;
        1:  371:			  in_word = true;
        -:  372:			}
        2:  373:		      break;
        -:  374:		    }
        -:  375:		}
        -:  376:	    }
       80:  377:	  while (bytes_read > 0);
branch  0 taken 88%
branch  1 taken 13% (fallthrough)
        -:  378:
        -:  379:# if SUPPORT_OLD_MBRTOWC
       10:  380:	  if (bytes_read > 0)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
        -:  381:	    {
    #####:  382:	      if (bytes_read == BUFFER_SIZE)
branch  0 never executed
branch  1 never executed
        -:  383:		{
        -:  384:		  /* Encountered a very long redundant shift sequence.  */
    #####:  385:		  p++;
    #####:  386:		  bytes_read--;
        -:  387:		}
    #####:  388:	      memmove (buf, p, bytes_read);
        -:  389:	    }
       10:  390:	  prev = bytes_read;
        -:  391:# endif
        -:  392:	}
       12:  393:      if (linepos > linelength)
branch  0 taken 83% (fallthrough)
branch  1 taken 17%
       10:  394:	linelength = linepos;
       12:  395:      words += in_word;
        -:  396:    }
        -:  397:#endif
        -:  398:  else
        -:  399:    {
    #####:  400:      bool in_word = false;
    #####:  401:      uintmax_t linepos = 0;
        -:  402:
    #####:  403:      while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0)
call    0 never executed
branch  1 never executed
branch  2 never executed
        -:  404:	{
    #####:  405:	  const char *p = buf;
    #####:  406:	  if (bytes_read == SAFE_READ_ERROR)
branch  0 never executed
branch  1 never executed
        -:  407:	    {
    #####:  408:	      error (0, errno, "%s", file);
call    0 never executed
    #####:  409:	      ok = false;
    #####:  410:	      break;
        -:  411:	    }
        -:  412:
    #####:  413:	  bytes += bytes_read;
        -:  414:	  do
        -:  415:	    {
    #####:  416:	      switch (*p++)
branch  0 never executed
branch  1 never executed
branch  2 never executed
branch  3 never executed
branch  4 never executed
branch  5 never executed
        -:  417:		{
    #####:  418:		case '\n':
    #####:  419:		  lines++;
        -:  420:		  /* Fall through. */
    #####:  421:		case '\r':
        -:  422:		case '\f':
    #####:  423:		  if (linepos > linelength)
branch  0 never executed
branch  1 never executed
    #####:  424:		    linelength = linepos;
    #####:  425:		  linepos = 0;
    #####:  426:		  goto word_separator;
    #####:  427:		case '\t':
    #####:  428:		  linepos += 8 - (linepos % 8);
    #####:  429:		  goto word_separator;
    #####:  430:		case ' ':
    #####:  431:		  linepos++;
        -:  432:		  /* Fall through. */
        -:  433:		case '\v':
    #####:  434:		word_separator:
    #####:  435:		  words += in_word;
    #####:  436:		  in_word = false;
    #####:  437:		  break;
    #####:  438:		default:
    #####:  439:		  if (isprint (to_uchar (p[-1])))
call    0 never executed
branch  1 never executed
branch  2 never executed
        -:  440:		    {
    #####:  441:		      linepos++;
    #####:  442:		      if (isspace (to_uchar (p[-1])))
call    0 never executed
branch  1 never executed
branch  2 never executed
    #####:  443:			goto word_separator;
    #####:  444:		      in_word = true;
        -:  445:		    }
    #####:  446:		  break;
        -:  447:		}
        -:  448:	    }
    #####:  449:	  while (--bytes_read);
branch  0 never executed
branch  1 never executed
        -:  450:	}
    #####:  451:      if (linepos > linelength)
branch  0 never executed
branch  1 never executed
    #####:  452:	linelength = linepos;
    #####:  453:      words += in_word;
        -:  454:    }
        -:  455:
       13:  456:  if (count_chars < print_chars)
branch  0 taken 0% (fallthrough)
branch  1 taken 100%
    #####:  457:    chars = bytes;
        -:  458:
       13:  459:  write_counts (lines, words, chars, bytes, linelength, file_x);
call    0 returned 100%
       13:  460:  total_lines += lines;
       13:  461:  total_words += words;
       13:  462:  total_chars += chars;
       13:  463:  total_bytes += bytes;
       13:  464:  if (linelength > max_line_length)
branch  0 taken 77% (fallthrough)
branch  1 taken 23%
       10:  465:    max_line_length = linelength;
        -:  466:
       13:  467:  return ok;
        -:  468:}
        -:  469:
        -:  470:static bool
function wc_file called 18 returned 100% blocks executed 87%
       18:  471:wc_file (char const *file, struct fstatus *fstatus)
        -:  472:{
       18:  473:  if (! file || STREQ (file, "-"))
branch  0 taken 56% (fallthrough)
branch  1 taken 44%
branch  2 taken 30% (fallthrough)
branch  3 taken 70%
        -:  474:    {
       11:  475:      have_read_stdin = true;
        -:  476:      if (O_BINARY && ! isatty (STDIN_FILENO))
        -:  477:	freopen (NULL, "rb", stdin);
       11:  478:      return wc (STDIN_FILENO, file, fstatus);
call    0 returned 100%
        -:  479:    }
        -:  480:  else
        -:  481:    {
        7:  482:      int fd = open (file, O_RDONLY | O_BINARY);
call    0 returned 100%
        7:  483:      if (fd == -1)
branch  0 taken 71% (fallthrough)
branch  1 taken 29%
        -:  484:	{
        5:  485:	  error (0, errno, "%s", file);
call    0 returned 100%
        5:  486:	  return false;
        -:  487:	}
        -:  488:      else
        -:  489:	{
        2:  490:	  bool ok = wc (fd, file, fstatus);
call    0 returned 100%
        2:  491:	  if (close (fd) != 0)
call    0 returned 100%
branch  1 taken 0% (fallthrough)
branch  2 taken 100%
        -:  492:	    {
    #####:  493:	      error (0, errno, "%s", file);
call    0 never executed
    #####:  494:	      return false;
        -:  495:	    }
        2:  496:	  return ok;
        -:  497:	}
        -:  498:    }
        -:  499:}
        -:  500:
        -:  501:/* Return the file status for the NFILES files addressed by FILE.
        -:  502:   Optimize the case where only one number is printed, for just one
        -:  503:   file; in that case we can use a print width of 1, so we don't need
        -:  504:   to stat the file.  */
        -:  505:
        -:  506:static struct fstatus *
function get_input_fstatus called 17 returned 100% blocks executed 100%
       17:  507:get_input_fstatus (int nfiles, char * const *file)
        -:  508:{
       17:  509:  struct fstatus *fstatus = xnmalloc (nfiles, sizeof *fstatus);
call    0 returned 100%
        -:  510:
       17:  511:  if (nfiles == 1
branch  0 taken 94% (fallthrough)
branch  1 taken 6%
       32:  512:      && ((print_lines + print_words + print_chars
branch  0 taken 6% (fallthrough)
branch  1 taken 94%
       16:  513:	   + print_bytes + print_linelength)
        -:  514:	  == 1))
        1:  515:    fstatus[0].failed = 1;
        -:  516:  else
        -:  517:    {
        -:  518:      int i;
        -:  519:
       33:  520:      for (i = 0; i < nfiles; i++)
branch  0 taken 52%
branch  1 taken 48% (fallthrough)
       44:  521:	fstatus[i].failed = (! file[i] || STREQ (file[i], "-")
branch  0 taken 30% (fallthrough)
branch  1 taken 70%
       10:  522:			     ? fstat (STDIN_FILENO, &fstatus[i].st)
       27:  523:			     : stat (file[i], &fstatus[i].st));
branch  0 taken 59% (fallthrough)
branch  1 taken 41%
call    2 returned 100%
call    3 returned 100%
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值