摘要
文章提出了一个产生归纳循环不变式的方法。其中的循环不变式是由线性整数约束的布尔组合组成。
文章的主要想法是,通过回溯(backtracking)将霍尔逻辑形式的验证条件和逻辑推因(logical abduction)方法结合起来,基于量词消去(quantifier elimination)来推测候选不变变量。或者说,不断加入新的条件进行试错,直到找到真正的循环不变式或者所有候选都不满足。
技术的亮点是:
- 只关注对证明程序正确性来说必要的变量
- 可以推测随机的线性约束的布尔组合(包括析取disjunctions)
介绍
自动取推断循环不变式是程序分析领域非常基本的问题,在软件验证、编译优化和程序理解方面都有非常重要的应用。
在自动生成循环不变式的方法中比较突出的有抽象解释、一些基于约束的技术、反例引导的抽象细化(counterexample guided abstraction refinement),和基于插值的方法。
inductive:归纳的。如果某个变量可以由前提推出(implied),并且在循环每次迭代的前后都保持不变,那么我们就说这个循环不变量是inductive的
文章展示了一个自动生成 可以表示为线性整数约束程序变量的布尔组合的 归纳循环不变量的方法。由于给定循环的前置条件(precondition)后,归纳循环不变量的争取性可以被局部(locally)的检查,所以归纳循环不变量在许多程序验证系统里都起到了至关重要的作用。
Q:如何理解局部(locally)?
文章中的方法主要是为了做程序验证,所以,文章只关心对证明程序正确性来说是必要的不变量。此外,文章之关心数值循环的不变量,因为这样的不变量在许多基础的程序验证任务中非常的重要,比如证明数组越界或者缓冲区溢出。
文章技术的一个显着特点是它可以自然地推断出线性整数约束的任意布尔组合的不变量。在这里,线性整数约束是指整数上的线性不等式以及整除性谓词。此外,还包括了disjunction
∧
\wedge
∧,和implication
⇒
\Rightarrow
⇒。比如可以推断出下面的循环不变量
(
x
%
2
=
0
⇒
x
+
2
y
<
n
)
∧
(
x
%
2
=
1
⇒
x
+
2
y
=
n
)
(x \% 2=0 \Rightarrow x+2 y<n) \wedge(x \% 2=1 \Rightarrow x+2 y=n)
(x%2=0⇒x+2y<n)∧(x%2=1⇒x+2y=n)
另一个优点是文章中的技术不需要提前给出循环不变量的句法模板,比如
a
x
+
b
y
≤
c
ax+by\leq c
ax+by≤c。其他的工具可能是求解a,b,c但是文章中不需要给出模板求解未知数,而是自动给出不变量的形式。
方法概览
logical abduction: 逻辑溯因,给定结论,推断出确实的前提或者说是假设
文章的主要想法是利用回溯进行搜索,将霍尔逻辑式的程序推理和逻辑溯因(logical abduction)结合起来。
verification condition: 将所需要的性质抽象而成的条件,如果最后发现该条件成立,那么程序的性质成立
开始时,从最弱但是总是正确的循环不变量true开始,然后不断产生验证条件(verification condition, VC),他的正确性保证了程序的正确行。
如果一个VC是无效的,那么我们技术会使用溯因推断的方法来找到其所需要的前提,以此来使当前的VC正确;这个前提就代表了对新循环不变量的推测,这个推测用于加强现有的不变量,并加入到原来的VC中,之后产生新的VC来检查推测的正确性。这个过程不断的进行,除非所有的VC都成立或者推断出了矛盾。前面的情况代表了我们找到了一个足够强的推断循环不变量来证明程序的正确行,然后算法寻找终止;后一种情况表示进行了错误的推断,所以搜索程序会进行回溯并且尝试不同的推断。
文章中产生的验证条件是 χ ⇒ γ \chi\Rightarrow\gamma χ⇒γ形式的语句的conjunction,断言循环不变量的归纳性和程序中断言的正确性。具体来说,右边的 γ \gamma γ是我们想要产生的目标,例如循环的前置;而左边的 χ \chi χ表示程序中已知的事实,例如已经推断出来的循环不变量。
给定一个无效的VC: χ ⇒ γ \chi\Rightarrow\gamma χ⇒γ,该技术会尝试加入一些条件来增强 χ \chi χ,进而使得当前的VC正确。也就是说,找到这样一个 ψ \psi ψ,让当前的循环不变量 χ \chi χ满足:
- ⊨ ( χ ∧ ψ ) ⇒ γ \models(\chi \wedge \psi) \Rightarrow \gamma ⊨(χ∧ψ)⇒γ
- SAT ( χ ∧ ψ ) \operatorname{SAT}(\chi \wedge \psi) SAT(χ∧ψ)
其中(1)表示,添加上 ψ \psi ψ不需要其他条件就可以推出 γ \gamma γ。同时(2)表示添加上 ψ \psi ψ之后,必须与左边的 χ \chi χ一致(所谓的一致可以简单理解为二者不会推到出冲突),因为 χ \chi χ代表了已知(或推断)出来的不变量。
推断 ψ \psi ψ的方法被叫做logic abduction,由Peirce给出。因此,文章背后的一个主要观点是,逻辑溯因对于找到足以使程序的验证条件有效的循环不变量的适当强化很有用。
现在,虽然推断的强化 ψ \psi ψ 修正了不正确的VC,但它仍然是一种推测,并且可能并不正确。所以,为了保证方法的soundness,文章在每步产生新的VC时都会检测所有不变量的正确性。如果 ψ \psi ψ是不正确的推测,那么新的VC会包含无效的、需要被修正的命题。如果算法没办法再去修正当前的VC,那么意味着当前的推断是错误的,必须要进行回溯。在另一方面,如果所有VC都成立了,那么意味着所有的推测都是争取的。然后程序终止,并且找到了足够强的循环不便量来证明程序的正确性。但是,文中的技术没有终止性或完整性保证;算法确实有可能在无限的推测链中走不出来。
由于文章执行溯因的算法是基于量词消除的,因此本文只关注可在 Presburger 算法中表达的循环不变量的推理。
执行示例
下面是一个例子,程序如下:
第四行的注解[ ϕ \phi ϕ]表示 ϕ \phi ϕ是一个占位符,用来表示这个循环的循环不变式。目标就是推测一个具体的公式 φ \varphi φ,使得它足够强,能够在证明第8行assertion。
首先,初始化占位符
ϕ
\phi
ϕ为true,使用霍尔逻辑式的推断,然后产生VC,在这个例子中为:
t
u
r
e
⇒
(
f
l
a
g
⇒
a
=
b
)
ture\Rightarrow(flag\Rightarrow a=b)
ture⇒(flag⇒a=b)
该 VC 表示当前循环不变量 true 足够强以暗示post-condition条件,即标志 ⇒ a = b。显然,这个VC式不正确的,所以我们尝试找到一个
ϕ
\phi
ϕ以使得:
(
t
r
u
e
∧
ϕ
)
⇒
(
f
l
a
g
⇒
a
=
b
)
(true \wedge \phi)\Rightarrow(flag \Rightarrow a= b)
(true∧ϕ)⇒(flag⇒a=b)
有很多
ϕ
\phi
ϕ都可以使得该表达式成立,比如
¬
f
l
a
g
,
a
=
b
,
f
l
a
g
⇒
a
=
b
,
⋯
\neg flag,a=b,flag\Rightarrow a=b,\cdots
¬flag,a=b,flag⇒a=b,⋯,这里假设选择了
ϕ
=
f
l
a
g
⇒
a
=
b
\phi=flag\Rightarrow a=b
ϕ=flag⇒a=b,也就是说
f
l
a
g
⇒
a
=
b
flag\Rightarrow a=b
flag⇒a=b是推测出来的循环不变量。
我们继续生成新的 VC,以断言的候选不变量的正确性。假设程序继续运行,a++,b+=j-1。那么我们会更新VC为:
(
f
l
a
g
⇒
a
=
b
)
⇒
(
f
l
a
g
⇒
(
a
+
1
=
b
+
j
−
1
)
)
(flag\Rightarrow a = b) \Rightarrow (flag \Rightarrow (a + 1 = b + j - 1))
(flag⇒a=b)⇒(flag⇒(a+1=b+j−1))
每次更新,只会更新和VC中存在的相关的变量,比如这里的a和b。
这个 VC 简单地规定我们的候选不变量是归纳的,因为蕴涵的右侧是候选不变量的最弱先决条件。然而这个VC还不是有效的,所以要继续寻找一个
ϕ
\phi
ϕ,使得:
(
f
l
a
g
⇒
a
=
b
∧
ϕ
)
⇒
(
f
l
a
g
⇒
(
a
+
1
=
b
+
j
−
1
)
)
(
∗
)
(flag\Rightarrow a = b \wedge\phi) \Rightarrow (flag \Rightarrow (a + 1 = b + j - 1)) \ (*)
(flag⇒a=b∧ϕ)⇒(flag⇒(a+1=b+j−1)) (∗)
继续搜索可能使其成立的
ϕ
\phi
ϕ,可以得到候选:
¬
f
l
a
g
,
j
=
i
+
1
,
f
l
a
g
⇒
j
=
i
+
1
\neg flag, j=i+1,flag\Rightarrow j=i+1
¬flag,j=i+1,flag⇒j=i+1。
假设我们选择了不正确的候选,比如
j
=
i
+
1
j=i+1
j=i+1,这意味着新的候选循环不变式变成了
j
=
i
+
1
∧
(
f
l
a
g
⇒
a
=
b
∧
ϕ
)
j=i+1 \wedge(flag\Rightarrow a = b \wedge\phi)
j=i+1∧(flag⇒a=b∧ϕ)。因为
j
+
1
j+1
j+1在最开始时并不成立(也就是说当前条件
j
=
i
+
1
∧
(
f
l
a
g
⇒
a
=
b
∧
ϕ
)
j=i+1 \wedge(flag\Rightarrow a = b \wedge\phi)
j=i+1∧(flag⇒a=b∧ϕ)在第1-3行并不成立),所以我们拒绝
j
=
i
+
1
j=i+1
j=i+1称为候选,进行回溯,回到
(
f
l
a
g
⇒
a
=
b
)
(flag\Rightarrow a = b)
(flag⇒a=b)的状态,并尝试选择一个新的候选来使得(*)成立,比如:
ϕ
=
(
f
l
a
g
⇒
j
=
i
+
1
)
\phi=(flag\Rightarrow j = i + 1)
ϕ=(flag⇒j=i+1)
现在,循环不变量边成了:
I
=
(
flag
⇒
(
a
=
b
∧
j
=
i
+
1
)
)
\mathcal{I}=(\text { flag } \Rightarrow(a=b \wedge j=i+1))
I=( flag ⇒(a=b∧j=i+1))
这表明新的推测
I
\mathcal{I}
I现在正确了,但是
I
\mathcal{I}
I仍然不是归纳的(inductive),因为此时的VC依然时不成立的:
(
flag
⇒
(
a
=
b
∧
j
=
i
+
1
)
)
⇒
(
f
l
a
g
⇒
(
a
+
1
=
b
+
j
−
1
∧
(
i
%
2
=
0
⇒
j
=
i
+
1
)
∧
(
i
%
2
≠
0
⇒
i
=
j
)
)
(\text { flag } \Rightarrow(a=b \wedge j=i+1)) \Rightarrow (flag \Rightarrow (a + 1 = b + j - 1\wedge (i \%2=0\Rightarrow j=i+1) \wedge (i\%2\neq0 \Rightarrow i = j))
( flag ⇒(a=b∧j=i+1))⇒(flag⇒(a+1=b+j−1∧(i%2=0⇒j=i+1)∧(i%2=0⇒i=j))
这里最外边的蕴含右侧的部分是
I
\mathcal{I}
I的最弱前提条件。为了让这个VC有效,要继续执行溯因。其中一个解是
f
l
a
g
⇒
i
%
2
=
0
flag\Rightarrow i \%2=0
flag⇒i%2=0,所以,我们最后得到了新的循环不变量,通过将这个解和
I
\mathcal{I}
I及逆行合取。
I
′
=
(
flag
⇒
(
a
=
b
∧
j
=
i
+
1
∧
i
%
2
=
0
)
)
\mathcal{I}^{\prime}=(\text { flag } \Rightarrow(a=b \wedge j=i+1 \wedge i \% 2=0))
I′=( flag ⇒(a=b∧j=i+1∧i%2=0))
这是,VC变得有效。换句话说
I
′
\mathcal{I}^{\prime}
I′是一个推断不变量,并且可以证明第八行的assertion。
语言
P r o g r a m π : = s Program \ \pi \quad:=s Program π:=s
Statement
s
:
=
skip
∣
v
:
=
e
∣
s
1
;
s
2
∣
choose
s
1
s
2
∣
while
C
[
ϕ
]
do
{
s
}
∣
assert p
∣
assume p
\begin{aligned} \text { Statement } s \quad:=& \text { skip } \mid v:=e \\ &\left|s_{1} ; s_{2}\right| \text { choose } s_{1} s_{2} \\ & |\text { while } C[\phi] \text { do }\{s\} \\ &|\text{assert p} \ | \ \text{assume p} \end{aligned}
Statement s:= skip ∣v:=e∣s1;s2∣ choose s1s2∣ while C[ϕ] do {s}∣assert p ∣ assume p
E
x
p
r
e
s
s
i
o
n
e
:
=
v
∣
i
n
t
∣
e
1
+
e
2
Expression \ e \quad:=v \mid int \mid e_{1}+e_{2}
Expression e:=v∣int∣e1+e2
∣
e
∗
int
∣
e
%
int
Conditional
C
:
=
e
1
⊙
e
2
(
⊙
∈
{
<
,
>
,
=
}
)
∣
C
1
∧
C
2
∣
C
1
∨
C
2
∣
¬
C
\begin{aligned} & \mid e * \text { int } \mid e \% \text { int } \\ \text { Conditional } C:=& e_{1} \odot e_{2}(\odot \in\{<,>,=\}) \\ &\left|C_{1} \wedge C_{2}\right| C_{1} \vee C_{2} \mid \neg C \end{aligned}
Conditional C:=∣e∗ int ∣e% int e1⊙e2(⊙∈{<,>,=})∣C1∧C2∣C1∨C2∣¬C
:=表示定义,program由一个多条statement组成;statement可以是跳过、变量赋值(变量=表达式)、语句序列、choose语句(没有决定执行s1或者s2中的哪一个)等等。
这种语言的循环中未知的循环不变式用 ϕ \phi ϕ表示,但并不影响程序的语义。对每个占位符 ϕ \phi ϕ,文章中的技术的目标就是推断出一个具体的逻辑法则 ψ \psi ψ,使得 ψ \psi ψ既是推断的,又足够蕴含出循环的后置条件。
使用 i n v s ( π ) invs(\pi) invs(π)来表示程序 π \pi π中的所有占位符。
产生归纳循环不变量的算法
INVGEN用来产生归纳循环不变量。输入是一个程序 π \pi π输出是 Δ \Delta Δ,是一个map,保存着每个占位符 ϕ \phi ϕ到循环不变量 ψ \psi ψ的映射。如果不能证明程序是争取的,那么算法返回一个的map ∅ \empty ∅。
算法最开始将每个占位符都初始化为true,然后调用VERIFY对每个占位符代表的循环不变量进行加强。
在VERIFY程序中,输入 Δ \Delta Δ代表现在推断得到的循环不变量。先用 Δ \Delta Δ调用VCGEN(将在后面说明)来生成最弱前置条件 χ \chi χ和相应的VC φ \varphi φ 。生成的 VC φ \varphi φ断言 ∆ 中的每个候选不变量 φ \varphi φ 是归纳的,并且它蕴含循环后置条件。具体来说,生成的VC是 ( β ∧ ϕ ) ⇒ γ (\beta \wedge\phi) \Rightarrow \gamma (β∧ϕ)⇒γ语句的合取。这里, ϕ \phi ϕ是潜在加强当前循环不变量的占位符。 β , γ \beta,\gamma β,γ中不含任何占位符,并且由 Δ \Delta Δ中的候选循环不变量生成。如果VC φ \varphi φ中的所有占位符替换成true之后,公式是有效的,这意味着不再需要加强了,当前的 Δ \Delta Δ就是一个可以验证当前程序的解。
另一方面,如果elim( φ \varphi φ)不是有效的,这意味着当前的循环不变量不够强,我们需要继续进行加强。为了达成这个目的,程序会选择VC中仅包含一个占位符的无效 φ i \varphi_i φi,然后利用ABDUCE来找到一个可以使得其有效的候选公式集合S。然后递归的进行调用,直到找到一组有效的解,或者无法继续搜索下去。
VC的生成
VCGEN可以由一系列的推导规则表示。推导规则的形式如下:
Δ
,
χ
⊢
s
:
χ
′
,
φ
\Delta,\chi \vdash s : \chi^{\prime},\varphi
Δ,χ⊢s:χ′,φ
这里
Δ
\Delta
Δ是候选循环不变量map,将占位符
ϕ
\phi
ϕ映射到公式
ψ
\psi
ψ。
VC φ \varphi φ是 ( β ∧ ϕ ) ⇒ γ (\beta \wedge\phi) \Rightarrow \gamma (β∧ϕ)⇒γ语句的合取。 ϕ \phi ϕ是对当前循环不变量一个可能的加强, β , γ \beta,\gamma β,γ中不含任何占位符。
语句的含义是,如果 elim( φ \varphi φ)(也就是将VC中所有的占位符都替换成true)是有效的,那么 { χ ′ } s { χ } \{\chi'\}s \{\chi \} {χ′}s{χ}是一个有效的霍尔三元组。
P[e/x]:该记号的意思是将谓词公式P中出现的x全都换成表达式e。例如2中,意思就是不需要任何条件,{ χ [ e / v ] } v : = e { χ } \chi [e/v]\}v:=e\{\chi\} χ[e/v]}v:=e{χ}表示将表达式中的v全都替换成e,结果无条件成立。比如{y+5>5}x=y+5{x>5}无条件成立,因为y+5相当于把x>5中的x替换成了y+5
具体的规则如下:
- Δ , χ ⊢ s k i p : χ , true ‾ \overline{\Delta, \chi \vdash skip:\chi, \text { true }} Δ,χ⊢skip:χ, true
- Δ , χ ⊢ v : = e : χ [ e / v ] , true ‾ \overline{\Delta, \chi \vdash v:=e: \chi[e / v], \text { true }} Δ,χ⊢v:=e:χ[e/v], true
- Δ , χ ⊢ assume C : C ⇒ χ , true ‾ \overline{\Delta, \chi \vdash \text { assume } C: C \Rightarrow \chi, \text { true }} Δ,χ⊢ assume C:C⇒χ, true
- Δ , χ ⊢ a s s e r t C : χ ∧ C , t r u e ‾ \overline{\Delta, \chi \vdash assert\ C: \chi \wedge C, true} Δ,χ⊢assert C:χ∧C,true
- Δ , χ ⊢ s 2 : χ 2 , φ 2 Δ , χ 2 ⊢ s 1 : χ 1 , φ 1 Δ , χ ⊢ s 1 ; s 2 : χ 1 , φ 1 ∧ φ 2 \begin{gathered} \Delta, \chi \vdash s_{2}: \chi_{2}, \varphi_{2} \\ \Delta, \chi_{2} \vdash s_{1}: \chi_{1}, \varphi_{1} \\ \hline \Delta, \chi \vdash s_{1} ; s_{2}: \chi_{1}, \varphi_{1} \wedge \varphi_{2} \end{gathered} Δ,χ⊢s2:χ2,φ2Δ,χ2⊢s1:χ1,φ1Δ,χ⊢s1;s2:χ1,φ1∧φ2
- Δ , χ ⊢ s 1 : χ 1 , φ 1 Δ , χ ⊢ s 2 : χ 2 , φ 2 Δ , χ ⊢ choose s 1 s 2 : χ 1 ∧ χ 2 , φ 1 ∧ φ 2 \begin{gathered} \Delta, \chi \vdash s_{1}: \chi_{1}, \varphi_{1} \\ \Delta, \chi \vdash s_{2}: \chi_{2}, \varphi_{2} \\ \hline \Delta, \chi \vdash \text { choose } s_{1} s_{2}: \chi_{1} \wedge \chi_{2}, \varphi_{1} \wedge \varphi_{2} \end{gathered} Δ,χ⊢s1:χ1,φ1Δ,χ⊢s2:χ2,φ2Δ,χ⊢ choose s1s2:χ1∧χ2,φ1∧φ2
- Δ , Δ ( ϕ ) ⊢ s : χ ′ , φ ′ φ 1 = ( ¬ C ∧ Δ ( ϕ ) ∧ ϕ ) ⇒ χ φ 2 = ( C ∧ Δ ( ϕ ) ∧ ϕ ) ⇒ χ ′ Δ , χ ⊢ while C [ ϕ ] do { s } : Δ ( ϕ ) , φ 1 ∧ φ 2 ∧ φ ′ \begin{gathered} \Delta, \Delta(\phi) \vdash s: \chi^{\prime}, \varphi^{\prime} \\ \varphi_{1}=(\neg C \wedge \Delta(\phi) \wedge \phi) \Rightarrow \chi \\ \varphi_{2}=(C \wedge \Delta(\phi) \wedge \phi) \Rightarrow \chi^{\prime} \\ \hline \Delta, \chi \vdash \text { while } C[\phi] \text { do }\{s\}: \Delta(\phi), \varphi_{1} \wedge \varphi_{2} \wedge \varphi^{\prime} \end{gathered} Δ,Δ(ϕ)⊢s:χ′,φ′φ1=(¬C∧Δ(ϕ)∧ϕ)⇒χφ2=(C∧Δ(ϕ)∧ϕ)⇒χ′Δ,χ⊢ while C[ϕ] do {s}:Δ(ϕ),φ1∧φ2∧φ′
1-6式是很常规的最弱前置条件计算。7中利用了当前 Δ \Delta Δ给出的不变量。更具体的,循环的最弱前置条件是 Δ ( ϕ ) \Delta(\phi) Δ(ϕ),代表了当前推测的循环不变量。在这个规则中, χ ′ \chi' χ′是循环体s用来产生候选循环不变量 Δ ( ϕ ) \Delta(\phi) Δ(ϕ)的最弱前置条件。VC中的 φ 1 \varphi_1 φ1是说在循环退出的时候,加强 φ \varphi φ得到的候选不变量 Δ ( ϕ ) \Delta(\phi) Δ(ϕ)可以蕴含循环的前置条件 χ \chi χ; φ 2 \varphi_2 φ2是说,当 ϕ \phi ϕ成立时,执行循环会重建不变量 Δ ( ϕ ) \Delta(\phi) Δ(ϕ)。特别来说,如果 ϕ \phi ϕ是true,那么这些是while循环的标准VC。(Q:这里没有理解)。额外的占位符 ϕ \phi ϕ允许我们通过abduction产生的假设弱化VC。
定理(Soundness):如果 Δ , χ ⊢ s : χ ′ , φ \Delta,\chi \vdash s : \chi^{\prime},\varphi Δ,χ⊢s:χ′,φ , φ \varphi φ是可推倒的,并且elim( φ \varphi φ)是有效的,那么 { χ } s { χ ′ } \{\chi\}s\{\chi'\} {χ}s{χ′}是有效的。
证明:假设
φ
1
∧
φ
2
∧
φ
′
\varphi_{1} \wedge \varphi_{2} \wedge \varphi^{\prime}
φ1∧φ2∧φ′是有效的,我们需要证明
{
Δ
(
ϕ
)
}
w
h
i
l
e
C
[
ϕ
]
d
o
{
s
}
{
χ
}
(1)
\{\Delta(\phi)\}\ while\ C[\phi] d o\{s\}\{\chi\} \tag{1}
{Δ(ϕ)} while C[ϕ]do{s}{χ}(1)
通过归纳假设(最上面的条件),我们有
{
χ
′
}
s
{
Δ
(
ϕ
)
}
\{\chi'\}s\{\Delta(\phi)\}
{χ′}s{Δ(ϕ)}。因为elim(
φ
2
\varphi_2
φ2)蕴含
C
∧
Δ
(
ϕ
)
⇒
χ
′
C \wedge \Delta(\phi) \Rightarrow \chi'
C∧Δ(ϕ)⇒χ′,通过加强前提(联立前两个式子)我们有
{
C
∧
Δ
(
ϕ
)
}
s
{
Δ
(
ϕ
)
}
\{C \wedge \Delta(\phi)\} s\{\Delta(\phi)\}
{C∧Δ(ϕ)}s{Δ(ϕ)}。通过标准的关于loop的霍尔逻辑规则,有:
{
Δ
(
ϕ
)
}
while
C
do
{
s
}
{
Δ
(
ϕ
)
∧
¬
C
}
(2)
\{\Delta(\phi)\} \text { while } C \text { do }\{s\}\{\Delta(\phi) \wedge \neg C\} \tag{2}
{Δ(ϕ)} while C do {s}{Δ(ϕ)∧¬C}(2)
因为elim(
φ
1
\varphi_1
φ1)`是有效的,我们有
¬
C
∧
Δ
(
ϕ
)
⇒
χ
\neg C \wedge \Delta(\phi) \Rightarrow \chi
¬C∧Δ(ϕ)⇒χ。最后,通过对(2)使用前置条件弱化规则,我们得到(1)。
通过Abduction产生候选
给定一个形式为 ( χ ∧ ϕ ) ⇒ γ (\chi \wedge \phi)\Rightarrow\gamma (χ∧ϕ)⇒γ的无效的VC,abduce程序会尝试加入一些条件 ψ \psi ψ代替 ϕ \phi ϕ来增强条件,进而使得当前的VC正确。也就是说,找到这样一个 ψ \psi ψ,让当前的循环不变量 χ \chi χ满足:
- ⊨ ( χ ∧ ψ ) ⇒ γ \models(\chi \wedge \psi) \Rightarrow \gamma ⊨(χ∧ψ)⇒γ
- SAT ( χ ∧ ψ ) \operatorname{SAT}(\chi \wedge \psi) SAT(χ∧ψ)
一个显然但是并不有用的解是 ϕ = γ \phi=\gamma ϕ=γ。至于为什么没有用,考虑对某个循环体s,断言某些候选不变量$ I$ 的归纳性的 VC : I ∧ C = w p ( s , I ) I\wedge C=wp(s,I) I∧C=wp(s,I)。
那么,推断出的结果 γ \gamma γ与其最弱前提条件 w p ( s , I ) wp(s,I) wp(s,I)来加强当前的循环不变量 I I I。然而, w p ( s , I ) wp(s,I) wp(s,I)太弱,通常会导致divergence。事实上,从循环后置条件 I 开始,并反复将其与 wp(s, I) 连接,相当于展开循环体。
所以,需要找到在逻辑上比最弱前提条件更强的一些约束。这里主要的强化机制是量词消除(quantifier elimination)。
那么如何使用量词消除技术来解决abduction问题呢,首先,下面的蕴含式:
⊨
(
χ
∧
ψ
)
⇒
γ
\models (\chi \wedge \psi) \Rightarrow \gamma
⊨(χ∧ψ)⇒γ
可以被写成:
ψ
⊨
χ
⇒
γ
\psi \models \chi \Rightarrow \gamma
ψ⊨χ⇒γ
考虑公式
χ
⇒
γ
\chi \Rightarrow \gamma
χ⇒γ中自由变量集合V的子集,我们有:
∀
V
.
χ
⇒
γ
⊨
χ
⇒
γ
\forall V. \chi \Rightarrow \gamma \models \chi \Rightarrow \gamma
∀V.χ⇒γ⊨χ⇒γ
所以任何公式
φ
\varphi
φ在逻辑上与
∀
V
.
χ
⇒
γ
\forall V. \chi \Rightarrow \gamma
∀V.χ⇒γ而且不与
χ
\chi
χ冲突的解都是答案。
定义:(Universal subset)我们把变量集合V叫做 ϕ \phi ϕ关于 ψ \psi ψ的Universal subset(US),如果公式 ∀ ( V . ϕ ) ∧ ψ \forall (V.\phi) \wedge \psi ∀(V.ϕ)∧ψ可满足。
所以,如果V是 χ ⇒ ϕ \chi \Rightarrow \phi χ⇒ϕ关于 ψ \psi ψ的US,我们可以通过从公式 χ ⇒ ϕ \chi \Rightarrow \phi χ⇒ϕ中通过Perburge算数中的量词消除技术投影(projection)变量V。根据经验,这种方式产生的候选循环不变量非常有用。直觉上,量词消除是有用的,因为它允许我们投射出不相关的变量并防止不变量被归纳。
考虑下面的程序:
最开始的循环不变量是true,产生的VC是 x ≥ n ⇒ y ≥ n x\geq n \Rightarrow y \geq n x≥n⇒y≥n,并不包含归纳不变量。这是,如果将通过universally quantifying将n投影出来,并使用量词消除技术,我们可以得到 y ≥ x y\geq x y≥x,这就是一个循环不变量。直观地说,在这里,量词消除允许我们将循环的最后一次迭代推广到归纳断言。
图4展示了产生所有 χ ⇒ γ \chi \Rightarrow \gamma χ⇒γ关于 χ \chi χ的US,通过将US中的变量投影出来从而推断出来一个候选的加强条件集合。
一个重要的需要考虑的点是先考虑哪一个US,如果找到的加强条件天若,那么算法可以持续时间很长,或者找到了一个无穷的推测链。如果找到的太强,往往算法会很快找到冲突,并且回退。所以算法会先从最强的条件开始依次进行考虑。
在每次遍历while循环后,首先计算出最大 χ ⇒ γ \chi \Rightarrow \gamma χ⇒γ的US,与集合 θ \theta θ中的所有约束一致,最开始只包括 χ \chi χ。如果没有MUS(在其他文章中定义),算法返回集合S作为候选的强化变量。然而,如果存在MUS V,那么会通过量词消去技术投影V中的所有变量,维护一个新的候选变量 ψ \psi ψ。这个策略确保了在下一次遍历维护的MUS和之前的不同,因为V不可能成为和 ¬ ψ ≡ ¬ ( ∀ V . ( χ ⇒ γ ) ) \neg \psi \equiv \neg(\forall V .(\chi \Rightarrow \gamma)) ¬ψ≡¬(∀V.(χ⇒γ))一致的MUS。所以该方法返回的是约束力递减的一个集合。
注意到这个算法并不完全,不能找到所有的候选,而只能找出来使用量词消除技术可以产生的。