Kiểm chứng mô hình trong JavaPathFinder

Một phần của tài liệu Nghiên cứu về kiểm chứng bất biến của đối tượng sử dụng lập trình hướng khía cạnh (Trang 45)

Đối lập với kiểm thử chỉ thực hiện một đường với một dữ liệu đầu vào, JPF có thể thực hiện mô phỏng không tất định (non-determinism). Mô phỏng không tất định yêu cầu hệ thống sinh tất cả các lựa chọn có thể cho tất cả các trạng thái của hệ thống. Hệ thống này có hai khả năng: backtracking và state matching. Hai cách để tạo ra mô

phỏng không tất định trong JPF là quay lui (backtracking) và so khớp trạng thái (state matching).

1) Quay lui có nghĩa là JPF có thể khôi phục các trạng thái thực thi trước đó để xem xét các lựa chọn còn lại chưa được duyệt. Ví dụ, nếu JPF duyệt đến một trạng thái kết thúc chương trình thì nó có thể quay lui trở lại để tìm các chuỗi lịch trình có thể xảy ra khác vẫn chưa được thực thi. Trong khi điều này về mặt lý thuyết được thực hiện bằng việc thực thi lại chương trình từ đầu, quay lui là kỹ thuật hiệu quả hơn nhiều nếu việc lưu trữ trạng thái được tối ưu hóa.

2) So khớp trạng thái là một kỹ thuật khóa khác để tranh các công việc dư thừa. Trạng thái thực thi của một chương trình chủ yếu bao gồm các ảnh (snapshot) của bộ nhớ heap và ngăn xếp tiến trình (thread-stack). Trong khi JPF thực thi, nó kiểm tra mọi trạng thái mới nếu nó đã nhận thấy một trạng thái tương tự, trong trường hợp này không cần thiết phải tiếp tục theo đường thực thi hiện tại và có thể quay lui tới lựa chọn không tất định chưa được duyệt gần nhất.

Về lý thuyết, kiểm chứng mô hình trạng thái tường minh là phương pháp chính xác – tất cả các lựa chọn được duyệt, nếu có bất kỳ lỗi nào thì nó sẽ được phát hiện. Không may là kiểm chứng mô hình có thể chỉ phù hợp với các chương trình nhỏ, thường là nhỏ hơn 10 nghìn dòng lệnh. Số trạng thái sẽ tăng vượt qua các giới hạn tính toán một cách nhanh chóng đối với các chương trình phức tạp. Vấn đề này được biết đến như là sự bùng nổ không gian trạng thái và có thể dễ dàng được minh họa bằng các chuỗi lịch trình có thể xảy ra đối với một số tiến trình (process) cụ thể bao gồm các phân đoạn nguyên tử (atomic section).

Hình 2.3. Bùng nổ không gian trạng thái do sự đan xen giữa các luồng JPF giải quyết vấn đề hiệu năng này bằng ba cách: (1) các chiến lược tìm kiếm có thể cấu hình được, (2) giảm sổ lượng trạng thái, và (3) giảm các chi phí lưu trữ trạng thái.

1) Các chiến lược tìm kiếm cấu hình được thử giải quyết bài toán mà toàn bộ không gian trạng thái không thể được tìm kiếm bằng việc định hướng tìm kiếm cốt để các lỗi được tìm ra nhanh hơn, ví dụ với các nguồn tính toán ít hơn. Điều này về cơ bản nghĩa là sử dụng công cụ kiểm chứng mô hình không phải như là một công cụ chứng minh mà như là một công cụ gỡ lỗi mà hầu hết được thực hiện bằng việc sử

dụng các tìm kiếm kinh nghiệm (heuristic) để sắp xếp và lọc tập các trạng thái tiềm năng kế tiếp theo một số thuộc tính thích hợp liên quan. Sự tính toán các giá trị theo kinh nghiệm được giao phó cho một lớp được cấu hình của người dùng, tức là không được viết mã cố định trong nhân của JPF.

2) Việc làm giảm số trạng thái phải được lưu trữ là cách được ưu thích để cải thiện hiệu năng và được hỗ trợ bởi một số kỹ thuật sau đây:

Bộ sinh các lựa chọn theo kinh nghiệm (Heuristic Choice Generators): có nghĩa là tập các lựa chọn trong một trạng thái nào đó không cần phải hoàn thành. Kiểu động không thể sinh ra tất cả các giá trị có thể xảy ra nhưng về mặt kiểm tra hành vi hệ thống nó có thể đủ để thử chỉ ba lựa chọn: nhỏ hơn, bằng và lớn hơn ngưỡng. Khả năng quan trọng là để các kinh nghiệm này có thể cấu hình được cốt để chúng có thể dễ dàng được mở rộng hay được thích nghi với các nhu cầu của ứng dụng cụ thể.

