module LastPass
/*
* LastPass password map
*
* A simple example to explain basics of Alloy.
*
* The 'PassBook' keeps track of a set of users' passwords for a set of URLs.
* For each User/URL pair, there is one password. Passwords can be added,
* deleted, looked up, and changed.
* A user can also request all of their password for all URLs.
*
* author: Tim Miller; updated by Toby Murray for Alloy 6, including simplifications
*/
sig URL {}
sig Username {}
sig Password {}
sig PassBook {var password : Username -> URL -> Password}
fact NoDuplicates
{
always all pb : PassBook, user : Username, url : URL | lone pb.password[user][url]
}
//Add a password for a new user/url pair
pred add [pb : PassBook, url : URL, user: Username, pwd: Password] {
no pb.password[user][url]
pb.password' = pb.password + (user->url->pwd)
}
//Delete an existing password
pred delete [pb : PassBook, url : URL, user: Username] {
one pb.password[user][url]
pb.password' = pb.password - (user->url->Password)
}
//Update an existing password
pred update [pb : PassBook, url : URL, user: Username, pwd: Password] {
one pb.password[user][url]
pb.password' = pb.password ++ (user->url->pwd)
}
//Return the password for a given user/URL pair
fun lookup [pb: PassBook, url : URL, user : Username] : lone Password {
pb.password[user][url]
}
//Check if a user's passwords for two urls are the same
pred samePassword [pb : PassBook, url1, url2 : URL, user : Username] {
lookup [pb, url1, user] = lookup [pb, url2, user]
}
//Retrieve all of the passwords and the url they are for, for a given user
pred retrieveAll [pb: PassBook, user : Username, pwds : URL -> Password] {
pwds = (pb.password)[user]
}
//Initialise the PassBook to be empty
pred init [pb: PassBook] {
no pb.password
}
//If we add a new password, then we get this password when we look it up
assert addWorks {
all pb : PassBook, url : URL, user : Username, p : Password |
add [pb, url, user, p] => (after (lookup [pb, url, user]) = p)
}
//If we update an existing password, then we get this password when we look it up
assert updateWorks {
all pb : PassBook, url : URL, user : Username, p, p2 : Password |
after (lookup [pb, url, user]) = p =>
(add [pb, url, user, p2] => (after (lookup [pb, url, user]) = p2))
}
//If we add and then delete a password, we are back to 'the start'
assert deleteIsUndo {
all pb : PassBook, url : URL, user : Username, pwd : Password |
(add [pb, url, user, pwd] ; delete [pb, url, user])
=> pb.password = pb.password''
}
run add for 3 but 1 PassBook
run add for 5 URL, 5 Username, 10 Password, 1 PassBook
check addWorks for 3 but 1 PassBook expect 0
check updateWorks for 3 but 1 PassBook expect 0
check deleteIsUndo for 1 but 1 PassBook
题目: Write an assertion that checks that, after running the
add
operation, no two users have the same password for the same URL unless that was already the case before add was run. Check this assertion on your model. Of course, this assertion is trivially false, as users should never know anything the existence of another password. The exercise is intended to have you think about how to model this assertion.
pred noSharedPasswords[pb : PassBook] {
all user1, user2 : Username, url : URL |
user1 != user2 => no (pb.password[user1][url] & pb.password[user2][url])
}
assert addNoSharedPasswords {
all pb : PassBook, url : URL, user : Username, pwd : Password |
noSharedPasswords[pb] =>
add[pb, url, user, pwd] => after (noSharedPasswords[pb])
}
check addNoSharedPasswords for 5 but 1 PassBook expect 0
counter example
出现反例表示在你的模型中存在至少一个情况,使得你的断言addNoSharedPasswords不成立。这说明,在执行 add 操作之后,即使在添加操作之前没有两个用户对同一URL有相同的密码,也可能出现两个用户对同一URL有相同的密码。
让我们来看看可能的原因:
- 首先,add 操作是向密码本 pb 添加一个新的用户/URL/密码组合。这并不保证新添加的密码与其他用户对同一URL的密码不同。 这意味着,如果添加的新密码与某个用户对同一URL的现有密码相同,那么 add 操作就会导致出现共享密码。
- 因此,你的断言 addNoSharedPasswords 在当前的模型中可能不成立,这可能是由于 add 操作并未尽可能地避免添加共享密码。
- 为了确保断言的成立,需要修改
add
操作以避免添加共享密码。例如,你可以在 add 操作中添加一个检查,确保新密码与其他用户对同一URL的密码不同。这可能会使你的模型变得复杂,但是可以提高密码的安全性。- 总的来说,模型的问题在于,它允许 add 操作创建共享密码,这与 addNoSharedPasswords 断言的要求相冲突。你需要修改你的模型以消除这种冲突。
add
改成如下这样:pred add [pb : PassBook, url : URL, user: Username, pwd: Password] {
no pb.password[user][url]
// 在添加之前注明,新增的这个密码 pwd 不能和其他 user 的密码冲突
all otherUser: Username | pwd != pb.password[otherUser][url]
pb.password' = pb.password + (user->url->pwd)
}
module LastPass
/*
* LastPass password map
*
* A simple example to explain basics of Alloy.
*
* The 'PassBook' keeps track of a set of users' passwords for a set of URLs.
* For each User/URL pair, there is one password. Passwords can be added,
* deleted, looked up, and changed.
* A user can also request all of their password for all URLs.
*
* author: Tim Miller; updated by Toby Murray for Alloy 6, including simplifications
*/
sig URL {}
sig Username {}
sig Password {}
sig PassBook {var password : Username -> URL -> Password}
fact NoDuplicates
{
always all pb : PassBook, user : Username, url : URL | lone pb.password[user][url]
}
//Add a password for a new user/url pair
pred add [pb : PassBook, url : URL, user: Username, pwd: Password] {
no pb.password[user][url]
// 对 add 操作进行限制,避免加入的时候与其他的 user 的密码重复
all otherUser: Username | pwd != pb.password[otherUser][url]
pb.password' = pb.password + (user->url->pwd)
}
//Delete an existing password
pred delete [pb : PassBook, url : URL, user: Username] {
one pb.password[user][url]
pb.password' = pb.password - (user->url->Password)
}
//Update an existing password
pred update [pb : PassBook, url : URL, user: Username, pwd: Password] {
one pb.password[user][url]
pb.password' = pb.password ++ (user->url->pwd)
}
//Return the password for a given user/URL pair
fun lookup [pb: PassBook, url : URL, user : Username] : lone Password {
pb.password[user][url]
}
//Check if a user's passwords for two urls are the same
pred samePassword [pb : PassBook, url1, url2 : URL, user : Username] {
lookup [pb, url1, user] = lookup [pb, url2, user]
}
//Retrieve all of the passwords and the url they are for, for a given user
pred retrieveAll [pb: PassBook, user : Username, pwds : URL -> Password] {
pwds = (pb.password)[user]
}
//Initialise the PassBook to be empty
pred init [pb: PassBook] {
no pb.password
}
//If we add a new password, then we get this password when we look it up
assert addWorks {
all pb : PassBook, url : URL, user : Username, p : Password |
add [pb, url, user, p] => (after (lookup [pb, url, user]) = p)
}
//If we update an existing password, then we get this password when we look it up
assert updateWorks {
all pb : PassBook, url : URL, user : Username, p, p2 : Password |
after (lookup [pb, url, user]) = p =>
(add [pb, url, user, p2] => (after (lookup [pb, url, user]) = p2))
}
//If we add and then delete a password, we are back to 'the start'
assert deleteIsUndo {
all pb : PassBook, url : URL, user : Username, pwd : Password |
(add [pb, url, user, pwd] ; delete [pb, url, user])
=> pb.password = pb.password''
}
pred noSharedPasswords[pb : PassBook] {
all user1, user2 : Username, url : URL |
user1 != user2 => no (pb.password[user1][url] & pb.password[user2][url])
}
assert addNoSharedPasswords {
all pb : PassBook, url : URL, user : Username, pwd : Password |
noSharedPasswords[pb] =>
add[pb, url, user, pwd] => after (noSharedPasswords[pb])
}
check addNoSharedPasswords for 5 but 1 PassBook expect 0
//Addresses and data
sig Addr {}
sig Data {}
//A cache system consists of main memory and cached memory, but mapping addresses to data
one sig CacheSystem {
var main, cache: Addr -> lone Data
}
//Initially there is no memory allocated
pred init [c: CacheSystem] {
no c.main + c.cache
}
//Write data to a specified address in the cache
pred write [c : CacheSystem, a: Addr, d: Data] {
c.main' = c.main
c.cache' = c.cache ++ a -> d
}
//Read data from a specified address in the cache
pred read [c: CacheSystem, a: Addr, d: Data] {
some d
d = c.cache [a]
c.main' = c.main
c.cache' = c.cache
}
//Load some data from the main memory in the cache
//Note that this is non-deterministic: it does not specific what to load. This is left to the implementation
pred load [c : CacheSystem] {
some addrs: set c.main.Data - c.cache.Data |
c.cache' = c.cache ++ addrs <: c.main
c.main' = c.main
}
//Flush memory from the cache back into the main memory.
//Note this is also non-deterministic
pred flush [c : CacheSystem] {
some addrs: some c.cache.Data {
c.main' = c.main ++ addrs <: c.cache
c.cache' = c.cache - addrs -> Data
}
}
//If a load is performed between a write and a read, this should not be observable.
//That is, if we perform the sequence: read, write, load, and read,
//then reading the same address in both reads should return the same value,
//unless the write in between overwrote that value (i.e. a1=a2)
assert LoadNotObservable {
all c : CacheSystem, a1, a2: Addr, d1, d2, d3: Data |
{
read [c, a2, d2];
write [c, a1, d1];
load [c];
read [c, a2, d3]
} implies d3 = (a1=a2 => d1 else d2)
}
check LoadNotObservable for 5
题目:What is the meaning of the
one
andvar
keywords in thesig
declaration?
one
是限制集合中元素的数量var
是定义一个可变的 relation
代表其在运行途中可以发生改变(例如update
或者扩展等操作)题目:Why does the
read
predicate need the following two lines? Try removing them and see how it affects the model (e.g. whether the LoadNotObservable assertion holds).
在 Alloy 中,如果你在断言、谓词或函数中使用到带撇号('
)的变量,这表示你在引用系统的下一个状态,而不是当前状态。在这个缓存系统模型中,main'
和 cache'
分别代表下一状态的主内存和缓存内存。
这两行代码的作用是确保在读取操作中,主内存和缓存内存的状态不会改变。 如果你删除了这两行,就相当于允许在读取操作中改变内存的状态,这可能会导致模型的行为与预期不符。
题目:Write an
assertion
calledLoadPreserves
, which specifies that when memory is loaded, the main memory does not change. Check this assertion.
assert LoadPreserves{
all c: CacheSystem | load[c] => c.main'=c.main
}
check LoadPreserves
题目:Write an
assertion
calledCannotReadInitially
, which specifies that in the initial state, nothing can be read (i.e., the read operation is not valid). Check this assertion.
assert CannotReadInitially{
all c: CacheSystem, a: Addr, d: Data | init[c] => not read[c, a, d]
}
check CannotReadInitially
Write an operation called
destroy
, which takes a memory address as input and removes that memory address and its associated data, whether the address is in the cache or the main memory.
pred destroy[c: CacheSystem, a: Addr]{
c.main' = c.main - (a -> Data)
c.cache' = c.cache - (a -> Data)
}
Write an assertion called
OnlyOneMemoryDestroyed
, which specifies that when memory is destroyed by the destroy operation, it removes memory from either the cache or main memory, but not both. Does this assertion hold? If not, why not?
assert OnlyOneMemoryDestroyed{
all c: CacheSystem, a: Addr | (destroy[c, a] => c.main'=c.main-(a->Data) and c.cache'=c.cache)
or (destroy[c, a] => c.cache'=c.cache-(a->Data) and c.main' = c.main)
}
check OnlyOneMemoryDestroyed
hold
,因为 destroy
的定义和这个是违背的。