Tải bản đầy đủ (.pdf) (17 trang)

Báo cáo " Kiểm chứng từng phần cho chương trình C " pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (726.06 KB, 17 trang )

Kiểm chứng từng phần cho chương trình C

Hoàng Mạnh Khôi

Trường Đại học Công nghệ
Luận văn Thạc sĩ ngành: Công nghệ phần mềm; Mã số: 60 48 10
Người hướng dẫn: PGS.TS Nguyễn Việt Hà
Năm bảo vệ: 2012

Abstract: Trình bày cơ sở lý luận về kiểm chứng. Trình bày các khái niệm cơ bản liên
quan như các khái niệm về mô hình chuyển trạng thái được gán nhãn Hệ chuyển trạng
thái gán nhãn (LTS), các phương pháp biểu diễn LTS, khái niệm về trừu tượng hóa
hành vi của hệ thống Trìu tượng hóa thủ tục (PA), cũng như các khái niệm cần thiết
trong kĩ thuật kiểm chứng … Trình bày nội dung chính của Kiểm thử từng phần cho
chương trình C: nêu cách xây dựng mô hình LTS biểu diễn hành vi của hệ thống từ mã
nguồn bắt đầu bằng việc xây dựng sơ đồ luồng xử lý Otomat luồng điều khiển (CFA);
sơ đồ luồng xử lý mở rộng (Expanding Control flow Automata) của chương trình có
sử dụng các LTS giả thiết; giới thiệu phương pháp trừu tượng mệnh đề để xây dựng
được mô hình LTS biểu diễn hành vi của mã nguồn từ sơ đồ luồng xử lý mở rộng; nêu
cách kiểm chứng mô hình LTS của phần cài đặt có đảm bảo với mô hình LTS của đặc
tả. Đưa ra ứng dụng của phương pháp bằng cách giới thiệu các công cụ Copper. Đầu
vào của công cụ này là tập file mã nguồn C của chương trình và các đặc tả của các
thuộc tính cần kiểm chứng, đầu ra là kết luận phần cài đặt đã đúng với đặc tả của nó
hoặc đưa ra phản ví dụ chứng minh cài đặt không đúng với đặc tả. Giới thiệu một vài
ứng dụng đơn giản được áp dụng thực tế trên công cụ bằng cách nêu chi tiết cách xây
dựng các file đặc tả cũng như cách xây dựng các PA giả thiết bằng ví dụ.

Keywords: Công nghệ phần mềm; Kiểm chứng mô hình; Mã nguồn C

Content
Chương 1: Giới thiệu


Đảm bảo chất lượng phần mềm là một trong những giai đoạn quan trọng bậc nhất trong quy
trình phát triển phần mềm. Có rất nhiều phương pháp được sử dụng trong việc đảm bảo chất
lượng phần mềm, trong đó kiểm chứng mô hình [6] là một trong những cách tiếp cận hiệu quả
nhất, và ngày càng được sử dụng rộng rãi, đặc biệt là trong các hệ thống phần mềm đòi hỏi độ
chính xác cao.
Kiểm chứng mô hình là một nhóm các kĩ thuật kiểm định tự động một mô hình với các
đặc tả tính năng của mô hình đó. Mô hình là một hệ thống bao gồm tập hợp có giới hạn các
trạng thái và tập hợp các bước chuyển tiếp giữa các trạng thái đó. Kiểm chứng mô hình là xác
minh tính đúng đắn của mô hình bằng cách xác định xem thuộc tính mà người dùng mong
muốn có được thõa mãn bởi mô hình đó hay không [6].

2
Trong kiểm chứng mô hình phần mềm có hai bài toán được quan tâm chính đó là kiểm
chứng đặc tả và kiểm chứng mã nguồn. Bài toán kiểm chứng tự động mã nguồn được xem là
ứng dụng đầu tiên của kiểm chứng mô hình trong việc đảm bảo chất lượng phần mềm. Dù đã
có từ lâu nhưng đến nay nó vẫn là vấn đề mở và chưa có giải pháp thỏa đáng, cho nên nó vẫn
đang nhận được sự quan tâm rộng rãi.
Như chúng ta đã biết một chương trình C có mã nguồn lớn, sẽ tồn tại nhiều lời gọi
hàm hay nhiều lời gọi đến các thư viện hàm. Việc kiểm chứng theo mô hình một chương trình
như vậy sẽ gặp khó khăn và phức tạp vì hoặc có thể chúng ta chưa có được đầy đủ mã nguồn
của các thư viện hàm hoặc dễ gây ra bùng nổ không gian trạng thái. Trong luận văn này tôi
xin giới thiệu một phương pháp mới trong kiểm chứng tự động để kiểm chứng một cài đặt của
chương trình C có mã nguồn lớn và có nhiều thành phần.
Cách tiếp cận của phương pháp là chúng ta đưa việc kiểm chứng một chương trình
phần mềm lớn về việc kiểm chứng các thành phần con nhỏ hơn và đơn giản hơn bằng cách
trừu tượng hóa hành vi [3] (procedure abtraction-PA) của các thành phần con (hay các hàm
thư viện) theo một khái niệm đặc tả của máy hữu hạn trạng thái đó là hệ thống chuyển trạng
thái được gán nhãn LTS (Label Transition System) [4]. Phương pháp cho phép chúng ta tự
định nghĩa các hành vi của các hàm thư viện (chưa có mã nguồn hoặc chưa rõ hành vi) và sử
dụng chúng như là giả thiết trong quá trình xây dựng mô hình kiểm chứng.

