ĐẠi học quốc gia hà NỘi trưỜng đẠi học công nghệ Trần Bình Dương sinh ca kiểm thử tham số HÓa cho chưƠng trình java khóa luận tốt nghiệP ĐẠi học hệ chính quy ngành: Công Nghệ Thông Tin



tải về 446.91 Kb.
trang8/10
Chuyển đổi dữ liệu02.09.2016
Kích446.91 Kb.
#31271
1   2   3   4   5   6   7   8   9   10

3.2. Symbolic JPF


Thực thi tương trưng một chương trình Java bằng cách cài đặt một trình thông dịch bytecode đặc biệt. Symbolic JPF là sự mở rộng của JPF bằng cách thông dịch bytecode theo ngữ nghĩa thực thi tượng trưng. Symbolic JPF không đòi hỏi phải sửa đổi chương trình mã nguồn như các cách tiếp cận thực thi tượng trưng một chương trình sử dụng JPF trước đây[16, 20, 26]. Với Symbolic JPF, việc thực thi tượng trưng với các phương thức mà gọi các phương thức khác sẽ được thực hiện dễ dàng.

3.2.1. Bộ tạo chỉ thị


Bộ tạo chỉ thị (Instuction Factory) cho phép thay thế và mở rộng ngữ nghĩa thực thi cụ thể của bytecode bằng ngữ nghĩa thực thi tượng trưng. Các thông tin tượng trưng được lưu trữ trong các thuộc tính (attributes) kết hợp với dữ liệu chương trình (fields, stack operands, local variables) và được cập nhật trong quá trình thực thi.

Thực thi các chương trình Java đã được biên dịch (.class files) bằng cách thông dịch các chỉ thị bytecode bằng một máy ảo Java (VM). Theo mặc định, VM thông dịch các bytecode theo ngữ nghĩa thực thi cụ thể. VM của JPF cũng dựa trên mô hình ngăn xếp (stack machine) và tuân theo các đặc tả về máy ảo Java. JPF cho phép thay đổi ngữ nghĩa thực thị cụ thể cho bytecode bằng cách sử dụng Instruction Factory.



Hình 14. Bộ tạo chỉ thị trong JPF

Với mỗi phương thức được thực thi, JPF quản lý một đối tượng đó là MethodInfo. Đối tượng MethodInfo lưu trữ các chỉ thị bytecode như là mảng các đối tượng Instruction. Các đối tượng Instruction được tạo ra từ việc đọc các các bytecode từ tập tin .class tương ứng. JPF xây dựng một số ràng buộc trên lớp Instruction hơn là yêu cầu một phương thức execute().

JPF sử dụng mẫu thiết kế abstract factory[3] để khởi tạo các đối tượng Instruction. Lớp SymbolicInstructionFactory chứa các chỉ thị bytecode theo ngữ nghĩa thực thi tượng trưng. Lớp SymbolicInstructionFactory ủy quyền (delegate) tới lớp cha DefaultInstructionFactory chứa các chỉ thị bytecode theo nghĩa thực thi cụ thể.


3.2.2. Các thuộc tính


JPF quản lý các trạng thái chương trình tương tự như máy ảo Java chuẩn. Mỗi trạng thái bao gồm một ngăn xếp lời gọi cho mỗi tiến trình, giá trị của các trường (fields) và thông tin lập lịch. Ngăn xếp lời gọi bao gồm các stack frame tương ứng với các phương thức sẽ được thực thi. Mỗi stack frame lưu trữ thông tin tượng trưng trong các vùng biến cục bộ (local variables) và các toán hạng ngăn xếp (stack operands).

Hình 15. Trạng thái chương trình thực thi trong Symbolic JPF

Hình 15 minh họa trạng thái của chương trình được thực thi bên trong JPF. Phương thức caller là phương thức đang được thực thi hiện thời. Stack frame tương ứng với phương thức caller nằm trên cùng của ngăn xếp lời gọi. Phương thức caller được thực thi và gọi phương thức callee. Trạng thái chương trình bao gồm heap, stack frame tương ứng với phương thức caller, và stack frame tương ứng với phương thức callee. Giá trị của heap và các stack frame được tạo ra thông qua việc thực thi các chỉ thị bytecode khác nhau. Chỉ thị dup làm nhiệm vụ sao lưu các giá trị giữa các khay toán hạng (operand slots) của stack frame. Chỉ thị istore để sao lưu giá trị giữa vùng biến cục bộ(local variable slots) và vùng toán hạng (operand slots). Chỉ thị putfield và getfield để lấy và thiết lập giá trị cho các trường của đối tượng được lưu trong heap. Chỉ thị invokevirtual để gọi phương thức callee và giá trị trả về sẽ được lưu vào vùng toán hạng của stack frame tương ứng với phương thức caller.

