Precise Error Conditions

Dirk Beyer1, Lars Grunske2, Matthias Kettl1, Marian Lingsch-Rosenfeld1, Moeketsi Raselimo2, and Stefan Winter1

1 LMU Munich, Germany   ·   2 Humboldt-Universität zu Berlin, Germany

This page belongs to the article Precise Error Conditions, which is under review at ACM TOSEM. It shows all our results, the plots of the paper, the log file of every benchmark run, and every query and answer of our case study on program repair.


Abstract

Maintaining software projects and repairing defects are time-consuming activities in software engineering. Most approaches for program repair focus on a single unsafe trace or failing test case, but deciding whether a patch truly fixes a defect in all aspects, identifying duplicate bug reports, or performing a root-cause analysis all require a precise description of the defect. We therefore introduce precise error conditions. They describe all and only the traces of a program that violate the given specification. Formally, they map program locations to predicates over the program state that must hold either always or at least once in all unsafe traces. We define precise error conditions and implement DescribErr, an open-source tool that synthesizes them and validates them formally. DescribErr instruments a candidate error condition into the program, reducing the check that it describes all and only unsafe traces to a verification task. An imprecise candidate yields a counterexample, i.e., a wrongfully included safe trace or a wrongfully excluded unsafe one, which DescribErr uses to refine the candidate, guaranteeing progress towards a precise error condition. On the SV-Benchmarks set, DescribErr finds precise error conditions for 2 076 of 7 192 considered defective tasks (28.9 %), spanning the unreach-call, no-overflow, and termination specifications and including real-world programs, like Linux device drivers and Juliet benchmarks. All our results, DescribErr, and the experimental setup are publicly available.


Approach

DescribErr searches for a precise error condition in a loop. A generator proposes a candidate. A validator then checks two things. Is the candidate sound, so does it describe only failing traces? And is it complete, so does it describe every failing trace? When a check fails, the verifier hands back a counterexample, and a refiner uses that counterexample to build a better candidate. A pruner drops the parts of a candidate that cannot help any more. A candidate that is both sound and complete is precise, and the loop stops.

Workflow of DescribErr

An error condition assigns a predicate to a program location. The predicate either holds every time the location is reached (□l p) or at least once (◇l p). We evaluate three ways to generate, prune, and refine candidates. The explicit analysis uses the values from a counterexample. The interval analysis uses ranges of single variables. The template analysis uses relations between several variables and fills them in with an SMT solver. All three run as a combination analysis, so one error condition can talk about several locations at once.


Tool and Reproduction Information

Our reproduction package on Zenodo (DOI) has everything needed to run the experiments again, including the full log files. DescribErr itself is open source and lives in our GIT repository.

Tool Versions

ToolWhat it doesVersion
DescribErr Synthesizes the precise error conditions commit 7aeedbdf
TransVer Turns no-overflow and termination tasks into reachability tasks commit ecd35a59
CPAchecker Validates a candidate, the first tool we ask 4.2.2, commit ad55ac6a
CBMC Validates a candidate, good at finding bugs 6.8.0
UAutomizer Validates a candidate, good at proving correctness SV-COMP 2026 version (00d43373)
BenchExec Runs the benchmarks reliably 3.32

Benchmark Set

We use SV-Benchmarks in the version of SV-COMP 2026 (branch svcomp26) and run DescribErr on every unsafe benchmark in the categories unreach-call, no-overflow, and termination. We leave out a task if the parser of DescribErr cannot read the program, or if TransVer cannot turn it into a reachability task.

Specification Tasks with an error Left out Considered
unreach-call4 1331 0203 113
no-overflow3 720493 671
termination974566408
Total9 8271 6357 192

Machines and Limits

We run all benchmarks with BenchExec on machines with an Intel Xeon E3-1230 CPU (4 physical cores with 2 processing units each), 33 GB of RAM, and Ubuntu 24.04. Every run of DescribErr may use 28 GB of memory and 3 600 s of CPU time. To validate a candidate, we call three verifiers one after the other. CPAchecker gets 450 s of wall time, then CBMC gets 90 s, then UAutomizer gets 120 s. We synthesize error conditions for the last location with a nondeterministic value in every block of straight line code, so in code without loops, calls, or branches. Running the whole benchmark set once takes about two years of CPU time.


Full Results

The tables below hold all results of the paper, one table per specification. Each table shows the three analyses next to each other. Click a status cell to read the log of that run, and click a task name to open its task definition in SV-Benchmarks. The tabs above a table switch to the plots.