Giảm thứ tự bộ phận (Partial Order Reduction): là kỹ thuật quan trọng nhất để giảm không gian trạng thái trong các chương trình tương tranh. Mục đích chỉ để xem xét các chuyển ngữ cảnh tại các hoạt động có ảnh hưởng ngang qua các biên tiến trình như các lệnh PUTFIELD trên các đối tượng có thể truy cập từ các luồng khác nhau. Sự làm giảm thứ tự bộ phận của JPF lợi dụng các mã Java bytecode và các thông tin có khả năng với tới đạt được từ trình thu thập rác.

Sự thực thi của máy ảo chủ (Host VM Execution): JPF là một máy ảo Java được viết bằng Java, tức là nó chạy trên đỉnh của một máy ảo chủ (host VM). Đối với các thành phần không phải là các đặc tính hợp lệ, nó hiểu là để giao phó sự thực thi từ JPF được theo dõi theo trạng thái cho máy ảo chủ được theo dõi không trạng thái. Kỹ thuật MJI tương ứng đặc biệt phù hợp để xử lý sự mô phỏng vào ra và chức năng thư viện chuẩn khác.

Sự trừu tượng hóa trạng thái (State Abstraction): JPF lưu trữ tất cả các thay đổi của bộ nhớ heap, ngăn xếp và tiến trình mà đôi khi có sự quá tải lớn nếu nó đi đến việc quyết định lựa chọn thực thi hai trạng thái khác nhau theo ngữ cảnh của một ứng dụng nào đó. Ví dụ, việc so khớp trạng thái được dựa trên sự phân tích hình thù của các cấu trúc dữ liệu có thể mang lại sự giảm trạng thái đáng kể và đã được sử dụng thành công trong các ứng dụng JPF gần đây.

3) Việc làm giảm chi phí lưu trữ trạng thái có liên quan chỉ yếu đến các đặc trưng cài đặt lõi JPF. Nó không phải là thước đo chính để giải quyết sự bùng nổ không gian trạng thái nhưng việc lưu trữ trạng thái hiệu quả có tính chất bắt buộc đối với một công cụ kiểm chứng mô hình phần mềm. Vì các chuyển trạng thái thường dẫn đến một số lượng nhỏ các thay đổi, JPF sử dụng một kỹ thuật được gọi là sụt trạng thái (state collapsing) để làm giảm các yêu cầu về bộ nhớ mỗi trạng thái bằng việc lưu trữ các chỉ số trong các bể chứa thành phần trạng thái riêng (hash table) thay vì việc lưu trữ các giá trị thay đổi một cách trực tiếp.

2.3. Các đề án mở rộng của Java PathFinder

JPF là một nền tảng có thể mở rộng được. Do đó nhiều dự án đã được triển khai nhằm mở rộng những tính năng cho JPF. Đề án mở rộng nổi bật nhất là thực thi ký hiệu (symbolic execution) để sinh ra các ca kiểm thử. Thực thi ký hiệu sẽ được trình bày chi tiết và được ứng dụng để sinh các ca kiểm thử trong chương 4.

Hiện tại, các mở rộng của JPF bao gồm:

Kiểm chứng mô hình giao diện người dùng (User Interface Model Checking): đây là một ví dụ về kiểm chứng mô hình một lớp cụ thể của ứng dụng Java – các chương trình Swing và AWT. Mở rộng này được thực hiện như là một tập của MJI (Model Java Interface) được mô hình hóa bởi các thư viện chuẩn thay thế về mặt chức năng của các thư viện javax.swing.* and java.awt.* để các ứng dụng giao diện người dùng Java chuẩn có thể được kiểm thử đối với các lựa chọn đầu vào khác nhau của người sử dụng.

Sinh dữ liệu kiểm thử ký hiệu (Symbolic Test Data Generation): phần mở rộng thực thi ký hiệu này sử dụng BytecodeFactory để nạp chồng các mã bytecode lõi của JPF để tạo ra các ca kiểm thử cụ thể. Công việc này được thực hiện bằng việc sử dụng hệ thống thuộc tính trường (field)/cấu trúc ngăn xếp (stackframe) của JPF để thu thập các điều kiện dẫn ký hiệu sau đó đưa chúng vào một bộ giải quyết giàng buộc nhằm thu được dữ liệu kiểm thử cụ thể.

Sinh kiểm thử luồng an toàn ký hiệu (Symbolic Threadsafety Test Generation): đây là một chế độ thực thi ký hiệu rất đơn giản cố gắng nhận dạng các vấn đề an toàn luồng tiềm tàng và sau đó sinh mã để kiểm thử điều này với chế độ thực thi JPF giá trị cụ thể. Thông tin ký hiệu đơn giản hơn nhiều so với phần mở rộng thực thi ký hiệu trên và có thể được giữ lại trong vết thực thi. Danh mục kiểm thử được thực hiện đầu tiên phát hiện sự truy cập không đồng bộ của các trường từ bên trong cùng phương thức công cộng, để nhận dạng các ứng viên cho bộ nghe PreciseRaceDetector.