Nội dung chính của luận văn là giới thiệu phương pháp kiểm chứng phần cài đặt của
một hệ thống viết bằng ngôn ngữ C có đảm bảo đúng với đặc tả của nó, để làm được điều đó
trước hết từ mã nguồn C chúng ta phải xây dựng được mô hình LTS biểu diễn hành vi của hệ
thống, sau đó sử dụng kĩ thuật kiểm chứng để thẩm định xem nó có đảm bảo đúng với mô
hình LTS của đặc tả hay không.
Nội dung của luận văn được trình bày trong 4 chương:
Chương 1 giới thiệu về đề tài, trình bày tổng quan về nội dung phương pháp được
nghiên trong đề tài, mục tiêu của đề tài và cấu trúc của luận văn.
Chương 2 trình bày các khái niệm cơ bản phục vụ cho đề tài, chương này đưa ra các
khái niệm về mô hình chuyển trạng thái được gán nhãn LTS, khái niệm về trừu tượng hóa
hành vi của hệ thống PA, cũng như các khái niệm cần thiết trong kĩ thuật kiểm chứng …
Chương 3 trình bày nội dung chính của luận văn, đó là nêu cách xây dựng mô hình
LTS biểu diễn hành vi của hệ thống từ mã nguồn bắt đầu bằng việc xây dựng sơ đồ luồng xử
lý CFA (Control Flow Automata) [3] và sơ đồ luồng xử lý mở rộng (Expanding Control flow
Automata) [3] của chương trình có sử dụng các LTS giả thiết. Nêu cách kiểm chứng mô hình
LTS của phần cài đặt có đảm bảo với mô hình LTS của đặc tả.
Chương 4 luận văn đưa ra ứng dụng thực tế của phương pháp bằng cách giới thiệu các
công cụ Copper [2]. Đầu vào của công cụ này là tập file mã nguồn C của chương trình và các

3
đặc tả của các thuộc tính cần kiểm chứng, đầu ra là kết luận phần cài đặt đã đúng với đặc tả
của nó hoặc đưa ra phản ví dụ chứng minh cài đặt không đúng với đặc tả.





















Chương 2: Một Số Khái Niệm Cơ Bản
Trong chương này chúng ta sẽ tìm hiểu một số khái niệm cần thiết như máy hữu hạn trạng
thái, hệ chuyển trạng thái được gán nhãn và khái niệm về trừu tượng hóa hành vi một chương
trình…
2.1 Hệ chuyển trạng thái được gán nhãn - LTS
Định nghĩa 2.1: Hệ thống chuyển trạng thái được gán nhãn (Labeled Transition System –
LTS[4])
Một hệ chuyển trạng thái được gán nhãn M là một bộ có thứ tự gồm 4 thành phần
trong đó:
 S là một tập khác rỗng các trạng thái của M
 là trạng thái khởi tạo
 Act là tập các hành động quan sát được
 là hàm chuyển trạng thái
Ta kí hiệu nếu có một hành động chuyển hệ thống từ trạng thái S sang
trạng thái S’. Trạng thái kết thúc STOP là trạng thái mà ở đó không có hành động để chuyển
sang một trạng thái nào khác, tức với .

4

Chú ý 2.1: Chúng ta dùng để kí hiệu trạng thái lỗi đặc biệt của hệ thống, và  để biểu diễn
LTS <{π}, Act, , π>.
Ví dụ 2.1:
Ở trên là một ví dụ về hệ chuyển trạng thái được gán nhãn M = , trong
đó:
 S = {S0, S1, STOP}
 Act = {lock, return {0}, return {1}}
 T = { (S0, lock, S1), (S1, return {0}, STOP), (S0, return {1}, STOP)}
 S0 là trạng thái khởi đầu.
Định nghĩa 2.2: Dẫn xuất của một hệ chuyển trạng thái được gán nhãn M.
Dẫn xuất của một hệ chuyển trạng thái được gán nhãn M = là một
chuỗi hữu hạn các hành động với , (i = 1, ,n).
Như vậy dẫn xuất của hệ chuyển trạng thái được gán nhãn M là một chuỗi các hành
động quan sát được mà M có thể thực hiện từ trạng thái khởi tạo .
Chú ý 2.2: Ta ký hiệu ↑Σ là một dẫn xuất thu được bằng cách loại bỏ khỏi tất cả các hành
động a mà a