Các thuộc tính kết hợp với dữ liệu chương trình được gọi là các thuộc tính khe (slot attributes). Trong symbolic JPF, các thuộc tính được sử dụng để lưu trữ các giá trị tượng trưng và các biểu thức tượng trưng được tạo ra trong quá trình thực thi.

Các thuộc tính được tạo ra hoặc truy cập bởi các chỉ thị bytecode thông qua các accessors (phương thức setAttr, getAttr). Các thuộc tính cũng có thể được tạo hoặc truy cập thông qua các listener hoặc native peers. Việc thao tác với các thuộc tính được thực hiện hoàn toàn bên trong JPF bởi các hành động khác nhau. Các hành động đó là thay đổi và lưu trữ các trạng thái chương trình (như dup, istore, putfield và getfield).

Các lớp chỉ thị bytecode mà thực hiện việc tạo hoặc thay đổi các thông tin tượng trựng sẽ được ghi đè (override) để thay đổi ngữ nghĩa từ thực thi cụ thể thành thực thi tượng trưng. Các lớp chỉ thị bytecode đó là các chỉ thị rẽ nhánh, tính toán số học, chuyển đổi kiểu… Các chỉ thị bytecode mà chỉ lấy ra hoặc lưu trữ các thông tin tượng trưng sẽ được giữ không đổi theo ngữ nghĩa thực thi cụ thể.


3.2.3. Xử lý các điều kiện rẽ nhánh


Thực thi tượng trưng các điều kiện rẽ nhánh liên quan tới việc tạo ra các lựa chọn khác nhau cho việc tìm kiếm của JPF. Việc điều hướng việc thực thi đi theo các nhánh thông qua một bộ sinh lựa chọn mới là PCChoiceGenerator. Một điều kiện đường đi được kết hợp với một lựa chọn được tạo ra bởi PCChoiceGenerator. Điều kiện rẽ nhánh hoặc phủ định điều kiện rẽ nhánh sẽ được thêm vào điều kiện đường đi tương ứng với mỗi lựa chọn.

Điều kiện đường đi sẽ được kiểm tra sử dụng một bộ xử lý ràng buộc để xác định việc thực thi theo nhánh tương ứng với điều kiện đường đi đó có khả thi. Nếu điều kiện đường đi không thỏa mãn, JPF sẽ quay lui để đi theo các nhánh khả thi khác.

Symbolic JPF sử dụng bộ xử lý ràng buộc choco[27] và Iasolver[30] cho các ràng buộc số học tuyến tính. IAsolver có thể xử lý được cả ràng buộc liên quan tới các hàm toán học phức tạp. Hai bộ xử lý ràng buộc này được tích hợp vào JPF thông qua môt giao tiếp (interface) và ta có thể sử dụng một trong 2 bộ xử lý này để thực thi tượng trưng một chương trình thông qua việc cấu hình.

3.2.4. Ví dụ


Như đã trình bày ở trên, chỉ những chỉ thị bytecode liên quan tới việc tạo hoặc thay đổi thông tin tượng trưng sẽ được thay đổi ngữ nghĩa từ thực thi cụ thể thành thực thi tượng trưng. Một trong những chỉ thị như thế là chỉ chị IADD, chỉ thị này thực hiện việc tính tổng của 2 số nguyên.

public class IADD extends Instruction {...



public Instruction execute(...ThreadInfo th){

1: int v1 = th.pop();

2: int v2 = th.pop();

3: th.push(v1 + v2, false);

4: return getNext(th);

}

}



Đoạn mã ở trên thực hiện việc thông dịch chỉ thị IADD theo ngữ nghĩa thực thi cụ thể. Theo mặc định, JPF gồm các lớp chỉ thị tương ứng với các chỉ thị bytecode được cài đặt để thông dịch bytecode theo ngữ nghĩa thực thi cụ thể.

Máy ảo JPF thực hiện việc thông dịch bytecode tương tự như các máy ảo Java chuẩn khác. Các máy ảo Java thông dịch bytecode bằng cách thao tác với phần tử ở đỉnh của ngăn xếp (operand stack) trong stack frame. Có 2 thao tác chính là push và pop:

