From 308c8b71d8995688b03320f39cf92b4c75427d32 Mon Sep 17 00:00:00 2001 From: Michael Tautschnig Date: Fri, 18 Jul 2025 16:55:33 +0000 Subject: [PATCH 1/3] Fixup regression test after merging #1115 It seems that changes between that PR's original base and current main caused the completeness threshold to shrink. --- regression/smv/expressions/smv_union1.desc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regression/smv/expressions/smv_union1.desc b/regression/smv/expressions/smv_union1.desc index 393af51d4..c93f81da7 100644 --- a/regression/smv/expressions/smv_union1.desc +++ b/regression/smv/expressions/smv_union1.desc @@ -1,7 +1,7 @@ CORE broken-smt-backend smv_union1.smv -^\[spec1\] x != 3: PROVED \(CT=1\)$ +^\[spec1\] x != 3: PROVED \(CT=0\)$ ^\[spec2\] x != 2: REFUTED$ ^EXIT=10$ ^SIGNAL=0$ From 1b0a506de867e1aed9a682f16c2e2aee1223029c Mon Sep 17 00:00:00 2001 From: Daniel Kroening Date: Wed, 16 Jul 2025 15:47:29 -0700 Subject: [PATCH 2/3] BMC: looping constraint must consider all inputs The SMV top-level main module does not have explicit inputs or output. This change adds all inputs to the list of variables that are compared when creating the lasso condition. --- regression/smv/LTL/smv_ltlspec_F5.desc | 6 +++--- regression/smv/LTL/smv_ltlspec_F5.smv | 2 +- regression/smv/LTL/smv_ltlspec_F7.desc | 8 ++++++++ regression/smv/LTL/smv_ltlspec_F7.smv | 6 ++++++ src/trans-word-level/lasso.cpp | 21 +++------------------ 5 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 regression/smv/LTL/smv_ltlspec_F7.desc create mode 100644 regression/smv/LTL/smv_ltlspec_F7.smv diff --git a/regression/smv/LTL/smv_ltlspec_F5.desc b/regression/smv/LTL/smv_ltlspec_F5.desc index 952a884b6..6dd404fba 100644 --- a/regression/smv/LTL/smv_ltlspec_F5.desc +++ b/regression/smv/LTL/smv_ltlspec_F5.desc @@ -1,10 +1,10 @@ -KNOWNBUG +CORE smv_ltlspec_F5.smv ---bound 3 --numbered-trace +--bound 1 --numbered-trace ^\[spec1\] F \(!some_input \& X !some_input\): REFUTED$ +^Counterexample with 2 states:$ ^EXIT=10$ ^SIGNAL=0$ -- ^warning: ignoring -- -The BMC engine gives the wrong answer. diff --git a/regression/smv/LTL/smv_ltlspec_F5.smv b/regression/smv/LTL/smv_ltlspec_F5.smv index dd929f90a..d65f4ea4f 100644 --- a/regression/smv/LTL/smv_ltlspec_F5.smv +++ b/regression/smv/LTL/smv_ltlspec_F5.smv @@ -3,5 +3,5 @@ MODULE main VAR some_input : boolean; -- Expected to fail --- The shortest loop is FALSE, FALSE +-- The shortest loop is FALSE, FALSE or TRUE, TRUE LTLSPEC F (!some_input & X !some_input) diff --git a/regression/smv/LTL/smv_ltlspec_F7.desc b/regression/smv/LTL/smv_ltlspec_F7.desc new file mode 100644 index 000000000..ae735bb83 --- /dev/null +++ b/regression/smv/LTL/smv_ltlspec_F7.desc @@ -0,0 +1,8 @@ +CORE +smv_ltlspec_F7.smv +--bound 1 --show-formula +^\(1\) lasso::1-back-to-0 ⇔ \(smv::main::var::some_input@1 = smv::main::var::some_input@0\)$ +^EXIT=0$ +^SIGNAL=0$ +-- +^warning: ignoring diff --git a/regression/smv/LTL/smv_ltlspec_F7.smv b/regression/smv/LTL/smv_ltlspec_F7.smv new file mode 100644 index 000000000..8a9629d7f --- /dev/null +++ b/regression/smv/LTL/smv_ltlspec_F7.smv @@ -0,0 +1,6 @@ +MODULE main + +VAR some_input : 1..3; + +-- Expected to fail +LTLSPEC F (some_input >= 2) diff --git a/src/trans-word-level/lasso.cpp b/src/trans-word-level/lasso.cpp index c15e5a64e..b13efb1eb 100644 --- a/src/trans-word-level/lasso.cpp +++ b/src/trans-word-level/lasso.cpp @@ -95,7 +95,7 @@ void lasso_constraints( std::vector variables_to_compare; - // Gather the state variables. + // Gather the state variables, and the inputs. const symbol_tablet &symbol_table = ns.get_symbol_table(); auto lower = symbol_table.symbol_module_map.lower_bound(module_identifier); auto upper = symbol_table.symbol_module_map.upper_bound(module_identifier); @@ -104,26 +104,11 @@ void lasso_constraints( { const symbolt &symbol = ns.lookup(it->second); - if(symbol.is_state_var) + if(symbol.is_state_var || symbol.is_input) variables_to_compare.push_back(symbol.symbol_expr()); } - // gather the top-level inputs - const auto &module_symbol = ns.lookup(module_identifier); - DATA_INVARIANT(module_symbol.type.id() == ID_module, "expected a module"); - const auto &ports = module_symbol.type.find(ID_ports); - - for(auto &port : static_cast(ports).operands()) - { - DATA_INVARIANT(port.id() == ID_symbol, "port must be a symbol"); - if(port.get_bool(ID_input) && !port.get_bool(ID_output)) - { - symbol_exprt input_symbol(port.get(ID_identifier), port.type()); - input_symbol.add_source_location() = port.source_location(); - variables_to_compare.push_back(std::move(input_symbol)); - } - } - + // Create the constraint for(mp_integer i = 1; i < no_timeframes; ++i) { for(mp_integer k = 0; k < i; ++k) From 7225aec0574f72cbf915b8a329c7ad3891de0588 Mon Sep 17 00:00:00 2001 From: Daniel Kroening Date: Sun, 13 Jul 2025 14:53:42 -0700 Subject: [PATCH 3/3] BMC: reconsider encoding of F --- regression/smv/LTL/smv_ltlspec_FG1.bmc.desc | 4 +- regression/smv/LTL/smv_ltlspec_FG1.smv | 4 + regression/smv/LTL/smv_ltlspec_or1.desc | 4 +- src/trans-word-level/property.cpp | 176 ++++++++++++++------ 4 files changed, 133 insertions(+), 55 deletions(-) diff --git a/regression/smv/LTL/smv_ltlspec_FG1.bmc.desc b/regression/smv/LTL/smv_ltlspec_FG1.bmc.desc index be44f81a8..c48ea51f3 100644 --- a/regression/smv/LTL/smv_ltlspec_FG1.bmc.desc +++ b/regression/smv/LTL/smv_ltlspec_FG1.bmc.desc @@ -1,7 +1,7 @@ KNOWNBUG smv_ltlspec_FG1.smv ---bound 2 -^\[spec1\] F G x!=1: PROVED$ +--bound 3 +^\[spec1\] F G x != 1: PROVED up to bound 3$ ^EXIT=0$ ^SIGNAL=0$ -- diff --git a/regression/smv/LTL/smv_ltlspec_FG1.smv b/regression/smv/LTL/smv_ltlspec_FG1.smv index 15da57962..21f20312d 100644 --- a/regression/smv/LTL/smv_ltlspec_FG1.smv +++ b/regression/smv/LTL/smv_ltlspec_FG1.smv @@ -10,4 +10,8 @@ TRANS x=1 -> next(x)=2 TRANS x=2 -> next(x)=2 +-- This should pass. +-- There are traces of two kinds: +-- 0, 0, 0, ... +-- 0, ..., 0, 1, 2, 2, ... LTLSPEC F G x!=1 diff --git a/regression/smv/LTL/smv_ltlspec_or1.desc b/regression/smv/LTL/smv_ltlspec_or1.desc index f097f716c..de46295cb 100644 --- a/regression/smv/LTL/smv_ltlspec_or1.desc +++ b/regression/smv/LTL/smv_ltlspec_or1.desc @@ -1,4 +1,4 @@ -CORE +KNOWNBUG smv_ltlspec_or1.smv --bound 10 ^\[spec1\] F !x \| G x: PROVED up to bound 10$ @@ -6,3 +6,5 @@ smv_ltlspec_or1.smv ^SIGNAL=0$ -- ^warning: ignoring +-- +The BMC engine gives the wrong answer. diff --git a/src/trans-word-level/property.cpp b/src/trans-word-level/property.cpp index c5751e052..bd67a2913 100644 --- a/src/trans-word-level/property.cpp +++ b/src/trans-word-level/property.cpp @@ -176,13 +176,27 @@ Function: property_obligations_rec \*******************************************************************/ +/// Timeframe index of the start and end of the loop of the lasso. +struct bmc_loopt +{ + // State 'start' is required to be equal to state 'end'. + mp_integer start, end; +}; + static obligationst property_obligations_rec( const exprt &property_expr, const mp_integer ¤t, - const mp_integer &no_timeframes) + const mp_integer &no_timeframes, + const std::optional &loop) { PRECONDITION(current >= 0 && current < no_timeframes); + if(loop.has_value()) + { + PRECONDITION(loop.value().start < loop.value().end); + PRECONDITION(loop.value().end < no_timeframes); + } + if( property_expr.id() == ID_AG || property_expr.id() == ID_G || property_expr.id() == ID_sva_always) @@ -203,7 +217,7 @@ static obligationst property_obligations_rec( for(mp_integer c = current; c < no_timeframes; ++c) { - obligations.add(property_obligations_rec(phi, c, no_timeframes)); + obligations.add(property_obligations_rec(phi, c, no_timeframes, loop)); } return obligations; @@ -230,7 +244,8 @@ static obligationst property_obligations_rec( for(mp_integer u = current + from; u <= current + to; ++u) { - auto obligations_rec = property_obligations_rec(op, u, no_timeframes); + auto obligations_rec = + property_obligations_rec(op, u, no_timeframes, loop); disjuncts.push_back(obligations_rec.conjunction().second); } @@ -243,41 +258,42 @@ static obligationst property_obligations_rec( { const auto &phi = to_unary_expr(property_expr).op(); - obligationst obligations; + // Counterexamples to Fφ are infinite paths, and we look + // for a loop. + + if(!loop.has_value()) + return {}; + + const auto l = loop.value(); - // Traces with any φ state from "current" onwards satisfy Fφ + // we want counterexamples starting no later than 'current' + if(l.start < current) + return {}; + + // The following needs to be satisfied for a counterexample + // to Fφ that is a lassp with loop from l.start to l.end: + // + // (stem) No state j with current(no_timeframes - current)); + phi_disjuncts.reserve(numeric_cast_v(l.end - current)); - for(mp_integer j = current; j < no_timeframes; ++j) + for(mp_integer j = current; j < l.end; ++j) { - auto tmp = property_obligations_rec(phi, j, no_timeframes); + auto tmp = property_obligations_rec(phi, j, no_timeframes, loop); phi_disjuncts.push_back(tmp.conjunction().second); } auto phi_disjunction = disjunction(phi_disjuncts); - // Counterexamples to Fφ must have a loop. - // We consider l-k loops with l= 1); + + obligationst obligations; + + // This iterates over the possible shapes of a lasso. + // This is quadratic in the bound. + // State loop.start is required to be equal to state loop.end. + bmc_loopt loop; + + for(loop.end = 1; loop.end < no_timeframes; ++loop.end) + for(loop.start = 0; loop.start < loop.end; ++loop.start) + { + auto obligations_loop = + property_obligations_rec(property_expr, 0, no_timeframes, loop); + obligations.add(obligations_loop); + } + + return obligations; +} + +/*******************************************************************\ + Function: property_obligations Inputs: @@ -762,7 +818,23 @@ obligationst property_obligations( const exprt &property_expr, const mp_integer &no_timeframes) { - return property_obligations_rec(property_expr, 0, no_timeframes); + // The word-level BMC encoding works on NNF. + auto nnf = property_nnf(property_expr); + + // A counterexample to an LTL property can have one of two forms. + // 1. A linear, finite path + // 2. An infinite path, given in the form of a lasso, i.e., + // a stem followed by a loop. + + obligationst obligations; + + // Form 1 -- linear path + auto form_1 = property_obligations_rec(property_expr, 0, no_timeframes, {}); + + // Form 2 -- lasso + auto form_2 = property_obligations_lasso(property_expr, no_timeframes); + + return obligations_union(form_1, form_2); } /*******************************************************************\