Constraint-Logic Object-Oriented Programming with Free Arrays of
Reference-Typed Elements via Symbolic Aliasing
Hendrik Winkelmann
a
and Herbert Kuchen
Department of Information Systems, University of M
¨
unster, Leonardo-Campus 3, M
¨
unster, Germany
Keywords:
Constraint-Logic Object-Oriented Programming, Constraint Solving, Symbolic Execution, Symbolic Arrays.
Abstract:
Constraint-logic object-oriented programming is a young programming paradigm that aims to bring constraint-
solving techniques to an audience more accustomed to imperative programming. A prototypical language of
this paradigm, Muli, allows for the use not only of primitive-typed free variables, but also for free objects and
free arrays of primitive-typed elements. In the work at hand, we extend the current version of Muli so that it
supports free arrays of arrays and free arrays of objects. We do so by utilizing the concept of symbolic aliasing.
Our evaluation shows that the presented approach can speed up program validation and test case generation,
as well as solving complex constraint satisfaction problems.
1 INTRODUCTION
Constraint-Logic Object-Oriented Program-
ming (CLOOP) is a programming paradigm
combining the concepts of non-deterministic ex-
ecution and constraint solving, known from Prolog
(Wielemaker et al., 2012), with features known
from object-oriented programming languages.
These features comprise an imperative style of
coding with mutability, dynamic dispatch, as well
as inheritance (Dagef
¨
orde and Kuchen, 2018;
Dagef
¨
orde et al., 2021). Instead of purely logic
programming languages, CLOOP languages can
be deemed more accessible for programmers that
mainly use object-oriented languages and need to
solve constraint(-logic) problems as parts of their
applications. A particularly convincing example is
the use of the prototypical CLOOP language Muli
when applying these mechanisms to solve constraint
problems for generating test cases (Winkelmann
et al., 2022). One important feature of Muli is free
arrays, i.e., unbound logic variables of an array type
(Winkelmann et al., 2021). Up to now, the elements
of free arrays were restricted to primitive types. Our
main contribution in this paper is the extension of
Muli’s current runtime engine, Mulib (Winkelmann
and Kuchen, 2022), with a mechanism for symbol-
ically representing free arrays of primitive- as well
as reference-typed elements while accounting for
a
https://orcid.org/0000-0002-7208-7411
null values and mutability. The presented approach
is general in that not only Satisfiability Modulo
Theories (SMT) solvers such as Z3 (De Moura and
Bjørner, 2008) but also Finite Domain (FD) solvers
such as JaCoP (Kuchcinski, 2003) can employ it.
We present use cases for free arrays in CLOOP and
furthermore evaluate the approach with regards to its
efficiency on several benchmark examples.
Subsequently, we first describe the core concepts
of Muli and CLOOP, and motivate our contribution
in Section 2. In Section 3 the implementation is de-
scribed. In Section 4 we empirically compare the ap-
proach to an alternative and in Section 5, we contrast
it from related work. We end the paper by concluding
and outlining future work in Section 6. The overall
implementation is available as open source software.
1
2 AN OVERVIEW OF CLOOP
CLOOP is a paradigm that is based on symbolic ex-
ecution, a technique well-known in the software val-
idation and verification community (Cadar and Sen,
2013). Hence, it has been shown that CLOOP lan-
guages are a good fit for test case-generation (Winkel-
mann et al., 2022). Yet, they can also be used to rep-
resent search problems in an object-oriented manner.
1
https://github.com/NoItAll/mulib
412
Winkelmann, H. and Kuchen, H.
Constraint-Logic Object-Oriented Programming with Free Arrays of Reference-Typed Elements via Symbolic Aliasing.
DOI: 10.5220/0011849200003464
In Proceedings of the 18th International Conference on Evaluation of Novel Approaches to Software Engineering (ENASE 2023), pages 412-419
ISBN: 978-989-758-647-7; ISSN: 2184-4895
Copyright
c
2023 by SCITEPRESS Science and Technology Publications, Lda. Under CC license (CC BY-NC-ND 4.0)
2.1 Approach of Muli
CLOOP, as implemented by Muli (Dagef
¨
orde and
Kuchen, 2018), combines typical features of object-
oriented programming languages with features such
as non-deterministic execution and the native use of
constraints by executing code encapsulated in so-
called search regions. In these search regions, Muli
allows to use free variables, i.e., variables with a sym-
bolic value. If such a symbolic value is part of the
evaluation of, e.g., the condition of an if statement
(called if-condition in the sequel), a choice point with
two choice options is created. The first option con-
tains a constraint, as described by the mentioned con-
dition. The other option contains the negation of the
condition. Both cases are regarded separately.
The respective constraint is pushed to a constraint
stack. An automated constraint solver, such as Z3
(De Moura and Bjørner, 2008), analyzes the satisfia-
bility of the constraints on the constraint stack. If the
constraints are satisfiable, the choice option is further
evaluated, e.g., by entering the first branch of the if
statement. The work at hand treats the improvement
of free arrays in Muli.
2.2 Motivation for Free Arrays
Free arrays (Winkelmann et al., 2021) are a feature of
Muli that allows for
1. accessing arrays with indices that are symbolic
values,
2. initializing arrays with a symbolic length, and/or
3. initializing arrays that again have symbolic en-
tries.
Free arrays in CLOOP can be used to express the
element constraint, known from constraint program-
ming, and combine it with mutability to express
search problems in an elegant manner. Consider the
following code depicting a search region in Muli:
1 public static int[] assign(
2 int[] mCaps, int[] workloads) {
3 int[] as = new int[workloads.length];
4 for (int i=0; i<workloads.length; i++) {
5 int indexOfM free;
6 int capOfM = mCaps[indexOfM];
7 if (workloads[i] > capOfM) {
8 throw Muli.fail(); }
9 mCaps[indexOfM] = capOfM-workloads[i];
10 as[i] = indexOfM; }
11 return as; }
In the search region, we look for an assignment of ma-
chines to workloads. The capacities of the machines,
mCaps, as well as the set of workloads, workloads,
are encoded as an array of integer values.
We assume that a machine finishes an assigned
workload completely and not just a fraction of it. An
assignment array, as, is initialized in line 3. Then, we
iterate over all workloads to assign them (lines 4–10).
For this, we first spawn a free index variable (line 5)
and use it to select an entry of mCaps that we store
in capOfM (line 6). The value capOfM represents the
remaining capacity of some machine represented by
mCaps. Thereafter, we create a choice point with two
options (line 7). If the chosen machine has an insuf-
ficient capacity, a special exception is thrown declar-
ing the choice option to be invalid (line 8). Thus, the
runtime evaluates the choice option with the negation
of the condition in line 7. If this constraint, in con-
junction with constraints for earlier iterations that are
already on the constraint stack, is satisfiable, we can
assign the current workload to a machine: We store
the difference of the machine’s capacity and the cur-
rent workload in the array (line 9). This is done to
evaluate whether the chosen machine can still work
on other workloads in later iterations. Finally, we
store the index of the machine at the position of the
current workload (line 10). There are two outcomes
to this procedure. The first outcome is that, at some
point, there is no way to make the negated if-condition
workloads[i] > capOfM satisfiable. In this case,
there are no valid paths to reach line 11 and Muli
terminates without giving a solution to the user. The
other outcome is that we reach line 11 and return the
assignments. The solver is asked to assign a concrete
value to each of the elements in as.
This simple example demonstrates the applicabil-
ity of free arrays to a variety of scheduling and plan-
ning problems (Drexl and Kimms, 1997). The orig-
inal implementation of Muli is already able to repre-
sent such problems (Winkelmann et al., 2021). How-
ever, it is not yet possible to represent reference typed-
arrays, such as int[][] or Machine[] symbolically.
While single int values are immutable, accounting
for object identity, symbolic length, and mutability of
the array elements is non-trivial. State-of-the-art tools
(P
˘
as
˘
areanu and Rungta, 2010; Winkelmann et al.,
2021) have left a symbolic representation for future
work. Instead, simplifications are used that can lead to
path explosion and can better be delegated to the con-
straint solver. Nevertheless, it is desirable to allow for
a symbolic treatment of such arrays: Consider, for in-
stance, an assignment problem where there are multi-
ple periods and it is allowed to preproduce workloads
of later periods. The machine capacities might then
be represented via an int[][] array where the first
dimension is the period in which the workload is to be
worked on, while the second dimension is the capac-
ity of a machine in this period. Moreover, in the con-
Constraint-Logic Object-Oriented Programming with Free Arrays of Reference-Typed Elements via Symbolic Aliasing
413
text of object-oriented programming, it might be even
more desirable to represent machine capacities as an
array of Machines, exhibiting specific behavior using
dynamic polymorphism (Dagef
¨
orde et al., 2021). An-
other reason is that, in the context of symbolic exe-
cution, built-in procedures for dealing with array con-
straints oftentimes are not as performant as custom-
tailored ones (Perry et al., 2017). In the following sec-
tion, we describe a solver-independent high-level ap-
proach for treating array constraints. We then extend
this approach to allow for arrays of reference types
while specifically accounting for null-values, arrays
of arrays, and arrays of other object-types. For space
reasons, we will assume arrays with a concrete length
in the subsequent elaborations.
3 APPROACH &
IMPLEMENTATION
In a Muli program, some variables and data must be
represented in a constraint solver. For instance, when
we have a constraint such as i < 0, we must create
a solver-specific representation of i, 0, and the con-
straint i < 0 to push to the constraint stack of the
solver. For primitive data types and operations, this
is rather straight-forward. In the following, we de-
scribe how we represent objects and/or their content.
We follow the Java denomination where arrays are ob-
jects and thus have an object identity. Content is used
to either describe the elements in the case of an array,
or, in the case of a non-array object, its field values.
Note that we deliberately use the phrase representing
an object for the solver. This is because the object it-
self is not represented in some solver-specific fashion,
e.g., via algebraic data types or an array theory often-
times supported by SMT-type constraint solvers (Bar-
rett et al., 2017). Instead, we create a representation
that transforms accesses to the object to constraints,
which then are forwarded to the solver.
3.1 Representing Selects and Stores
For representing operations on an integer array a we
must support two operations: Selecting from a with
index i and storing an element e in a with index i.
Algorithm 1 depicts the devised high-level proce-
dure (compare with Perry et al. (2017)) where the
goal is to encode a select operation a[index] as a con-
straint. This constraint is then added to the constraint
stack to assure that the selected value, here passed
as Sprimitive val (line 4), is a valid element of the
respective array at index (line 3). An instance of
Sprimitive represents either a concrete primitive value
or a symbolic value. An instance of its subtype, Sint,
represents either a concrete integer value, or a sym-
bolic one (Winkelmann and Kuchen, 2022).
Algorithm 1: Simplified procedure for encoding a select
constraint.
1 select(ArrayHistory ah,
2 Constraint selectGuard,
3 Sint index,
4 Sprimitive val) : Constraint
5 List[(Sint,Sprimitive)] initial =
6 ah.getInitialState ();
7 Constraint result = true;
8 foreach (i,v) initial do
9 Constraint implication =
10 (index == i) = (val == v);
11 result = result implication;
12 List[(Constraint,Sint,Sprimitive)] stores =
13 ah.getStores ();
14 foreach (c,i,v) stores do
15 Constraint validStore =
16 (c index == i);
17 Constraint implication =
18 (validStore = (val == v));
19 result = implication
20 (¬(validStore) = result);
21 return selectGuard = result;
To better understand this algorithm, consider the
following example with an integer array a:
int[] a = new int[] {4, s};
int index free;
int val0 = a[index];
We create an array a, select from it using a free index
index, and store the value in val0. This access can
effectively be encoded by the following constraint:
index 0 index < 2 (index = 0 = val0 = 4)
(index = 1 = val0 = s)
The constraint index 0 index < 2 is necessary
to ascertain that index is in the valid index range of
a. In the following, we abstract from this length-
constraint. This condition, together with the follow-
ing two implications, fix val0 to be either 4 or s with-
out prematurely assigning a fixed value to index or
val.
Effectively, this constraint is generated by execut-
ing lines 5-11 in Algorithm 1. ArrayHistory here is
a well-known representation of the respective array
(Perry et al., 2017) containing two lists. The first list,
the one that is used in lines 5–11, contains the index-
value pairs of the array at the time it was represented
for the solver. Thus the initial set of index-value pairs
is retrieved (lines 5 and 6) and implications are cre-
ated and conjoined (lines 7–11). Each time index
equals one of the indices i, the selected value val must
ENASE 2023 - 18th International Conference on Evaluation of Novel Approaches to Software Engineering
414
equal the value v (lines 9 and 10). index has been re-
stricted to a valid range before entering Algorithm 1.
The second list, which is empty in this example,
contains triples of store statements executed for the
array. As an example for stores, again consider the
array a and the following operations performed on it:
int i0 free; int i1 free;
a[i0] = 5;
int val1 = a[i1];
In this example, we store 5 in a using the free index
i0. The subsequent select operation using the free
index i1 must take into account that some value in
a has been overwritten. To achieve this, the store
adds a triple to the aforementioned second list of
ArrayHistory. For now, we assume the first element
of the triple as well as selectGuard to equal true. The
second and third element of the triple again form a
key-value pair. In the example, the triple (true, i0, 5)
is added to the second list of ArrayHistory. The sub-
sequent select operation a[i1] now effectively gen-
erates the following constraint:
(i0 = i1 = val1 = 5) (i0 ̸= i1 =
((i1 = 0 = val1 = 4) (i1 = 1 = val1 = s)))
(i0 = i1 = val1 = 5) is an implication using the
stored index and value. The second part of this con-
straint, (i0 ̸= i1 = ...), is the constraint in the form
known from the previous scenario guarded by the
condition that the index used for selecting, i1, does
not equal the index used for storing, i0. Algorithm 1
implements the effect of the store statement in lines
12–20. We encode overwriting a value via an impli-
cation. If the antecedent of the implication (lines 15
and 16) holds, val must equal v of this store (lines 17
and 18). Otherwise, validStore must not hold, and the
non-overwritten prior key-value pairs must be consid-
ered (line 20). Finally, we return the overall constraint
(line 21).
3.2 Representing Non-Aliasing Objects
When extending the representation approach from
Subsection 3.1 to arrays of reference types and, in
general, objects, it must be considered how we rep-
resent object identity symbolically. For this, we en-
dow each object with an additional integer variable of
type Sint. These integer variables represent the iden-
tity of an object and subsequently are called (object)
identifiers. If an object identifier equals another ob-
ject identifier, these two objects are considered to be
aliases of one another.
Each object created by ’usual’ means, i.e., in Java
by using the new operator, receives a unique concrete
integer number as its identifier. In consequence, no
two objects that are created in this fashion can be
aliases of one another.
We represent the elements of an array a as follows:
If a is an array with primitive-typed elements, such as
an int[], the initial elements of a are represented as a
list. Values that are stored in the array are added to the
aforementioned list of stores to account for symboli-
cally overwriting the initial values. Objects are repre-
sented via maps. The keys of the map are the names
of the object’s fields. If a field is primitive-typed, we
directly store its value at the time of representation.
On the other hand, if the field’s type is a reference
type, we instead store the identifier of the referenced
object in it. Analogously, arrays with reference-typed
elements are represented by storing the list of identi-
fiers of the elements in a. Nulls are represented by the
reserved identifier 1.
As an example, consider the following code snip-
pet, creating an array with reference-typed elements:
T o0 = new T();
U u = new U(); u.f2 = 42.0;
o0.f0 = 7; o0.f1 = u;
T o1 = new T();
o1.f0 = 5;
T[] a = new T[] {o0, o1};
Here, we create an array a with two elements, o0
and o1. Their fields f0 and f1 are set to suitable val-
ues. If array a were to be represented for the solver,
the following representations, here depicted in JSON,
are the outcome:
Ro
0
= {id : 0, es” : { f 0” : [[7], []], f 1” : [[1], []]}}
Ru = {id” : 1, es” : { f 2” : [[42.0], []]}}
Ro
1
= {id : 2, es” : { f 0” : [[5], []], f 1” : [[1], []]}}
Ra = {id” : 3, len” : 2, es” : [[0, 2], []]}
The representation of o0, Ro
0
, consists of a con-
crete identifier, here 0, and its fields es. The represen-
tation for each field consists of a pair of lists. The first
list represents the initial value of the field. The second
element of the pair is the aforementioned list of store
triples for when the field is set to a new value. For
o0.f1, we store the identifier of Ru, here, 1. For o1,
we proceed similary, except that o1.f1 is null, and
thus the identifier 1 is stored. Finally, the represen-
tation of the array a, Ra, contains an identifier, and the
length of a, len. Moreover, it contains the identifiers
of its represented elements in the same data structure
that is used for arrays with primitive typed-elements.
Consider Algorithm 2 in which we describe how
we can retrieve the field value from the representation
for the solver of a non-array object. Since we only
represent a single field value, index of Algorithm 1
is always set to 0. In turn, setting a new value adds
a new triple (guard,0, val) to the list of stores of the
array history. For array objects, each representation
only has one ArrayHistory and index is not always 0.
Constraint-Logic Object-Oriented Programming with Free Arrays of Reference-Typed Elements via Symbolic Aliasing
415
Algorithm 2: Simplified procedure for getting a field’s
value.
1 getField(Constraint guard,
2 String fieldName,
3 Sprimitive val) : Constraint
4 ArrayHistory ah =
5 this. f ields.get ( f ieldName);
6 return select (ah, guard, 0, val);
3.3 Representing Aliasing Objects
It becomes necessary to represent an object for the
constraint solver if it is
1. an array that is accessed with a free index for the
first time,
2. contained in an object that is in the process of be-
ing represented for the solver, or if it is
3. stored into an object that is already represented for
the solver.
Such an object, or its content, is a potential target for
symbolic aliasing. In the following, we say that we
return an aliasing object where the elements of the ar-
ray are aliasing targets thereof. To account for alias-
ing, while running the program, each time the con-
tent of an aliasing object or an aliasing target is to be
accessed, instead its representation for the constraint
solver is accessed.
Aliasing objects are created either when
1. an array of reference-typed elements is selected
from with an unknown index,
2. an aliasing array of reference-typed elements is
selected from, or
3. an object is retrieved from a reference-typed field
of a non-array aliasing object.
The identifier of aliasing objects is a symbolic in-
teger. Consider an array a = {o0, o1} of type T[],
where T is a reference type. Every time a is accessed
via an unknown symbolic index i, we spawn an object,
o
i
, of type T. The identifier of o
i
is a symbolic integer
value. The domain of o
i
.id is determined by selecting
from the representation of a. Effectively, the follow-
ing constraint is pushed onto the constraint stack:
((i = 0) = (o
i
.id = o
0
.id))
((i = 1) = (o
i
.id = o
1
.id))
In consequence, o
i
.id equals either o
0
.id or o
1
.id. In
other words: o
i
is an aliasing object and o
0
and o
1
are
aliasing targets.
Aliasing objects are immediately represented for
the solver. When representing the aliasing object, we
generate a metadata constraint. The metadata con-
straint enforces that the object can only be null if one
of its aliasing targets can be null, and, in case of an
array, that the length is equal to the length of one of
its aliasing targets. It does so by restricting the identi-
fier of the aliasing object to be in the set of identifiers
of the aliasing targets, and implies the respective state
using the symbolic identifier identifier.
After setting its identifier and pushing the meta-
data constraint, the aliasing object is returned to the
search region.
If an instance method is called or the content of
o
i
is accessed, it is checked if the identifier o
i
.id can
equal 1. If this is the case, we create a choice point.
The choice options are that either a null-pointer ex-
ception is thrown or that we continue assuming that
o
i
.id ̸= 1.
o
i
itself does not have any content of its own:
Again, consider array a of type T []. Here, the se-
lected aliasing object o
i
is of type T . If we get a value
val from the field o
i
. f , Algorithm 2 is executed on o
0
and o
1
. Additionally, we add guards based on the o
i
s
symbolic identifier to these calls, effectively generat-
ing the following constraint:
(Ro
0
.getField(o
i
.id = o
0
.id, f , val))
(Ro
1
.getField(o
i
.id = o
1
.id, f , val))
Here, Ro
0
and Ro
1
are the respective representations
of o
0
and o
1
for the constraint solver. Note that we
pass the constraint o
i
.id = o
0
.id or o
i
.id = o
1
.id to the
guard parameter of Algorithm 2. By using this guard,
and since we restricted o
i
.id to be in {o
0
.id, o
1
.id},
the result is a correct select constraint given that o
i
is
an alias of o
0
or o
1
. The constraint-generating proce-
dure is described in more detail in Algorithm 3. The
algorithms for selecting and storing in arrays or set-
ting a field value for an aliasing object behave anal-
ogously. In it, we iterate over the set of representa-
tions of aliasing targets (lines 5–12). Since we access
the aliasing target’s getField-method, we must add
a guard for showing that this is a conditional select
statement (lines 7 and 10). Since this guard is used in
Algorithm 1, line 21, as the argument to selectGuard,
it is stated that val is only restricted by the content
of o
0
or o
1
, if this is an alias of the current aliasing
target a. It may even occur that the current aliasing
object is the aliasing target of another aliasing object.
Thus, we have to conjoin this guard with the guard
constraint passed via the parameter guard of Algo-
rithm 3 (line 8). Thereafter, we call getField on the
aliasing target a (lines 9–11). We conjoin the outcome
of this method call to the result (line 12). Finally, we
return the resulting constraint (line 13).
On the other hand, setting a new value for the
content of an aliasing object modifies each alias-
ing target’s representation. Recall that the triple has
the form (c, i, v) where i and v are the index and value
ENASE 2023 - 18th International Conference on Evaluation of Novel Approaches to Software Engineering
416
Algorithm 3: Simplified procedure for getting an aliasing
non-array object’s field value.
1 getField(Constraint guard,
2 String fieldName,
3 Sprimitive val) : Constraint
4 Constraint result = true;
5 Set[NonArrayRep] at = this.aliasingTarget;
6 foreach a at do
7 Constraint idEq = a.id == this.id;
8 Constraint guards = idEq guard;
9 Constraint g f =
10 a.getField (guards, a. f ields,
11 f ieldName, val);
12 result = result g f ;
13 return result;
of the store statement. c is the guard statement us-
ing the identifier of the aliasing object and the iden-
tifier of the aliasing target. In other words, storing
a value in an aliasing object o
i
also is a conditional
store for an aliasing target, e.g., o
0
, based on the con-
straint o
i
.id = o
0
.id. The validity of a store statement
is considered in Algorithm 1 in lines 15–18, where the
added guard statement is used to evaluate whether the
store is valid for the current select call. Thus, by do-
ing so we also can reuse Algorithm 1 for representing
conditional field updates.
4 EVALUATION
In the following, we will compare the symbolic ap-
proach described in this paper with an eager ap-
proach. The eager approach works as follows: As
soon as an array of objects, for instance an int[][],
is selected from or stored within using a symbolic
index, choice options are created. Each choice op-
tion is the choice of a concrete value for the sym-
bolic index. Internally, for each of these choice op-
tions subsequent operations are considered to be on
disjoint paths, i.e., the eager approach creates one
path for each chosen value for such array accesses.
In summary, in the eager approach we eagerly de-
cide on an index value for a given array operation
while in the symbolic approach proposed in this pa-
per, we delegate this choice to the constraint solver
using the mechanisms described in Section 3. In con-
sequence, the need to create the mentioned paths is re-
moved for such symbolic array accesses. In the past,
approaches similar to the eager approach have been
used in symbolic execution tools such as Symbolic
Pathfinder (P
˘
as
˘
areanu and Rungta, 2010).
For evaluating symbolic index accesses, we have
devised six scenarios of varying complexity, where
each one represents an assignment problem. We exe-
cuted all scenarios on an HP ProBook 445 G7 laptop
with an AMD Ryzen 5 4500U CPU and 16 GB of
working memory. Each scenario has been executed
20 times. We then removed the first five measure-
ments to assure that the first few iterations, oftentimes
exhibiting a more variable run time profile, do not
skew the results. A time budget is set so that after
30 seconds no new explorations of the search region
are started.
The first scenario, PCAP, is the initial motivational
example from Subsection 2.2 executed with 18 ma-
chines and 24 workloads. MPCAP is a scenario in
which we have two periods. There are a total of
nine machines working on 12 workloads in each of
the two periods. Machines are represented as an
int[][], where the first dimension determines the
period in which a machine works and the second di-
mension determines the concrete machine. MCAP is
the same as PCAP, only that we now encode the ma-
chines as objects, i.e., we use Machine[] instead of
int[], where Machine has a single int-field. Hence,
instead of int values, we retrieve Machine objects
and access their fields. This scenario serves the pur-
pose of evaluating the computational cost associated
with introducing a layer of indirection, thus making
use of symbolic aliasing (see Subsection 3.3). On
the other hand, MPMCAP analogously uses an array of
type Machine[][] instead of an int[][] argument.
MCAP-RED defines a smaller variant of MCAP with eight
machines and eleven workloads. Finally, DLSPV de-
notes a variant in which certain workloads can only be
worked on by certain machines and each machine can
only work on one single workload in a given period,
i.e., it is a simplification of the Discrete lot-sizing
and scheduling problem (Drexl and Kimms, 1997).
This example contains nine machines and four peri-
ods, each with nine workloads. Z3 (De Moura and
Bjørner, 2008) is used as an incremental constraint
solver to evaluate the valid paths through the program.
We evaluate multiple configurations for solving
the scenarios. The results are given in Table 1. For
each configuration we specify the mean time needed
to find a first path solution as well as the standard de-
viation in the first row, and the mean time to extract
further paths until there are no more valid paths and
its standard deviation in the second row of a cell. The
next column shows the total number of found path so-
lutions, followed by the number of explicit fails (see
line 8 in the example of Subsection 2.2).
For the PCAP scenario, we compare the high-
level procedure described in Algorithm 1 (HL) with
an implementation using the array theory of Z3
(Solver-specific). Compared to the Z3 implemen-
tation our high-level approach is able to retrieve a first
Constraint-Logic Object-Oriented Programming with Free Arrays of Reference-Typed Elements via Symbolic Aliasing
417
path solution and validate that there are no more paths
in the search region considerably faster. Furthermore,
the standard deviation here is lower than for the Z3
implementation. Z3 employs heuristics that result in
comparatively high, yet reproducible, differences in
the various run times, causing a high standard devia-
tion in both cases.
In the MPCAP scenario we compare our purely
symbolic approach, described in Section 3, with the
aforementioned eager approach. After retrieving an
array of type int[] from the set of machines, repre-
sented as an int[][], the arrays with primitive-typed
elements are treated with the high-level array theory.
As can be seen, for MPCAP, the eager approach times
out and does not find any path solution. This can be
attributed to a high number of costly constraint solver
calls where the constraint stack is unsatisfiable. In
contrast, the purely symbolic approach can solve the
problem and extract a solution in about one second.
Table 1: The mean run times and their standard deviation
for finding a first and for enumerating all path solutions,
as well as the mean number of found path solutions and
fails for the benchmark examples. The times are given in
seconds rounded to two digits, the number of path solutions
and fails are rounded to a full number. TO denotes a time
out for all runs.
Scenario
Setting Time #PS+#Fails
PCAP
Solver
-specific
10.83 (4.22)
11.35 (4.28)
1+23
HL
2.90 (2.00)
3.04 (2.02)
1+23
MPCAP
Eager
TO
0+5
Symbolic
0.92 (0.21)
1.18 (0.26)
1+35
MCAP
Eager
TO
TO
0+266572
Symbolic
4.16 (3.97)*
4.28 (3.97)
1+23
MPMCAP
Eager
18.67 (0.07)
TO
1+217550
Symbolic
0.43 (0.09)
0.50 (0.11)
1+32
MCAP
-RED
Eager
4.60 (0.02)
TO
144+333752
Symbolic
0.05 (0.02)
0.06 (0.02)
1+10
DLSPV Eager
2.40 (0.01)
2.48 (0.01)
1+12082
Symbolic
0.30 (0.03)
0.36 (0.04)
1+96
The MCAP scenario was designed to evaluate the
cost of the additional identifier guard constraints.
Since we use free indices for selecting from an ob-
ject, i.e., we select from Machine[], the eager ap-
proach here is forced to perform a brute-force search
and times out. Hundreds of thousands of fails were
encountered without finding a single path solution. It
must be noted that for Symbolic one of the fifteen
measurements timed out. The depicted times do not
consider this time out. For the eager configuration,
MPMCAP enforces a brute-force approach: When com-
paring the number of paths, one can see that the eager
approach suffers from path explosion and takes much
longer to find its first solution.
Finally, DLSPV shows a scenario in which there
is a reasonable number of overall paths, so that no
approach times out. The symbolic approach outper-
forms the eager approach. Compared to the eager ap-
proach, the symbolic approach has to regard far fewer
paths, as it summarizes the decision on an index.
5 RELATED WORK
The use of free arrays in CLOOP is novel with the ex-
ception of primitive free arrays using the array theory
available in some SMT solvers (Winkelmann et al.,
2021). There are, however, related subjects in the area
of constraint programming and software validation.
In the area of constraint programming, tools
(Kuchcinski, 2003) offer the element constraint. By
means of the element constraint, it can be checked
whether a certain value is located in a list in a declar-
ative manner. This resembles selecting from an ar-
ray with a free index. However, since constraint pro-
gramming is declarative, it does not directly account
for mutating objects or overwriting values in the list.
Also, Muli allows for defining custom data structures
instead of using integer encodings.
Similarly, approaches like JSetL (Rossi and
Bergenti, 2015) offer non-deterministic programming
with logical variables in the form of a library, mak-
ing use of a custom API and focus on lists and sets.
For instance, to add a new choice point, the Solver-
API must be called. In contrast, Muli allows for
mutability and uses native language constructs such
as if-conditions to automatically create choice points
where a free variable is involved. Furthermore, via
the contributions of this paper, Muli enables the use
of objects of user-defined classes in arrays which can
also represent set operations.
Muli can be used to generate test cases (Winkel-
mann et al., 2022). As can be seen in Section 4, us-
ing our approach for symbolic arrays can significantly
reduce the number of encountered paths. This is in
line with research for path merging conducted, for in-
stance, by Java Ranger (Sharma et al., 2020), where,
e.g., if-then-else-constructs are summarized and
the differentiation of various paths through the pro-
gram is delegated to the constraint solver. In conse-
ENASE 2023 - 18th International Conference on Evaluation of Novel Approaches to Software Engineering
418
quence, more complex constraints are pushed to the
constraint stack, yet, fewer paths have to be evaluated
by the tool. While promising, Java Ranger does not
treat purely symbolic arrays (Sharma et al., 2020).
Our approach draws from past research on speed-
ing up array constraints (Perry et al., 2017). This
research is comparable to Algorithm 1. Our imple-
mentation differs in that we use implication opposed
to nested if-then-else statements with summarized
index ranges as the latter has reduced the performance
for our benchmark examples. Furthermore, we pro-
vide a mechanism to express symbolic aliasing, thus
enabling a purely symbolic treatment of arrays of ob-
jects for all solvers supporting negation, disjunction,
and conjunction of constraints as well as value equal-
ity.
6 CONCLUSION AND FUTURE
WORK
We have described an approach for symbolically rep-
resenting arrays of arrays and, more generally, arrays
of objects. We have shown that the approach outper-
forms related work for retrieving path solutions. In
the future, it might be interesting to compare the per-
formance of other types of constraint solvers, such
as FD solvers, with SMT solvers in the context of
CLOOP. This is possible since the presented mecha-
nisms pose very few requirements towards employed
solvers.
REFERENCES
Barrett, C., Fontaine, P., and Tinelli, C. (2017). The SMT-
LIB Standard: Version 2.6. Technical report, Depart-
ment of Computer Science, The University of Iowa.
Available at www.SMT-LIB.org.
Cadar, C. and Sen, K. (2013). Symbolic execution for soft-
ware testing: Three decades later. Commun. ACM,
56(2):82–90.
Dagef
¨
orde, J. C. and Kuchen, H. (2018). A constraint-logic
object-oriented language. In Proceedings of the 33rd
Annual ACM Symposium on Applied Computing, SAC
’18, page 1185–1194, New York, NY, USA. Associa-
tion for Computing Machinery.
Dagef
¨
orde, J. C., Winkelmann, H., and Kuchen, H. (2021).
Free objects in constraint-logic object-oriented pro-
gramming. In 23rd International Symposium on
Principles and Practice of Declarative Programming,
PPDP 2021, New York, NY, USA. Association for
Computing Machinery.
De Moura, L. and Bjørner, N. (2008). Z3: An efficient smt
solver. In Proceedings of the Theory and Practice of
Software, 14th International Conference on Tools and
Algorithms for the Construction and Analysis of Sys-
tems, TACAS’08/ETAPS’08, page 337–340, Berlin,
Heidelberg. Springer-Verlag.
Drexl, A. and Kimms, A. (1997). Lot sizing and schedul-
ing survey and extensions. European Journal of
Operational Research, 99(2):221–235.
Kuchcinski, K. (2003). Constraints-driven scheduling and
resource assignment. ACM Transactions on Design
Automation of Electronic Systems, 8(3):355–383.
P
˘
as
˘
areanu, C. S. and Rungta, N. (2010). Symbolic
pathfinder: Symbolic execution of java bytecode. In
Proceedings of the IEEE/ACM International Confer-
ence on Automated Software Engineering, ASE ’10,
page 179–180, New York, NY, USA. Association for
Computing Machinery.
Perry, D. M., Mattavelli, A., Zhang, X., and Cadar, C.
(2017). Accelerating array constraints in symbolic
execution. In Proceedings of the 26th ACM SIG-
SOFT International Symposium on Software Testing
and Analysis, ISSTA 2017, page 68–78, New York,
NY, USA. Association for Computing Machinery.
Rossi, G. and Bergenti, F. (2015). Nondeterministic pro-
gramming in java with jsetl. Fundamenta Informati-
cae, 140(3-4):393–412.
Sharma, V., Hussein, S., Whalen, M. W., McCamant, S.,
and Visser, W. (2020). Java ranger: Statically sum-
marizing regions for efficient symbolic execution of
java. In Proceedings of the 28th ACM Joint Meeting
on European Software Engineering Conference and
Symposium on the Foundations of Software Engineer-
ing, ESEC/FSE 2020, page 123–134, New York, NY,
USA. Association for Computing Machinery.
Wielemaker, J., Schrijvers, T., Triska, M., and Lager, T.
(2012). Swi-prolog. Theory and Practice of Logic
Programming, 12(1-2):67–96.
Winkelmann, H., Dagef
¨
orde, J. C., and Kuchen, H. (2021).
Constraint-logic object-oriented programming with
free arrays. In Hanus, M. and Sacerdoti Coen, C.,
editors, Functional and Constraint Logic Program-
ming, pages 129–144, Cham. Springer International
Publishing.
Winkelmann, H. and Kuchen, H. (2022). Constraint-logic
object-oriented programming on the java virtual ma-
chine. In Proceedings of the 37th ACM/SIGAPP
Symposium on Applied Computing, SAC ’22, page
1258–1267, New York, NY, USA. Association for
Computing Machinery.
Winkelmann, H., Troost, L., and Kuchen, H. (2022).
Constraint-logic object-oriented programming for test
case generation. In Proceedings of the 37th
ACM/SIGAPP Symposium on Applied Computing,
SAC ’22, page 1499–1508, New York, NY, USA. As-
sociation for Computing Machinery.
Constraint-Logic Object-Oriented Programming with Free Arrays of Reference-Typed Elements via Symbolic Aliasing
419