+ push: chèn một giá trị lên đỉnh operand stack

+ pop: lấy giá trị lưu ở đỉnh operand stack đồng thời loại bỏ phần tử đó ra khỏi operand stack.

Việc thông dịch chỉ thị IADD theo ngữ nghĩa thực thi cụ thể như sau: lấy lần lượt 2 giá trị lưu ở 2 stack operand trên cùng của operand stack (dòng 1 và 2), sau đó tính tổng của chúng và lưu lại kết quả vào operand stack (dòng 3). Cuối cùng là trả về chỉ thị tiếp theo để thực thi (dòng 4).

Đoạn mã cài đặt việc thông dịch chỉ thị IADD theo ngữ nghĩa thực thi tượng trưng trong Symbolic JPF:



public class IADD extends gov.nasa.jpf.jvm.bytecode.IADD {

@Override



public Instruction execute (...ThreadInfo th) {

1: StackFrame sf = th.getTopFrame();

2: IntegerExpression sym_v1 = ...sf.getOperandAttr(0);

3: IntegerExpression sym_v2 = ...sf.getOperandAttr(1);

4: if(sym_v1==null && sym_v2==null)

5: return super.execute(ss, ks, th);

6: else {

7: int v1 = th.pop();

8: int v2 = th.pop();

9: th.push(0, false);

10: IntegerExpression result = null;

if(sym_v1!=null) {

if (sym_v2!=null)

13: result = sym_v1._plus(sym_v2);



else

15: result = sym_v1._plus(v2);

}

else if (sym_v2!=null)

18: result = sym_v2._plus(v1);

19: sf.setOperandAttr(result);

20: return getNext(th);

}

}

}



Ý tưởng của việc thay đổi ngữ nghĩa thực thi của bytecode cũng tương tự như việc sửa đổi (instrument) một chương trình để hỗ trợ việc thực thi tượng trưng. Với các kiểu dữ liệu chuẩn (int, float,…) cần cài đặt các lớp để biểu thị cho kiểu của các biến tượng trưng tương ứng với các kiểu dữ liệu chuẩn đó. Trong Symbolic JPF, IntegerExpression biểu thị cho biểu thức số nguyên tượng trưng. RealExpression biểu thị cho biểu thức số thực tượng trưng.

Trong quá trình thực thi tượng trưng, các thông tin tượng trưng được lưu trữ bởi các thuộc tính trong các toán hạng ngăn xếp (stack operand) của ngăn xếp toán hạng (operand stack) tương ứng với stack frame của phương thức đang được thực thi tượng trưng. Việc thực thi chỉ thị IADD theo nghĩa thực thi tượng trưng như sau:

Trước tiên, các giá trị tượng trưng lưu trong các thuộc tính của 2 toán hạng ngăn xếp trên cùng được lấy ra (dòng 2 và 3). Việc lấy ra các giá trị tượng trưng lưu trong các thuộc tính không loại bỏ các toán hạng ngăn xếp chứa các thuộc tính đó. Các giá trị tương trưng được biểu thị bằng biểu thức của các giá trị đầu vào tượng trưng. Nếu các giá trị tượng trưng này đều bằng null (dòng 4) thì Symbolic JPF sẽ thực thi chỉ thị IADD theo ngữ nghĩa thực thi cụ thể (dòng 5) . Ngược lại nếu ít nhất một trong 2 giá trị đó khác null, việc thực thi theo nghĩa cụ thể vẫn được thực hiện (dòng 7, 8, 9). Tuy nhiên trong việc thực thi cụ thể này ta không quan tâm tới kết quả của việc tính toán các giá trị cụ thể. Ta cần tính toán các giá trị tượng trưng để kết hợp với thuộc tính tương ứng với kết quả đó. Phương thức _plus thực hiện việc xây dựng một biểu thức tượng trưng mới biểu thị cho kết quả của việc tính tổng của 2 giá trị tượng trưng. Giá trị tượng trưng biểu thị cho tổng của 2 giá trị tượng trưng sẽ được lưu lại vào thuộc tính của kết quả trong toán hạng ngăn xếp (dòng 19). Cuối cùng, chỉ thị tiếp theo được trả về để thực thi (dòng 20).

Ví dụ 3.1:

