DEPLOYING CONTRACT DEFINITIONS FOR THE VERIFICATION OF REQUIREMENTS Dehua Zhang and Constantinos Constantinides Department of Computer Science and Software Engineering Concordia University, 1455 de Maisonneuve Blvd. West Montreal, Quebec, H3G 1M8, Canada
[email protected],
[email protected]
1.
Introduction
Design by contract (DBC) is a methodology for designing computer software. By expressing requirements of software as contract, the verification of requirements can be conducted as verifying if contracts are satisfied inside the implementation of the software. Contracts of the software can be clear described by production rules, which fill the gap between design and implementation when describing the definition of contract. Thus, the automated verification is possible. 2.
Theoretical Background
A contract is essentially a constraint that regulates the behavior of software component. A contract of a system operation may contain pre-condition and post-condition. The client, who is a software component calling other software components, has to satisfy the pre-condition to use the called software component, and the software component been invoked has to promise to produce results defined in the post-condition to the client. Aspect Java is an Aspect-oriented Programming (AOP) language that supports the explicit capture of crosscutting concerns (or aspects). In AspectJ, joinpoint is a well-defined point in the execution of a program; pointcut is a collection of joinpoints; advice is a block of code that specifies some behavior to be executed upon reaching a certain pointcut. 3.
Problem and motivation
Verification is a process that the executable system is checked against its specification. Normally, this process is conducted manually by inspecting or testing the behavior of the system and comparing the behavior with the requirement document. The main motivation behind this work is to automate the process of verification. In order to do this, a formal description of the semantics of a scenario and an execution log corresponding to this particular scenario should be produced, and then compare them to complete the verification. 1
4.
Proposal
The principle of verification is described by the following activity diagram (Figure 1).
Modeler
Translate Scenario
Run-time environment
Tracer
Verifier
Source Code :
Scenario Description : Trace
Perform execution
Log :
Verify
Comparison result :
Figure 1. Principle of verification In the above diagram (Figure 1), the Scenario Description is expressed by production rules and developed by the software designer according to the contract generated by DBC. After the source code is completed by the programmer, the program should be executed with the tracer, and the tracer should produce a log which captures the behavior of the program to be verified and is expressed by production rules too. After that, the verifier compares the production rules produced by the tracer and the production rules developed by the designer. If they are matched, the result of the verification is positive; otherwise, negative. 5.
Methodology
All production rules are divided into two categories: static and dynamic. Since the log is produced by tracing the execution of the program, only dynamic rules can be traced. In practice, the tracer is implemented as an Aspect, and the Aspect is implemented closely following the definition of productions rules. In the Aspect, a pointcut is defined at first for each production rule, and then in 2
the advice, the behavior of the program traced is analyzed and recorded in the format of production rules. Thus far, four dynamic productions rules have been defined; however, some of them are not traceable. The following four paragraphs will explain how these rules are captured by the tracer one by one. More detail documentation can be found in the source code of the tracer as comment. 5.1. ::=[calls] Two pointcuts are defined for this rule. One defines the joinpoint of call, and another defines the joinpoint of execution. Since for some method call, both call and execution can be captured, the tracer has to combine the call and execution to produce one production rule instead of two rules. This problem is solved by maintaining a stack to record all methods being called but not return. Even when an exception is thrown by the method being traced, the after call and after execution advice is still executed, so the stack could be well maintained. The is just the top element in the stack. 5.2. i ::= [creates] j and i ::= [deletes] j The ‘creates’ rule is captured by defining a pointcut with calling the constructor joinpoint. The method ‘thisJoinPoint.getThis()’ returns ‘i’ expressed in the format of “class name@memeory address”. However, only the class name of instance j can be obtained. The ‘deletes’ rule is not traceable since java has not explicit deletion. To judge if the object is created successfully, the tracer, maybe the verifier is better, should check if an exception is thrown by the constructor. 5.3. ::=[sets] The ‘set’ rule is captured by defining a pointcut with set joinpoint. The is the top element in the stack maintained for ‘calls’ production rule. 5.4. Container::= [adds] {D} and Container::= [deletes] {D} This production rule is hard to be captured. The set joinpoint can not capture it. In the case study, HashMap is used. The only way so far found to capture the ‘adds’ rule is to capture the put() method of the HashMap, and the way to capture the ‘deletes’ rule is to capture the remove() method of the HashMap. However, if the container is implemented by other container class or even by program’s own container class, methods that are responsible for adding and deleting elements of the container are unpredictable. Thus far, no general solution is found for this rule. 6.
Conclusion and future work
By describing contract as production rules, the gap between the design and implementation is filled. By designing scenario description based on production rules and providing scenario based trace log expressed as production rules, automated verification can be conducted. 6.1.
The tracer 3
Problem about the container should be solved or be proved unsolvable. In addition, the tracer had better produce two logs at the same, one contains as many of information as it can produce, maybe called ‘maximum log’, and another one contains information that is just necessary to compare with the scenario description, maybe called ‘minimum log’. The current log produced by the tracer can be considered as a maximum log. The job of identifying the redundancy information inside the maximum log had better be done by the person who is expected to complete the verifier. Moreover, as more production rules will be invented, the tracer has to be upgraded to be able to capture those new production rules. 6.2. The verifier Ideally, there only two output of the verifier: negative and positive. However, in practice, we found that there may be the third output: ambiguous. The figure 2 shows these three outputs. If the log records the same production rules with the scenario description, as shown in Log1, the verification is positive. If the log records the different production rules with the scenario description, as shown in Log3, the verification is negative. The ambiguous happens if the log records more production rules than the scenario description, as shown in Log2. Hence, the verifier should figure out if those extra production rules (shown as ‘??’ in Log2) are only redundancy or violates the scenario description. Scenario Description
positive
1.
x
2.
y
3.
z
…
negative ambiguous
1.
x
1.
x
1.
x
2.
y
2.
y
2.
z
3.
z
…??...
3.
y
7.
…
…
z
… Log1
Log3 Log2
Figure 2. Possible results of verifier 7.
References
[1] Constantinos Constantinides and Therapon Skotiniotis, The provision of contracts to enforce system semantics throughout software development, Software Engineering and Applications (SEA
4
2004), November 9-11, 2004, Cambridge, MA, USA.
[2] Constantinos Constantinides and Therapon Skotiniotis, Providing multidimensional decomposition in object-oriented analysis and design, Software Engineering (SE 2004), February 17-19, 2004, Innsbruck, Austria.
8.
Appendix: sample run
void ProcessSale.main(String[]) ;; ::=[call]ProcessSale.main(..) Store() ;; null::=[creates]Store ProductCatalog() ;; Store@19b49e6::=[creates]ProductCatalog java.util.HashMap() ;; ProductCatalog@6ca1c::=[creates]java.util.HashMap java.util.HashMap() Map ProductCatalog.descriptions ;; ProcessSale.main(..)::=[sets]ProductCatalog.descriptions ProductCatalog.descriptions . . . void Register.endSale() ;; ProcessSale.main(..)::=[call]Register.endSale() void Sale.becomeComplete() ;; Register.endSale()::=[call]Sale.becomeComplete() void Sale.becomeComplete() void Register.endSale() void ProcessSale.main(String[])
5