140 likes | 255 Views
Specification of Java code with jpf-mango. Frank Rimlinger Department of Defense. Summary. Intrinsic specification Formal modeling with JavaPathfinder Algorithm Subdivision Invocation Parameterization Invariant detection Translation Definitions Guessing hypotheses Garbage collection
E N D
Specification of Java code with jpf-mango Frank Rimlinger Department of Defense
Summary • Intrinsic specification • Formal modeling with JavaPathfinder • Algorithm • Subdivision • Invocation • Parameterization • Invariant detection • Translation • Definitions • Guessing hypotheses • Garbage collection • Secondary invariants • Factor paths • Rewriter engine • Automation • Status • Live demo
Intrinsic specification • Most general form: input constraint implies outcome. • There can be a lot of these, most are not interesting, (thrown exceptions, hung loops) • Could be undecidable, (halting problem), but lots are practical cases aren’t. • Find the interesting cases, present them to user in a manner that is useful for program debugging (somewhat like java doc, except precise) • Present to an automated theorem prover for formal proofs of loop termination and other constraints involving loop output. • Re-cycle all results, bottom up analysis. • User must train tool, but only as tool requests help. Tool provides the context required for user comprehension of the issue.
Formal modeling with JavaPathfinder • Extensions of JPF, SCANNER and JPFrewriter. • SCANNER : Instead of executing a byte code,create a formal state transition and formal branch conditions. • JPFrewriter : traverse internally generated graphs, composing and simplifying state transitions and branch conditions along each path == update the path annotation. • New choice generator at each branch • Listener keeps track of bookkeeping for splicing path annotations • Special consideration for finding exit paths from a loop body. These are cached to allow for guessing of hypotheses. Follow-up entry points are dynamically added to the choice generator. • Bad outcomes result in abandoned paths, (but may also be valuable) • The annotation of a survivor path becomes part of the specification. • Works best when there are lots of abandoned paths and just a few survivors.
Algorithm: subdivision • Result of SCANNER is a control flow diagram. • Pass to the acyclic quotient. • Each non-trivial quotient vertex lifts to a graph, which is excised. Remainder is like dough after cookies cut out, in particular acyclic. • Blow up a vertex in each lift. So a vertex on some cycle becomes two vertices, alpha and omega. Alpha gets outgoing edges, omega gets incoming. • Paths from alpha to omega are cases of a recursive function body. JPFrewriter bookkeeping to keep track of this. • Now recursively apply above algorithm to all blown up graphs, until everything is acyclic. Resulting graphs form a hierarchy of modules which are specified bottom-up. • Methods not part of mutually co-recursive systems are excised at their entry and exit points to form distinct modules, allowing for specification of methods.
JPFrewriter heartbeat • JPFrewriter systematically walks the paths of a module. At each vertex (byte code instruction) and each edge (branch condition) there is a “heartbeat”. This involves: • Invocation: Called methods not part of a mutually co-recursive system have already been specified, and the formal state transition along with its input constraints is composed with the input caller state transition. • Parameterization: substitute local variable names for formal state accessors, which are ugly looking, e.g. “@this” instead of (getFromLocalVar 0 (topFrame @inputStack)) • Invariant detection: Given an expression x, to “know” when x= f(L(I))=f(I) for some loop L, where I is the input state to L. Much computational effort is expended to eliminate loop invocations whenever possible.
JPFrewriter heartbeat, continued • Translation: once parameterized, expressions can be rewritten in natural language somewhat resembling javadoc. This rendering is profitably exposed to user and in practice is effective at detecting bugs. • Failure modes: expression too complex. When this happens, user must introduce definitions which reduce complexity. Example: • ( if ( < 127 ( iand x 255 ) ) ( + ( iand x 255 ) -256 ) ( iand x 255 ) )--> bytecast(x). Implicit assumption is user familiar enough with what is going on in code to recognize such simplification. In practice requires trial and error. • Expression does not translate: If expression doesn’t reduce to a single string, then user is required to add to the translation grammar. This is generally an easy thing to do, but there are pitfalls to avoid.
Occasional tasks • Guessing hypotheses: • when graph traversal encounters a loop, (hits omega), then the recursive function generated by the specification of the loop module becomes part of the state transition of the current path. • Now traversal magically jumps to alpha, but as you leave alpha, the initial path might still be part of the lifted graph for this loop module. • At the point you finally arrive at the boundary, a determination is made, good or bad exit. • The bad exits are traversed first, and show up in light pink in the user feedback. • When a good exit is reached, the “deductive system” analyzes the information about the bad exits to try and guess a hypothesis on loop input state which implies that the good exit is achieved. • Failure mode: can’t guess. In this case user must supply rules which will accomplish the guess when applied to the case at hand.
Occasional tasks, continued • When a good exit from the module being specified is attained, the resulting state transition is translated, and combined with previously translated assumptions for this path. The result is the natural language specification for this case of the module. • Before this happens, the heap must be garbage collected. The most successful garbage collection tools are temporal analysis and type analysis • Temporal analysis leverages the fact that an object cannot be referred to before it is created. In particular, references in the input state to this module cannot refer to objects created by this module. • References to objects created by this module are concrete, and so type resolution kicks in. • Failure mode: If a heap fails to garbage collect, the resulting translation will be ugly. “Expert” must then augment the address resolution facility. This activity is fraught with peril and requires much testing to do correctly.
Secondary invariants • The easiest invariants have the form f(L)=f. These are verified by showing f(Ci)=f for each case Ci of the loop. But if the invariance depends on the input state s0, then use the technique of “secondary invariants” • Secondary Induction: Suppose have property P such that • 1) P(s0) • 2) P(s) implies P(C(s)) for all cases C • 3) P(s) implies f(C(s))=f(s) for all cases C • Then fL(s0)=f(s0) • Point is that when the attempt at the easy invariant fails, the failure mode is often such that a candidate P for the secondary test is generated.
Factor paths • When an expression f(L) is replaced by f(I), it still “remembers” it was once f(L). This allows an expression of the form exp(g(L),f(I)) to recognize that it factors as exp(g,f)(L). • Unfortunately, this binding is not persistent. In other words, f(I) could be part of the state transition of a previously specified module, and exp could result from calling that module. Attempts to remember every binding that ever was tend to have exponential growth issues. • Instead, the so-called “factor path”, or trail of uninstantiated state transitions is stored persistently for each case. This information is sufficient to reconstruct factorizations, but the process is still computationally expensive. Feeling is that the factorization problem burns more cycles than any other aspect of the problem. There is ample room for optimization here.
Rewriter engine • The core engine to transform expressions is a rule-driven general purpose pattern matcher. • Basic rules are exposed in a fully editable rulebase. • Templates are used to automatically build families of rules to accomplish tasks such as operator commutation. • Variables of a pattern are bound to requirements, which are boolean valued methods intended to execute very quickly, accepting or rejecting a match. • If an expression matches, then an action is called. Actions are arbitrarily complex methods. They can and often do recursively invoke the rewriter engine. Hierarchies of actions can work together to accomplish complex tasks such as invariance detection. • Basic design has been stable for a number of years. The low level implementation is a flat queue that is fed from a tree of events. Although “powerful”, unfortunate consequence is complex overhead at higher design levels. This design evolved to deal with stack overflow issues that may no longer be relevant. But it is functional.
Automation • New with the JPF implementation jpf-mango is MangoMonitor. • Basic Mango tool implemented in RCP, and as such, stores a cache of preferences. • As training sessions progress, these preferences are stored hierachically in the file system. • MangoMonitor walks this hierarchy, calling Mango with each set of preferences. • Mango must timeout after a user specified amount of time (a few minutes), in order to avoid choking on its own garbage. An overtime period is allowed if needed for generation and storage of persistent artifacts, so that each invocation makes some progress. MangoMonitor automatically restarts Mango with fresh memory. • Tool can run for hours, possibly days, in this unattended mode. This is essential for regression testing as the system evolves.
Status • Port to JavaPathfinder has been accomplished. • Specification time is linear in the amount of code, for code tested to date. • Work in progress: Generalization technique for known case explosion problem. The deductive system for guessing hypotheses really needs to get off the drawing board. Embedding of Mango model in ACL2, essential for relieving conjectures. • Anticipated use of tool in the near future to analyze small (1,000 lines) amounts of real java code. • Need significant examples, faster execution, and deployment as a WorkBench plugin. With all this in place, maybe a GSOC candidate. • Ample opportunity for MangoMonitor to exploit parallelism, for example.