Ví dụ này sẽ minh họa việc sử dụng bộ sinh lựa chọn trong việc thực thi tượng trưng với các điều kiện rẽ nhánh. Ta xét chỉ thị rẽ nhánh IF_ICMPEQ, chỉ thị rẽ nhánh này thực hiện việc so sánh giá trị của 2 biến kiểu int trong điều kiện rẽ nhánh có bằng nhau hay không để điều hướng việc thực thi đi theo nhánh mà giá trị trả về của điều kiện rẽ nhánh là true hoặc false.

Trong JPF, lớp IfInstruction là lớp trừu tượng cài đặt việc thông dịch các chỉ thị rẽ nhánh if. Lớp này có một phương thức trừu tượng (dòng 2) để nhận giá trị trả về true hoặc false của điều kiện rẽ nhánh. Phương thức trừu tượng này cần được cài đặt tương ứng với mỗi chỉ thị rẽ nhánh if khác nhau. Việc thông dịch các chỉ thị rẽ nhánh if một cách tổng quát như sau:

Thực hiện việc đánh giá biểu thức điều kiện rẽ nhánh (dòng 4). Nếu điều kiện rẽ nhánh nhận giá trị true (dòng 5) thì nhảy tới chỉ thị tiếp theo của nhánh tương ứng với điều kiện rẽ nhánh nhận giá trị true (dòng 6). Nếu không thì trả về chỉ thị tiếp theo để thực thi (dòng 8).

public abstract class IfInstruction extends Instruction {

1: protected boolean conditionValue;

2: public abstract boolean popConditionValue(ThreadInfo ti);

public Instruction execute (... ThreadInfo ti){

4: conditionValue = popConditionValue(ti);

5: if (conditionValue)

6: return getTarget();

7: else

8: return getNext(ti);

}

...


}

Với chỉ thị IF_ICMPEQ thì việc đánh giá điều kiện rẽ nhánh được thực hiện như sau. Lấy giá trị của 2 phần tử trên cùng của stack (dòng 1, 2) và trả về kết quả so sánh giữa 2 giá trị đó (dòng 3). Các giá trị này là các giá trị thực do đó điều kiện rẽ nhánh sẽ chỉ nhận một trong hai giá trị là true hoặc false.



public class IF_ICMPEQ extends IfInstruction {

public boolean popConditionValue (ThreadInfo ti){

1: int v1 = ti.pop();

2: int v2 = ti.pop();

3: return (v1 == v2);

}

...


}

Lớp IF_ICMPEQ bên dưới cài đặt việc thông dịch chỉ thị IF_ICMPEQ theo ngữ nghĩa thực thi tượng trưng. Khi thông dịch chỉ thị rẽ nhánh theo ngữ nghĩa thực thi cụ thể thì việc thực thi chỉ có thể đi theo một trong hai nhánh tương ứng với điều kiện rẽ nhánh nhận giá trị true hoặc false. Thông dịch một chỉ thị rẽ nhánh theo ngữ nghĩa thực thi tượng trưng thì cả 2 nhánh đều được xem xét để điều hướng việc thực thi đi theo.