Khung làm việc kiểm chứng cấu thành (Compositional Verification Framework): phần mở rộng này thực hiện một giải thuật học máy có thể được sử dụng cho việc suy luận đảm bảo giả định, để phân hoạch một hệ thống thành các thành phần có thể kiểm chứng riêng biệt. Mục đích của thực thi này là để cải thiện đáng kể hiệu năng của JPF. Nó cũng có thể được sử dụng để sinh ra các môi trường giả định cho kiểm chứng mô hình UML, để xác định các chuỗi sự kiện đúng đắn.

Kiểm chứng đặc tính số (Numeric Property Verification): sự mở rộng thay thế mã bytecode này được khởi đầu như là một tập các lớp chỉ thị số để phát hiện sự tràn số và nó cũng bao gồm sự truyền giá trị không chính xác (NaN, Inf), sự so sánh chính

xác số dấu phảy động và sự hủy bỏ thê thảm tiềm tàng (thiếu chính xác bởi phép trừ/cộng).

Kiểm chứng mô hình biểu đồ trạng thái UML (UML State Chart Model Checking): khung làm việc biểu đồ trạng thái là một biến thể của kiểm chứng mô hình giao diện người dùng, cho mục đích kiểm chứng các biểu đồ trạng thái UML. Nó là sự kết hợp của các thư viện MJI được dựa trên một giản đồ chuyển đổi duy nhất đối với các biểu đồ trạng thái UML, cùng với các ứng dụng tổng quát (các trình điều khiển kiểm thử) để thực thi các biểu đồ trạng thái này. Khung làm việc hỗ trợ cả kiểm chứng mô hình và kiểm thử sử dụng cùng ngôn ngữ kịch bản cho đặc tả môi trường.

2.4. Kết luận

Java Path Finder (JPF) là một hệ thống để kiểm chứng các chương trình bytecode của Java thực thi được. Nó là một máy ảo Java được sử dụng như là một công cụ kiểm chứng mô hình phần mềm trạng thái tường minh. JPF sẽ duyệt một cách có hệ thống tất cả các đường dẫn thực thi tiềm tàng của một chương trình để tìm các vi phạm các đặc tính như các khóa chết (deadlock) hay các ngoại lệ không được xử lý (unhandled exception). Ngoài ra, khi phát hiện một vi phạm nó sẽ thông báo toàn bộ đường thực thi dẫn đến lỗi đó. JPF đặc biệt phù hợp với việc tìm các lỗi liên đới trong các chương trình đa luồng hay hệ thống tương tranh mà các lỗi này rất khó phát hiện bằng các phương pháp kiểm thử thông thường.

JPF là một hệ thống mở được cấu thành bởi các thành phần. Do đó chúng ta hoàn toàn có thể bổ sung thêm các tính năng mới cho JPF dưới dạng các thành phần mở rộng.

Chương 3 – Kiểm chứng bất biến của chương trình Java sử dụng lập trình hướng khía cạnh

3.1. Đặt vấn đề

Khi ngành công nghiệp phần mềm càng phát triển, hệ thống sẽ ngày càng phức tạp hơn. Khả năng nắm bắt và kiểm soát sự phức tạp đó đi kèm với khả năng biểu diễn hệ thống một cách toàn diện. Từ yêu cầu đó đòi hỏi phải ra đời một công cụ mô hình hóa hệ thống mang tính trực quan. Mô hình được mô tả bằng ngôn ngữ trực quan có nghĩa là đa phần các thông tin được thể hiện bằng các ký hiệu đồ họa và các kết nối giữa chúng, chỉ khi cần thiết một số thông tin mới được biểu diễn ở dạng văn bản.

Ngôn ngữ mô hình hóa thống nhất (Unified Modeling Language – UML) [4] là một ngôn ngữ mô hình hoá thống nhất có phần chính bao gồm những ký hiệu hình học, được các phương pháp hướng đối tượng sử dụng để thể hiện và miêu tả các thiết kế của một hệ thống. Nó là một ngôn ngữ để đặc tả, trực quan hoá, xây dựng và làm sưu liệu cho nhiều khía cạnh khác nhau của một hệ thống phần mềm theo phương pháp hướng đối tượng. UML có thể được sử dụng làm công cụ giao tiếp giữa người dùng, nhà phân tích, nhà thiết kế và nhà phát triển phần mềm. (adsbygoogle = window.adsbygoogle || []).push({});