Σ. Tập tất cả các dẫn xuất của M được gọi là ngôn ngữ của M, ký hiệu L(M).
Một dẫn xuất = a1a2 an là một dẫn xuất hữu hạn trên hệ chuyển trạng thái được gán nhãn
M. Ta ký hiệu hệ chuyển trạng thái được gán nhãn Mσ = (S, S0, , ) với S = {s0, s1, ,
sn} và = { (si-1, ai, si)} với i=1, ,n. Ta nói rằng một hành động a được chấp nhận từ
một trạng thái s S nếu tồn tại s S sao cho (s, a, s ) . Tương tự vậy ta nói rằng một
dẫn xuất a1a2 …an được chấp nhận từ trạng thái s S nếu tồn tại một dãy các trạng thái s0,
s1, …, sn với s0 = S0 sao cho i= thì (si-1, ai, si) .
Định nghĩa 2.3:
Cho một dẫn xuất = và hai trạng thái s, t của hệ chuyển trạng thái
được gán nhãn M = . Ta nói rằng t có thể đi đến được từ s thông qua dẫn xuất
(viết là ) nếu tồn tại một tập các trạng thái với s = và t = sao cho
.
2.2 Trừu tượng hóa thủ tục (Procedure Abstraction-PA)

Để kiểm chứng một chương trình từ mã nguồn chúng ta phải trừu tượng hóa được các hành vi
của chương trình và các hàm thư viện của nó bằng các đặc tả LTS. Trong một chương trình C,

5
một hàm thư viện có thể thực hiện những chức năng khác nhau tùy thuộc vào tham số đầu vào
hay ngữ cảnh thực hiện, trong cách tiếp cận của phương pháp này chúng ta đưa ra khái niệm
trừu tượng hóa thủ tục (procedure abstraction - PA) [3] cho phép nhiều đặc tả LTS biểu diễn
cho một thủ tục hàm.
PA (procedure abstraction) của một thủ tục hàm proc là một tập hữu hạn các cặp
,… trong đó:
 là điều kiện (guard) ràng buộc trên các tham số của proc.
 là LTS trừu tượng hóa hành vi của proc ứng với điều kiện đạt giá trị true.
Chú ý 2.3: Trong quá trình kiểm chứng, đối các hàm thư viện không có mã nguồn thì các PA
của nó được người dùng tự định nghĩa và cung cấp như là các giả thiết trong việc xây dựng
mô hình kiểm chứng, ta xem đó là các PA giả thiết.
Chương 3: Phương Pháp Kiểm Chứng
Mục đích chính của chúng ta là cần phải kiểm chứng xem phần cài đặt mã nguồn của một
chương trình C có thõa mãn với đặc tả của nó (được biểu diễn bằng một LTS) hay
không? Để làm được việc đó thì trước hết từ phần cài đặt chúng ta phải xây dựng được một
mô hình (biểu diễn bằng một LTS) mô tả hành vi của phần cài đặt, sau đó sử dụng kĩ
thuật kiểm chứng để kiểm định sự thõa mãn của với .
3.1 Xây dựng mô hình
Cho một chương trình C và một tập các mệnh đề logic (predicates) P, hệ trạng thái được gán
nhãn =( ) là đặc tả của chương trình và các PAs giả
thiết . Trong phần này sẽ trình bày cách xây dựng từ mã nguồn của
chương trình bằng cách sử dụng các PA giả thiết, các điều kiện (Guard) và tập một các
mệnh đề logic P. Việc xây dựng dựa trên các nguyên tắc sau:
 Mỗi trạng thái của được mô hình từ một trạng thái của chương trình trong quá
trình thực hiện, vì vậy mỗi trạng thái sẽ bao gồm một thành phần điều khiển (control
component) và một thành phần dữ liệu (data component).

 Thành phần điều khiển là đại diện trực quan cho các giá trị của chương trình và nó thu
được từ otomat luồng điều khiển (CFA) của chương trình.
 Thành phần dữ liệu là đại diện trừu tượng cho trạng thái các biến của chương trình,
được tính toán dựa trên tập các mệnh đề logic P.
 Các bước chuyển trạng thái trên tương ứng với các bước chuyển trạng thái trên
CFA.