public class IF_ICMPEQ extends gov.nasa.jpf.jvm.bytecode.IF_ICMPEQ {

@Override



public Instruction execute(SystemState ss,KernelState ks,ThreadInfo ti) {

1: StackFrame sf = ti.getTopFrame();

2: IntegerExpression sym_v1 = (IntegerExpression) sf.getOperandAttr(0);

3: IntegerExpression sym_v2 = (IntegerExpression) sf.getOperandAttr(1);

// both conditions are concrete

4: if ((sym_v1 == null) && (sym_v2 == null)) {

5: return super.execute(ss, ks, ti);

6: }else{ // at least one condition is symbolic

...

7: ChoiceGenerator cg = new PCChoiceGenerator(2);



...

8: conditionValue = (Integer)cg.getNextChoice()==0 ? false: true;

...

9: int v2 = ti.pop();



10: int v1 = ti.pop();

...


11: if (conditionValue) {

...


12: pc._addDet(Comparator.EQ,sym_v1,sym_v2);

...


13: if(!pc.simplify()) {// not satisfiable

...//instruct JPF to backtrack

14: }else{

15: ((PCChoiceGenerator) cg).setCurrentPC(pc);

16: }

17: return getTarget();



18: }else {

...


19: pc._addDet(Comparator.NE,sym_v1,sym_v2);

...


20: if(!pc.simplify()) {// not satisfiable

...//instruct JPF to backtrack

21: }else {

22: ((PCChoiceGenerator) cg).setCurrentPC(pc);

23: }

24: return getNext(ti);



}

}

}



Việc thông dịch chỉ thị IF_ICMPEQ theo ngữ nghĩa thực thi tượng trưng bắt đầu bằng việc lấy ra các giá trị tượng trưng lưu trong các thuộc tính tương ứng với 2 phần tử trên cùng của stack (dòng 2, 3). Nếu cả 2 giá trị tượng trưng lấy ra là null thì JPF sẽ chuyển hướng sự thực thị chỉ thị IF_ICMPEQ theo ngữ nghĩa thực thi cụ thể (dòng 5). Nếu ít nhất một trong hai giá trị không bằng null, một bộ sinh lựa chọn (CG) sẽ được tạo ra (dòng 7). CG sinh ra lựa chọn cho phép cả 2 nhánh được xem xét để điều hướng sự thực thi đi theo (dòng 8).

3.2.5. Kết hợp thực thi cụ thể và thực thi tượng trưng


Symbolic JPF hỗ trợ tốt cho việc kiểm thử phần mềm mức đơn vị. Symbolic JPF có thể kết hợp thực thi cụ thể với thực thi tượng trưng do đó cho phép thực thi một phương thức bằng cách kết hợp giữa các giá trị đầu vào cụ thể và các giá trị đầu vào tượng trưng.

Để thực thi tượng trưng một phương thức, ta cần cấu hình để JPF sử dụng lớp thông dịch bytecode theo ngữ nghĩa thực thi tương trưng SymbolicInstructionFactory, chỉ định tên phương thức cần thực thi tượng trưng và các đầu vào nào của phương thức là cụ thể hoặc tượng trưng. Các đầu vào bao gồm các tham số đầu vào của phương thức và các trường toàn cục (global) mà phương thức đó truy cập.

Một chương trình bắt đầu thực thi theo ngữ nghĩa thực thi cụ thể. Tất cả các lớp chỉ thị bytecode tượng trưng (các lớp Instruction) chuyển quyền (delegate) thực thi tới các lớp chỉ thị bytecode cha mà cài đặt việc thông dịch bytecode theo ngữ nghĩa thực thi cụ thể khi không có các thông tin tượng trưng lưu trữ trong thuộc tính kết hợp với dữ liệu. Một listener quản lý việc thực thi cụ thể bên trong máy ảo JPF và bùng nổ (trigger) việc thực thi tượng trưng lần đầu tiên một phương thức với tên chỉ định được gọi. Từ thời điểm đó, sự thực thi tượng trưng được tiếp tục. Các thông tin tượng trưng lưu trữ trong các thuộc tính được xử lý. Khi phương thức được thực thi xong, JPF in ra các thông tin thu được trong quá trình thực thi phương thức đó.

Một listener đặc biệt được cài đặt để bùng nổ sự thực thi tượng trưng bất cứ lúc nào trong quá trình thực thi cụ thể (Hình 16).



Hình 16: Bùng nổ việc thực thi tượng trưng trong Symbolic JPF

Listener có thể chuyển về thực thi cụ thể như xử lý các ràng buộc trong PC hiện thời, tính toán các giá trị cụ thể tương ứng với các biến và tiếp tục thực thi trong chế độ thực thi cụ thể.

Từ việc có thể thực thi một chương trình kết hợp giữa các đầu vào cụ thể và các đầu vào tượng trưng với Symbolic JPF. Ta có thể sử dụng việc thực thi cụ thể một chương trình để thiết lập các ngữ cảnh thực thi cụ thể khác nhau trong phân tích tượng trưng mức đơn vị. Với một phương thức có nhiều đầu vào ta có thể chỉ định một số đầu vào là cụ thể để giảm đi sự phức tạp của các điều kiện đường đi trong việc thực thi tượng trưng phương thức đó. Tất nhiên, nếu chỉ định một số đầu vào là cụ thể thì việc thực thi tương trưng sẽ không đi theo được tất cả các nhánh mà chỉ đi theo nhánh mà phụ thuộc vào các đầu vào cụ thể đó.




tải về 446.91 Kb.

Chia sẻ với bạn bè của bạn:
1   2   3   4   5   6   7   8   9   10




Cơ sở dữ liệu được bảo vệ bởi bản quyền ©hocday.com 2024
được sử dụng cho việc quản lý

    Quê hương