Minh họa việc loại đường thực thi dư thừa

Một phần của tài liệu (LUẬN văn THẠC sĩ) kiểm chứng chương trình dựa trên SMT (Trang 62 - 66)

Hình 4 .6 là đồ thị luồng điều khiển của chương trình trên

Hình 4.7 Minh họa việc loại đường thực thi dư thừa

b) Chọn các đường thực thi có độ phủ mã nguồn lớn hơn (Coverage - Optimized Search): Trong trường hợp này KLEE sẽ tiến hành chọn đường thực thi có khả năng phủ mã cao. Nó sử dụng kỹ thuật dựa trên

Process (data, flag) Process (data, flag) flag = 1 flag = 0 arg1>100 flag = 1 false true arg2>100 false true null

data, arg1, arg2 = * flag = 0; if (arg1 > 100) flag = 1; if (arg2 > 100) flag = 1; process(data, flag);

kinh nghiệm để tính toán trọng số của các trạng thái. Sau đó, chọn đường thực thi đi qua các trạng thái có trọng số cao hơn.

KLEE sử dụng luân phiên hai chiến lược này. Nghĩa là, thực hiện ưu tiên thực thi các đường có độ bao phủ cao trước sau đó sử dụng kỹ thuật ngẫu nhiên trong trường hợp bị mắc kẹt trong quá trình duyệt.

ii) Loại bỏ các đường thực thi dưa thừa (Eliminating Redundant Paths): Nếu 2 đường thực thi cùng dẫn đến một điểm và có cùng một tập ràng buộc thì chúng ta có thể loại bỏ một đường. Với tiêu chí này đường thực thi nào có trong số lớn theo một tiêu chuẩn nào đó sẽ được ưu tiên thực hiện trước. Xét một thí dụ với đoạn mã và thực thi tương ứng của nó trong hình 4.7.

b) Bao phủ tất cả các lệnh (complete statement coverage).

Ca kiểm thử được gọi là phủ tất cả các lệnh của chương trình nếu tất cả các câu lệnh đều được thực hiện ít nhất một lần.

Xét ví dụ hình 4.6, tiến hành thực hiện tất cả các lệnh của hàm

ReturnAverage được biểu diễn trong đồ thị luồng điều khiển hình 4.6.

Khởi tạo ở nút 2 có 4 câu lệnh gán: i = 0; ti = 0; tv = 0; sum=0; Bước 2 vòng lặp while với điều kiện điều khiển vòng lặp ở 3 và 4 là: (ti < AS && value[i] != -999). Lựa chọn một trong hai nhánh đúng hoặc sai của biểu thức điều kiện để duyệt trong trường hợp câu lệnh rẽ nhánh (if).

Để đảm bảo độ phủ tất cả các câu lệnh chúng thì cần lựa chọn một hoặc nhiều hơn các đường xuất phát từ câu lệnh đầu tiên của hàm và kết thúc ở câu lệnh return, exit … của chương trình. Các đường này phải đi qua mỗi câu lệnh của chương trình ít nhất một lần. Vậy làm thế nào để chọn ra được những đường thực thi đảm bảo tiêu chí này mà vẫn đảm bảo được tính đơn giản luôn là một câu hỏi cần được trả lời trong trường hợp này.

Trong hình 4.6 một đường thực thi phủ tất cả các câu lệnh đó là đường thực thi: 1-2-3(T)-4(T)-5-6(T)-7(T)-8-9-3(F)-10(T)-12-13.

Một nhánh là một cạnh xuất phát từ một nút. Như vậy mỗi câu lệnh tính toán, lệnh gán … được biểu diễn bởi hình chữ nhật trong đồ thị luồng điều khiển có một nhánh đi qua. Mỗi câu lệnh điều kiện biểu diễn bởi hình thoi có hai nhánh đi qua. Mỗi câu lệnh return, exit không có nhánh đi qua.

Việc chọn ca kiểm thử phủ tất cả các nhánh ở đây nghĩa là lựa chọn một hoặc nhiều đường thực thi mà đi qua mỗi nhánh của chương trình ít nhất một lần. Trong hình 4.6 nếu ta lựa chọn 2 đường thực thi:

P1: 1-2-3(F)-10(F)-11-13

P2: 1-2-3(T)-4(T)-5-6(T)-7(T)-8-9-3(F)-10(T)-12-13.

Trong trường hợp này các nhánh 4(F),6(F), 7(F) là chưa được phủ. Vậy để đảm bảo phủ tất cả các nhánh của chương trình nên chọn năm đường thực thi sau:

P 1: 1-2-3(F)-10(F)-11-13 P 2: 1-2-3(T)-4(T)-5-6(T)-7(T)-8-9-3(F)-10(T)-12-13 P 3: 1-2-3(T)-4(F)-10(F)-11-13 P 4: 1-2-3(T)-4(T)-5-6(F)-9-3(F)-10(F)-11-13 P 5: 1-2-3(T)-4(T)-5-6(T)-7(F)-9-3(F)-10(F)-11-13. d) Phủ tất cả các vị từ (predicate coverage)