6
Không mất tính tổng quát ta giả thiết trong chương trình có 5 loại lệnh gán, gọi hàm,
rẽ nhánh if –then-else, return và lệnh goto. Ta cũng gọi Stmt là tập các câu lệnh của chương
trình và Exp là tập tất cả các biểu thức logic (ví dụ như các điều kiện rẽ nhánh) trên các biến
của chương trình.
3.1.1 Otomat luồng điều khiển
Việc xây dựng mô hình từ mã nguồn của một chương trình C bắt đầu bằng việc xây
dựng otomat luồng điều khiển (CFA) của chương trình theo nguyên tắc:
 Mỗi trạng thái của CFA là một điểm điều khiển (control location) trong chương trình
(tương ứng với một câu lệnh trong chương trình).
 Mỗi bước chuyển trạng thái trong CFA tương ứng với một bước chuyển giữa hai điểm
điều khiển (hai câu lệnh) trong chương trình.
Định nghĩa 3.1: CFA của một chương trình C
CFA của một chương trình là một bộ gồm 4 thành phần trong đó:
 là tập các trạng thái.
 là trạng thái khởi tạo.
 là tập các chuyển đổi trạng thái.
 là hàm gán nhãn các trạng thái của CFA.
{Final} là trạng thái kết thúc duy nhất của CFA. là lệnh khởi tạo của chương
trình, và nếu và chỉ nếu một trong các điều kiện sau đây được thõa mãn:
 Nếu là lệnh gán, lệnh gọi hàm hoặc lệnh goto và là lệnh kế tiếp duy nhất
của nó trong π.
 Nếu là lệnh rẽ nhánh và là lệnh kế tiếp nó sau then hoặc else trong π.

 Nếu là lệnh return và = {Final}.
Như vậy CFA là mô hình đơn giản nhất của chương trình tuy nhiên nó chỉ mới mô
hình được luồng điều khiển của chương trình mà chưa trừu tượng hóa được dữ liệu các biến
tại các trạng thái của chương trình. Để làm được điều này chúng ta sử dụng đến tập các mệnh
đề logic P và một phương pháp gọi là predicate abstraction [3] để xây dựng một otomat luồng
điều khiển mở rộng.
3.1.2 Otomat luồng điều khiển mở rộng
Mô hình otomat luồng điều khiển mở rộng 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à (P thường là các điều kiện rẽ nhánh trong chương
trình). Như vậy nếu có k phần tử mỗi phần tử nhận giá trị là true hoặc false thì lúc đó mỗi

7
trạng thái trong CFA sẽ tương ứng với trạng thái trong CFA mở rộng. Việc xây dựng LTS
thực hiện theo các bước như sau:
1) Xây dựng CFA
2) Xây dựng một CFA mở rộng theo nguyên tắc:
 Với mỗi trạng thái trong CFA, chúng ta bổ sung trạng thái trong , mỗi trạng
thái trong CFA mở rộng là tổ hợp trạng thái trong CFA và giá trị của tập các mệnh đề
logic đang xem xét.
 Xem xét một cạnh trong CFA. Lúc đó mỗi và sẽ tương ứng với trạng
thái trong tập các trạng thái . Như vậy có khả năng chuyển đổi
trạng thái tương ứng trong . Tuy nhiên không phải tất cả các khả năng chuyển
trạng thái đều thuộc . Chúng ta sẽ sử dụng kĩ thuật theorem prover [4] để quyết
định xem phép chuyển đổi nào là thực sự được chấp nhận. Chúng ta cũng sẽ chỉ loại
bỏ những chuyển trạng thái nào bị loại trừ bởi theorem prover.
3) là mô hình chính xác hơn so với CFA. Tuy nhiên nó cũng không mô hình hóa
được hành vi của các hàm thư viện mà thủ tục proc gọi đến. Để làm được việc đó thì
chúng ta phải kết hợp các PAs giả thiết với . LTS thu được sau khi kết hợp với các
PAs giả thiết chính là .
3.1.3 Phương pháp trừu tượng mệnh đề

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ó 4 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à . Việc xây dựng từ P sẽ được miêu tả bằng thuật toán Predicate

8
Inference sau đây với chú ý là nếu s là trạng thái kết thúc Final hoặc 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 Weakest Preconditon WP của một biểu thức logic p
theo một câu lệnh a trong π.
Định nghĩa 3.1: Weakest Precondition WP
Cho một câu lệnh a và là một biểu thức logic của C ( ) thì WP của đối
với a gọi là sẽ được định nghĩa như sau:
 Nếu a là một lệnh gán có dạng v = e thì 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. là tập các biến xuất hiện ở trong và
với , là lệnh gán . Lúc đó = ( )
( )
Ví dụ 3.1: Cho là biểu thức (x==5), và nếu a là lệnh gán x = e, ta có =

= . Còn nếu a là lệnh gán có dạng *x = e lúc đó ta có
= ( ) =
( ) .
Thuật toán Predicate Inference:
Input: Tập các lệnh rẽ nhánh P trong π.
Output: Tập các được kết hợp với tương ứng từng trạng thái s trong CFA của π.










