文章目录
- Specification Process 规格化过程
- State Invariants
- 案例
- check ... expect
Alloy是一种用于构建和检查抽象模型的语言和工具。当Alloy说所有断言都成立时,这意味着你的模型或规格在给定范围内已成功通过了所有的断言检查。换句话说,对于你所定义的所有属性,Alloy的分析器已经找到了证据来确认这些属性在你的模型中是正确的。
然而,这并不一定意味着你的规格就是正确的。这主要有以下几个原因:
-
有限范围: Alloy的分析器基于有限范围进行检查,所以只能证明在给定范围内没有找到反例。如果在更大的范围内有反例存在,Alloy就可能无法检测到。
-
规格的完整性: Alloy只能检查你明确编写的断言。如果你的规格不完整,即遗漏了某些重要的断言,那么Alloy就无法检查这些遗漏的部分。因此,即使Alloy没有发现任何问题,你的规格也可能是错误的。
-
规格的正确性: Alloy不会检查你的规格是否正确地描述了你想要的行为。如果你的断言是基于错误的理解或者假设编写的,那么Alloy会根据这些错误的断言进行检查。因此,Alloy可能会告诉你所有断言都成立,但这并不意味着你的规格是正确的。
Specification Process 规格化过程
在Alloy中,规格化过程(Specification Process)是使用Alloy语言来形式化问题描述并建立模型的过程。下面是规格化过程的一般步骤:
-
理解问题域: 首先,需要理解你要建模的问题或系统的领域。这可能涉及到研究相关的背景知识,理解领域的基本概念和规则。
-
定义
sig
和relation
: 使用Alloy语言中的签名(sig)来表示领域中的实体,使用关系(relation)来表示实体之间的联系。例如,如果你正在建模一个图书管理系统,你可能需要定义"Book"和"User"这样的签名,以及"borrow"这样的关系。 -
定义约束: 约束是描述实体和关系必须满足的规则。在Alloy中,你可以使用facts来定义全局约束,也可以在签名中定义局部约束。
-
定义断言: 断言(assertion)是你对系统预期行为的声明。断言是需要被验证的,如果Alloy分析器找不到违反断言的例子,那么就可以说这个断言在给定范围内成立。
-
分析和验证: 使用Alloy分析器来检查你的模型是否满足所有的断言。如果分析器找到了违反断言的例子,那么就需要对模型进行调整。
-
迭代: 随着你对问题的理解深入,或者随着问题本身的发展,你可能需要反复进行以上步骤,以逐步完善你的模型。
今天本文的目的是介绍,如何让 alloy 帮我们实现一个 “不会产生 bad 行为的” 系统
State Invariants
在计算机科学中,状态不变式(state invariant)是一种描述系统在其整个生命周期中都必须维持的属性或条件的表述。换言之,无论系统经历了怎样的状态转移或操作,状态不变式都必须保持为真。
举例来说,如果你在设计一个银行系统,你可能会有一个状态不变式,即所有账户的余额总和(这是系统的一个状态)必须等于某个固定的值。这就意味着无论怎样进行转账操作(这是状态转移),这个总和都不应该改变。
在Alloy模型中,状态不变式通常通过 fact
关键字来定义,表示一个 全局的约束条件,必须在所有时刻都成立。例如,以下是一个Alloy模型中的状态不变式的示例:
fact {
all a: Account | a.balance >= 0
}
这个状态不变式指出,在任何时刻,所有账户的余额都不能小于0。如果存在使这个状态不变式不成立的状态转移或操作,那么我们就可以说这个操作是非法的,或者说这个模型是有缺陷的。
- 我们的系统其实是使用有限状态机来表示的:
- 如果想让整个系统在运行过程中不发生
bad
行为,其实只需要:- 保证在
init state
的时候不发生bad
行为,即系统在初始状态满足state invariant
- 保证在每次执行了
O
p
t
i
o
n
i
Option_i
Optioni 之后依然不发生
bad
行为, 在任何一个state
都要满足条件不变式子,并且在这个state
执行完operation
后也要保证同样满足state invariant
- 保证在
- 那么就能够保证系统在整个的运行过程中都不会有
bad
行为
- 第一个
assert
语句保证了在开始状态s
的时候,整个系统是满足条件不变式的 - 第二个
assert
语句保证的是对于所有的时间步骤 (采用了always
来约束所有的时间步骤),所有的state
首先满足条件不变式Inv
,并且在Op_i
操作之后的状态依然是满足条件不变式Inv[s]
的
案例
sig URL{}
sig Password{}
sig Username{}
sig Passbook{
var password: Username -> URL -> Password
}
// 定义一个 sig 来标志是否正常地 add 了 password
abstract sig Report{}
one sig Failed extends Report{}
one sig Success extends Report{}
pred init[pb: Passbook]{
no pb.password
}
//首先定义一个所有状态都需要遵守的不变量 inv,表示在所有的 state 中,对于任意给定一个 username 和 url, 他们的密码最多只有 1 个
pred inv[pb: Passbook]{
all user: Username, url: URL | lone pb.password[user][url]
}
// 当给定一个 user 和 url 的时候,需要保证给定条件的 password 尚未存在,才能正常 add
pred addPreCondition[pb: Passbook, user: Username, url: URL]{
no pb.password[user][url]
}
// 当 add 操作正常执行的 post condition
pred addPostCondition[pb: Passbook, user:Username, url:URL, psd: Password]{
pb.password' = pb.password + (user -> url -> psd)
}
// 正常增加 add,需要满足 addPrecondition 如果成功之后会满足 addPostCondition
pred addNormal[pb: Passbook, user: Username, url: URL, psd: Password, report: Report]{
addPreCondition[pb, user, url]
addPostCondition[pb, user, url, psd]
report in Success
}
// 如果增加 add 的时候不满足 precondition 则会进入这个 pred,那么 password 的状态保持不变
pred addException[pb: Passbook, user:Username, url:URL, psd:Password, report: Report]{
not addPreCondition[pb, user, url]
pb.password' = pb.password
report in Failed
}
// 在这个函数中封装 addNormal 和 addException,add 只可能是这两种行为
pred add[pb: Passbook, user:Username, url:URL, psd:Password, report: Report]{
addNormal[pb, user, url, psd, report] or addException[pb, user, url, psd, report]
}
// 开始的时候就保证不变量满足条件
assert initEstablished{
all pb: Passbook | init[pb] => inv[pb]
}
// 保证完成 add operation 之后依然满足不变量
assert AddOperationInv{
always all pb: Passbook, user: Username, url: URL, psd: Password, report: Report |
inv[pb] and add[pb, user, url, psd, report] => after (inv[pb])
}
check initEstablished for 3 expect 0
check AddOperationInv for 3 expect 0
这段Alloy代码定义了一个名为Passbook的密码管理器的模型。密码管理器可以存储和管理用户的用户名(Username)、密码(Password)和对应的网址(URL)。
-
在这个模型中,
Passbook
有一个名为password
的变量,它是一个映射,可以将每个用户名和网址映射到对应的密码。 -
然后,模型定义了两个报告类型,分别是成功(
Success
)和失败(Failed
),表示添加密码的操作是否成功。
接下来,模型定义了几个断言和谓词:
init
:这是初始化的谓词,表示一个Passbook在初始状态下没有任何密码。inv
:这是一个不变量,表示在任何状态下,对于给定的用户名和网址,其密码最多只有一个。addPreCondition
:这是添加密码操作的预条件,表示对于给定的用户名和网址,其密码尚未存在。addPostCondition
:这是添加密码操作的后条件,表示在操作后,新的密码被添加到映射中。addNormal
:表示正常添加密码的操作,当预条件满足时,后条件会被满足,并且报告为成功。addException
:表示异常情况,当预条件不满足时,密码的状态不改变,并且报告为失败。add
:表示添加密码的操作,它可以是正常的添加或者异常情况。
最后,模型定义了两个断言:
initEstablished
:断言初始化确实建立了不变量,即所有的Passbook在初始状态下都满足不变量。AddOperationInv
:断言添加操作保持了不变量,即对于所有的Passbook,如果一个添加操作满足不变量并且被执行,那么在操作后,不变量仍然被满足。
这个模型的目标是检查这两个断言是否被满足。check
语句被用来验证这些断言是否在所有可能的情况下都被满足,expect 0
表示我们期望没有违反断言的情况。
在之前的一个
passbook
例子中 https://blog.csdn.net/qq_42902997/article/details/131046821
在之前的这个例子中,我们通过fact
来假设这个password[usr][url]
始终最多只有一个值。
- 但是现在我们通过
pred
定义了一个不变量inv
这样做的目的是,为了证明整个过程中确实不会出现password[usr][url]
有超过一个值的情况! - 根据前面的知识,这里我们如果要保证
state
在不同状态下始终不出现bad
的情况,就要保证:- 在初始化的时候,所有的
state
满足不变量 - 在操作完成后,所有的
state
也要满足不变量
- 在初始化的时候,所有的
check … expect
在Alloy中,expect
关键字用于指定预期的反例数量。这个关键字通常在 check
语句后面使用。check
语句用于验证一个断言,如果存在违反该断言的情况,Alloy分析器会产生一个反例。
expect
关键字可以帮助你确保你的模型的行为符合你的预期。如果你已经知道有 n
个反例,你可以写 check ... expect n
然后,当你运行Alloy分析器时,如果它找到的反例数量与你的预期不符,你就知道可能需要进一步检查你的模型。
例如,你可能会写这样一个语句:
check someAssertion for 4 expect 0
这个语句的意思是,对于给定的范围(在这个例子中是 4
),检查断言 someAssertion
,并预期找不到任何反例(即 expect 0
)。如果Alloy分析器找到了反例,那么就意味着someAssertion
在给定的范围内不成立,可能需要进一步调整模型。
需要注意的是,expect
关键字只能提供一种预期的反例数量,并不能改变Alloy分析器的行为。即使你写了expect 0
,如果存在违反断言的情况,Alloy分析器仍然会找到反例。同样,即使你写了expect n
,如果没有足够的反例,Alloy分析器也无法创造出反例。