Skip to content

Commit 1382ba8

Browse files
committed
Add bounded-like checker for LTS
1 parent c7e1c3e commit 1382ba8

File tree

5 files changed

+350
-0
lines changed

5 files changed

+350
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright 2025 Budapest University of Technology and Economics
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package hu.bme.mit.theta.analysis.algorithm.bounded;
17+
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
import hu.bme.mit.theta.analysis.Analysis;
21+
import hu.bme.mit.theta.analysis.LTS;
22+
import hu.bme.mit.theta.analysis.Prec;
23+
import hu.bme.mit.theta.analysis.Trace;
24+
import hu.bme.mit.theta.analysis.algorithm.EmptyProof;
25+
import hu.bme.mit.theta.analysis.algorithm.SafetyChecker;
26+
import hu.bme.mit.theta.analysis.algorithm.SafetyResult;
27+
import hu.bme.mit.theta.analysis.expr.ExprAction;
28+
import hu.bme.mit.theta.analysis.expr.ExprState;
29+
import hu.bme.mit.theta.core.model.ImmutableValuation;
30+
import hu.bme.mit.theta.core.type.Expr;
31+
import hu.bme.mit.theta.core.type.booltype.BoolLitExpr;
32+
import hu.bme.mit.theta.core.type.booltype.BoolType;
33+
import hu.bme.mit.theta.core.utils.ExprSimplifier;
34+
import hu.bme.mit.theta.core.utils.PathUtils;
35+
import hu.bme.mit.theta.core.utils.indexings.VarIndexing;
36+
import hu.bme.mit.theta.core.utils.indexings.VarIndexingFactory;
37+
import hu.bme.mit.theta.solver.Solver;
38+
import hu.bme.mit.theta.solver.utils.WithPushPop;
39+
import java.util.ArrayDeque;
40+
import java.util.Deque;
41+
import java.util.function.Predicate;
42+
43+
public class BoundedLtsChecker<S extends ExprState, A extends ExprAction, P extends Prec>
44+
implements SafetyChecker<EmptyProof, Trace<S, A>, P> {
45+
private final Solver solver;
46+
private final Analysis<S, A, ? super P> analysis;
47+
private final Predicate<? super S> target;
48+
private final LTS<? super S, A> lts;
49+
private final int bound;
50+
private final P defaultPrec;
51+
private final Deque<Transition<S, A>> transitions;
52+
private final ExprSimplifier simplifier = ExprSimplifier.create();
53+
54+
public BoundedLtsChecker(
55+
LTS<? super S, A> lts,
56+
Analysis<S, A, ? super P> analysis,
57+
Predicate<? super S> target,
58+
int bound,
59+
Solver solver) {
60+
this(lts, analysis, target, bound, null, solver);
61+
}
62+
63+
public BoundedLtsChecker(
64+
LTS<? super S, A> lts,
65+
Analysis<S, A, ? super P> analysis,
66+
Predicate<? super S> target,
67+
int bound,
68+
P defaultPrec,
69+
Solver solver) {
70+
this.solver = solver;
71+
this.analysis = analysis;
72+
this.target = target;
73+
this.lts = lts;
74+
this.bound = bound;
75+
this.defaultPrec = defaultPrec;
76+
transitions = new ArrayDeque<>(bound + 1);
77+
}
78+
79+
private int depth() {
80+
return transitions.size();
81+
}
82+
83+
@Override
84+
public SafetyResult<EmptyProof, Trace<S, A>> check(P prec) {
85+
return doCheck(prec == null ? defaultPrec : prec);
86+
}
87+
88+
private SafetyResult<EmptyProof, Trace<S, A>> doCheck(P prec) {
89+
checkNotNull(prec, "No prec provided");
90+
var indexing = VarIndexingFactory.indexing(0);
91+
boolean safe = true;
92+
for (var initialState : analysis.getInitFunc().getInitStates(prec)) {
93+
if (initialState.isBottom()) {
94+
continue;
95+
}
96+
try (var wpp = new WithPushPop(solver)) {
97+
solver.add(PathUtils.unfold(initialState.toExpr(), indexing));
98+
if (!solver.check().isSat()) {
99+
continue;
100+
}
101+
try {
102+
transitions.addLast(new Transition<>(null, initialState, indexing));
103+
var result = expand(prec);
104+
if (result.isUnsafe()) {
105+
return result;
106+
}
107+
if (!result.isSafe()) {
108+
safe = false;
109+
}
110+
} finally {
111+
transitions.removeLast();
112+
}
113+
}
114+
}
115+
return getSafeResult(safe);
116+
}
117+
118+
private SafetyResult<EmptyProof, Trace<S, A>> expand(P prec) {
119+
var transition = transitions.peekLast();
120+
assert transition != null : "No transition available";
121+
var state = transition.succState();
122+
if (target.test(state)) {
123+
return getCex();
124+
}
125+
if (depth() > bound) {
126+
return SafetyResult.unknown();
127+
}
128+
var indexing = transition.succIndexing();
129+
var actions = lts.getEnabledActionsFor(state);
130+
boolean safe = true;
131+
for (var action : actions) {
132+
try (var wpp = new WithPushPop(solver)) {
133+
solver.add(PathUtils.unfold(action.toExpr(), indexing));
134+
if (!solver.check().isSat()) {
135+
continue;
136+
}
137+
var nextIndexing = action.nextIndexing();
138+
for (var succState : analysis.getTransFunc().getSuccStates(state, action, prec)) {
139+
if (succState.isBottom()) {
140+
continue;
141+
}
142+
// Simplify the state expressions and do not call the SMT solver if it is
143+
// trivially satisfied. This reduces the number of SMT solver calls with,
144+
// e.g., UnitState, where the state expression is always trivial.
145+
var succExpr =
146+
simplifier.simplify(succState.toExpr(), ImmutableValuation.empty());
147+
boolean needsCheck = !isTrue(succExpr);
148+
if (needsCheck) {
149+
solver.push();
150+
}
151+
try {
152+
var succIndexing = indexing.add(nextIndexing);
153+
if (needsCheck) {
154+
solver.add(PathUtils.unfold(succExpr, succIndexing));
155+
if (!solver.check().isSat()) {
156+
continue;
157+
}
158+
}
159+
try {
160+
transitions.addLast(new Transition<>(action, succState, succIndexing));
161+
var result = expand(prec);
162+
if (result.isUnsafe()) {
163+
return result;
164+
}
165+
if (!result.isSafe()) {
166+
safe = false;
167+
}
168+
} finally {
169+
transitions.removeLast();
170+
}
171+
} finally {
172+
if (needsCheck) {
173+
solver.pop();
174+
}
175+
}
176+
}
177+
}
178+
}
179+
return getSafeResult(safe);
180+
}
181+
182+
boolean isTrue(Expr<BoolType> expr) {
183+
return expr instanceof BoolLitExpr boolLitExpr && boolLitExpr.getValue();
184+
}
185+
186+
private SafetyResult<EmptyProof, Trace<S, A>> getCex() {
187+
var states = transitions.stream().map(Transition::succState).toList();
188+
var actions = transitions.stream().skip(1).map(Transition::action).toList();
189+
var trace = Trace.of(states, actions);
190+
return SafetyResult.unsafe(trace, EmptyProof.getInstance());
191+
}
192+
193+
private SafetyResult<EmptyProof, Trace<S, A>> getSafeResult(boolean safe) {
194+
return safe ? SafetyResult.safe(EmptyProof.getInstance()) : SafetyResult.unknown();
195+
}
196+
197+
private record Transition<S extends ExprState, A extends ExprAction>(
198+
A action, S succState, VarIndexing succIndexing) {}
199+
}