Khởi tạo: ∀ s ∈ S
CF
, P
s
= ∅
Do forever
For each s ∈ S
CF
do
IF (ℒ(s) == lệnh gán and ℒ(s′) là lệnh tiếp theo của nó) THEN.
For each p′ ∈ P
s′
thêm WP(p


, ℒ(s)) vào trong P
s
.
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 P
s
.
IF (ℒ(s′) là lệnh kế tiếp ℒ(s) sau then hoặc else) THEN P
s
:=
P
s
∪ P
s′
.
Else IF (ℒ(s) là lệnh goto và ℒ(s′) là lệnh tiếp theo của nó) THEN
P
s
:= P
s
∪ P
s′
.
IF (không tồn tại P
s
để tiếp tục chỉnh sửa trong vòng lặp) THEN
EXIT.


9

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 đó.
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ử
thì giá trị của là một véc tơ kiểu Boolean . Gọi là tập tất
các các giá trị của thì hàm concretization của được xác định như sau:
Với thì trong đó = và =
. Chú ý thì và .
Ví dụ 3.2: Giả sử với , và
lúc đó ta có:
.
.
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
theorem prover. 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ì chúng ta nói rằng 2 biểu thức là được chấp nhận
(admissible) nếu theorem prover cho giá trị TRUE hoặc UNKNOWN trên biểu thức
, kí hiệu là Adm( ), 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à .
Như vậy với CFA của một chương trình là ta có thể định nghĩa
CFA mở rộng (hay mô hình A(π,P)) như sau đây. Mô hình A(π,P) là một bộ gồm 3 thành
phần trong đó:
 là tập các trạng thái.
 là tập các trạng thái khởi tạo.
 là tập các bước chuyển đổi trạng thái và được xây dựng như
sau nếu và chỉ nếu và một trong các điều kiện sau
được thõa mãn :
1. là lệnh gán và Adm( ).
2. là lệnh rẽ nhánh với điều kiện rẽ nhánh là c, là lệnh kế tiếp của nó
sau THEN, Adm( ) và Adm( ).


10
3. là lệnh rẽ nhánh với điều kiện rẽ nhánh là c, là lệnh kế tiếp của nó
sau ELSE, Adm( ) và Adm( ).
4. là lệnh goto và Adm( ).
5. là lệnh return và là trạng thái kết thúc (FINAL).
3.2 Kiểm chứng
Như vậy chúng ta đã trình bày cách xây dựng mô hình LTS từ mã nguồn của một
chương trình C. Trong phần này chúng ta sẽ trình bày cách kiểm chứng xem có thõa
mãn đặc tả của hệ thống hay không.
3.2.1 Phép ghép nối song song
Một toán tử ghép nối song song[7], ký hiệu || là một phép toán ghép nối 2 thành phần phần
mềm (được biểu diễn bằng 2 hệ trạng thái được gán nhãn) bằng cách đồng bộ các hành vi
chung trên bảng chữ cái và đan xen các hành động còn lại.
Giả sử có 2 hệ chuyển trạng thái được gán nhãn là M1= (S1, S01, Act1, T1) và M2=
(S2, S02, Act2, T2), ghép nối song song giữa M1 và M2, ký hiệu M1 || M2 được định nghĩa
như sau:
Nếu M1 =  hoặc M2 =  thì M1|| M2 = . Ngược lại, M1|| M2 = (S, S0, Act, T)
trong đó:
S= S1S2, Act= Act1 Act2, S0 = (S01, S02) và hàm T được xác định như sau:
 Với mọi (s
1
,a,s
2
) ∈ T
1
và (s
1
′, a, s
2

′) ∈ T
2
thì ( (s
1
, s
1
′), a, (s
2
, s
2
′) ) ∈ T
 Với (s
1
,a,s
2
) ∈ T
1
, a ∉ Act
2
thì s′ ∈ S
2
ta có ( (s
1
,s′), a, (s
2
,s′) ) ∈ T
 Với (s
1
′, a, s
2

′) ∈ T
2
, a ∉ Act
1
thì s ∈ S
1
ta có ( (s, s
1
′), a, (s, s
2
′) ) ∈ T
Ví dụ 3.3: Ghép nối song song hai LTS Input và Output như trên hình 3.1




Hình 3.1: LTS Input và Output
Khi ghép nối hai mô hình trên, hai hành động send và ack là đồng bộ, các hành động
còn lại đan xen nhau. Theo các quy tắc trên ta xác định được hệ chuyển trạng thái song song
được gán nhãn M′ = (S′, S
0
′, Act′, T′) trong đó:
 S′ = S
1
x S
2
= {(0,a), (0,b), (0,c), (1,a), (1,b), (1,c), (2,a), (2,b), (2,c)}
 Act′ = {in, send, out, ack}
Input
0


1

2

in
send
ack
a

b

c

send
out
ack
Output

