通过可达性分析进行残余运行时验证
在程序验证领域,我们常常面临着静态验证难以获取完整信息,而运行时验证又会带来较大开销的问题。本文将介绍一种通过可达性分析进行残余运行时验证的方法,旨在平衡静态验证和运行时验证的优势。
背景知识
在深入探讨残余分析之前,我们需要了解一些基础概念。
监控
- 事件与轨迹 :设 $\Sigma$ 为事件集合,$\Sigma^*$ 和 $\Sigma^\omega$ 分别是 $\Sigma$ 上所有有限和无限轨迹的集合。有限轨迹是事件的序列,可以用函数 $t : [1, n] \to \Sigma$ 来建模,其中 $n$ 是轨迹的长度。
- 属性与前缀 :属性 $\phi$ 是 $\Sigma$ 上的语言,是 $\Sigma^ $ 的子集。给定轨迹 $t \in \Sigma^ $,其前缀集合 $pre(t)$ 定义为 ${p \in \Sigma^ | \exists s \in \Sigma^ : t = ps}$。匹配前缀集合 $match_L(t)$ 是给定语言 $L$ 中轨迹 $t$ 的前缀集合,即 $match_L(t) = pre(t) \cap L$。
- 坏前缀与好前缀 :对于语言 $L \subseteq \Sigma^ $(或 $L \subseteq \Sigma^\omega$),有限轨迹 $u \in \Sigma^ $ 是 $L$ 的坏前缀,如果 $\forall w \in \Sigma^ : uw \notin L$(或 $\forall w \in \Sigma^\omega : uw \notin L$);$u$ 是 $L$ 的好前缀,如果 $\forall w \in \Sigma^ : uw \in L$(或 $\forall w \in \Sigma^\omega : uw \in L$)。
- 属性满足 :轨迹 $t \in \Sigma^ $ 满足属性 $\phi \subseteq \Sigma^ $,记为 $t \vDash \phi$,当且仅当 $t \in \phi$。对于安全属性 $\phi$ 及其坏前缀语言 $L$,$t \vDash \phi$ 当且仅当 $match_L(t) = \emptyset$;对于共安全属性 $\phi’$ 及其好前缀语言 $L’$,$t \vDash \phi$ 当且仅当 $match_{L’}(t) \neq \emptyset$。
例如,SafeIterator 监控器用于检查属性的违反情况。事件 $c$ 表示通过调用
list.iterator()
创建与列表关联的迭代器,事件 $u$ 表示通过调用
list.add(..)
更新列表,事件 $n$ 表示调用迭代器的
next
方法
iterator.next()
。监控器识别从运行程序接收的轨迹中的坏前缀,当看到模式 $c.n^*.u^+.n$ 时,监控器到达接受状态,表明整个运行违反了属性。
参数化监控
- 参数化事件与轨迹 :参数化监控器接收带有运行时信息的事件,允许分别监控程序中每个相关对象集。我们用 $X$ 表示参数化监控器定义的变量集合,$V$ 表示这些变量可以取的值集合。变量绑定 $\theta : X \rightharpoonup V$ 将监控器参数映射到其值,$B$ 是程序中所有可能绑定的集合。参数化事件 $e\langle\theta\rangle$ 是一个对 $(e, \theta) \in \Sigma \times B$,所有参数化事件的集合记为 $\Sigma\langle X \rangle$,参数化轨迹是 $\Sigma\langle X \rangle^*$ 中的一个单词。
- 参数化属性与投影 :参数化属性 $\Lambda X.\phi$ 定义在参数化事件轨迹上,即 $\Lambda X.\phi \subseteq \Sigma\langle X \rangle^*$。为了分别监控每个相关对象组,参数化监控器根据事件中携带的监控器参数绑定的值对参数化轨迹进行切片。通过投影函数 $\tau\downarrow\theta$ 将轨迹 $\tau$ 投影到所有看到的绑定上,投影结果是一组轨迹,称为投影轨迹,每个轨迹包含与程序中相关对象对应的非参数化事件,并发送到专门为该切片生成的监控器。
- 参数化属性满足 :参数化轨迹 $\tau \in \Sigma\langle X \rangle^*$ 满足参数化属性 $\Lambda X.\phi$,记为 $\tau \vDash \Lambda X.\phi$,当且仅当 $\forall t \in Proj(\tau) : t \vDash \phi$。
例如,程序可能生成的参数化轨迹 $\tau = (u, [l \mapsto o(l1)]) (c, [l \mapsto o(l1), i \mapsto o(it)]) (u, [l \mapsto o(l2)]) (u, [l \mapsto o(l2)]) (n, [l \mapsto o(l1), i \mapsto o(it)]) (c, [l \mapsto o(l1), i \mapsto o(it)]) (u, [l \mapsto o(l2)])$。如果 $o(l1) = o(l2)$,则 $Proj(\tau) = {ucuuncu}$;如果 $o(l1) \neq o(l2)$,则 $Proj(\tau) = {ucnc, uuu}$。
向上闭包
- 子词与超词 :对于单词 $x \in \Sigma^*$,其长度记为 $|x|$,$x_i$ 表示 $x$ 的第 $i$ 个字母,空单词记为 $\epsilon$。子词是通过从单词中任意位置删除某些字母得到的,超词是通过在单词中任意位置插入任意数量的字母得到的。如果存在位置 $0 < p_1 < p_2 < … p_l \leq |y|$ 使得 $x[i] = y[p_i]$ 对于所有 $1 \leq i \leq l = |x|$ 成立,则称单词 $x$ 是 $y$ 的子词,记为 $x \sqsubseteq y$,等价地,$y$ 是 $x$ 的超词。
- 向上闭包定义 :对于语言 $L \subseteq \Sigma^ $,其向上闭包记为 $\uparrow L$,定义为 ${x \in \Sigma^ | \exists y \in L : y \sqsubseteq x}$。对于任何语言 $L \subseteq \Sigma^*$,有 $L \subseteq \uparrow L$。如果 $L = \uparrow L$,则称语言 $L$ 是向上闭包的。对于由非确定性有限状态自动机(NFA)识别的正则语言 $L$,可以通过简单地添加转换而不增加状态数量来获得识别 $\uparrow L$ 的 NFA。
程序、CFG 和插桩
给定程序 $P$,设 $Methods$ 是其所有方法的集合,$Instructions$ 是所有字节码指令的集合,$Instructions_m$ 是方法 $m$ 的所有指令集合。方法 $m$ 的控制流图 $CFG_m = \langle B_m, E_m \rangle$ 是一个有向图,其中 $B_m$ 是节点集合,每个指令是一个节点,$E_m \subseteq B_m \times B_m$ 是连接节点到其后续节点的边。节点 $b$ 中的指令记为 $b.instr$,$b.entry$(或 $b.exit$)是一个布尔值,表示 $b$ 是否是方法的入口节点(或出口节点)。为了监控程序,我们通过插桩将其执行抽象为在运行时提取的事件轨迹,插桩可以用函数 $instrument : Instructions^ \rightharpoonup \Sigma\langle X \rangle^ $ 来建模。
参数化属性的残余分析
我们的目标是验证程序 $P$ 是否满足某个参数化属性 $\Lambda X.\phi$。程序的行为通过其在运行时可以产生的参数化事件轨迹集合来抽象,记为 $[P] \subseteq \Sigma\langle X \rangle^*$。验证问题可以表述为检查程序的所有轨迹是否满足属性:$P \vDash \Lambda X.\phi \stackrel{def}{=} \forall \tau \in [P] : \tau \vDash \Lambda X.\phi$。
静态地探索参数化轨迹需要程序的完整调用图知识,而探索投影轨迹需要产生它们的对象之间的别名关系知识,这些信息通常在静态时是不可判定的。运行时验证虽然可以获得这些信息,但会给程序的执行带来开销,并且这种开销通常与轨迹的大小正相关。因此,我们的兴趣是静态地验证程序的部分内容,并将残余部分留给运行时验证。
我们提出的残余分析静态地识别程序中一组指令 $S_P$,这些指令在运行时可以从监控器端安全地静默/忽略而不影响验证。忽略指令意味着在其执行时不需要产生事件。我们的目标是构造残余插桩函数 $residual : Instructions^ \to (S_P \to \Sigma\langle X \rangle^ )$。设 $Runs \subseteq Instructions^*$ 是程序 $P$ 的所有可能运行的集合。用 $residual$ 插桩程序理想情况下应该产生比 $instrument$ 更短的轨迹,但对于两者,我们应该得到相同的监控判决。残余分析应满足的条件如下:
$\forall r \in Runs : |residual(r)| \leq |instrument(r)| \land residual(r) \vDash \Lambda X.\phi \Leftrightarrow instrument(r) \vDash \Lambda X.\phi$
为了静态地执行残余分析并产生集合 $S_P$,我们可以通过构造一个集合 $[\hat{P}] \supseteq [P]$ 来过度近似程序行为。这允许我们探索程序可以产生的所有参数化轨迹,但也包括程序可能永远不会产生的轨迹。残余分析应该检查忽略某些指令是否不影响 $[\hat{P}]$ 中任何轨迹的验证判决,并安全地假设在 $[P]$ 中具有相同的效果。然而,由于 $[\hat{P}]$ 是一个过度近似,分析可能会产生误报,即某些指令实际上可以被忽略,但分析结果却相反。在接下来的内容中,我们考虑 $[\hat{P}]$ 的一个子集 $[\check{P}_m]$ 进行残余分析,这些轨迹是在单个方法中完全产生的。
通过过程内可达性分析进行残余分析
我们将展示如何在过程内级别使用可达性分析进行残余分析。由于我们避免了数据流和指针分析,因此我们没有程序的静态调用图和变量别名关系。
捕获行为
我们的分析分别处理每个方法,但需要谨慎。如果一个方法接收一个对象作为参数,该对象的类型能够产生属性字母表中的事件,那么我们不能假设任何先前的行为。因此,我们将此类方法排除在分析之外。出于同样的原因,我们排除所有操作涉及类型的静态实例的方法。
对于每个方法 $m$,我们将两种类型的指令映射到事件,并丢弃所有其他与我们的分析无关的指令。我们保留属性规范中产生 $\Sigma$ 中事件的指令,以及可能允许任何对象引用从方法 $m$ 的上下文中逃逸的指令;我们为这些指令引入新的逃逸事件 (#)。逃逸事件包括对类字段的赋值、通过引用传递对象的方法调用以及返回对象的返回语句。然而,我们的分析允许用户指定一个安全指令列表 $SafeList$,该列表基于编译类型信息(如方法名、包名和类型名以及操作码)定义。例如,调用
System.out.print(l1.toString())
是一个安全指令。所有逃逸事件且不在 $SafeList$ 中的指令都添加到集合 $Esc_m$ 中。
给定属性的字母表 $\Sigma$ 和方法 $m$ 的控制流图 $CFG_m = \langle B_m, E_m \rangle$,我们将 $B_m$ 中的每个块 $b$ 替换为 $b’$,并将其指令映射到一个事件,构造 CFG 自动机如下:
$b’.instr = b.instr.map\left(\begin{array}{l}i \mapsto \begin{cases}i & \text{if } i \in \Sigma\# & \text{else if } i \in Esc_m\\epsilon & \text{otherwise}\end{cases}\end{array}\right)$
CFG 自动机定义
:给定映射后的 $CFG_m$,CFG 自动机是一个非确定性有限状态自动机 $Ac_m = (\Sigma \cup {#}, Q, \delta, q_0, F)$,构造如下:
- $Q = {q_b | b \in B_m}$
- $q_0 = {q_b | b \in B_m \land b.entry = true }$
- $F = Q$
- $\delta = {\langle q_b, s, q_{b’}\rangle | \langle b, b’ \rangle \in E_m \land b.instr = s}$
每个控制流图中的节点现在表示为 CFG 自动机中的一个状态。我们将所有状态设为接受状态,并合并由 $\epsilon$ 转换连接的状态。通过遍历 CFG 自动机,我们可以探索方法 $m$ 在运行时可以采取的路径,从而探索其可以产生的参数化轨迹。
例如,图 3 展示了从第 2 节中的方法构造的 CFG 自动机,每个状态对应于程序中我们感兴趣的一个指令。从图 3 中的自动机可以探索两条轨迹,$t_1 = ucuuncu$,对应于示例 2 中的参数化轨迹 $\tau$,$t_2 = ucncu$。
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([q0]):::startend -->|u| B([q1]):::process
B -->|c| C([q2]):::process
C -->|u| D([q3]):::process
D -->|c| E([q4]):::process
E -->|u| F([q5]):::process
F -->|n| G([q6]):::process
G -->|c| H([q7]):::process
H -->|u| I([q8]):::startend
扩展坏前缀自动机
我们现在描述如何处理过度近似并扩展坏前缀自动机。
处理变量可能别名 :回想一下,参数化轨迹 $\tau \in \Sigma\langle X \rangle^*$ 在运行时根据事件中携带对象之间的别名关系投影到 $Proj(\tau)$ 中可能的多个轨迹。在运行时,参数化监控器可以使用此别名关系进行投影,但在静态残余分析中,此信息不可用。我们的核心思想是避免进行数据流分析,并假设方法中产生事件的对象可能别名。对于两个事件,我们的分析应该考虑它们绑定的对象必须别名和必须不别名的情况。在前一种情况下,两个事件将投影到同一轨迹中,在后一种情况下,它们将投影到不同的轨迹中。
例如,考虑可以从示例 4 中的 CFG 自动机探索的轨迹 $t_1$。在运行时,如果程序采取这样的控制流路径,它会发出一个参数化轨迹,根据 $l_1$ 和 $l_2$ 是否别名产生示例 3 中的投影轨迹之一。由于我们在静态时避免产生别名关系并假设 $l_1$ 和 $l_2$ 可能别名,我们应该在残余分析中考虑这两种情况的析取。因此,残余分析应检查的轨迹 $pt_1 = {ucuuncu, ucnc, uuu}$。对于示例 4 中的 $t_2$,通过相同的推理,应检查的轨迹是 $pt_2 = {ucncu, ucnc, u}$。因此,对于方法 $m$,应检查的轨迹集合是 $pt_1 \cup pt_2$。
CFG 自动机允许我们探索程序在运行时可以采取的不同路径,但其轨迹过于粗糙,可能包含与运行时同一轨迹不对应的事件。我们注意到,这相当于生成并考虑轨迹的所有子词,其中真实轨迹可以是自动机可以探索的轨迹的任何子词。因此,为了安全地处理不同的投影,我们使用第 3.3 节中的坏前缀语言 $L$ 的向上闭包 $\uparrow L$。通过使用向上闭包 $\uparrow L$,我们可以在完整轨迹或其任何子词中识别坏前缀,因为 $L \subseteq \uparrow L$,从而允许我们在所有可能的投影轨迹中找到坏前缀。然而,我们通过从初始和最终状态中移除 $\Sigma$ 自环来限制闭包,因为我们希望找到匹配坏前缀的最短路径。
处理逃逸事件 :在构造 CFG 自动机时,我们引入了逃逸 # 事件。由于我们的分析分别分析每个方法,我们不知道 # 转换中可能发生的情况。我们必须假设它们可能产生方法分析未跟踪的事件。为了安全地处理它们,我们在坏前缀自动机中从每个状态添加一个 # 转换到其所有可达状态。直观地说,这意味着当在路径中遇到 # 事件时,我们假设该路径不再安全,并且可能匹配坏前缀。
扩展坏前缀自动机 :给定由自动机 $A_{bad\phi} = (\Sigma, Q, \delta, Q_0, F)$ 识别的坏前缀语言 $L(bad\phi)$ 及其扩展转换函数 $\hat{\delta}$,扩展坏前缀自动机定义为 $A^{\uparrow}_{bad\phi} = (\Sigma \cup {#}, Q, \delta’, Q_0, F)$,其中:
$\delta’ = \delta \setminus { \langle q, s, q \rangle | s \in \Sigma \land (q \in F \lor q \in Q_0) } \cup { \langle q, s, q \rangle | s \in \Sigma \cup {#} \land q \in Q \land q \notin Q_0 \land q \notin F } \cup { \langle q, #, q’ \rangle | q, q’ \in Q \land \exists w \in \Sigma^*: \hat{\delta}(q, w) = q’ \land q’ \notin F }$
扩展自动机具有相同的状态。我们从初始和最终状态移除自环,以找到匹配坏前缀的最短路径;通过在所有其他状态添加 $\Sigma$ 和 # 自环来添加向上闭包;从每个状态添加 # 转换到其可达状态。
例如,图 4 展示了为 SafeIterator 属性构造的自动机 $A^{\uparrow}_{bad\phi}$。回想示例 1 中的模式 $c.n^*.u^+.n$,新自动机现在将识别这样的模式,同时处理上述两种过度近似。
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([q0]):::startend -->|c| B([q1]):::process
B -->|n| B
B -->|u| C([q2]):::process
C -->|u| C
C -->|n| D([q3]):::startend
A -->|#| E([q4]):::process
B -->|#| E
C -->|#| E
E -->|s| E
E -->|#| F([q5]):::process
F -->|s| F
F -->|#| G([q6]):::process
G -->|s| G
G -->|#| H([q7]):::process
H -->|s| H
H -->|#| I([q8]):::process
I -->|s| I
I -->|#| J([q9]):::process
J -->|s| J
J -->|#| K([q10]):::process
K -->|s| K
K -->|#| L([q11]):::process
L -->|s| L
L -->|#| M([q12]):::process
M -->|s| M
M -->|#| N([q13]):::process
N -->|s| N
N -->|#| O([q14]):::process
O -->|s| O
O -->|#| P([q15]):::process
P -->|s| P
P -->|#| Q([q16]):::process
Q -->|s| Q
Q -->|#| R([q17]):::process
R -->|s| R
R -->|#| S([q18]):::process
S -->|s| S
S -->|#| T([q19]):::process
T -->|s| T
T -->|#| U([q20]):::process
U -->|s| U
U -->|#| V([q21]):::process
V -->|s| V
V -->|#| W([q22]):::process
W -->|s| W
W -->|#| X([q23]):::process
X -->|s| X
X -->|#| Y([q24]):::process
Y -->|s| Y
Y -->|#| Z([q25]):::process
Z -->|s| Z
Z -->|#| AA([q26]):::process
AA -->|s| AA
AA -->|#| AB([q27]):::process
AB -->|s| AB
AB -->|#| AC([q28]):::process
AC -->|s| AC
AC -->|#| AD([q29]):::process
AD -->|s| AD
AD -->|#| AE([q30]):::process
AE -->|s| AE
AE -->|#| AF([q31]):::process
AF -->|s| AF
AF -->|#| AG([q32]):::process
AG -->|s| AG
AG -->|#| AH([q33]):::process
AH -->|s| AH
AH -->|#| AI([q34]):::process
AI -->|s| AI
AI -->|#| AJ([q35]):::process
AJ -->|s| AJ
AJ -->|#| AK([q36]):::process
AK -->|s| AK
AK -->|#| AL([q37]):::process
AL -->|s| AL
AL -->|#| AM([q38]):::process
AM -->|s| AM
AM -->|#| AN([q39]):::process
AN -->|s| AN
AN -->|#| AO([q40]):::process
AO -->|s| AO
AO -->|#| AP([q41]):::process
AP -->|s| AP
AP -->|#| AQ([q42]):::process
AQ -->|s| AQ
AQ -->|#| AR([q43]):::process
AR -->|s| AR
AR -->|#| AS([q44]):::process
AS -->|s| AS
AS -->|#| AT([q45]):::process
AT -->|s| AT
AT -->|#| AU([q46]):::process
AU -->|s| AU
AU -->|#| AV([q47]):::process
AV -->|s| AV
AV -->|#| AW([q48]):::process
AW -->|s| AW
AW -->|#| AX([q49]):::process
AX -->|s| AX
AX -->|#| AY([q50]):::process
AY -->|s| AY
AY -->|#| AZ([q51]):::process
AZ -->|s| AZ
AZ -->|#| BA([q52]):::process
BA -->|s| BA
BA -->|#| BB([q53]):::process
BB -->|s| BB
BB -->|#| BC([q54]):::process
BC -->|s| BC
BC -->|#| BD([q55]):::process
BD -->|s| BD
BD -->|#| BE([q56]):::process
BE -->|s| BE
BE -->|#| BF([q57]):::process
BF -->|s| BF
BF -->|#| BG([q58]):::process
BG -->|s| BG
BG -->|#| BH([q59]):::process
BH -->|s| BH
BH -->|#| BI([q60]):::process
BI -->|s| BI
BI -->|#| BJ([q61]):::process
BJ -->|s| BJ
BJ -->|#| BK([q62]):::process
BK -->|s| BK
BK -->|#| BL([q63]):::process
BL -->|s| BL
BL -->|#| BM([q64]):::process
BM -->|s| BM
BM -->|#| BN([q65]):::process
BN -->|s| BN
BN -->|#| BO([q66]):::process
BO -->|s| BO
BO -->|#| BP([q67]):::process
BP -->|s| BP
BP -->|#| BQ([q68]):::process
BQ -->|s| BQ
BQ -->|#| BR([q69]):::process
BR -->|s| BR
BR -->|#| BS([q
### 通过可达性分析进行残余运行时验证(续)
#### 可达性分析算法
在完成了对方法行为的捕获以及坏前缀自动机的扩展后,我们可以进行可达性分析算法的设计,其目的是在控制流图中找到安全和违反路径。
##### 算法思路
可达性分析算法基于模型检查的思想,将控制流图和扩展后的坏前缀自动机结合起来,遍历控制流图中的路径,同时模拟扩展坏前缀自动机的状态转移。通过这种方式,我们可以判断路径是否会导致属性的违反。
##### 具体步骤
1. **初始化**:从控制流图的入口节点开始,将其对应的 CFG 自动机状态和扩展坏前缀自动机的初始状态作为起始状态对。
2. **路径遍历**:
- 对于当前状态对,根据 CFG 自动机的转移规则,找到所有可能的下一个状态。
- 对于每个可能的下一个状态,更新扩展坏前缀自动机的状态,根据扩展坏前缀自动机的转移函数进行状态转移。
3. **状态判断**:
- 如果扩展坏前缀自动机到达接受状态,则说明当前路径是违反路径。
- 如果遍历完所有可能的路径都没有到达接受状态,则说明当前路径是安全路径。
4. **结果记录**:记录所有找到的安全路径和违反路径。
通过这种方式,我们可以全面地分析方法的控制流图,找出可能导致属性违反的路径。
##### 算法示例
假设我们有一个简单的控制流图,其对应的 CFG 自动机状态转移如下:
| 当前状态 | 输入事件 | 下一个状态 |
| ---- | ---- | ---- |
| q0 | u | q1 |
| q1 | c | q2 |
| q2 | u | q3 |
| q3 | n | q4 |
扩展坏前缀自动机的转移规则如前文所述。从 q0 开始,我们可以按照以下步骤进行可达性分析:
1. 初始状态对为 (q0, A↑badϕ 的初始状态)。
2. 输入事件 u,CFG 自动机转移到 q1,更新扩展坏前缀自动机的状态。
3. 输入事件 c,CFG 自动机转移到 q2,再次更新扩展坏前缀自动机的状态。
4. 依次类推,直到遍历完所有可能的路径。
```mermaid
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([(q0, 初始状态)]):::startend -->|u| B([(q1, 状态 1)]):::process
B -->|c| C([(q2, 状态 2)]):::process
C -->|u| D([(q3, 状态 3)]):::process
D -->|n| E([(q4, 状态 4)]):::startend
分析的正确性
我们需要确保通过可达性分析得到的结果是可靠的,即分析具有正确性。
正确性的定义
分析的正确性意味着:
- 如果分析判定某条路径是违反路径,那么在实际运行中,该路径确实会导致属性的违反。
- 如果分析判定某条路径是安全路径,那么在实际运行中,该路径不会导致属性的违反。
正确性的保证
为了保证分析的正确性,我们采取了以下措施:
1.
过度近似
:通过构造 $[\hat{P}] \supseteq [P]$ 对程序行为进行过度近似,虽然可能会产生误报,但可以确保不会遗漏真正的违反路径。
2.
扩展坏前缀自动机
:通过处理变量可能别名和逃逸事件,扩展坏前缀自动机能够更准确地识别违反路径。
3.
可达性分析算法
:算法基于模型检查的思想,全面遍历控制流图中的路径,确保不会遗漏任何可能的情况。
通过这些措施的综合作用,我们可以保证分析的正确性,从而为程序的验证提供可靠的依据。
总结与展望
本文介绍了一种通过可达性分析进行残余运行时验证的方法,旨在平衡静态验证和运行时验证的优势。通过对参数化属性的残余分析,我们可以静态地识别程序中可以安全忽略的指令,减少运行时验证的开销。具体步骤如下:
1.
背景知识学习
:了解监控、参数化监控、向上闭包、程序、CFG 和插桩等相关概念。
2.
参数化属性的残余分析
:明确验证目标,通过过度近似程序行为,静态地识别可以忽略的指令。
3.
过程内可达性分析
:
- 捕获方法的行为,构造 CFG 自动机。
- 扩展坏前缀自动机,处理变量可能别名和逃逸事件。
- 进行可达性分析,找到安全和违反路径。
4.
确保分析的正确性
:通过过度近似、扩展坏前缀自动机和可达性分析算法保证分析结果的可靠性。
未来的研究方向可以包括:
- 进一步优化过度近似的方法,减少误报的发生。
- 扩展分析方法,使其能够处理更复杂的程序结构和属性。
- 结合机器学习等技术,提高分析的效率和准确性。
通过不断的研究和改进,我们可以更好地利用残余运行时验证技术,提高程序的可靠性和性能。
超级会员免费看
32

被折叠的 条评论
为什么被折叠?



