In contrast to automatically allocated memory, which remains in the stack, dynamic memory refers to the main memory part that is altered by commands such as new, delete and heap data assignments. The dynamic memory contains heaps (see definition 2.1). Let us first review a few important definitions and discuss issues with heaps afterwards.
Jones et al. [Jones11] define a heap as a contiguous subscripted datastructure, and also, alternatively, as an organised graph of “discontiguous blocks of contiguous words”. All allocated memory cells have a reference and the liveness of a cell is defined by its reachability. The liveness is independent from the program statement that creates a dynamic memory cell.
Reynolds [reynolds02] defines heaps (not to be mixed up with a single heap) as the union of all mappings of address subsets to non-empty value cells. Following this definition a single heap would be some addresses pointing to some arbitrary data structure. Reynolds mentions his intention goes back to Burstall’s model [burstall72]. Both refer to trees as implied data structure - which, at least in Burstall’s proposition, denotes a simple heap graph (definition 2.2
formally introduces it, for the moment let us assume it is an arbitrary graph where edges represent some relationship between heap vertices) as for instance the expression x [r] ^a_1,a_2,a_3 & y denotes some path within the heap graph in Fig.1. The graph starts at and stops at a heap cell which is also pointed by some local variable by visiting , , , which all may have some unspecified content on its way there. Reynolds introduces the “”-operator for expressing that two heaps do not share common dynamic memory regions. In contrast to Burstall Reynolds’ model is slightly different: all except the start of a path from a stacked variable denotes its value rather its cell location. As shown in the graph in Fig. 2 by convention it is agreed that stacked variables, such as , only have outgoing edges, where all other vertices, such as , denote some concrete heap cell values and may have zero or more incoming and zero or more outgoing edges.
If we like to parameterise a heap graph so it contained genuine symbolic variables, we rather have to distinguish between parameterised and fixed variable meanings on each verification step. Reynolds introduces the “,”-operator to specify paths in heap graphs. For example, when using “,” the above data-structure could be fully specified by . For comparison, the same data structure without the path-operator “,” would be – we excuse ourselves variable locations and content were mixed up in this example for the sake of simplicity. Based on the “”-operator and “” the so-called Separation Logic was proposed [reynolds02],[parkinsonThesis05] and implemented [berdineCO05b]. The following example [reynolds02] demonstrates the definition of a binary tree predicate (we call a predicate “abstract” whenever it depends on parameters):
The abstraction parameter in Fig. 3 is some variable symbol and denotes the recursively defined predicate implying the left branch does not intersect with any part of the right branch, and vice versa. However, strictly speaking this must not always be the case, since in the above specification might be substituted by , which could hypothetically link back to again and so would breach the convention made previously – luckily, this can be excluded in most cases, except when references to dynamic memory are determined on runtime. For example, p[13+offset] where offset is decidable on runtime only might be such a scenario. The breach may be avoided for just by not passing neither recalling it globally. Even if the tree entirely fits into dynamic memory, remember and get stacked once the tree is traversed: first , then is accommodated at the next available address because of “,”. The authors of this paper are aware of dropping unbound heap memory access may induce considerable practical restrictions, however, we think this restriction can in many cases be overcome by a modification to the chosen data model.
By convention, whenever a vertex of the heap graph has at most one outgoing edge, the heap graph is simple, e.g. linearly-linked lists, trees and arbitrary heap graphs without multiple edges between two vertices. W.l.o.g. we consider only pointers that refer to particular heap cells or class objects that may union several pointers to heap cells. We will further also consider abstract predicates. In order to decide whether two heaps indeed do not share a common heap, it is necessary to check there exists no path from one heap graph to the other.
One alternative to Separation Logic is Shape Analysis [sagiv02]. It makes use of transfer functions in order to describe changes to the heap by every program statement. Another approach, as being demonstrated by Baby Modula 3 [abadi93], uses a class-free object calculus and a single unique result register. This register stores the result after each single statement and so allows to refer to the state before and after running a particular statement. Class-objects and their typeable theories are discussed in [abadi97],[abadi93].
At this point it is worth noting that a points-to model is considered in this paper due to its locality property w.r.t. heap graphs, modifications do not usually require a full specification update due to its edge-based graph representation.
The inspiration for this paper, even if coming from a different context, is [suzuki82], where a rather intuitive but incomplete set of ”safe“ symmetry operations on pointers is proposed in order to prove correctness of more complex pointer manipulations. Safe operations, as rotation or shift, raise big practical concerns as hard-to predict pointer behaviour even on very small modifications as well as incompleteness gaps on pointer rotations.
The main purpose of this paper is to present two new context-free binary operations for heap conjunction and heap disjunction, to show group properties hold and those can be used for example for proof simplifications on proof rules in the future.
Section I of this paper gave a brief overview of the topic and related problems. Section II introduces briefly Separation Logic, it introduces a concluded definition of heap and heap graph, and it comes with conventions for class objects and heap memory alignment. The main part, section III defines heap terms to be interpreted within heap formulae. Pointers of pointers and arrays are only very briefly discussed, heap conjunction is introduced for basic (”heaplet“) and generalised heaps (what is later expressed as heap term) as well as path accessors (see observation LABEL:obs:ObjectPathAccessor). Properties of the conjunction are investigated and established, canonisation steps are demonstrated in order to overcome transient inconsistency, which may occur from references no more alive. Heap inversion is proposed as notational trick. In companion with other properties it may eventually help to define equalities over heaps and so improve the comparison with expected heaps in the future. In particular, defining a partially-ordered set over conjunct heaps w.r.t. sub-graph inclusion, and distributivity along with other properties would eventually help to define for instance a satisfiability-modulo-theory. Partial specification is introduced in section LABEL:sec:PartialSpec for objects. Discussions propose an extension with aliases to the Object Constraint Language. Finally, conclusions follow a short discussion.
Ii Heap Separating Logic
Before going into more detail, let us first ask whether we cannot simply turn all dynamically allocated variables into stacked, as it was proposed, for instance, by [meyer1-03]. Often this will indeed work, however, sometimes it is not a good idea after all, because of performance issues [appel87], for instance. More often the nature of the problem forbids general static assumptions on stack bounds. An overview and numerous definitions of dynamic memory may be found in [Jones11].
The essence of Reynolds’ heap model and properties was briefly wrapped up in the previous section. So, one central problem seems to be expressibility, which is the main purpose of this paper. This section introduces a heap by referring to a graph, followed by numerous model discussions and property observations.
(concluded from Reynolds) A heap is defined as with , being some address set and is some value domain, for instance, integers or inductively defined structures containing . A heap may be composed inductively by other heaps as following:
, where describes some heap graph assertion and in analogy to that , where edges and edges are directed, s.t. iff , with and cases:
(Separation): , and .
(Conjunction): or then or contains some -separated .
Variables as well as pointers are stored in the stack, where the content pointed to remains in heap memory (the following domain equation [berdineCO05b] holds: ). Heap graph assertions are assertions about the heap graph constructed by program statements manipulating the dynamic memory. Those assertions are interpreted as true or false depending on whether an associated concrete heap corresponds or not. Definition 3.2 will introduce the syntax for heap assertions.
Regarding definition 2.1 the overloading of the binary operator ”“ happens in two ways: one is to express two heaps do not overlap, and the second way is to express two heaps are somehow linked together by using transient symbols. The ”“-operator is a logical and spatial conjunction, it links two prepositions about heaps together and it describes heap entities which have some configuration in space, both consume different dynamic memory regions. On the one hand, if we link strictly two separate heaps then we have to find a maximal matching in order to describe the given heap graph entirely, which is impractical. On the other hand, separation also seems to be a very elegant way to separate specification concerns locally: if there is an assertion regarding a particular data structure in heap, this should involve at most only that data structures and exclude unaffected ones. After all, the above initial definition seems complicated enough, because it is ambiguous and it refers to a single heap definition, which should ideally not be too different from Reynolds’ initial and rather intuitive definition of heaps – but as we have seen unfortunately, it is. So, two strict operators would be desired rather a single ”“, one operator to strictly separate and one to join heaps. Heaps shall be replaceable with symbolic placeholders in order to beat ambiguity whilst analysing verification conditions. Moreover, syntax and semantic intention of heap expressions shall be unified.
Once both heap operators are defined, properties and equivalences can be established separately. Finally, heap theories and term algebras may eventually be proposed in future over both heap operators. In definition 2.2 we first need to formally define what a heap actually is.
The underpinning theory behind [burstall72] is the so-called Substructural Logic [dosen93], which is a higher-order logic, a logic where, for instance, the contraction rule does no more hold, constants have in fact turned into predicates that may be quantified (details can be found in [dosen93]). Contraction-freeness [restall94] in this context has for our purpose the advantage of non-repeating heap entities within a heap assertion. By repeating we directly refer to points-to expressions as defined later.
A (finite) heap graph is a directed connected graph within the dynamic memory section which may contain cycles, but must remain simple. Each vertex has a type-dependent size and an unique memory address, but may not overlap with other vertices. Every edge represents a relationship, for instance, a pointer to some absolute memory address or a relative jump field displacement.
The absolute addresses are out of interest to the verification. The heap graph must be pointed by at least one stacked variable, otherwise the so-defined graph is considered as garbage. Stacked variables may also point to one vertex, in this case all except one variable are aliases of the variable considered.
The emphasis lies on finite, since only arbitrary big but finite address space shall be considered. The dynamic memory shall be linearly addressable, however some operations new and delete shall organise themselves how and where to allocate or free heap memory. We restrict ourselves pointers address correctly and sound, and for the purpose of this paper we neither care too much about an optimal memory coalescing strategy to pointers that is most likely expanded on runtime, nor primarily about garbage collection issues. What we concern about is only that there is a relationship between a pointer and a pointed-by region within the heap memory region, it does not even require a pointer contains an absolute address within the dynamic memory range as it is not the case with bi-directional XOR-calculated jump-fields, which are relative pointer offsets depending on the address provided ”somehow“ by an actual vertex address.
Objects are restricted w.l.o.g. to be
non-inner objects only. Inner objects may always be modelled as with associated outer objects, so that there are references to different locations rather than all objects being accommodated within one contiguous heap chunk.
Object fields and method names have to be all unique w.r.t. to its visibility. W.l.o.g. clashing names, for instance inherited names, are resolved by mangling the origin name with visibility mode and information from the deriving class. All references to mangled names need to be taken into account. This task is primarily done during the semantic program analysis phase.
Due to encapsulation, objects do not grow in size normally, and due to late binding object references may keep invariant. However, the size of an object may spontaneously change. Sub-class objects may suddenly grow, but they may also shrink, depending on whether the translating compilation phase does align memory for non-used fields or not. If choosing a forced stack-allocated memory alignment for objects, then an object which is bound lately and passed alone to some procedure may better be reordered within its memory chunk, s.t. the growing part rises upwards on the stack. Because the separating heap are non-contractive [burstall72], object fields specified once may not appear again within a conjuncted heap expression.
Arrays as base type are currently ignored. Multiple edges between the same two vertices are disallowed.
Sharing of same heap cells by multiple cells is allowed to all object fields.
In order to stay consistent with the following definitions, a simple check for the incidence relationship for memory cells for a given heap graph needs to be introduced. A given heap graph is composed of points-to heaplets meaning a directed edge represents a location points to a heap address which contains some value. The check requires all these heaplet conjuncts are traversed and the desired heap graph shall be in an edge-centric representation. However, whenever we like to determine if two heap vertices are incident with each other or not, we prefer a vertex-centric model encoding edges. So, we define the following built-in predicates: reaches(x,y), reaches(x,Y), reaches(X,y), reaches(X,Y), where x is a vertex and X denotes a vertex subset of a given heap graph (y, Y in analogy).
Both interleaved representations in Fig. 4 (the vertex-centric representation is marked by smaller filled midpoints on every edge) are dual and convertible to each other. Midpoints encode source and destination as one vertex and link with former neighbouring vertices. Naturally, this mapping is bijective (proof skipped). Since we in general need to interpret predicates of at least first order, we could do this now by describing one heap graph by one conjunction of ”“ pairs rather than more complex forms.
(Heap Alignment) Object fields do not overlap, fields have distinguishing memory addresses. Pointers to objects and its fields may alias. An object is expressed commonly by the points-to expression . It is agreed w.l.o.g. that object fields may not be accessed via arithmetic displacements but only by a valid object access path using the ”.“-operator and valid subordinate fields. W.l.o.g, but still for the sake of full computability late binding is skipped. A full support would imply the use of only the weakest common heap to be chosen.
Iii Conjunction and Disjunction
A heap term describes heap graphs and is syntactically defined as:
|… points-to heaplet|
|… heap conjunction|
|… heap disjunction|
|… partial heap spec|
|… subterm expression|
where denotes a variable location, a location of a compound object field or a symbol representing some heap variable, and denotes some value of any arbitrary domain. describes the current heap state.
can be considered as a formula since we do not restrict ourselves in not considering variable scopes as long as the syntax definition is obeyed. We further agree on that has lower precedence than , so . For the sake of notational simplicity, we do refer to , which besides is closer to Reynolds’ definition rather than Burstall’s. However, we really should better refer in practice to the address of the content being pointed to rather than the direct domain value, which naturally may be composed. Hence, we agree without any further notice on some polymorphic notational helpers, like , which will allow us to address given values.
implies certain (remaining) heap term(s) is a tautology, regardless of the actual term(s). In analogy to that stands , which implements a contradiction. is a constant built-in predicate implying a given heap must be empty to be satisfiable. This is why all three may be used to consume all not explicitly listed -conjuncted heaplets. The partial specification we get allows us to keep heap formulae simple, since we now may implicitly include all unaffected, but still intended, heaps belonging together. Partial specifications together with abstract predicates raise modularity. This becomes particularly of interest for class objects, where all field heaplets generate its own heaplet scope, which is different from the global non-object scope (see convention 2.4). In fact we just discussed extensions to our heap term definition, which ought to be summarised:
Extended heap terms are heap terms with constant formulae, negation and logical conjuncts:
|… heap term|
|… abstract predicate call|
|… logical negation|
|… logical conjunction|
|… logical disjunction|
The logical conjunctions do not really require more explanations than already said. A predicate call to a previously defined predicate may invoke all dependent subcalls, although predicate calls are not classic procedure calls. An brief introduction of Prolog using predicates and reasoning a specific Hoare-calculus may be found in [haberland14]. Predicates may be parameterised by zero or more heap terms bound to the predicate. Heap terms may be used as input or output terms, or even both at the same time. Intentionally, heap terms are used as sub-expressions in logical assertions. The verification of a predicate retrieves a Boolean value depending on if a given formula is obeyed.
Pointers of pointers are syntactic sugar. They do not fundamentally extend the expressibility of heap graph assertions. Their only purpose w.r.t. heap terms is to have an additional indirection level for increased programming language flexibility. They act as placeholder or symbol variable for pointer locations.
By pointers of pointers neither the heap graph itself gets extended nor the referenced heap in comparison with no pointers of pointers. Symbolic variables and placeholders are useful, because they may select numerous heaplets at once. But, the ”,“-operator can do this already for linearly-linked lists and this operator was found superficial in terms of expressibility. In addition to that, it should be noted, that abstract predicates may also perform a selection of numerous heap cells.
Although not too useful in a theoretic manner, the above conjecture does not necessarily exclude usability gains in practice.
Heap conjunction is defined as heap graph, where is ’s heap graph representation, is a heap graph, and is a points-to heaplet:
where determines all heap graph vertices being directly pointed by , which in case is an object includes all of the fields pointing to some vertices. Since must be a unique location (for instance an object access path) there may be only either one or no heap vertex matching in for a given heap . The assumption is there is always exactly one matching free vertex for conjunction when building up a heap graph from a scratch, otherwise two heaps are not linkable.