Tuy nhiên, trong nhiều trường hợp, ví dụ như một mô hình đồ họa như biểu đồ lớp không đủ cho các đặc tả chính xác và nhập nhằng. Các ràng buộc liên quan đến các đối tượng trong sơ đồ cần được mô tả. Các ràng buộc như vậy được mô tả bằng ngôn ngữ tự nhiên thường dẫn đến sự nhập nhằng. OCL (Object Constraint Language) [28] ra đời, được sử dụng để đặc tả các ràng buộc trên các mô hình được chính xác và ngắn ngọn hơn các ngôn ngữ hình thức truyền thống. OCL được sử dụng cùng với các biểu đồ UML.

Trong các giai đoạn phát triển của phần mềm, giai đoạn đảm bảo chất lượng phần mềm càng ngày càng trở nên quan trọng. Hiện nay, có một số phương pháp và công cụ cho phép kiểm tra các ràng buộc OCL cho các hệ thống hướng đối tượng như công cụ runtime constraint checker [20], công cụ Aspect-Oriented Runtime Verification Environment (ARVE) [21], nguyên lý thiết kế theo hợp đồng (Design by Contract) [16-18], … Hầu hết các phương pháp này đều thực hiện việc kiểm tra các điều kiện trước (pre-condition) và điều kiện sau (post-condition) của phương thức. Việc kiểm tra bất biến của lớp được thêm vào điều kiện trước và điều kiện sau của mỗi phương thức của lớp và đảm bảo rằng bất biến không bị vi phạm trước và sau khi thực hiện phương thức. Như vậy các mã kiểm tra ràng buộc sẽ phải gắn với từng phương thức và không kiểm tra được các bất biến có bị vi phạm trong quá trình thực hiện phương thức hay không. Nếu các phương thức của một lớp phải kiểm tra cùng một hoặc một số bất biến thì ta gặp lại hiện tượng dàn trải mã nguồn ngay trong một lớp. Bên cạnh đó, cũng có

một số công cụ nhận mã nguồn Java làm đầu vào và phân tích mã nguốn đó để kiểm chứng như ESC/Java [9] hay JavaPathFinder [19, 25, 30, 31]. Tuy nhiên, các công cụ này dựa vào việc phân tích tĩnh mã nguồn hoặc mã byte-code của Java chứ không kiểm chứng tại quá trình thực thi. Sự cần thiết của việc kiểm chứng bất biến trong quá trình thực thi là bất biến có thể bị vi phạm trong quá trình thực thi phương thức nhưng sau khi thực thi phương thức thì không và các vi phạm bất biến trong quá trình thực thi có thể làm sai kết quả khi tích hợp các mô-đun trong các môi trường khác nhau, đặc biệt là trong các hệ thống tương tranh.

Để có thể kiểm chứng bất biến tại quá trình thực thi và khắc phục tình trạng dàn trải mã kiểm chứng của các phương pháp trên, phương pháp lập trình hướng khía cạnh (Aspect-Oriented Programming - AOP) được đề xuất [23, 3]. Với phương pháp AOP, chương trình chính được cài đặt từ các đặc tả UML, các aspect kiểm chứng được cài đặt từ các ràng buộc OCL. Các aspect kiểm chứng được tạo ra với mục đích chèn thêm mã vào các vị trí cần thiết nhằm kiểm tra xem các giá trị của các thuộc tính của các đối tượng Java khi thay đổi có vi phạm các bất biến trong đặc tả OCL tại thời điểm thực thi hay không. Trong [23, 3] đã thực hiện được việc kiểm chứng bất biến của các đối tượng Java sử dụng AOP nhưng chưa xét đến sự thừa kế bất biến, sự thay đổi ràng buộc ở lớp con và bất biến của các thuộc tính mới được thêm vào ở lớp con. Trong chương này chúng tôi đề xuất phương pháp kiểm chứng các bất biến của các đối tượng Java tại thời điểm thực thi sử dụng AOP tính đến sự kế thừa các bất biến, sự thay đổi ràng buộc của bất biến ở lớp con và bất biến liên quan đến các thuộc tính mới được thêm vào ở lớp con.

3.2. Ngôn ngữ mô hình hóa thống nhất UML

Ngôn ngữ mô hình hóa thống nhất (Unified Modeling Language – UML) [3] là một ngôn ngữ mô hình hóa chuẩn cho việc soạn thảo các bản thiết kế chi tiết của một hệ thống phần mềm, bao gồm cấu trúc và hành vi của nó. Thành phần chính của nó bao gồm các biểu đồ và những ký hiệu hình học để diễn tả các khía cạnh thiết kế khác

Một phần của tài liệu Nghiên cứu về kiểm chứng bất biến của đối tượng sử dụng lập trình hướng khía cạnh (Trang 45)