Note: All 26 481 log files that the tables link to are contained in this folder, so that every status cell opens the log of that run. Since the unzipped logfiles amount to 86 GiB of text, we omit the middle part of the content if the file is too big. The full data can be found in our reproduction package.


Research Questions

Five research questions guide our evaluation. RQ 1 to RQ 3 ask what DescribErr can synthesize and how long it needs for it. RQ 4 and RQ 5 ask what a precise error condition is good for once we have one.

RQ 1: How many precise error conditions does DescribErr find, and how many of them need the ◇ operator?

We consider 7 192 tasks. For 4 836 of them (67.2 %) DescribErr proves at least one candidate incomplete, which means that it finds a counterexample. For 2 076 of them (28.9 %) it finds a precise error condition. This is a lot, given that finding all failing inputs is much harder than finding one.

For 971 of these 2 076 tasks, no verifier ever found a single counterexample. That surprised us. Asking for a soundness proof and a completeness proof lets the verifier reason about all loop unrollings and branches at once, and that is sometimes easier than walking one concrete path to the error. Each of the three analyses also finds error conditions that the other two miss, so we need all of them.

Specification Considered Cex. found Precise EC found ... by analysis ... uniquely by analysis
tasks without cex. ExplicitIntervalTemplate ExplicitIntervalTemplate
unreach-call3 1131 819 762 (24.5 %)274 694676461 594414
no-overflow3 6712 670 1 191 (32.4 %)674 1 103977691 209824
termination408347 123 (30.1 %)23 102116112 352
Total7 1924 836 (67.2 %) 2 076 (28.9 %)971 1 8991 7691 264

Having both operators makes error conditions more expressive, but also harder to find. Of the 4 932 precise error conditions, 303 (6.1 %) need the ◇ operator. The termination tasks need it by far the most.

SpecificationPrecise ECs foundNeed ◇
unreach-call1 83187 (4.8 %)
no-overflow2 77118 (0.6 %)
termination330198 (60.0 %)
Total4 932303 (6.1 %)

RQ 2: How much time does DescribErr need to find a precise error condition?

Most precise error conditions are found in less than 100 s of wall time. The first 10 s or so always go into parsing the program and into the transformation with TransVer. After that, almost all time goes into validation, because pruning and refinement are cheap. The template analysis is the exception, since it also spends time in the SMT solver that fills in the templates.

RQ 3: How close does DescribErr get on the tasks where it finds no precise error condition?

Even without a precise error condition, DescribErr reports partial results. A sound error condition describes only failing traces, so every input it allows really does trigger the failure. A complete error condition promises the other direction, that every trace it leaves out is safe.

The number of complete error conditions drops over the iterations, because the intervals get split at safe values and become small enough to be sound. The number of sound ones grows, because we keep every sound interval and then check whether their union is complete. The paper shows this for the interval analysis, one of our best analyses. We add the plots for the other two analyses here, and they behave the same way.

RQ 4: What does a precise error condition tell us about a defect?

A precise error condition tells us where to look. It names the locations that matter and the variables that matter there. It says whether visiting a location once is already enough to fail (◇), which parts of the program are safe (□l false), and which assertions always fail (□l true). It also gives the values or the ranges of the relevant variables, and it shows whether the failure depends on one variable alone or on a relation between several of them.

The paper walks through the following error conditions as examples for these four groups. We moved their line numbers back to the original source code, so that they do not point into the code that DescribErr adds.

Task Ana. Spec. Error condition What it says
termination-crafted-lit/cstrncmp.c EO □l 28 (n = −231) An edge case was not handled correctly
Juliet_Test/CWE191...multiply_67_bad.i IO □l 1559 (data < −262)
float-newlib/double_req_bl_0870a.c ER □l 576 (x = −1.0)
coreutils-v9.5-units/getlimits...cover_proof.i ER ◇l 568 true Once this location is reached, the error cannot be avoided
combinations/pc_sfifo_1.cil-1+token_ring.01.cil-1.c I, TR ◇l 267 true
verifythis/tree_max_incorrect.c TR ◇l 51 true
ldv-regression/rule57_ebda_blast_2.i E, IR □l 66 false The error happens when this location is not reached
combinations/pc_sfifo_2.cil-2+token_ring.04.cil-1.c ER □l 552 false
nla-digbench-scaling/hard-ll_unwindbound1.c TR □l 33 (B − 1 < A) Two variables have to relate in a certain way
systemc/token_ring.01.cil-2.c TO □l 58 (E_M ≤ t1_st)
bitvector-loops/diamond_2-1.c TR □l 15 (y % 2 ≠ 0)