subprojects/common/analysis/src/main/java/hu/bme/mit/theta/analysis/ptr/PtrUtils.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import hu.bme.mit.theta.analysis.expl.ExplState
2222
import hu.bme.mit.theta.analysis.expr.ExprState
2323
import hu.bme.mit.theta.analysis.pred.PredPrec
2424
import hu.bme.mit.theta.analysis.pred.PredState
25+
import hu.bme.mit.theta.analysis.unit.UnitPrec
26+
import hu.bme.mit.theta.analysis.unit.UnitState
2527
import hu.bme.mit.theta.core.decl.VarDecl
2628
import hu.bme.mit.theta.core.stmt.*
2729
import hu.bme.mit.theta.core.stmt.Stmts.Assume
@@ -208,6 +210,7 @@ fun <S : ExprState> S.patch(writeTriples: WriteTriples): S =
208210
when (this) {
209211
is PredState -> PredState.of(preds.map { it.patch(writeTriples) }) as S
210212
is ExplState -> this
213+
is UnitState -> this
211214
is PtrState<*> ->
212215
(this as PtrState<ExprState>).copy(innerState = innerState.patch(writeTriples)) as S
213216
else -> error("State $this is not supported")
@@ -217,6 +220,7 @@ fun <P : Prec> P.patch(writeTriples: WriteTriples): P =
217220
when (this) {
218221
is PredPrec -> PredPrec.of(preds.map { it.patch(writeTriples) }) as P
219222
is ExplPrec -> this
223+
is UnitPrec -> this
220224
else -> error("Prec $this is not supported")
221225
}
222226

@@ -231,13 +235,15 @@ fun <P : Prec> P.repatch(): P =
231235
when (this) {
232236
is PredPrec -> PredPrec.of(preds.map { it.repatch() }) as P
233237
is ExplPrec -> this
238+
is UnitPrec -> this
234239
else -> error("Prec $this is not supported")
235240
}
236241

237242
fun <S : ExprState> S.repatch(): S =
238243
when (this) {
239244
is PredState -> PredState.of(preds.map(Expr<BoolType>::repatch)) as S
240245
is ExplState -> this
246+
is UnitState -> this
241247
else -> error("State $this is not supported")
242248
}
243249

subprojects/xcfa/xcfa-analysis/src/main/java/hu/bme/mit/theta/xcfa/analysis/XcfaAnalysis.kt

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package hu.bme.mit.theta.xcfa.analysis
1818
import hu.bme.mit.theta.analysis.*
1919
import hu.bme.mit.theta.analysis.algorithm.arg.ArgBuilder
2020
import hu.bme.mit.theta.analysis.algorithm.arg.ArgNode
21+
import hu.bme.mit.theta.analysis.algorithm.bounded.BoundedLtsChecker
2122
import hu.bme.mit.theta.analysis.algorithm.cegar.ArgAbstractor
2223
import hu.bme.mit.theta.analysis.algorithm.cegar.abstractor.StopCriterion
2324
import hu.bme.mit.theta.analysis.expl.ExplInitFunc
@@ -32,7 +33,9 @@ import hu.bme.mit.theta.analysis.pred.PredAbstractors.PredAbstractor
3233
import hu.bme.mit.theta.analysis.ptr.PtrPrec
3334
import hu.bme.mit.theta.analysis.ptr.PtrState
3435
import hu.bme.mit.theta.analysis.ptr.getPtrInitFunc
36+
import hu.bme.mit.theta.analysis.ptr.getPtrPartialOrd
3537
import hu.bme.mit.theta.analysis.ptr.getPtrTransFunc
38+
import hu.bme.mit.theta.analysis.unit.*
3639
import hu.bme.mit.theta.analysis.waitlist.Waitlist
3740
import hu.bme.mit.theta.common.Try
3841
import hu.bme.mit.theta.common.logging.Logger
@@ -431,3 +434,72 @@ class PredXcfaAnalysis(
431434
coreInitFunc = getPredXcfaInitFunc(xcfa, predAbstractor),
432435
coreTransFunc = getPredXcfaTransFunc(predAbstractor, isHavoc),
433436
)
437+
438+
private fun getUnitXcfaPartialOrd(xcfa: XCFA): PartialOrd<XcfaState<PtrState<UnitState>>> {
439+
val ptrPartialOrd = UnitAnalysis.getInstance().partialOrd.getPtrPartialOrd()
440+
return if (xcfa.isInlined) {
441+
getPartialOrder(ptrPartialOrd)
442+
} else {
443+
getStackPartialOrder(ptrPartialOrd)
444+
}
445+
}
446+
447+
private fun getUnitXcfaInitFunc(
448+
xcfa: XCFA
449+
): (XcfaPrec<PtrPrec<UnitPrec>>) -> List<XcfaState<PtrState<UnitState>>> {
450+
val processInitState =
451+
xcfa.initProcedures
452+
.mapIndexed { i, it ->
453+
val initLocStack: LinkedList<XcfaLocation> = LinkedList()
454+
initLocStack.add(it.first.initLoc)
455+
Pair(
456+
i,
457+
XcfaProcessState(
458+
initLocStack,
459+
prefix = "T$i",
460+
varLookup = LinkedList(listOf(createLookup(it.first, "T$i", ""))),
461+
),
462+
)
463+
}
464+
.toMap()
465+
return { p ->
466+
InitFunc<UnitState, UnitPrec> { _ -> listOf(UnitState.getInstance()) }
467+
.getPtrInitFunc()
468+
.getInitStates(p.p)
469+
.map { XcfaState(xcfa, processInitState, it) }
470+
}
471+
}
472+
473+
private fun getUnitXcfaTransFunc(
474+
isHavoc: Boolean
475+
): (XcfaState<PtrState<UnitState>>, XcfaAction, XcfaPrec<PtrPrec<UnitPrec>>) -> List<
476+
XcfaState<PtrState<UnitState>>
477+
> {
478+
val unitTransFunc =
479+
TransFunc<UnitState, ExprAction, UnitPrec> { s, _, _ -> listOf(s) }.getPtrTransFunc(isHavoc)
480+
return { s, a, p ->
481+
val (newSt, newAct) = s.apply(a)
482+
unitTransFunc.getSuccStates(s.sGlobal, newAct, p.p).map { newSt.withState(it) }
483+
}
484+
}
485+
486+
class UnitXcfaAnalysis(xcfa: XCFA, isHavoc: Boolean) :
487+
XcfaAnalysis<UnitState, PtrPrec<UnitPrec>>(
488+
corePartialOrd = getUnitXcfaPartialOrd(xcfa),
489+
coreInitFunc = getUnitXcfaInitFunc(xcfa),
490+
coreTransFunc = getUnitXcfaTransFunc(isHavoc),
491+
)
492+
493+
fun getBoundedXcfaChecker(
494+
xcfa: XCFA,
495+
errorDetection: ErrorDetection,
496+
bound: Int,
497+
solver: Solver,
498+
isHavoc: Boolean = false,
499+
): BoundedLtsChecker<XcfaState<PtrState<UnitState>>, XcfaAction, XcfaPrec<PtrPrec<UnitPrec>>> {
500+
val lts = getXcfaLts()
501+
val analysis = UnitXcfaAnalysis(xcfa, isHavoc)
502+
val target = getXcfaErrorPredicate(errorDetection)
503+
val prec = XcfaPrec(PtrPrec(UnitPrec.getInstance()))
504+
return BoundedLtsChecker(lts, analysis, target, bound, prec, solver)
505+
}

0 commit comments

Comments
 (0)