SMT-LIB cung cấp các lý thuyết đặc tả, logic và các tiêu chuẩn đo lường [4]. Với một tiêu chuẩn đo lường đã có, một biểu thức có thể được kiểm tra tính thỏa mãn với khía cạnh của lý thuyết nào đó.
Phiên bản 2.0 đã cải tiến một số chức năng để làm tăng khả năng biểu diễn cũng như linh động hơn. Phiên bản này đưa ra:
Môt ngôn ngữ được sử dụng để chuyển các công thức dưới dạng một kiểu phiên bản của logic bậc một.
Một ngôn ngữ cho việc đặc tả các lý thuyết nền tảng (background theories) và chỉnh sửa một số từ vựng chuẩn cho các kiểu, các phương thức, và các ký hiệu vị từ (predicate symbol).
25
Một ngôn ngữ cho đặc tả logic, các lớp được giới hạn của các biểu thức được kiểm tra tính thỏa được trên lý thuyêt nền tảng.
Một ngôn ngữ dòng lệnh (command language) cho việc tương tác với các công cụ tìm lời giải SMT theo giao diện ngữ cảnh. Nó cho phép xác nhận hoặc hủy bỏ các biểu thức, truy vấn về khả năng thỏa mãn của chúng, thực thi mô hình của chúng.
Dưới đây là một ví dụ về một biểu thức được biểu diễn dưới dạng SMT – LIB: Với: x1, x2, x3, x4, x5, x6 là số nguyên, biểu thức:
((x1 – x2)>=1) && ((x1 - x2) <=3 )&&( x1 = (2*x3 + x5))&&( x3 = x5)&&(x2 = 6* x4)
Khi đó có thể biểu diễn dưới dạng SMT-LIB
(benchmark example :status sat
:logic QF_LIA
:extrafuns((x1 Int)(x2 Int)(x3 Int)(x4 Int)(x5 Int)) :formula (and (>= (- x1 x2) 1) (<= (- x1 x2) 3) (= x1 (+ (* 2 x3) x5)) (= x3 x5) (= x2 (* 6 x4))) ) 2.7 Các quan hệ, hàm, và hằng số.
Các khối cơ bản tạo nên các biểu thức SMT là các hằng số (constant), hàm, và quan hệ [10]. Các hằng số là các hàm không có tham số. Các quan hệ là các hàm nếu hàm đó trả về một giá trị kiểu Boolean. Các hàm có thế có tham số kiểu Boolean, vì vậy chúng ta có thể ghép các phương thức với các quan hệ một cách tùy biến. Tóm lại mọi thứ có thể coi như là một hàm.
2.7.1 Tất cả các hàm là tuyệt đối (total) [10]
Không giống như các ngôn ngữ lập trình, nơi các phương thức có nhiều phía ảnh hưởng, chúng có thể ném ra các ngoại lệ hay không bao giờ có giá trị trả về. Các phương thức trong logic bậc một là tuyệt đối. Đó là chúng được định nghĩa trên tất cả các giá trị đầu vào. Ví dụ như phép chia. Trong Z3 phép chia cho 0 vẫn được định nghĩa, nó chưa được chỉ định rằng nó nghĩa là gì. Bất kỳ diễn tả nào chia cho 0 là có thể chấp nhận được trong Z3. Ví dụ, x chia cho x là bằng 1 với mọi giá trị của x, và 0 chia cho x là bằng 0 với mọi x. 0/0 có thể bằng mọi x.
Hãy xét 2 ví dụ sau, Z3 sẽ cho kết quả là thỏa mãn (sat) Ví dụ 1:
26 (assert (= 1 ( div 0 0) )) (check-sat) ; sat (pop) Ví dụ 2: (push) (assert (= 0 ( div 0 0) )) (check-sat) ; sat (pop)
Trong 2 ví dụ này Z3 kiểm tra biểu thức 0/0 = 1 và 0/0 = 0 và đều cho kết quả là thỏa mãn.
2.7.2 Hàm không dịch (uninterpreted function) và hằng số
Hàm và ký hiệu hằng số trong logic bậc 1 là uninterpreted hoặc free, điều này có nghĩa rằng không có 1 thể hiện được đính kèm. Điều này tương phản với các hàm phụ thuộc vào các lý thuyết tín hiệu như số học, nơi các hàm có một thể hiện chuẩn cố định. Hàm không dịch và hằng số là linh động lớn nhất [10].
Để hiểu rõ hơn về các hàm không dịch và hằng số, chúng ta xem ví dụ bên dưới. Một kiểu A và 2 hằng số x, y thuộc khoảng A. Và một hàm f có tham số là một kiểu A và giá trị của hàm cũng là một kiểu A.
(declare-sort A)
(declare-funs ( (x A) (y A)) (declare-fun f (A) A)
(assert (= (f (f x)) x ) ) (assert (= (f x) y))
(assert ( not (= x y))) (check-sat) ;sat (model) ;(“model” “x -> val!0 ; y -> val!1 ; f ->{ ; val!0 -> val!1 ;val!1->val!0 ;else -> val!0 ;}”)
Kết quả trong mô hình trên là những giá trị trừu tượng, bởi vì kiểu A là kiểu trừu tượng (không có thể hiện).
27
2.7.3 Hàm đệ quy
Z3 không cung cấp bất kỳ hỗ trợ đặc biệt nào cho các hàm đệ quy [10]. Chúng ta có thể tiền đề hóa sơ đồ của hàm đệ quy bằng việc sử dụng các tiền đề của logic bậc 1, nhưng chú ý rằng Z3 chỉ định các ngữ nghĩa của logic bậc 1 với các phép bằng và không chỉ định một giải pháp điểm cố định (fixed point) như là 1 chuẩn với các ngôn ngữ lập trình.
Ví dụ về hàm fibonaxi chúng ta có thể biễu diễn chúng như sau:
(declare-fun fib ( Int) Int) (assert (=1 (fib 0)))
(assert (-1 (fib 1)))
(assert ( foall (x int) (=>(x >=2)(=( fib x) *+ (fix (- x 1) (-x 3)))))))
2.8 Số học
Z3 chứa các thủ tục quyết định cho số học tuyến tính trên các số nguyên và số thực. Hiện nay Z3 cũng cung cấp một phần các hỗ trợ cho số học phi tuyến tính.
2.8.1 Số học tuyến tính thực
Trong số học tuyến tính thực Z3 hỗ trợ các phép toán cho kiểu số thực như: + , -, ~ (unary minus), *, / (phép chia) và sử dụng các phép so sánh: =, <, <=, >=, >.
Ví dụ biễu diễn một biểu thức:
(declare-funs ((x Real) (y Real) (z Real))) (push) (assert (> (+ x y) (* 2.0 z))) (assert (< (/ z 2.3) x)) (check-sat) ; sat (model) ; ("model" "x -> 0 ; y -> -18/5 ; z -> -23/10") (pop) (assert (> x 2.0)) (assert (>= y x)) (assert (< y 1.3)) (check-sat) ; unsat 2.8.2 Số học tuyến tính nguyên
Hỗ trợ phép toán +, -, ~, *, /, mod. Trong phép toán / và mod, tham số thứ 2 phải khác 0. Sử dụng các phép so sánh: =, >,>=, <, <=
28
Ví dụ:
(declare-funs ((x Int) (y Int) (z Int))) (push) (assert (> (+ x y) (* 2 z))) (assert (< (div z 3) x)) (check-sat) ; sat (model) ; ("model" "x -> 0 ; y -> 0 ; z -> 0") (pop) (assert (and (> x 2) (>= y x) (< y 1)) (check-sat) ; unsat 2.8.3 Trộn giữa số nguyên và số thực.
Z3 cũng cho phép có thể trộn giữa số nguyên và số thực. Ví dụ:
(declare-fun to_real (Int) Real) (declare-fun to_int (Real) Int) (declare-fun is_int (Real) Bool) (= 4.0 (to_real 4))
(= 4 (to_int 4.5))
(iff (is_int x) (= x (to_real (to_int x))))
2.8.4 Số học phi tuyến tính
Z3 hỗ trợ số học phi tuyến tính: Ví dụ:
(declare-funs ((x Int) (y Int) (z Int))) (assert (= (* x x) (+ x 2)))
(assert (= (* x y) x))
(assert (= (* (- y 1) z) 1)) (check-sat)
; unsat
Ví dụ trên sẽ kiểm tra (x*x = 2*x AND x*y = x AND (y-1)*z = 1). Kết quả là biểu thức trên không thỏa mãn vì không có bất kỳ một mô hình nào để kết quả là TRUE.
29
2.9 Kiểu dữ liệu
Z3 hỗ trợ rất nhiều kiểu dữ liệu, từ kiểu nguyên tử như số nguyên, số thực cho đến các kiểu mảng, kiểu danh sách, kiểu liệt kê, kiểu bản ghi.
2.9.1 Kiểu bản ghi
Một bản ghi được đặc tả như một kiểu dữ liệu với một phương thức khởi tạo và rất nhiều các tham số như là các thành phần bản ghi. Số lượng các tham số cho một bản ghi là luôn giống nhau. Kiểu hệ thống không cho phép mở rộng các bản ghi và không có kiểu bản ghi con.
Ví dụ dưới trình bày 2 bản ghi là bằng nhau nếu tất cả các tham số của nó là bằng nhau. Nó khai báo kiểu int-pair, với phương thức khởi tạo là mk-pair và 2 tham số có thể được truy nhập bằng sử dụng các phương thức lựa chọn first và second
(declare-datatypes ((int-pair (mk-pair (first Int) (second Int)))))
(declare-funs ((p1 int-pair) (p2 int-pair))) (push)
(assert (= p1 p2))
(assert (not (= (first p1) (first p2)))) (check-sat)
;unsat (pop)
2.9.2 Kiểu liệt kê (enumeration)
Kiểu liệt kê là một loại miền hữu hạn. Các thành phần của miền hữu hạn được liệt kê như là các hằng số phân biệt [10]. Ví dụ, kiểu S là kiểu liệt kê với 3 giá trị A, B và C. Khi đó nó là có thể cho 3 biến kiểu S là khác nhau nhưng không phải cho 4 biến:
(declare-datatypes ((S (A) (B) (C)))) (declare-funs ((x S) (y S) (z S) (u S))) (assert (distinct x y z))
(check-sat) ;sat
(assert (distinct x y z u)) (check-sat)
;unsat
2.9.3 Kiểu dữ liệu đệ qui.
Một kiểu dữ liệu đệ quy bao gồm chính nó. Một ví dụ cho kiểu đệ qui được trình bày bên dưới
30
Ngoài các kiểu dữ liệu trên Z3 còn hỗ trợ nhiều kiểu dữ liệu khác như kiểu bit-vector, kiểu mảng…Chi tiết về những kiểu này có thể tham khảo ở tài liệu [10].
2.10 Ví dụ về Z3
Z3 có khả năng đưa ra mô hình như một phần của đầu ra. Các mô hình sẽ gán các giá trị cho các hằng số trong đầu vào và sinh các lược đồ chức năng từng phần cho các ký hiệu vị từ và ký hiệu phương thức.
Sau đây là một ví dụ về Z3, giả sử chúng ta có file example1.smt là file chứa 1 biểu thức dưới dạng SMT-LIB format. Khi đó chúng ta có thể sử dụng z3 để kiểm chứng biểu thức này bằng cách sử dụng dòng lệnh: z3 /m example1.smt
(benchmark example1 :status sat
:logic QF_LIA
:extrafuns((x1 Int)(x2 Int)(x3 Int)(x4 Int)(x5 Int)) :formula (and (>= (- x1 x2) 1) (<= (- x1 x2) 3) (= x1 (+ (* 2 x3) x5)) (= x3 x5) (= x2 (* 6 x4))) )
Khi đó ta sẽ nhận được kết quả là sat (thỏa mãn) và một mô hình của biểu thức này đó là x1 = -3, x2 = -6, các biến khác = -1 .
2.11 Một vài ứng dụng của Z3
Z3 hiện nay đang được sử dụng trong một số các dự án của Microsoft. Trong phần này sẽ giới thiệu một vài ứng dụng của Z3.
Thực thi tượng trưng động
Công cụ tìm lời giải cho SMT đóng vai trò trung tâm trong thực thi tượng trưng tự động. Hiện nay có một số lượng các công cụ được sử dụng trong công nghiệp dựa trên thực thi tượng trưng tự động như: CUTE, Exe, DART, SAGE, Pex, và YOGI [10]. Những công cụ này thu thập các đường đi của chương trình và biểu diễn chúng như là các biểu thức và sử dụng các công cụ tìm lời giải để xác định những đầu vào để kiểm thử. Z3 là một sự phù hợp tốt cho thực thi tương trưng bởi vì các ngữ nghĩa của hầu hết các câu lệnh có thể được mô hình hóa dễ dàng bằng cách sử dụng các lý thuyết được hỗ trợ bởi các công cụ đó.
Kiểm chứng mô hình chương trình
Thực thi tượng trưng tự động sẽ tìm các đầu vào để có thể hướng dẫn việc thực hiện để tìm các lỗi. Phương pháp này đứng độc lập không đảm bảo ràng các chương trình là không có lỗi. Mục đích của kiểm chứng mô hình chương trình là
31
tự động kiểm tra xem có lỗi hay không từ các danh mục lỗi. Ý tưởng cơ bản của kiểm chứng mô hình là thám hiểm tất cả các nhánh có thể của chương trình bằng cách sử dụng một trừu tượng hóa đầy đủ và có hạn của không gian trạng thái chương trình.
Phân tích chương trình tĩnh
Các công cụ phân tích chương trình tĩnh làm việc tương tự như cách các công cụ thực thi tượng trưng động. Chúng cũng kiểm tra các đường đi có thể của chương trình. Ở một khía cạnh khác chúng không bao giờ yêu cầu thực thi chương trình và chúng có thể phân tích các thư viện phần mềm tiện ích hóa chung một cách độc lập làm sao chúng được sử dụng. Ưu điểm của việc sử dụng công cụ tìm lời giải SMT trong phân tích chương trình tĩnh đó là ngày nay chúng nắm bắt rất nhanh các ngữ nghĩa của hầu hết các quá trình hoạt động cơ bản bằng cách sử dụng các ngôn ngữ lập trình.
Ngoài các ứng dụng trên Z3 còn được ứng dụng trong kiểm chứng chương trình, mô hình hóa, Qex, VS3 [10].
32
CHƢƠNG 3- MỞ RỘNG JPF VỚI Z3
Chương 1 và 2 đã trình bày về Z3 và JPF. Chúng ta có thể thấy rằng: khả năng giải quyết các vấn đề cho thực thi tượng trưng của JPF vẫn còn hạn chế, vì vậy chương này của luận văn sẽ trình bày về tích hợp Z3 vào JPF trong việc sinh dữ liệu để kiểm thử chương trình Java.
3.1 Nghiên cứu, đánh giá các giải pháp
Hiện nay Z3 hỗ trợ một số kiểu định dạng đầu vào đó là: Z3 native, SMT- LIB, simplify, DIMACS. Qua quá trình tìm hiểu tác giả thấy:
Định dạng SMT-LIB được sử dụng trong rất nhiều công cụ tìm lời giải cho SMT.
Đầy đủ và dễ hiểu trong việc biểu diễn các biểu thức.
Vì vậy chương trình sẽ sử dụng định dạng SMT-LIB để tích hợp giữa Z3 và JPF. Tích hợp thành công giữa Z3 và JPF thông qua SMT – LIB sẽ giúp cho việc tích hợp các công cụ tìm lời giải khác trở nên dễ dàng.
Sau khi chọn định dạng SMT-LIB làm định dạng chung để trao đổi giữa Z3 và JPF. Ta có thể thực hiện giao tiếp bằng các cách sau:
Cách 1: Thực hiện thông qua socket
Cách 2: Thực hiện trực tiếp thông qua thực thi bằng dòng lệnh (command line).
Cách 1 có ưu điểm là chương trình có thể chạy phân tán tuy nhiên mất nhiều thời gian cho việc phát triển hơn. Cách 2 có nhược điểm là Z3 và JPF phải chạy trên cùng một máy tuy nhiên ưu điểm là cài đặt tốn ít thời gian hơn.
Cả 2 cách đều phải giải quyết một vấn đề đó là chuyển đổi các ràng buộc hiện thời của JPF sang SMT – LIB sau đó sử dụng Z3 để lấy kết quả về.
Trong luận văn này sẽ sử dụng cách 2 để cài đặt chương trình.
3.2 Kiến trúc hệ thống
Như ta đã biết JPF hiện nay đang sử dụng một số các công cụ tìm lời giải viết bằng Java như là: Choco, IAsolver. Vì vậy ý tưởng của việc thiết kế hệ thống là ta sẽ xây dựng một wrapper, wrapper này là giao diện để liên kết giữa JPF và Z3. Wrapper sẽ tương tác với Z3 tương tư như Choco hay IAsolver.
33
Hình 3-1: Kiến trúc hệ thống
Đầu vào của hệ thống sẽ là các lớp Java ở dạng byte code, sau khi thực thi tượng trưng sẽ ra được kết quả là các ràng buộc của JPF, các ràng buộc này bằng cách sử dụng wrapper sẽ chuyển sang định dạng SMT-LIB. Sau đó sẽ thực hiện việc gọi Z3 thông qua dòng lệnh, xử lý dữ liệu trả về để sao cho lấy được kết quả đúng với định dạng của JPF.
3.3 Chuyển đổi dữ liệu
Thực thi tự động trong JPF sử dụng đầu và là Java byte code, từ Java byte code, việc kết hợp giữa thực thi tự động với JPF lõi (core) sẽ sinh ra các đối tượng PC. Các PC này sẽ chứa ràng buộc. Một ràng buộc là một lớp bao gồm biểu thức phía trái, biểu thức phía phải, phép so sánh và chính nó:
public abstract class Constraint {
JPF Wrapper Z3
Java class ( byte code) Thực thi Tượng trưng Các ràng buộc định dạng JPF Định dạng SMT - Lib
34
private final Expression left; private final Comparator comp; private final Expression right; Constraint and;
}
Ví dụ: x + 1 > y +z
Khi đó biểu thức bên trái là x + 1, bên phải là y + z và phép so sánh là phép lớn hơn: > .
Một biểu thức cũng sẽ được định nghĩa bao gồm biểu thức bên trái, phép toán và biểu thức bên phải. Bên dưới là lớp định nghĩa biểu thức cho số thực:
class BinaryRealExpression extends RealExpression {
RealExpression left; Operator op;
RealExpression right;
BinaryRealExpression (RealExpression l, Operator o, RealExpression r) {
left = l; op = o; right = r; }
public double solution() { double l = left.solution(); double r = right.solution(); switch(op){
case PLUS: return l + r; case MINUS: return l - r; case MUL: return l * r; case DIV: assert(r!=0); return l/r;
default: throw new RuntimeException("## Error: BinaryRealSolution solution: l " + l + " op " + op + " r " + r);
} }
public void getVarsVals(Map<String,Object> varsVals) { left.getVarsVals(varsVals);
right.getVarsVals(varsVals); }
35
JPF đưa ra một lớp chuẩn để chuyển từ những định dạng của JPF sang một công cụ tìm lời giải nào đó. Đó là lớp trừu tượng ProblemGenenal, lớp này sẽ chứa các phương thức để chuyển các biểu thức nguyên tử hoặc ràng buộc nguyên tử sang định dạng của công cụ tìm lời giải, các lớp cụ thể sẽ được mở rộng từ những lớp này. Trong mở rộng này đã có 3 cài đặt là ProblemChoco, ProblemIAsolver, ProblemCVC3, tương ứng với việc sử dụng: Choco, IAsolver, CVC3. Ví dụ để chuyển đổi sang định dạng của ràng buộc phép lớn hơn của IAsolver sẽ như sau:
Object gt(Object exp1, Object exp2){
return (String)exp1 + " > " + (String)exp2 + "; "; }
Việc mở rộng với Z3 cũng nên tuân thủ mô hình tích hợp với các công cụ tìm lời giải khác của JPF. Bây giờ ta phải tổ chức để sao cho chuyển ràng buộc từ JPF sang SMT-LIB. Một SMT-LIB sẽ có dạng như sau:
(benchmark:// Tên
:logic // kiểu cua logic - ví dụ số học tuyến tính là: QF_LIA :extrafuns: // Khai báo biến
:formula :// Phần định nghĩa biểu thức )
Trong đó formula chính là biểu thức cần phải chuyển ràng buộc của JPF