11
 S
0
′ = (0, a)
 T′ = {((0,a), in, (1,a)), ((1,a), send, (2,b)), ((2,b), out, (2,c)), ((2,c), ack, (0,a)), ((0,b),
in, (1,b)), ((0,b), out, (0,c)), ((1,b), out, (1,c)), ((0,c), in, (1,c)) }
Chúng ta tiến hành loại bỏ tất cả các trạng thái không đến được từ trạng thái khởi tạo
(0,a) và tất cả các hành động đưa hệ thống về trạng thái đó ta sẽ thu được một hệ thống
chuyển trạng thái ghép nối song song được gán nhãn M = (S, S
0
, Act, T) trong đó:

 S = S
1
x S
2
= {(0,a), (1,a), (2,b), (2,c)}
 Act = {in, send, out, ack}
 S
0
= (0, a)
 T = {((0,a), in, (1,a)), ((1,a), send, (2,b)), ((2,b), out, (2,c)), ((2,c), ack, (0,a))}
3.2.2 Kiểm chứng tính đúng đắn của chương trình
Định nghĩa 3.2: Hệ chuyển trạng thái được gán nhãn an toàn
Hệ chuyển trạng thái được gán nhãn an toàn là một hệ chuyển trạng thái được gán
nhãn không chứa bất kỳ một trạng thái lỗi π nào.
Định nghĩa 3.3: Thuộc tính an toàn
Thuộc tính an toàn là thuộc tính đảm bảo không có lỗi xảy ra trong quá trình thực hiện
của hệ thống. Một thuộc tính an toàn p được biểu diễn dưới dạng một hệ chuyển trạng thái
được gán nhãn an toàn p = (S
p
, S
0p
, Act
p
, T
p
). Ngôn ngữ của nó L(p) là tập tất cả các hành vi
được đoán nhận trên Act
p
.
Định nghĩa 3.4: Hệ chuyển trạng thái được gán nhãn lỗi

Hệ chuyển trạng thái được gán nhãn lỗi của một thuộc tính p = (S
p
, S
0p
, Act
p
, T
p
),
được ký hiệu là p
err
= (S
p
{π}, S
0p
, Act
perr
, T′), trong đó:
Act
perr
= Act
p
, T′ = T { (s, a, π) | a Act và s′ S sao cho (s, a, s′) T }.
Ví dụ 3.7: Xây dựng hệ chuyển trạng thái được gán nhãn lỗi từ một hệ chuyển trạng thái ứng
với thuộc tính p

12


Hình 3.3: LTS của thuộc tính p và LTS lỗi tương ứng của p.

Định nghĩa 2.8: Tính thỏa mãn
Một hệ chuyển trạng thái được gán nhãn M được gọi là thỏa mãn thuộc tính p, ký hiệu
M╞ p khi và chỉ khi   L(M) sao cho: (Act)  L(p).
Như vậy, để kiểm chứng một LTS thoả mãn một thuộc tính p hay không, chúng
ta thực hiện phép ghép nối song song ||p
err
. Nếu LTS thu được sau phép ghép nối tồn tại
một dẫn xuất có thể tới trạng thái lỗi π, khi đó ta nói thành phần vi phạm thuộc tính p.
Ngược lại, thoả mãn thuộc tính p.
Chương 4: Ứng Dụng Với Công Cụ Copper
4.1 Công cụ Copper
Trong các phần trước của luận văn đã đề cập phương pháp kiểm chứng mô hình cho một
chương trình viết bằng ngôn ngữ C, trong chương này chúng ta sẽ tìm hiểu một vài ví dụ ứng
dụng thực tế của phương pháp trên công cụ có tên là Copper.
Copper là một công cụ kiểm chứng tự động mã nguồn một chương trình C được phát
triển bởi một nhóm tác giả của viện nghiên công nghệ phần mềm (SEI) của trường đại học
Carnegie Mellon University (CMU).
Copper cài đặt trên môi trường UNIX hoặc giả UNIX (Cygwin), giao tiếp với người
dùng thông qua các dòng lệnh và sử dụng khái niêm FSP (finite state process) để biểu diễn
các LTS. Kiến trúc của Copper được miêu tả như trên hình 4.1.
i
ii
in
out
i
ii
in
out
π
in

out
LTS của thuộc tính p
Error LTS p
err

13

Hình 4.1: Kiến trúc công cụ Copper
Đầu vào của Copper là một file chứa mã nguồn C của chương trình và một file đặc tả
của nó. File đặc tả sẽ cung cấp thông tin về các LTS của các hàm thư viện được dùng trong
chương trình (kể cả các hàm không có mã nguồn) cũng như các LTS của thuộc tính cần kiểm
chứng. Copper sẽ đưa ra kết quả thõa mãn nếu mã nguồn chương trình C là đảm bảo với thuộc
tính cần kiểm chứng, trong trường hợp ngược lại sẽ đưa ra phản ví dụ.
4.2 Một vài ví dụ
Ví dụ 4.1:










