=Paper= {{Paper |id=Vol-3185/paper1180 |storemode=property |title=An SMT-LIB Theory of Heaps |pdfUrl=https://ceur-ws.org/Vol-3185/paper1180.pdf |volume=Vol-3185 |authors=Zafer Esen,Philipp Rümmer |dblpUrl=https://dblp.org/rec/conf/smt/EsenR22 }} ==An SMT-LIB Theory of Heaps== https://ceur-ws.org/Vol-3185/paper1180.pdf
An SMT-LIB Theory of Heaps
Zafer Esen1 , Philipp Rümmer1,2
1
    Uppsala University, Sweden
2
    University of Regensburg, Germany


                                         Abstract
                                         Constrained Horn Clauses (CHCs) are an intermediate program representation that can be generated by
                                         several verification tools, and that can be processed and solved by a number of Horn solvers. One of the
                                         main challenges when using CHCs in verification is the encoding of heap-allocated data-structures: such
                                         data-structures are today either represented explicitly using the theory of arrays, or transformed away
                                         with the help of invariants or refinement types, defeating the purpose of CHCs as a representation that is
                                         language-independent as well as agnostic of the algorithm implemented by the Horn solver. This paper
                                         presents an SMT-LIB theory of heaps tailored to CHCs, with the goal of enabling a standard interchange
                                         format for programs with heap data-structures. We introduce the syntax of the theory of heaps, define
                                         its semantics in terms of axioms and using a reduction to SMT-LIB arrays and data-types, provide an
                                         experimental evaluation and outline possible extensions and future work.

                                         Keywords
                                         SMT, Constrained Horn Clauses, Theory of Heaps, SMT-LIB




1. Introduction
Constrained Horn Clauses (CHCs) are a convenient intermediate verification language that can
be generated by several verification tools in many settings, ranging from verification of smart
contracts [1] to verification of computer programs in various languages [2, 3, 4, 5, 6]. The CHC
interchange language provides a separation of concerns, allowing the designers of verification
systems to focus on high-level aspects like the applied proof rules and verification methodology,
while giving CHC solver developers a clean framework that can be instantiated using various
model checking algorithms and specialised decision procedures. Solver performance is evaluated
in the annually held CHC-COMP [7].
   CHCs are usually expressed using the SMT-LIB standard, which itself is a common language
and interface for SMT solvers [8]. Abstractly, both SMT solvers and CHC solvers are tools that
determine if a first-order formula is satisfiable modulo background theories such as arithmetic,
bit-vectors, or arrays.
   One of the main challenges when using CHCs, and in verification in general, is the encoding
of programs with mutable, heap-allocated data-structures. Since there is no native theory of
heaps in SMT-LIB, one approach to represent such data-structures is using the theory of arrays
(e.g., [9, 10]). This is a natural encoding since a heap can be seen as an array of memory locations;


SMT 2022: Satisfiability Modulo Theories, August 11–12, 2022, Haifa, Israel
 0000-0002-1522-6673 (Z. Esen); 0000-0002-2733-7098 (P. Rümmer)
                                       © 2022 Copyright for this paper by its authors. Use permitted under Creative Commons License Attribution 4.0 International (CC BY 4.0).
    CEUR
    Workshop
    Proceedings
                  http://ceur-ws.org
                  ISSN 1613-0073
                                       CEUR Workshop Proceedings (CEUR-WS.org)

                                                                                                             38
however, as the encoding is byte-precise, in the context of CHCs it tends to be low-level and
often yields clauses that are hard to solve.
    An alternative approach is to transform away such data-structures with the help of invariants
or refinement types (e.g., [11, 12, 13, 4]). In contrast to approaches that use the theory of arrays,
the resulting CHCs tend to be over-approximate (i.e., can lead to false positives), even with
smart refinement strategies that aim at increasing precision. This is because every operation
that reads, writes, or allocates a heap object is replaced with assertions and assumptions about
local object invariants, so that global program invariants might not be expressible. In cases
where local invariants are sufficient, however, they can enable efficient and modular verification
even of challenging programs.
    Both approaches leave little design choice with respect to handling of heap to CHC solvers.
Dealing with heap at encoding level implies repeated effort when designing verifiers for different
programming languages, makes it hard to compare different approaches to encode heap, and is
time-consuming when a verifier wants to switch to another encoding. The benefits of CHCs are
partly negated, since the discussed separation of concerns does not carry over to heap.
    The vision of this paper is to extend CHCs to a standardised interchange format for programs
with heap data-structures. To this end, we present a high-level theory of heaps that does not
restrict the way in which CHC solvers approach heap, while covering the main functionality
of heap needed for program verification: (i) representation of the type system associated with
heap data; (ii) reading and updating of data on the heap; (iii) handling of object allocation.
    We use algebraic data-types (ADTs), as already standardised by SMT-LIB v2.6, as a flexible
way to handle (i). The theory offers operations akin to the theory of arrays to handle (ii) and
(iii). The theory is deliberately kept simple, so that it is easy to add support to SMT and CHC
solvers: a solver can, for instance, internally encode heap using the existing theory of arrays
(we provide one such encoding in [14]), or implement transformational approaches like [12, 13].
Since we want to stay high-level, arithmetic operations on pointers are excluded in our theory,
as are low-level tricks like extracting individual bytes from bigger pieces of data through pointer
manipulation. Being language-agnostic, the theory of heaps allows for common encodings
across different applications, and is in the spirit of both CHCs and SMT-LIB.
    Contributions of the paper are (i) the definition of syntax and semantics of the theory of
heaps, (ii) a collection of an initial set of benchmarks, (iii) experimental results.
    Acknowledgements This is the first full paper introducing the theory of heaps (a detailed
account of an earlier version of the theory is available as a technical report [14]). Earlier versions
of the theory were presented at the HCVS Workshop 2020 [15] and the SMT Workshop 2020 [16].
An invited paper at LOPSTR 2020 discusses preliminary work on decision and interpolation
procedures [17]. We are grateful for the discussion and feedback provided by the different
communities. This work was supported by the Swedish Research Council (VR) under grant
2018-04727, by the Swedish Foundation for Strategic Research (SSF) under the project WebSec
(Ref. RIT17-0011), and by the Knut and Alice Wallenberg Foundation under the project UPDATE.


2. Motivating Example
  We start with a high-level explanation how heap is handled by our theory. Listing 1 shows


                                                   39
     Listing 1
     The motivating example in Java
 1   abstract c l a s s IntList {
                                                      17        i n t hd ( ) { r e t u r n _hd ; }
 2     p r o t e c t e d i n t _sz ;
                                                      18        v o i d setHd ( i n t hd ) { _hd = hd ; }
 3     abstract i n t hd ( ) ;
                                                      19        IntList tl ( ) { r e t u r n _tl ; }
 4     abstract v o i d setHd ( i n t hd ) ;
                                                      20        Cons ( i n t hd , IntList tl ) {
 5     abstract IntList tl ( ) ;
                                                      21            _hd = hd ;
 6     i n t sz ( ) { r e t u r n _sz ; } }
                                                      22            _tl = tl ;
 7
                                                      23            _sz = 1 + tl . sz ( ) ; } }
 8   c l a s s Nil extends IntList {
                                                      24    c l a s s Motivation {
 9       Nil ( ) { _sz = 0 ; }
                                                      25        v o i d main ( ) {
10       i n t hd ( ) { err ( ) ; }
                                                      26            IntList l = new Cons ( 4 2 ,
11       v o i d setHd ( i n t hd ) { err ( ) ; }
                                                      27                                   new Nil ( ) ) ;
12       IntList tl ( ) { err ( ) ; } }
                                                      28            l . setHd ( l . hd ( ) + 1 ) ;
13
                                                      29            assert ( l . hd ( ) == 4 3 ) ;
14   c l a s s Cons extends IntList {
                                                      30        }
15       i n t _hd ;
                                                      31    }
16       IntList _tl ;




     a simple Java program that constructs a singly-linked list through heap operations such as
     allocation on the heap (lines 26–27), reading (lines 28–29) and modifying (line 28) heap data.
        In order to encode this program we use constrained Horn clauses (CHCs). We refer to sources
     such as [18, 2] for a comprehensive explanation of using CHCs in this context. Although the
     theory of heaps is presented in the context of CHCs, there is nothing CHC-specific in the theory
     itself; the theory can be supported by both SMT and CHC solvers since it is kept deliberately
     high level and simple. The encoding is given in Listing 2 in SMT-LIB v2.6 format.

     Heap declaration To encode this program using the theory of heaps, first a heap has to be
     declared that covers the program types as shown at lines 1–12 of Listing 2. Each heap comes
     with its own sorts for the heap itself and for heap locations (or addresses). Lines 2 and 3 are the
     names of declared heap and address sorts. We next need to define which data can be placed on
     the heap, which is done by choosing the sort of heap objects; this sort can be any of the sorts
     declared prior to or together with the heap declaration, excluding the heap sort itself. Line 4
     specifies the object sort to be the ADT Object, declared later.
        Line 5 defines the object assumed to be stored at unallocated heap locations. Since functions
     in SMT-LIB are total, semantics has to be defined also for reads from such unallocated addresses.
     The theory of heaps leaves the choice of object produced by such reads to the user; the term
     specified at line 5 must have the object sort chosen at line 4. We call this the default object (or
     defObj ), which in this case is created using the object constructor O_Empty. There are two
     main reasons why the result of a read from an unallocated location is not left unspecified: 1. the
     axioms become more complicated (for instance [row2] from Table 2 would require limiting 𝑝2
     to allocated addresses); 2. deallocation semantics can be partly achieved (see Section 4).
        The rest of the heap declaration at lines 6–12 corresponds to an SMT-LIB data-type declaration.
     In line 6, in addition to Object we declare data-types IntList, Cons, and Nil, encoding the
     classes of the program. The constructors at lines 7–9 specify the fields of each class, and in
     addition give Cons and Nil each a field containing the parent IntList object. In lines 10–12,

                                                       40
     the constructors of the Object sort are declared, which correspond to the classes Cons and
     Nil, as well as the default object O_Empty. The class IntList is abstract and does not occur
     directly on the heap, so that no constructor for this type is provided.
        Since each heap theory has its own address sort, cases are immediately prevented in which
     multiple heaps share the same address sort, or in which some other interpreted sort (say, Int)
     is used to store addresses. This rules out accidental cases of pointer arithmetic, and leaves full
     flexibility to solvers on how to internally represent addresses (e.g., see [4]). This choice also
     implies that ADT declarations for heap objects that refer to address sorts need to be part of
     declare-heap.
        Within one heap, all pointers are represented using a single Addr sort. No distinction is made
     between pointers to objects from different constructors. This is close in semantics to languages
     like C, where casts between arbitrary pointer types are possible and it has to be verified for
     each heap access that indeed an object of the right type is accessed. In languages like Java, the

     Listing 2: SMT-LIB encoding of the motivating example from Listing 1. The symbols of some
                sorts and operations of the theory are abbreviated and the list of quantified variables
                is skipped in some cases for brevity.
 1   ( declare-heap
 2    Heap                                              ; d e c l a r e d Heap s o r t
 3    Addr                                              ; d e c l a r e d Address s o r t
 4    Object                                            ; chosen Object s o r t
 5    O_Empty                                           ; the d e f a u l t Object
 6    ( ( IntList 0 )
                    ( Cons 0 ) ( Nil 0 ) ( Object 0 ) ) ; ADTs
 7    ( ( ( IntList ( sz           Int ) ) )            ; Class constructors
 8      ( ( Cons    ( parentCons IntList ) ( hd Int ) ( tl Addr ) ) )
 9      ( ( Nil     ( parentNil IntList ) ) )
10      ( ( O_Cons  ( getCons      Cons ) )             ; Object sort constructors
11        ( O_Nil   ( getNil       Nil ) )
12        ( O_Empty ))))
13                                                      ; invariant declarations
14   ( declare-fun I1 ( Heap )       Bool )             ; 
15   ( declare-fun I2 ( Heap Addr ) Bool )              ; 
16   ( declare-fun I3 ( Heap Addr ) Bool )              ; 
17   ( declare-fun I4 ( Heap Addr ) Bool )              ; 
18
19   ( assert ( I1 emptyHeap ) )
20   ( assert ( forall ( ( h Heap ) ( h1 Heap ) ( p1 Addr ) )
21      ( => ( and ( I1 h ) ( = ( ARHeap h1 p1 ) ( alloc h ( O_Nil ( Nil ( IntList 0 ) ) ) ) ) )
22           ( I2 h1 p1 ) ) ) )
23   ( assert ( forall ( . . . )
24      ( => ( and ( I2 h p )
25                 ( = ( ARHeap h1 p1 ) ( alloc h ( O_Cons ( Cons ( IntList 1 ) 42 p ) ) ) ) )
26           ( I3 h1 p1 ) ) ) )
27   ( assert ( forall ( . . . )
28      ( => ( and ( I3 h l ) ( not ( valid h l ) ) ) false ) ) )
29   ( assert ( forall ( ( pn IntList ) ( head Int ) ( tail Addr ) . . . )
30      ( => ( and ( I3 h l ) ( = h1 ( write h l ( O_Cons ( Cons pn ( + 1 head ) tail ) ) ) )
31                 ( = ( O_Cons ( Cons pn head tail ) ) ( read h l ) ) ) ( I4 h1 l ) ) ) )
32   ( assert ( forall ( . . . )
33      ( => ( and ( I3 h l ) ( = ( O_Nil ( Nil pn ) ) ( read h l ) ) ) false ) ) )
34   ( assert ( forall ( . . . )
35      ( => ( and ( I4 h l ) ( = ( O_Cons ( Cons pn head tail ) ) ( read h l ) )
36                 ( not ( = head 4 3 ) ) ) false ) ) )
37   ( assert ( forall ( . . . )
38      ( => ( and ( I4 h l ) ( not ( is-O_Cons ( read h l ) ) ) ) false ) ) )

                                                            41
stronger type system will provide information about the objects a variable can refer to, but
exceptions can be raised when performing casts. The theory of heaps is flexible enough to cover
those different settings.
   Apart from the sorts mentioned, the heap declaration implicitly declares (among others) an
ADT ARHeap (also called 𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap later in the paper) that holds pairs ⟨Heap, Addr ⟩
returned as a result of allocations.

Program encoding Predicates representing program states are declared at lines 14–17. The
first set of arguments in the parentheses lists the sorts of the variables we want to keep track of
at that point. E.g., for line 17, we want to have a global view of the heap, as well as all variables
on the stack at that point. The only variable on the stack at this point is a temporary variable p
that corresponds to the newly allocated Nil object’s address (line 27 in Listing 1).
   Line 19 is the program entry point, where the heap is initially empty. The function emptyHeap
returns an empty heap (i.e., unallocated at all locations) of the declared Heap sort specified at
line 2. Lines 20–26 allocate, respectively, a Nil object and a Cons object on the heap. Allocation
is done using the alloc function of the theory, which takes as arguments the old heap and the
new object to be put on the heap, and returns an ARHeap pair with the new heap and the
allocated address. Constructor calls are inlined and slightly simplified in the encoding. For
example, line 25 shows the simplified encoding of the Java constructor for Cons at lines 20–23
of Listing 1. The update of the _sz field is simplified by directly assigning a value to it, which
would actually require another clause with a read due to the statement at line 23.
   Lines 27–33 illustrate the use of read and write functions. read reads from the provided heap
at the given location, and write writes the provided object to the heap at the specified location.
The assertion at lines 27–28 checks the validity of accesses in order to ensure memory safety.
The dynamic dispatch needed when calling hd is implemented through pattern matching using
the O_Cons and O_Nil constructors: in lines 29–31 the method call is successful, and the heap
object is subsequently updated, while the clause at lines 32–33 models the error when executing
Nil.hd. The same property can be expressed using the tester is-O_Cons in lines 37–38. Lastly,
lines 34–36 encode the assertion at line 29 from Listing 2.


3. Vocabulary and Syntax of the Theory of Heaps
3.1. SMT-LIB-style Declaration of Heaps
A theory of heaps is declared as follows:

                   (declare-heap 𝑐ℎ 𝑐𝑎 𝑐𝑜 𝜏𝑜 ((𝛿1 𝑘1 ) ... (𝛿𝑛 𝑘𝑛 )) (𝑑1 ...𝑑𝑛 ))

where 𝑐ℎ , 𝑐𝑎 , 𝑐𝑜 are symbols corresponding to the names of declared heap, declared address
and chosen object respectively. 𝜏𝑜 is a term of the chosen object which is returned on invalid
accesses (i.e. the default object). The object sort can be chosen as any sort without 𝑐ℎ in its
signature. The rest of the declaration resembles the declare-datatypes declaration from the
SMT-LIB standard v2.6 [8], with the exception that polymorphism is (currently) not supported
in constructor declarations (a discussion is provided in [14]), and that there should be 𝑛 (where

                                                   42
Table 1
Operations defined by the theory of heaps


                       emptyHeap : ()                     → Heap                                    (1)
                         nullAddr : ()                    → Addr                                    (2)
                             alloc : Heap × Object        → Heap × Addr                             (3)
                             valid : Heap × Addr          → Bool                                    (4)
                             read : Heap × Addr           → Object                                  (5)
                             write : Heap × Addr × Object → Heap                                    (6)




𝑛 ≥ 0) instead of 𝑛 + 1 ADT sort declarations (i.e., the object sort can also be declared before the
heap declaration and specified using 𝑐𝑜 , if it does not use the address sort (𝑐𝑎 ) in its declaration).
  The concrete syntax for the heap declaration is given below, which extends ⟨command ⟩ in
the concrete syntax of SMT-LIB v2.6.
         ⟨command ⟩          ::= ...
                                |    ( declare-heap ⟨symbol ⟩ ⟨symbol ⟩ ⟨sort⟩ ⟨term⟩
                                     ( ⟨sort_dec⟩𝑛 ) ( ⟨heap_datatype_dec⟩𝑛 ) )
    ⟨heap_datatype_dec⟩        ::=   ⟨constructor _dec⟩+
   The first two symbols and the following sort in the declaration correspond respectively to 𝑐ℎ ,
𝑐𝑎 and 𝑐𝑜 from the abstract syntax. ⟨term⟩ is the default object.

3.2. Sorts
Each heap declaration introduces several sorts: 1. a sort Heap of heaps, 2. a sort Addr of heap
addresses, 3. zero or more ADT sorts used to represent heap data, 4. an additional ADT sort
that holds the pair ⟨Heap, Addr ⟩ which is the result of calling alloc. In order to make this ADT
sort distinguishable, it is suffixed with its associated heap sort Heap (e.g. 𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap).
The names of these sorts are defined by the variables in the declare-heap command, which
we assume in this paper to be Heap for 𝑐ℎ and Addr for 𝑐𝑎 .

3.3. Operations and Semantics
A list of operations of the theory of heaps is given in Table 1. Although not listed in the table,
we also assume access to all ADT operations for the ADTs declared by the theory of heaps.
Some operations contain the symbols Heap and Addr in their signatures. This is done with the
assumption that the declared heap and address sorts are named Heap and Addr respectively.
E.g., nullAddress would be nullA if the declared address sort was named 𝐴, and it would
return a value of sort 𝐴. Including the sort name in some function and sort names makes it
possible to determine their associated heap declarations without using the SMT-LIB command
“as”. This is not required in sorts and operations where the associated heap sort is clear, such as
in read (its first argument is of heap sort).

                                                    43
   A set of axioms formalising the semantics of the theory of heaps is given in Table 2. In
addition, a definition of the axioms in terms of the theory of arrays is provided in [14]. Below
we provide an informal description for each operation of the theory.
   Function alloc takes a Heap and an Object, and returns a data-type 𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap repre-
senting the pair ⟨Heap, Addr ⟩. The returned Heap at Addr contains the passed Object, with
all other locations unchanged. The pair ADT is required as the return sort since it is not possible
in SMT-LIB to return the two values separately. In Section 4 we discuss other alternatives such
as using multiple allocation functions.
   Functions read and write are similar to the array select and store operations [19]; however,
unlike an array, a heap also carries information about allocatedness. The predicate valid checks
if accesses to a given Heap at a given Addr are valid. We say that an access is valid if and only
if that location was allocated beforehand by using the function alloc, and invalid otherwise.
The function nullAddr returns the Addr that is always unallocated and emptyHeap returns the
Heap that is unallocated at all locations.
   The functions read and write behave as their array counterparts if the access is valid. Invalid
reads return a default Object to make the function total (as explained in Section 2). The
write function returns a new Heap if the access is valid, otherwise the original Heap is returned
without any changes. Validity of a write can be independently checked via memory-safety
assertions as shown in lines 27–28 of Listing 2.
   We propose a further short-hand notation nthAddr𝑖 , which is useful when presenting satisfy-
ing assignments. It is used to concisely represent Addr values which would be returned after 𝑖
alloc calls. This short-hand notation is only possible with the deterministic allocation axiom
[alloc2] given in Table 2.
   The properties of the theory of heaps are given in [14]. In particular, satisfiability of quantifier-
free heap formulas is NP-complete (provided that the theory chosen to represent heap objects
is by itself in NP). Like for arrays, NP-completeness can be observed already for conjunctions of
heap literals.


4. Alternative Definitions and Extensions
This section explains the rationale behind some of the design choices in the theory of heaps, as
well as some natural extensions. It is intended as a starting point for further discussions and a
standardisation within SMT-LIB.

𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap Allocation on the heap needs to produce both a new heap and a fresh
address. In our theory, the pair of new heap and new address is handled using the ADT
𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap, which enables us to stick to just a single allocation function alloc. Alterna-
tively, alloc could be represented using a pair of functions, choosing for instance alloc(ℎ, 𝑜) =
⟨allocHeap(ℎ, 𝑜), allocAdress(ℎ, 𝑜)⟩; this would be preferable from a solver implementation
point of view, but not necessarily for users. Altogether this point is more of aesthetic concern.

Deterministic allocation In its current semantics, object allocation in the theory of heaps
is deterministic: since alloc is a function, it will always produce the same fresh address when

                                                    44
Table 2
Axiomatic semantics of the theory of heaps. All variables occurring in the axioms are universally
quantified with sorts ℎ : Heap, 𝑝 : Addr , 𝑟 : AddrRange, 𝑜 : Object and ar : 𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap.
Variables can appear subscripted. 𝐴𝑙𝑙𝑜𝑐𝑅𝑒𝑠𝑢𝑙𝑡Heap is a pair ⟨Heap, Addr ⟩; we use the notation ar ._1
and ar ._2 to select the pair’s fields.

   Read over                        valid(ℎ, 𝑝) → read(write(ℎ, 𝑝, 𝑜), 𝑝) = 𝑜                 [row1]
     write                    𝑝1 ̸= 𝑝2 → read(write(ℎ, 𝑝1 , 𝑜), 𝑝2 ) = read(ℎ, 𝑝2 )           [row2]

   Read over                      alloc(ℎ, 𝑜) = 𝑎𝑟 → read(𝑎𝑟._1, 𝑎𝑟._2) = 𝑜                   [roa1]
    allocate             alloc(ℎ, 𝑜) = 𝑎𝑟 ∧ 𝑝 ̸= 𝑎𝑟._2 → read(𝑎𝑟._1, 𝑝) = read(ℎ, 𝑝)          [roa2]
                         alloc(ℎ, 𝑜) = 𝑎𝑟 → ¬valid(ℎ, 𝑎𝑟._2) ∧ valid(𝑎𝑟._1, 𝑎𝑟._2) ∧
                                                                                             [alloc1]
                         (∀𝑝 : Addr .(𝑎𝑟._2 ̸= 𝑝 → (valid(ℎ, 𝑝) ↔ valid(𝑎𝑟._1, 𝑝))))
   Allocation
                                 (∀𝑝 : Addr .(valid(ℎ1 , 𝑝) ↔ valid(ℎ2 , 𝑝))) →
                                                                                             [alloc2]
                                      alloc(ℎ1 , 𝑜1 )._2 = alloc(ℎ2 , 𝑜2 )._2

     Invalid                           ¬valid(ℎ, 𝑝) → write(ℎ, 𝑝, 𝑜) = ℎ                      [ivwt]
     access                          ¬valid(ℎ, 𝑝) → read(ℎ, 𝑝) = defObj                        [ivrd]
                                              ¬valid(emptyHeap, 𝑝)                            [vld1]
    Validity                                   ¬valid(ℎ, nullAddr)                            [vld2]
                           ℎ2 = write(ℎ1 , 𝑝1 , 𝑜1 ) → (valid(ℎ1 , 𝑝2 ) ↔ valid(ℎ2 , 𝑝2 ))    [vld3]
                                  (∀𝑝 : Addr .(valid(ℎ1 , 𝑝) ↔ valid(ℎ2 , 𝑝)) ∧
 Extensionality                                                                                [ext]
                                    read(ℎ1 , 𝑝) = read(ℎ2 , 𝑝)) → ℎ1 = ℎ2
                            ∃𝑓 : N → Heap, 𝑔 : N → Addr .𝑓 (0) = emptyHeap ∧
    No-junk                                     𝑔(0) = nullAddr ∧
                                                                                              [cons]
(constructability)           ∀𝑖 : N. ⟨𝑓 (𝑖 + 1), 𝑔(𝑖 + 1)⟩ = alloc(𝑓 (𝑖), defObj ) ∧
                                           ∀𝑝 : Addr . ∃𝑖 : N. 𝑔(𝑖) = 𝑝


applied to the same arguments. Moreover, [alloc2] implies that the new address is determined
entirely by the set of already allocated addresses on the heap. Determinism simplifies the
presentation of models and counterexamples through the function nthAddr. Determinism also
simplifies the computation of program invariants, since it implies the existence of a linear order
of the heap addresses (as witnessed by the array semantics discussed in [14]): an invariant
can distinguish fresh and used addresses using a simple inequality. Determinism will in many
practical cases not be observable in programs: the syntax of the theory of heaps prevents
arithmetic on addresses, and normal program semantics does not allow alloc to be called
repeatedly on the same heap in any case.
   In cases where it is needed, there is an elegant way to reintroduce non-determinism: the
alloc function can be given a third argument (nonce/entropy), as in alloc(ℎ, 𝑜, 𝑒), and the axiom
[cons] be relativised to only hold for fixed values of 𝑒. The axiom [alloc2] could be dropped.
The translation of programs to CHCs can then choose a non-deterministic value for 𝑒 when
encoding an allocation operation like new. A side effect of this change would be that decision
procedures and correct encoding of heaps using arrays become more complex, and for instance

                                                     45
Table 3
Extended operations over address ranges


                 batchAlloc : Heap × Object × N        → Heap × AddrRange                        (7)
                batchWrite : Heap × AddrRange × Object → Heap                                    (8)
                nthInRange : AddrRange × N             → Addr                                    (9)
                   contains : AddrRange × Addr         → Bool                                   (10)
                validRange : Heap × AddrRange          → Bool                                   (11)




have to store the allocation status of each address using a bit-array.

Deallocation A natural extension of the theory is the addition of a function for deallocating
objects, which would be helpful to capture languages without garbage collection like C/C++; for
such languages deallocation otherwise has to be encoded using an explicit flag added to objects.
The effect on the theory semantics would be similar as for non-deterministic allocation: decision
procedures would need to maintain a bit-array to remember the allocatedness of addresses.
   The theory of heaps already provides a way to obtain partial deallocation semantics without
the additional bit-array. If the default object sort is chosen to be one of the sorts not correspond-
ing to any program type, then the semantics of deallocation in most programming languages
can be achieved by writing back the default object to deallocated locations. Then valid heap/ad-
dress pairs that return the default object on a read imply that they were deallocated. Although
this covers most properties to be verified (i.e., detecting memory leaks and deallocation of
unallocated locations), this is partial semantics because those addresses can still not be returned
from an allocation.

Sorts and operations ranging over sequences of addresses Program arrays can be
modelled by introducing an additional AddrRange sort and related operations, such as those
shown in Table 3. batchAlloc would return an AddrRange containing 𝑛 addresses, where 𝑛
is its last argument. batchWrite can be used to batch update an address range, nthInRange to
extract an address from an address range, contains to check if an address range contains an
address, and validRange to check whether all addresses in a range are allocated. In our tools
TriCera and Eldarica, we are already using this extended version of the theory of heaps.


5. Related Work
Separation Logic extends the assertions of Hoare’s logic [20] to succinctly express properties
of heap and shared mutable data-structures [21]. Research has been done on specialised decision
procedures for separation logic in SMT [22, 23], and there is a proposal for encoding separation
logic in SMT-LIB 2.5 [24].
   The theory of heaps and separation logic both provide mechanisms for reasoning about
the heap; however, their approaches are orthogonal. Separation logic extends the assertion

                                                   46
language with additional operators, while the theory of heaps provides an interchange format
for encoding programs with the goal of preserving as much information about the heap as
possible. Both could be used in a complementary way to encode program assertions and the
program itself.

Linear Maps provide a similar proof strategy to that of separation logic, while staying within
the confines of classical logic [25]. The authors describe a two-way erasure transformation,
transforming between imperative programs with a single unified heap and programs with
multiple disjoint linear maps. Since the transformation is completely in classical logic, off-the-
shelf SMT solvers and theorem provers can be used without a special decision procedure by
making use of the existing theories such as the theory of arrays and the theory of sets.
   Unlike the transformational approach of linear maps, the theory of heaps aims to defer the
handling of heap to the solvers. In fact, the linear maps strategy could also make use of the
theory of heaps in order to have access to more specialised decision procedures, and not be
restricted to the theory of arrays.

Other related work The authors of [26] extend an SMT solver with a decision procedure to
decide unbounded heap reachability with support for Boolean and integer data fields. [27] also
describes a decision procedure for verification of heap-manipulating programs. Both papers are
about verifying heap reachability, and both of them highlight the need for a standard theory
of heaps as that would have provided a framework for the research and ease the adoption of
proposed decision procedures by different solvers.


6. Experiments
In order to highlight the feasibility of using the theory in a more concrete setting, we have
collected C benchmarks from the ReachSafety and MemSafety categories of SV-COMP 2022 [28].
TriCera1 , a CHC-based model checker for C programs, was extended to produce CHCs in the
theory of heaps. To create a preliminary set of CHC benchmarks modulo heaps, we filtered out
programs that require heap, but none of the features not yet supported by TriCera (e.g., stack
pointers, floats etc.). We have also excluded benchmarks that any of the tested tools reported a
parsing error for, in the end, 361 benchmarks remained.
   In order to focus on the evaluation of the theory of heaps rather than that of bit-vectors
(which are already challenging for Horn solvers on their own [29]), integer types in the CHCs
were encoded using mathematical integers. This meant some benchmarks did not return their
expected result, as some of them depend on the correct modelling of overflow; however, no
conflicting answers were observed in the results by Eldarica and Z3/Spacer.
   We then compared the performance of different solvers on those 361 benchmarks. As tools
providing (early) native support for the theory of heaps, the SMT solver Princess [30] was
extended to support the theory using the reasoning and interpolation procedures from [17],
and the CHC solver Eldarica [31] was extended to make use of the newly added theory in
Princess. Other solvers can process CHCs output by TriCera after converting constraints
1
    https://github.com/uuverifiers/tricera

                                                 47
Table 4
Results for the 361 heap benchmarks with Eldarica, Z3/Spacer, and CPAchecker. Eldarica is run
on both heap benchmarks and their array encodings. Z3/Spacer is only run on the array encoded
benchmarks. Portfolio row shows the combined best results of the previous three rows. The last row
shows CPAchecker’s performance on the same set of benchmarks using their original C files.
                                                         sat     unsat   unknown
                                  Eldarica (heap)         14       40           307

                                  Eldarica (array)       41        57           263

                                  Z3/Spacer (array)       25       47           289

                                  Portfolio              42        63           256

                                  CPAchecker               7       71           283


in the theory of heaps to array constraints; we used an extended2 version of the encoding
given in [32] implemented in the tool heap2array 3 for this conversion. Both Eldarica and
Z3/Spacer [33] were run on the array versions of the benchmarks. Lastly, we provide results
that CPAchecker [34] (version 2.1.1), one of the top C model checkers [35], produced on the
source SV-Comp benchmarks. We have made available all benchmarks used in our experiments
[36].
   The experiments were run on an AMD Opteron 2220 SE (2.8 GHZ with 4 CPUs) machine
running 64-bit Linux with 6 GB of RAM and a wall-clock timeout of 900 seconds.
   The results are shown in Table 4. In the first three rows, Eldarica with the array encoded
benchmarks performed best. One benchmark could only be solved by Eldarica (heap), 16
benchmarks only by Eldarica (array) and one benchmark only by Z3/Spacer. The best results
that can be obtained using a portfolio approach (i.e., running the solvers in parallel and taking
the first result) are shown in the Portfolio row. The last row shows CPAchecker’s performance
on the source C programs with the combined results of checking memory safety and reachability
properties. There were 27 benchmarks that could only be solved by CPAchecker.
   The comparison with CPAchecker shows that real-world C programs can indeed be encoded
and analysed using the proposed theory of heaps, resulting in a competitive verification tool.
Although our current decision procedure for the theory of heaps does not perform as well as its
array counterpart yet, the theory provides a uniform representation of programs that can easily
be mapped to different back-ends.


7. Conclusions and Outlook
We have proposed a theory of heaps, along with its syntax and semantics, and discussed possible
alternative definitions and extensions. The intention is that the ideas presented here will initiate
discussions, and eventually result in a common interchange language for programs with heap.
2
    encoding of sorts and operations ranging over sequences of addresses discussed in Section 4
3
    https://github.com/zafer-esen/heap2array

                                                            48
As a long-term goal, we would like to include a heap track also at the CHC-COMP competition;
a part of the benchmarks in the LIA-nonlin-Arrays-nonrecADT category of CHC-COMP 2022
already stems from heap theory benchmarks, using an encoding into the theory of arrays.
   The algorithms from [17] are direct and unrefined adaptions of procedures for the theory
of arrays, and more work is needed to obtain, e.g., practical interpolation methods. However,
now that the design choice is shifted to the solvers, alternative approaches can be employed to
improve the results without changing the CHC representation of programs. In this context, two
directions we are currently pursuing are improved decision and interpolation procedures for
the heap theory, and the adaptation of the invariant-based heap encoding used in JayHorn [4].




                                                49
References
[1] S. Kalra, S. Goel, M. Dhawan, S. Sharma, ZEUS: analyzing safety of smart con-
    tracts, in: 25th Annual Network and Distributed System Security Symposium, NDSS
    2018, San Diego, California, USA, February 18-21, 2018, The Internet Society, 2018.
    URL: http://wp.internetsociety.org/ndss/wp-content/uploads/sites/25/2018/02/ndss2018_
    09-1_Kalra_paper.pdf.
[2] S. Grebenshchikov, N. P. Lopes, C. Popeea, A. Rybalchenko, Synthesizing software ver-
    ifiers from proof rules, in: J. Vitek, H. Lin, F. Tip (Eds.), ACM SIGPLAN Conference
    on Programming Language Design and Implementation, PLDI ’12, Beijing, China - June
    11 - 16, 2012, ACM, 2012, pp. 405–416. URL: https://doi.org/10.1145/2254064.2254112.
    doi:10.1145/2254064.2254112.
[3] A. Gurfinkel, T. Kahsai, A. Komuravelli, J. A. Navas, The SeaHorn verification framework,
    in: D. Kroening, C. S. Pasareanu (Eds.), Computer Aided Verification - 27th International
    Conference, CAV 2015, San Francisco, CA, USA, July 18-24, 2015, Proceedings, Part I,
    volume 9206 of Lecture Notes in Computer Science, Springer, 2015, pp. 343–361. URL: https:
    //doi.org/10.1007/978-3-319-21690-4_20. doi:10.1007/978-3-319-21690-4\_20.
[4] T. Kahsai, R. Kersten, P. Rümmer, M. Schäf, Quantified heap invariants for object-oriented
    programs, in: T. Eiter, D. Sands (Eds.), LPAR-21, 21st International Conference on Logic
    for Programming, Artificial Intelligence and Reasoning, Maun, Botswana, May 7-12, 2017,
    volume 46 of EPiC Series in Computing, EasyChair, 2017, pp. 368–384. URL: https://easychair.
    org/publications/paper/Pmh.
[5] Y. Matsushita, T. Tsukada, N. Kobayashi, RustHorn: CHC-based verification for Rust pro-
    grams, in: P. Müller (Ed.), Programming Languages and Systems - 29th European Sympo-
    sium on Programming, ESOP 2020, Held as Part of the European Joint Conferences on The-
    ory and Practice of Software, ETAPS 2020, Dublin, Ireland, April 25-30, 2020, Proceedings,
    volume 12075 of Lecture Notes in Computer Science, Springer, 2020, pp. 484–514. URL: https:
    //doi.org/10.1007/978-3-030-44914-8_18. doi:10.1007/978-3-030-44914-8\_18.
[6] R. Sato, N. Iwayama, N. Kobayashi, Combining higher-order model checking with re-
    finement type inference, in: M. V. Hermenegildo, A. Igarashi (Eds.), Proceedings of
    the 2019 ACM SIGPLAN Workshop on Partial Evaluation and Program Manipulation,
    PEPM@POPL 2019, Cascais, Portugal, January 14-15, 2019, ACM, 2019, pp. 47–53. URL:
    https://doi.org/10.1145/3294032.3294081. doi:10.1145/3294032.3294081.
[7] P. Rümmer, Competition report: CHC-COMP-20, in: L. Fribourg, M. Heizmann (Eds.),
    Proceedings 8th International Workshop on Verification and Program Transformation and
    7th Workshop on Horn Clauses for Verification and Synthesis, VPT/HCVS@ETAPS 2020
    2020, and 7th Workshop on Horn Clauses for Verification and SynthesisDublin, Ireland,
    25-26th April 2020, volume 320 of EPTCS, 2020, pp. 197–219. URL: https://doi.org/10.4204/
    EPTCS.320.15. doi:10.4204/EPTCS.320.15.
[8] C. Barrett, P. Fontaine, C. Tinelli, The SMT-LIB Standard: Version 2.6, Technical Re-
    port, Department of Computer Science, The University of Iowa, 2017. Available at
    www.SMT-LIB.org.
[9] A. Komuravelli, N. Bjørner, A. Gurfinkel, K. L. McMillan, Compositional verification of
    procedural programs using Horn clauses over integers and arrays, in: R. Kaivola, T. Wahl

                                               50
     (Eds.), Formal Methods in Computer-Aided Design, FMCAD 2015, Austin, Texas, USA,
     September 27-30, 2015, IEEE, 2015, pp. 89–96.
[10] E. De Angelis, F. Fioravanti, A. Pettorossi, M. Proietti, Program verification using constraint
     handling rules and array constraint generalizations, Fundam. Inform. 150 (2017) 73–117.
     URL: https://doi.org/10.3233/FI-2017-1461. doi:10.3233/FI-2017-1461.
[11] P. M. Rondon, M. Kawaguchi, R. Jhala, Liquid types, in: R. Gupta, S. P. Amarasinghe (Eds.),
     Proceedings of the ACM SIGPLAN 2008 Conference on Programming Language Design
     and Implementation, Tucson, AZ, USA, June 7-13, 2008, ACM, 2008, pp. 159–169. URL:
     https://doi.org/10.1145/1375581.1375602. doi:10.1145/1375581.1375602.
[12] N. Bjørner, K. L. McMillan, A. Rybalchenko, On solving universally quantified Horn clauses,
     in: F. Logozzo, M. Fähndrich (Eds.), Static Analysis - 20th International Symposium, SAS
     2013, Seattle, WA, USA, June 20-22, 2013. Proceedings, volume 7935 of Lecture Notes in Com-
     puter Science, Springer, 2013, pp. 105–125. URL: https://doi.org/10.1007/978-3-642-38856-9_
     8. doi:10.1007/978-3-642-38856-9\_8.
[13] D. Monniaux, L. Gonnord, Cell morphing: From array programs to array-free Horn clauses,
     in: X. Rival (Ed.), Static Analysis - 23rd International Symposium, SAS 2016, Edinburgh,
     UK, September 8-10, 2016, Proceedings, volume 9837 of Lecture Notes in Computer Science,
     Springer, 2016, pp. 361–382. URL: https://doi.org/10.1007/978-3-662-53413-7_18. doi:10.
     1007/978-3-662-53413-7\_18.
[14] Z. Esen, P. Rümmer, A theory of heap for constrained Horn clauses (extended
     technical report), CoRR abs/2104.04224 (2021). URL: https://arxiv.org/abs/2104.04224.
     arXiv:2104.04224.
[15] Z. Esen, P. Rümmer, Towards an SMT-LIB theory of heap (extended abstract), in: L. Fri-
     bourg, M. Heizmann (Eds.), 8th International Workshop on Verification and Program
     Transformation and 7th Workshop on Horn Clauses for Verification and Synthesis, VP-
     T/HCVS@ETAPS 2020 2020, and 7th Workshop on Horn Clauses for Verification and
     SynthesisDublin, Ireland, 25-26th April 2020, volume 320 of EPTCS, 2020.
[16] Z. Esen, P. Rümmer, Abstract: Towards an SMT-LIB theory of heap, in: F. Bobot, T. Weber
     (Eds.), Proceedings of the 18th International Workshop on Satisfiability Modulo Theories co-
     located with the 10th International Joint Conference on Automated Reasoning (IJCAR 2020),
     Online (initially located in Paris, France), July 5-6, 2020, volume 2854 of CEUR Workshop
     Proceedings, CEUR-WS.org, 2020, p. 60. URL: http://ceur-ws.org/Vol-2854/abstract4.pdf.
[17] Z. Esen, P. Rümmer, Reasoning in the theory of heap: Satisfiability and interpolation, in:
     M. Fernández (Ed.), Logic-Based Program Synthesis and Transformation, LNCS, Springer,
     Cham, 2021, pp. 173–191.
[18] N. Bjørner, A. Gurfinkel, K. L. McMillan, A. Rybalchenko, Horn clause solvers for
     program verification, in: L. D. Beklemishev, A. Blass, N. Dershowitz, B. Finkbeiner,
     W. Schulte (Eds.), Fields of Logic and Computation II - Essays Dedicated to Yuri Gure-
     vich on the Occasion of His 75th Birthday, volume 9300 of Lecture Notes in Computer
     Science, Springer, 2015, pp. 24–51. URL: https://doi.org/10.1007/978-3-319-23534-9_2.
     doi:10.1007/978-3-319-23534-9\_2.
[19] J. McCarthy, Towards a mathematical science of computation, in: Information Processing,
     Proceedings of the 2nd IFIP Congress 1962, Munich, Germany, August 27 - September 1,
     1962, North-Holland, 1962, pp. 21–28.

                                                  51
[20] C. A. R. Hoare, An axiomatic basis for computer programming, Commun. ACM 12 (1969)
     576–580. URL: https://doi.org/10.1145/363235.363259. doi:10.1145/363235.363259.
[21] J. C. Reynolds, Separation logic: A logic for shared mutable data structures, in: 17th
     IEEE Symposium on Logic in Computer Science (LICS 2002), 22-25 July 2002, Copenhagen,
     Denmark, Proceedings, IEEE Computer Society, 2002, pp. 55–74. URL: https://doi.org/10.
     1109/LICS.2002.1029817. doi:10.1109/LICS.2002.1029817.
[22] A. Reynolds, R. Iosif, C. Serban, T. King, A decision procedure for separation logic in SMT,
     in: C. Artho, A. Legay, D. Peled (Eds.), Automated Technology for Verification and Analysis -
     14th International Symposium, ATVA 2016, Chiba, Japan, October 17-20, 2016, Proceedings,
     volume 9938 of Lecture Notes in Computer Science, 2016, pp. 244–261. URL: https://doi.org/
     10.1007/978-3-319-46520-3_16. doi:10.1007/978-3-319-46520-3\_16.
[23] J. A. N. Pérez, A. Rybalchenko, Separation logic modulo theories, in: C. Shan (Ed.),
     Programming Languages and Systems - 11th Asian Symposium, APLAS 2013, Melbourne,
     VIC, Australia, December 9-11, 2013. Proceedings, volume 8301 of Lecture Notes in Computer
     Science, Springer, 2013, pp. 90–106. URL: https://doi.org/10.1007/978-3-319-03542-0_7.
     doi:10.1007/978-3-319-03542-0\_7.
[24] R. Iosif, C. Serban, A. Reynolds, M. Sighireanu, Encoding separation logic in smt-lib v2.5,
     2018. URL: https://sl-comp.github.io/docs/smtlib-sl.pdf.
[25] S. K. Lahiri, S. Qadeer, D. Walker, Linear maps, in: R. Jhala, W. Swierstra (Eds.), Proceedings
     of the 5th ACM Workshop Programming Languages meets Program Verification, PLPV
     2011, Austin, TX, USA, January 29, 2011, ACM, 2011, pp. 3–14. URL: https://doi.org/10.
     1145/1929529.1929531. doi:10.1145/1929529.1929531.
[26] Z. Rakamaric, R. Bruttomesso, A. J. Hu, A. Cimatti, Verifying heap-manipulating programs
     in an SMT framework, in: K. S. Namjoshi, T. Yoneda, T. Higashino, Y. Okamura (Eds.), Auto-
     mated Technology for Verification and Analysis, 5th International Symposium, ATVA 2007,
     Tokyo, Japan, October 22-25, 2007, Proceedings, volume 4762 of Lecture Notes in Computer
     Science, Springer, 2007, pp. 237–252. URL: https://doi.org/10.1007/978-3-540-75596-8_18.
     doi:10.1007/978-3-540-75596-8\_18.
[27] S. Lahiri, S. Qadeer, A decision procedure for well-founded reachability, 2007.
[28] D. Beyer, SV-Benchmarks: Benchmark Set for Software Verification and Testing (SV-
     COMP 2022 and Test- Comp 2022), 2022. URL: https://doi.org/10.5281/zenodo.5831003.
     doi:10.5281/zenodo.5831003.
[29] P. Backeman, P. Rümmer, A. Zeljic, Bit-vector interpolation and quantifier elimina-
     tion by lazy reduction, in: N. S. Bjørner, A. Gurfinkel (Eds.), 2018 Formal Methods
     in Computer Aided Design, FMCAD 2018, Austin, TX, USA, October 30 - Novem-
     ber 2, 2018, IEEE, 2018, pp. 1–10. URL: https://doi.org/10.23919/FMCAD.2018.8603023.
     doi:10.23919/FMCAD.2018.8603023.
[30] P. Rümmer, A constraint sequent calculus for first-order logic with linear integer arithmetic,
     in: Proceedings, 15th International Conference on Logic for Programming, Artificial
     Intelligence and Reasoning, volume 5330 of LNCS, Springer, 2008, pp. 274–289.
[31] H. Hojjat, P. Rümmer, The ELDARICA horn solver, in: N. Bjørner, A. Gurfinkel (Eds.), 2018
     Formal Methods in Computer Aided Design, FMCAD 2018, Austin, TX, USA, October 30 -
     November 2, 2018, IEEE, 2018, pp. 1–7. URL: https://doi.org/10.23919/FMCAD.2018.8603013.
     doi:10.23919/FMCAD.2018.8603013.

                                                  52
[32] Z. Esen, P. Rümmer, A theory of heap for constrained Horn clauses (extended
     technical report), CoRR abs/2104.04224 (2021). URL: https://arxiv.org/abs/2104.04224.
     arXiv:2104.04224.
[33] A. Komuravelli, A. Gurfinkel, S. Chaki, E. M. Clarke, Automatic abstraction in SMT-
     based unbounded software model checking, in: N. Sharygina, H. Veith (Eds.), Com-
     puter Aided Verification - 25th International Conference, CAV 2013, Saint Petersburg,
     Russia, July 13-19, 2013. Proceedings, volume 8044 of Lecture Notes in Computer Sci-
     ence, Springer, 2013, pp. 846–862. URL: https://doi.org/10.1007/978-3-642-39799-8_59.
     doi:10.1007/978-3-642-39799-8\_59.
[34] D. Beyer, M. E. Keremoglu, CPAchecker: A tool for configurable software verification, in:
     G. Gopalakrishnan, S. Qadeer (Eds.), Computer Aided Verification - 23rd International
     Conference, CAV 2011, Snowbird, UT, USA, July 14-20, 2011. Proceedings, volume 6806
     of Lecture Notes in Computer Science, Springer, 2011, pp. 184–190. URL: https://doi.org/10.
     1007/978-3-642-22110-1_16. doi:10.1007/978-3-642-22110-1\_16.
[35] D. Beyer, Progress on software verification: SV-COMP 2022, in: D. Fisman, G. Rosu (Eds.),
     Tools and Algorithms for the Construction and Analysis of Systems - 28th International
     Conference, TACAS 2022, Held as Part of the European Joint Conferences on Theory and
     Practice of Software, ETAPS 2022, Munich, Germany, April 2-7, 2022, Proceedings, Part II,
     volume 13244 of Lecture Notes in Computer Science, Springer, 2022, pp. 375–402. URL: https:
     //doi.org/10.1007/978-3-030-99527-0_20. doi:10.1007/978-3-030-99527-0\_20.
[36] Z. Esen, P. Rümmer, TriCera Benchmarks: SMT-LIB Encodings of SV-COMP 2022 Bench-
     marks by TriCera, 2022. URL: https://doi.org/10.5281/zenodo.6950363. doi:10.5281/
     zenodo.6950363.




                                                53