Một hoặc nhiều đường thực thi được gọi là phủ tất cả các vị từ khi và chỉ khi nó phủ cả tất cả các lệnh và phủ tất cả các nhánh của chương trình. Có nghĩa là ta cần chỉ ra các đường thực thi phủ tất cả các giá trị đúng hoặc sai của của biểu thức điều kiện. Trong trường hợp biểu thức điều kiện gồm nhiều điều kiện thành phần thì phủ tất cả các vị từ nghĩa là tất cả các nhánh đúng hoặc sai của biểu từng điều kiện thành phần phải được duyệt ít nhất một.

4.3. Thực thi tƣợng trƣng động với KLEE

Cùng với sự phát triển nhanh chóng cả về số lượng và chất lượng của phần mềm thì độ phức tạp của phần mềm ngày càng tăng. Nó thể hiện ở số lượng dòng mã lớn, những luồng điều khiển phức tạp, phần mềm không hoạt

động một cách độc lập mà tương tác với môi trường quanh nó. Tuy nhiên, có thể dự đoán được các hành vi của hệ thống thậm chí cả những hành vi phát sinh lỗi.

Kỹ thuật thực thi tượng trưng động cho chúng ta lý luận một cách thức tự động hóa về hành vi và sự tương tác của hệ thống với người sử dụng và với môi trường quanh nó. Kỹ thuật thực thi tượng trưng động đã được trình bày một cách cụ thể trong chương 1 (phần 2.2). Kỹ thuật thực thi tượng trưng động cho chúng phép duyệt qua tất cả các giá trị cũng như các đường thực thi có thể có của chương trình.

Tuy nhiên, với khi áp dụng kỹ thuật này với các chương trình trong thực tế thì có thể gặp phải vấn đề bùng nổ đường dẫn. Và nếu áp dụng nó một cách đơn giản thì có thể bị mặc kẹt trong quá trình duyệt các đường thực thi có thể có của mã nguồn.

KLEE đã sử dụng một vài kỹ thuật để khắc phục được vấn đề này.

4.4. Thực thi tƣợng trƣng động và giải pháp của KLEE

Kỹ thuật thực thi tượng trưng giải quyết được vấn đề bùng nổ trạng thái nhưng lại gặp phải 2 vấn đề sau:

Bùng nổ số lƣợng đƣờng thực thi: Đây có lẽ là đặc trưng quan trọng nhất của bộ giải ràng buộc trong kỹ thuật thực thi tượng trưng. Đặc điểm của thực thi tượng trưng đó là duyệt qua tất cả các đường thực thi có thể có của chương trình. Tuy nhiên, với các chương trình lớn trong thực tế thì lượng đường thực thi sẽ rất lớn. KLEE sử dụng kỹ thuật thực thi tượng trưng động EGT được trình bày trong chương 2 phần 2.2 để giải quyết vấn đề này.

Các thao tác trên phần tử mảng: Trong trường hợp giá trị tượng trưng nằm ở chỉ số mảng. Chẳng hạn như mảng A[i], trong đó i là giá trị tượng trưng, khi đó việc sinh giá trị cụ thể cho i rất có thể sẽ dẫn đến trường hợp truy cập ra ngoài kích thước của mảng. KLEE giải quyết được vấn đề này nhờ sử dụng kỹ thuật thực thi tượng trưng động EGT.

4.5. Một số bài toán kiểm chứng và kiểm thử tự động dựa trên KLEE

Như đã biết, KLEE là công cụ sử dụng thực thi tượng trưng để vét cạn không gian trạng thái của chương trình và sinh giá trị vào cụ thể để phát hiện các lỗi trong mã nguồn tương ứng ở đầu vào. KLEE hoạt động bằng cách sử dụng trình biên dịch LLVM để tạo ra mã Bit code cho ứng dụng. Sau đó, người sử dụng sẽ tiến hành chạy KLEE với một lượng tham số tượng trưng nhất định. Việc này có thể được thực hiện thông qua một số lượng các thư viện được tạo sẵn trong KLEE. Khi ứng dụng được thực thi, KLEE sử dụng tham số tượng trưng để xác định các đường thực thi cùng với các điều kiện tương ứng khác nhau. KLEE xử lý trên từng đường thực thi riêng bằng cách xử lý các mã Byte code được tạo ra bởi trình biên dịch LLVM và xem xét thêm những thông tin trạng thái. Những thông tin trạng thái được sử dụng để phát hiện lỗi truy cập bộ nhớ, lỗi gọi hàm abort(), lỗi hàm đánh giá assert(), lỗi chia cho không. Dưới đây là một số bài toán kiểm chứng chương trình áp dụng KLEE.

4.3.1. Kiểm tra lỗi chia cho 0 trong chƣơng trình

Thực nghiệm tiến hành kiểm tra hàm example (int a, int b) trong hình 4.3.1.a Với 2 đối số a, b thuộc kiểu nguyên. Hàm trả về kết quả của phép chia a

cho b nếu a >0 và trả về a trong trường hợp ngược lại. Hàm

example (int a, int b) có khả năng xảy ra chia cho 0 vì có thực hiện phép chia

a/b.

Một phần của tài liệu (LUẬN văn THẠC sĩ) kiểm chứng chương trình dựa trên SMT (Trang 62 - 66)

Tải bản đầy đủ (PDF)

(82 trang)