Chúng ta xem xét một chương trình C đơn giản như trong ví dụ 2.4.













Trong quy trình phát triển phần mềm thì đặc tả phải được xây dựng trước khi thực
hiện cài đặt mã nguồn. Tuy nhiên để dễ hiểu hơn chúng ta sẽ xây dựng đặc tả cho thủ tục
my_proc. Ta có thể mô tả lại thủ tục my_proc như sau bằng ngôn ngữ tự nhiên:
int my_proc(int x)
{
int y;
if(x == 0) {
y = foo();
if(y > 0) return 10;
else return 20;
} else {
y = bar();
if(y < 0) return 30;
else return 40;
}
}

14
1. Nếu tham số đầu vào của my_proc bằng 0 thì
a) my_proc gọi thủ tục foo.
b) Phụ thuộc vào giá trị trả về của thủ tục foo là lớn hay nhỏ hơn 0 mà thủ tục
my_proc sẽ trả về giá trị 10 hay 20.

2. Trường hợp ngược lại nếu tham số đầu vào của my_proc là khác 0 thì
a) my_proc gọi thủ tục bar
b) phụ thuộc vào giá trị trả về của thủ tục bar là lớn hay nhỏ hơn 0 mà my_proc
sẽ trả về giá trị 30 hoặc 40.
Như vậy trong hành vi của my_proc ứng với trường hợp tham số đầu vào bằng 0 có
thể được mô tả bằng một máy trạng thái đơn giản như sau:

Hình 4.2: LTS miêu tả hành vi của chương trình
Công cụ Copper sử dụng khái niệm FSP mở rộng để đặc tả máy trạng thái. Như vậy
máy trạng thái ở trên có thể được đặc tả như sau:
S1 = (call_foo -> return {$0 == 10} -> STOP | call_foo -> return {$0 == 20} ->
STOP).
Tương tự ta cũng có LTS biểu diễn hành vi của thủ tục my_proc khi tham số đầu vào
khác 0 như sau:
S2 = (call_foo -> return {$0 == 10} -> STOP | call_foo -> return {$0 == 20} ->
STOP).
Như vậy PA đặc tả cho hàm my_proc sử dụng trong công cụ Copper sẽ được miêu tả
như sau:
procedure my_proc {
abstract {($1 == 0), S1 };
abstract {($1 != 0), S3 };
}
Trong đó ($1 == 0) hay ($1 != 0) chính là điều kiện (guard) trên tham số đầu vào thứ
nhất của hàm my_proc.
Chúng ta cũng cung cấp cho Copper 2 PA giả thiết miêu tả hành vi 2 hàm thư viện foo
và bar như sau:

15
FOO = (call_foo -> return {$0 == -1} -> STOP ).
procedure foo { abstract {1, FOO};}

BAR = (call_bar -> return {$0 == 50} -> STOP ).
procedure bar { abstract {1, BAR};}
Giả sử bây giờ chúng ta muốn kiểm chứng với điều kiện tham số thứ nhất của
my_proc bằng 0, LTS S1 là đặc tả đúng của hàm my_proc. Trong file đặc tả ta định nghĩa
điều cần phải kiểm chứng như sau:
program my_proc {
specification abs_1, {$1 == 0}, S1;
}
Trong đó abs_1 là tên định danh sử dụng để Copper nhận biết khi thực hiện việc kiểm
chứng.
Lưu chương trình C ở trên vào một file có đuôi là “.pp”, ví dụ như là my_proc.pp. Sau
đó lưu đặc tả của nó vào một file khác có đuôi là “.spec” ví dụ là my_proc.spec, nội dung file
spec như sau:
FOO = (call_foo -> return {$0 == -1} -> STOP ).
BAR = (call_bar -> return {$0 == 50} -> STOP ).
procedure foo { abstract {1, FOO} ;}
procedure bar { abstract {1, BAR};}
S1 = (call_foo -> return {$0 == 10} -> STOP | call_foo -> return {$0 == 20} -> STOP)
+ {call_bar}.
S2 = (call_foo -> return {$0 == 10} -> STOP | call_foo -> return {$0 == 20} -> STOP)
+ {call_foo}.
program my_proc {
specification abs_1, {$1 == 0}, S1;
specification abs_2, {$1 != 0}, S1;
specification abs_3, {$1 == 0}, S2;
specification abs_4, {$1 != 0}, S2;
}
Chúng ta chạy Copper với lệnh sau:
copper default specification abs_1 my_proc.c.pp my_proc.spec –trace
Kết quả output Copper kết luận my_proc thõa mãn S1 với điều kiện tham số đầu vào