The analysis that found the error condition (Ana.) is the explicit (E), the interval (I), or the template (T) analysis. The specification (Spec.) is unreach-call (R), no-overflow (O), or termination (T).

RQ 5: Do precise error conditions help an LLM repair a program?

We ran a small case study on 9 programs from SV-Benchmarks, three per specification. All three prompts are the same, except for what they say about the defect. The baseline prompt gives the program and the specification it violates. The test case prompt adds one failing input, which is what a repair tool based on tests would know. The guided prompt adds the precise error condition that DescribErr found.

The model is Qwen3-8B in the 4 bit quantization Q4_K_M. We run it on the CPU with llama.cpp on 4 threads, with a context of 16 384 tokens, an answer limit of 6 144 tokens, thinking mode off, and the fixed seed 42, so the repairs come out the same every time. The 27 answers take 55 min on an AMD Ryzen 7 with 16 cores. CPAchecker then decides whether a repair satisfies the specification. Two authors only answer what a verifier cannot answer, namely whether a verified repair is plausible and which of several verified repairs is the better one. They see the repairs blind and in a random order, and on all nine programs they came to the same judgment.

Task Specification Specification holds Better and plausible repair
baselinetest caseguided baselinetest caseguidedtienone
diamond_2-1 unreach-call nonoyes •
dijkstra-u_unwindbound5 unreach-call nonoyes •
stateful_check unreach-call nonono •
CWE191...multiply_01_bad no-overflow ?yesyes •
ChenFlurMukhopadhyay-SAS2012-Ex2.10 no-overflow nonono •
NonTermination1 no-overflow yes?yes •
AlternKonv termination nonoyes •
ComplInterv2 termination nonoyes •
dijkstra1-both-nt-2 termination ??? •
Total (9) 116 00513

The specification columns say whether CPAchecker could prove the repair of the model against the original specification. A question mark means that the verdict was inconclusive, and none means that no repair was verified or plausible.

One failing input is no replacement for a precise error condition. It tells the model where to look, but not what the repair has to achieve, so the model guesses the criterion. The error condition simply states it. The table below shows what the two prompts actually revealed about each defect. A failing input is a list of the values that the calls to the nondeterministic input functions return, in the order of the calls. The prompt does not state the error condition in the compact form shown here, it spells it out in words, for example as every time line 15 is reached, y % 2 == 1 holds.

TaskFailing input in the test case prompt Precise error condition in the guided prompt
diamond_2-1 1 ☐ y % 2 == 1 (line 15)
dijkstra-u_unwindbound5 16 ◇ 15 < n && n < 64 (line 23)
stateful_check 1, 0, 1, 1, 1, 1, 2, 1, 3 ☐ the value that __VERIFIER_nondet_int() returns here is not 0 (line 59)
ChenFlurMukhopadhyay-SAS2012-Ex2.10 1, -2147483648 (☐ 0 < x (line 25) ∧ ☐ y < -2147483647 (line 25))
CWE191_Integer_Underflow__int64_t_fscanf_multiply_01_bad -9223372036854775808 ☐ data < -4611686018427387903 (line 40)
NonTermination1 2147483522 ◇ 1 < x (line 13)
AlternKonv 0 ◇ (-2 < i) && (i < 2) (line 8)
ComplInterv2 10 (☐ !(i == -3) (line 9) ∧ ☐ !(i == 2) (line 9) ∧ ☐ !(i == -1) (line 9) ∧ ☐ !(i == 1) (line 9) ∧ ☐ !(i == -2) (line 9) ∧ ☐ !(i == 3) (line 9) ∧ ☐ !(i == -4) (line 9) ∧ ☐ !(i == 4) (line 9) ∧ ☐ !(i == 0) (line 9))
dijkstra1-both-nt-2 1073977166 ◇ h == q (line 31)

Every query and every answer of this case study is on its own page, together with the verdict that CPAchecker gave for each repair and the judgment of the two authors. Browse the 27 repairs, or take the raw records from fixes.json, validation.json, and labels.json. The nine original programs and the 27 repaired ones are in this folder too, and the page links to each of them.


The run sets are named after their analysis. cex is the explicit analysis, interval is the interval analysis, and template is the template analysis, which runs together with the explicit one. Every analysis has two dates, one run for unreach-call and termination, and one run for no-overflow.