Trong mục này luận văn sẽ trình bày cách sử dụng phương pháp trừu tượng mệnh đề (Predicate Abstraction) để thực hiện bước 2 và bước 3 trong quá trình xây dựng CFA mở rộng.
Trừu tượng mệnh đề [8] là phương pháp tiếp cận nhằm mô hình hóa trạng thái của một chương trình dựa trên một tập các mệnh đề logic (predicates). Ở đây ta cũng sử dụng các kí hiệu ∧,∨ và ¬ để thay thế cho những kí hiệu của phép toán logic tương đương trong C đó là &&, || và !.
Gọi π là một chương trình C và P là một tập các mệnh đề logic (predicates) của các biến trong π, ta kí hiệu A(π,P) là một mô hình trừu tượng của π theo P (hay A(π,P) chính là CFA mở rộng của π). Ta cũng định nghĩa Stmt là tập các câu lệnh của π và Exp là tập tất cả các biểu thức trên các biến của π.
Giả sử rằng π chỉ bao gồm 1 khối mã nguồn và không có các con trỏ hàm hay các gọi hàm đệ quy. Như vậy không mất tính tổng quát ta giả thiết trong π có 5 loại là lệnh gán, lệnh gọi hàm, lệnh rẽ nhánh if-then-else, lệnh return và lệnh goto.
Như ta đã biết thì CFA là mô hình đơn giản nhất của một chương trình và từ cách xây dựng CFA ta có thể thấy CFA tương đương với A(π,∅).
Mô hình A(π,P) là sự kết hợp giữa mỗi trạng thái s của CFA với một tập con của Exp thu được từ P, gọi là Ps. Việc xây dựng Ps từ P sẽ được miêu tả bằng thuật toán Predicate Inference sau đây với chú ý là Ps = ∅ nếu s là trạng thái kết thúc Final hoặc ℒ(s) là một lệnh return và P là một tập con của các lệnh rẽ nhánh trong π.
Trước hết ta tìm hiểu khái niệm tiền điều kiện yếu nhất WP (Weakest Preconditon) của một biểu thức logic p theo một câu lệnh a trong π.
Định nghĩa 3.1: Tiền điều kiện yếu nhất WP
Cho một câu lệnh a và φ là một biểu thức logic của C (φ ∈ Exp) thì WP của φ đối với a gọi là WP(φ, a) sẽ được định nghĩa như sau:
Nếu a là một lệnh gán có dạng v = e thì WP(φ, a) thu được từ φ bằng cách thay thế tất cả v xuất hiện trong φ bằng e.
Nếu a là một lệnh gán có dạng *v = e. {v1, … vn} là tập các biến xuất hiện ở trong φ và với 1 ≤ i ≤ n, ai là lệnh gán vi = e. Lúc đó WP(φ, a) = (∨i=1 n ( v = &vi ∧ WP(φ, ai))) ∨ (∨i=1 n v ! = &vi ∧ φ)
Ví dụ 3.2: Cho φ là biểu thức (x==5), và nếu a là lệnh gán x = e, ta có WP(φ, a) = WP((x == 5), (x = e)) = (e == 5). Còn nếu a là lệnh gán có dạng *x = e lúc đó ta có WP(φ, a) = ( v = &𝑥 ∧ WP((x == 5), (x = e))) ∨ ( v ! = &𝑥 ∧ x == 5 ) = ( v = &𝑥 ∧ (e == 5)) ∨ ( v ! = &𝑥 ∧ x == 5 ).
Thuật toán Predicate Inference:
Input:Tập các lệnh rẽ nhánh P trong π.
Output: Tập các Ps được kết hợp với tương ứng từng trạng thái s trong CFA của π.
Ví dụ 3.3: Xem xét chương trình C đã nêu ở ví dụ 3.2, ta có P chỉ gồm lệnh rẽ
Ví dụ 3.3: Xem xét chương trình C đã nêu ở ví dụ 3.1, giả sử P gồm lệnh rẽ nhánh S4, S5 và GSpec = TRUE, MSpec là một LTS được biểu diễn như hình 3.2, các PA giả thiết của hàm do_a() và do_b() lần lượt là (True, DO_A) và (True,
Khởi tạo: ∀ s ∈ SCF, Ps = ∅ Do forever
For each s ∈ SCF do
IF (ℒ(s) == lệnh gán and ℒ(s′) là lệnh tiếp theo của nó) THEN. For each p′∈ Ps′ thêm WP(p′, ℒ(s)) vào trong Ps.
Else (If ℒ(s) == lệnh rẽ nhánh với điều kiện rẽ nhánh là c) THEN IF (ℒ(s) ∈ P) THEN thêm c vào Ps.
IF (ℒ(s′) là lệnh kế tiếp ℒ(s) sau then hoặc else) THEN Ps := Ps ∪ Ps′. Else IF (ℒ(s) là lệnh gọi hàm hoặc lệnh goto và ℒ(s′) là lệnh tiếp theo của nó) THEN Ps:= Ps ∪ Ps′.
Else IF (ℒ(s) là lệnh return trả về hằng số e và 𝑟 ∈ ActSpec ∩ 𝐼𝑛𝑡𝑅𝑒𝑡) THEN thêm (e==RetVal(r)) vào Ps.
Do_B) trong đó DO_A, DO_B là các LTS được biểu diễn lần lượt trên hình 3.3 và 3.4.
Như vậy thuật toán của chúng ta bắt đầu với PS4 = {(y < 10)} và PS5 = {(y > 5)}. Từ đó áp dụng thuật toán ta có :
PS2 = PS2 ∪ PS4 = ∅ ∪ y < 10 = { y < 10 } PS3 = PS3 ∪ PS5 = ∅ ∪ y > 5 = { y > 5 }
PS1 = PS1 ∪ PS2 ∪ PS3 = ∅ ∪ y < 10 ∪ y > 5 = { y < 10 , y > 5 } PS0 = {WP y < 10 , y = 8; , WP y > 5 , y = 8; = { 8 < 10 , (8 > 5)}
Hình 3.2: Spec của chương trình C trong ví dụ 3.1.
Vì các mệnh đề logic (predicate) là hiển nhiên đúng hoặc hiển nhiên sai sẽ bị loại bỏ nên chúng ta không thêm nó vào trong PS0 vì vậy PS0 = ∅. Lệnh return trong Spec trả về hai giá trị là {0, 2}, cho nên PS6 = { 0 == 0 , (0 == 2)} theo đó thì PS6 = ∅. Tương tự có PS7 = PS8 = PS9 = PFINAL = ∅.
Hình 3.3: LTS biểu diễn hàm do_a.
Hình 3.4: LTS biểu diễn hàm do_b. C8 a 𝜏 C1 C10 C3 C4 C6 C5 C12 C7 C9 C11 1 C13 STOP 𝜏 𝜏 𝜏 𝜏 𝜏 𝜏 𝜏 b 𝜏 𝜏 Return {0} Return {2} C2 A1 A2 STOP a Return{} B1 B2 STOP b Return{}
Hình 3.5 minh họa CFA với mỗi trạng thái s đã được gán các tập Ps tương ứng. Trong đó P = {p1, p2} với p1 = {(y < 10)} và p2 = {(y > 5)}.
Hình 3.5: CFA với các trạng thái được gán tập các mệnh đề logic.
Cho một CFA của một chương trình và một trạng thái s trên CFA đó, giả sử Ps = {p1, … , pk} thì giá trị của Ps là một véc tơ kiểu Boolean v1, … , vk.
Gọi Vs là tập tất các các giá trị của Ps thì hàm concretization của Vs Γs: Vs Exp được xác định như sau:
Với V = {v1, … , vk} ∈ Vs thì Γs V =∧i=1k pivi trong đó pitrue = pi và pifalse = ¬ pi.
Chú ý Ps = ∅ thì Vs = {⊥} và Γs ⊥ = true.
Ví dụ 3.4: Với CFA như trong ví dụ 3.3 PS1 = { y < 10 , y > 5 }, giả sử V1 = {0, 1} và V2 = {1, 0} lúc đó ta có: ΓS1 V1 = ¬(y<10) ∧ y > 5 . ΓS1 V2 = y < 10 ∧ (¬ y > 5 ). Return 1 FINAL FINAL
Return 0 Return 2 Return 3
S7 S8 S9 S6 y < 10 S4 S5 y > 5 a() S3 b() x == 0 S1 S2 y = 8 S0 {p1, p2} {} {p1} {p1} {} {} {} {} {} {p2} {p2}
Trạng thái và tập các bước chuyển trạng thái trong mô hình
Mỗi trạng thái của mô hình A(π,P) (hay trong CFA mở rộng) sẽ tương ứng với một trạng thái trong CFA kết hợp với tập các giá trị của tập mệnh đề logic tại các trạng thái đó. Gọi s ∈ SCF là một trạng thái trong CFA lúc đó tập các trạng thái Ss trong CFA mở rộng tương ứng với trạng thái s sẽ được xác định như sau:
SFINAL = {STOP}.
Nếu ℒ(s) là lệnh gán, lệnh rẽ nhánh, lệnh goto hoặc lệnh return thì Ss = {s} × Vs.
Nếu ℒ(s) là một lệnh gọi hàm thư viện và g1, M1 , … , gn, Mn PA của hàm thư viện. Với 1 ≤ i ≤ n, đặt Mi = (Si, S0i, Acti, Ti) thì Ss = ∪i=1n s × Vs × Si ∪ ({s} × Vs).
Với các trạng thái trong CFA mở rộng có dạng (s, V) ta gọi đó là các trạng thái chuẩn (normal state) còn các trạng thái có dạng (s, V, c) ta gọi đó là các trạng thái nội tuyến (inline state) trong quá trình xây dựng mô hình với các PA giả thiết của các hàm thư viện.
Đến đây ta có thể xác định MImp là một LTS (SImp, S0,Imp, ActImp, TImp) trong đó:
SImp = ∪s∈SCF Ss ∪ {INIT} là tập các trạng thái. S0,Imp = INIT là trạng thái khởi tạo.
ActImp = ActSpec là tập các hành động.
TImp ⊆ SImp × ActImp × SImp là tập các bước chuyển trạng thái
Việc xây dựng các bước chuyển trạng thái trong mô hình yêu cầu phải sử dụng công cụ chứng minh định lý. Trước hết ta tìm hiểu hai khái niệm được chấp nhận (admisible) và không được chấp nhận (inadmisible).
Cho hai biểu thức bất kì ψ1, ψ2 chúng ta nói rằng 2 biểu thức là được chấp nhận (admissible) nếu chứng minh định lý cho giá trị TRUE hoặc UNKNOWN trên biểu thức ¬ (ψ1 ∧ ψ2), kí hiệu là Adm(ψ1, ψ2), ngược lại các trường hợp khác là không được chấp nhận (inadmissible), kí hiệu là ¬Adm(ψ1,ψ2).
Tập các bước chuyển trạng thái TImp sẽ được xây dựng dựa trên nguyên tắc sau:
(INIT 𝜏 (ICF, VCF)) ∈ TImp khi và chỉ khi Amd (ΓICF VCF , GSpec). ((s1, V1), 𝜏, (s2, V2)) ∈ TImp khi và chỉ khi (s1, s2) ∈ TCF và một trong
các điều kiện sau được thõa mãn:
1. ℒ(s1) là lệnh gán và Adm(Γs1 V1 , WP(Γs2 V2 , ℒ(s1))).
2. ℒ(s1) là lệnh rẽ nhánh với điều kiện rẽ nhánh là c, ℒ(s2) là lệnh kế tiếp của nó sau THEN, Adm(Γ𝑠1 𝑉1 ,Γ𝑠2 𝑉2 ) và Adm(Γ𝑠1 𝑉1 , 𝑐). 3. ℒ(s1) là lệnh rẽ nhánh với điều kiện rẽ nhánh là c, ℒ(s2) là lệnh kế
tiếp của nó sau ELSE, Adm(Γ𝑠1 𝑉1 ,Γ𝑠2 𝑉2 ) và Adm(Γ𝑠1 𝑉1 ,¬𝑐). 4. ℒ(s1) là lệnh goto và Adm(Γs1 V1 , Γs2 V2 ).
5. ℒ(s1) là lệnh return vàs2 là trạng thái kết thúc (FINAL).
ℒ(s1) là một lệnh gọi hàm thư viện và g1, M1 , … , gn, Mn PA của hàm thư viện đó. Với 1 ≤ i ≤ n, (s1, s2) ∈ TCF, 𝑉1 ∈ Vs1, 𝑉2 ∈ Vs2:
1. Thay thế tất cả các tham số của hàm thư viện trong gi bằng các đối số tương ứng truyền qua nó tại ℒ(s1) ta được g′i. Nếu
Adm(g′i,Γs1 𝑉1 ), đặt Mi = (Si, S0,i, Acti, Ti) và thực hiện tiếp bước 2. Nếu không tiến hành lại bước 1 với giá trị i tiếp theo.
2. ((s1, V1), 𝜏, (s1, V1, S0,i)) ∈ TImp.
3. Với mỗi bước chuyển đổi trạng thái (s, a, t) ∈ Ti trong đó t ≠ STOP thì ((s1, V1, s), a, (s1, V1, t)) ∈ TImp.
4. Nếu ℒ(s1) là lệnh gọi hàm thư viện kết hợp với phép gán có dạng x = lib(…) thì:
- Với mỗi bước chuyển trạng thái (s, VoidRet, STOP) ∈ Ti,
Adm(Γs1 V1 ,Γs2 V2 ) thì ((s1, V1, s), 𝜏, (s2, V2)) ∈ TImp.
- Với mỗi bước chuyển trạng thái (s, a, STOP) ∈ Ti trong đó a ∈ IntRet, và có Adm(Γs1 V1 , WP(Γs2 V2 , x = RetVal a ; )) thì ((s1, V1, s), 𝜏, (s2, V2)) ∈ TImp.
5. Nếu ℒ(s1) là lệnh gọi hàm thư viện không kết hợp với phép gán có dạng lib(…) thì với mỗi bước chuyển trạng thái s, a, STOP ∈ Ti, và có Adm(Γs1 V1 ,Γs2 V2 ) thì ((s1, V1, s), 𝜏, (s2, V2)) ∈ TImp.
Ví dụ 3.5: Xem xét chương trình C như trong ví dụ 3.1, GSpec, MSpec và CFA với các trạng thái đã được gán các tập mệnh đề logic (predicate) được xây dựng
trong ví dụ 3.3, chúng ta có mô hình MImp tương ứng của chương trình như hình 3.6.
Hình 3.6: MImp của một chương trình C.