As in the termination category of the
SV-COMP 2016 and
TermComp 2015, AProVE
analyzes termination of the main()
function of a C
program. To this end, AProVE uses the
Clang compiler to translate the
C program into a program in the intermediate representation of
LLVM (without compiler optimizations
as these are not sound for termination proving, i.e., the compiler may
remove non-terminating code if this code does not contribute to the return
value of a function). To compile C programs to LLVM
programs, we use the following command:
clang program.c -S -emit-llvm -o program.llvm
This LLVM program is then analyzed for memory safety and
termination. So in contrast to several other termination tools, AProVE
only considers a program as "terminating" if it can also prove its memory
safety.
If one only wants to analyze termination and memory safety of a specific
C function instead of main()
, this is possible by adding a query
as a comment in the first line. This query comment must be of the form
/*query: <function_name>(<list_of_arguments>)*/
.
The function_name
is the name of the function to analyze. The
list_of_arguments
is a (possibly empty) comma-separated list
of types explained at the bottom of this page.
struct
) yet. Moreover, the handling of arrays is
restricted to programs where arrays are compiled to pointers in LLVM.
This means that statements like int* array = alloca(4*sizeof(int))
are
supported, but statements like int array[4]
are not, because these statements are compiled to an LLVM-specific array data type.switch
indirectbr
invoke
unwind
va_arg
The tables below summarize our experiments. They show that our approach is more powerful than the other tools for proving termination of the selected programs. The second row of the table shows the results for only those 498 programs of the example set which are not known to be non-terminating. Here, T gives the number of examples where termination was proven, N gives the number of examples where non-termination was proven, F means that the proof failed in less than 900 seconds or took longer than 900 seconds, and RT gives the average runtime of those examples where termination could either be proven or disproven. By clicking on the name of the example set, you can inspect the details of the respective experiments.
Example Set | Size | AProVE | Ultimate | HipTNT+ | SeaHorn | KITTeL | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
T | N | F | RT | T | N | F | RT | T | N | F | RT | T | N | F | RT | T | N | F | RT | ||
All Programs | 631 | 409 | 91 | 131 | 34.4 | 392 | 111 | 128 | 23.1 | 312 | 107 | 212 | 1.4 | 247 | 78 | 306 | 12.7 | 221 | 0 | 410 | 0.2 |
Terminating Programs | 498 | 409 | - | 89 | 39.9 | 392 | - | 106 | 24.6 | 310 | 1 | 187 | 1.3 | 245 | - | 253 | 10.9 | 220 | - | 278 | 0.2 |
Example Set | Size | AProVE | Predator | Thor | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
T | N | F | RT | T | N | F | RT | T | N | F | RT | ||
Memory Safety | 631 | 547 | 0 | 84 | 12.5 | 431 | 154 | 46 | 2.0 | 336 | 0 | 295 | 0.7 |
Int
Pos
Neg
NonPos
NonNeg
Alloc
i8*
to some allocated area. This
means that if v is the symbolic variable associated to the corresponding
parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL for a fresh
symbolic variable w.Alloc(c)
i8*
to an allocated area of at least
c
bytes. This means that if v is the symbolic variable associated to the
corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL
and w ≥ v + c -1 to the knowledge base KB for a fresh symbolic
variable w.Alloc(#n)
i8*
to an allocated area of at least
as many bytes as indicated by the argument with index n
(the first
argument has index 0). This means that if v is the symbolic variable
associated to the corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL for a fresh symbolic
variable w. Moreover, we add an arithmetic
formula to the knowledge base KB of that initial state. The formula we
add depends on the type of the argument with index n
.n
is just a number whose associated symbolic
variable is u, then we add the formula
w ≥ v + u -1 to KB. So the size of the memory block described by
alloc(v,w) is at least as large as the number u.n
should be a pointer to some allocated memory area. So if
u is the symbolic variable associated to the argument with index n
, then in the initial state
we should have alloc(u,u') in the allocation list AL. In this case, we add the formula w ≥ v + u' - u to
KB. So the size of the memory block described by alloc(v,w) is at least as large as the length of
the memory block described by alloc(u,u').String
i8*
to a zero-terminated string.
This means that if v is the symbolic variable
associated to the corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL,
w → z to the points-to list PT,
and z = 0 to the knowledge base
KB for fresh symbolic variables w and z.String(c)
i8*
to a zero-terminated string,
where the allocated area after the string contains at least c
- 1 more
bytes.
This means that if v is the symbolic variable
associated to the corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL,
w' → z to the points-to list PT,
and the formulas z = 0, v ≤ w', w' ≤ w, and w ≥ w' + c
to the knowledge base
KB for fresh symbolic variables w, w', and z.String(#n)
i8*
to a zero-terminated string,
where the allocated area after the string contains at least
as many bytes as indicated by the argument with index n
(the first
argument has index 0). This means that if v is the symbolic variable
associated to the corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL,
w' → z to the points-to list PT,
and the formulas z = 0, v ≤ w', and w' ≤ w
to the knowledge base
KB of that initial state.
Moreover, we add another arithmetic
formula to KB which
depends on the type of the argument with index n
.n
is just a number whose associated symbolic
variable is u, then we add the formula
w ≥ w' + u to KB. So the memory block described by
alloc(v,w) has at least u more bytes after the string.n
should be a pointer to some allocated memory area. So if
u is the symbolic variable associated to the argument with index n
, then in the initial state
we should have alloc(u,u') in the allocation list AL). In this case, we add the formula w ≥ w' + u' - u to
KB. So the memory block described by alloc(v,w) consists of a string and a subsequent memory sub-block that is at least as large as the memory block described by alloc(u,u').<ty>[]
ty*
to some allocated area. This
means that if v is the symbolic variable associated to the corresponding
parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL for a fresh
symbolic variable w.<ty>[c]
ty*
to an allocated area of at least
c
entries of type ty
. This means that if v is the symbolic variable associated to the
corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL
and w ≥ v + c * size(ty) -1 to the knowledge base KB for a fresh symbolic
variable w.<ty>[#n]
ty*
to an allocated area of at least
as many bytes as indicated by the argument with index n
(the first
argument has index 0). This means that if v is the symbolic variable
associated to the corresponding parameter, then in the initial state of the symbolic execution
graph, we add alloc(v,w) to the allocation list AL for a fresh symbolic
variable w. Moreover, we add an arithmetic
formula to the knowledge base KB of that initial state. The formula we
add depends on the type of the argument with index n
.n
is just a number whose associated symbolic
variable is u, then we add the formula
w ≥ v + u* size(ty) -1 to KB. So the size of the memory block described by
alloc(v,w) is large enough to contain u entries of type ty.n
should be a pointer to some allocated memory area. So if
u is the symbolic variable associated to the argument with index n
, then in the initial state
we should have alloc(u,u') in the allocation list AL). In this case, we add the formula
w ≥ v + (u' - u + 1) * size(ty) - 1 to KB.
So the size of the memory block described by
alloc(v,w) is large enough to contain as many entries as the length of the memory block
described by alloc(u,u').