bằng 0.
conformance relation exists !!
specification abs_1 is valid
Chúng ta kiểm tra abs_2 bằng Copper theo lệnh sau:
copper default specification abs_2 my_proc.c.pp my_proc.spec –trace
Kết quả output Copper kết luận my_proc không thõa mãn S1 với điều kiện tham số
đầu vào bằng 1.
conformance relation does not exist !!

16
specification abs_2 is invalid
Ví dụ 4.2:
Xem xét chương trình C như sau



















Tương tự như ở ví dụ một ta cũng định nghĩa file đặc tả “philosopher.spec” như sau:
PickLeft = ( pick_left -> return {} -> STOP ).
procedure pick_left { abstract { 1, PickLeft}; }
PutRight = ( put_right -> return {} -> STOP ).
procedure put_right { abstract { 1, PutRight}; }
PickRight = ( pick_right -> return {} -> STOP ).
procedure pick_right { abstract { 1, PickRight }; }
PutLeft = ( put_left -> return {} -> STOP ).
procedure put_left { abstract { 1, PutLeft }; }

PhilSpec1 = ( pick_left -> put_left -> PhilSpec1 ) + { pick_right }.
program philosopher {
specification abs_1, {1}, PhilSpec1;
}
Giả sử chúng ta muốn kiểm chứng rằng trong hàm philosopher() thực hiện hai lời gọi
hàm pick_left và put_left liên tiếp mà không thực hiện lời gọi hàm pick_right xen giữa. Thực
hiện chạy với Copper rõ ràng hàm philosopher không thõa mãn đặc tả PhilSpec1.
conformance relation does not exist !!
specification abs_1 is invalid
KẾT LUẬN
Luận văn tập trung vào nghiên cứu phương pháp kiểm chứng mô hình một chương trình C từ
mã nguồn và ứng dụng thực tiễn của phương pháp trên công cụ kiểm chứng tự động Copper.
Chương 1 và chương 2 trình bày ngữ cảnh và các khái niệm cơ sở của đề tài như các
khái niệm về hệ chuyển trạng thái, phương thức trừu tượng hóa hành vi PA của một hàm…
void philosopher()
{
int eating;
eating = 0;
while(1) {

pick_left();
pick_right();
eating = 1;
if(eating != 1) assert(0);
eating = 0;
put_left();
put_right();
}
}

17
Chương 3 là nội dung chính của đề tài trình bày cách xây dựng mô hình LTS đặc tả
chương trình từ mã nguồn bằng cách sử dụng các PA giả thiết mô tả các hàm thư viện không
có mã nguồn, và một tập các biểu thức logic trên các biến của chương trình và nêu cách kiểm
chứng sự thõa mãn của một LTS M với một đặc tính p bằng cách sử dụng phép ghép nối song
song LTS M và một LTS lỗi của p.
Chương 4 nêu một số ví dụ đơn giản ứng dụng thực tiễn của phương pháp trên công
cụ Copper.
Hướng phát triển
Luận văn chỉ mới trình bày được các kiến thức cơ bản và ý tưởng giải thuật của
phương pháp, áp dụng trên các ví dụ đơn giản. Hướng nghiên cứu tiếp theo của luận văn này
là tìm hiểu về kĩ thuật theorem prover ứng dụng trong quá trình xây dựng mô hình từ mã
nguồn, nghiên cứu áp dụng phương pháp cho việc kiểm chứng một chương trình viết bằng
ngôn ngữ lập trình hướng đối tượng như JAVA, C++ …, tìm hiểu và áp dụng với các ví dụ
phức tạp hơn.

References
[1] Magic tool website
[2] Copper tool website
[3] Sagar Chaki, Edmund Clarke, Alex Groce, Somesh Jha, Helmut Veith (June 2004),

“Modular Verification of Software Component in C”, IEEE Transactions on Software
Engineering, vol.30, no.6, pp.388-402.
[4] S.Chaki, E.Clarke, A.Groce, J.Ouaknine, O.Strichman, K.Yorav (June 2004), “Efficient
Verification of Sequential and Concurrent C”, Formal Methods in System Design,
vol.25, no.3, pp.129-166.
[5] Robert Gold (December 2010), “Control Flow Graphs And Code Coverage”, Internal
Journal of Applied Mathematics and Computer Science, vol.20, no.4, pp.739-749.
[6] E. M. Clarke, O. Grumberg, and D. Peled (2000), "Model Checking", The MIT Press.
[7] P.N.Hung (2009), Assume-Guarantee verification of Evolving component based
software, PhD. Dissertation.
[8] Sagar Chaki, Edmund Clarke, Alex Groce, Ofer Strichman (2003) “Predicate
Abstraction with Minimum Predicates”, In Proceedings of CHARME, pp.19-34.
[9] Jeff Magee (2006), “Concurrency: State Models And Java Programs”. John Wiley &
Sons.

×