LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/theory/strings - eager_solver.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 149 157 94.9 %
Date: 2026-05-02 10:46:03 Functions: 12 12 100.0 %
Branches: 146 227 64.3 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * This file is part of the cvc5 project.
       3                 :            :  *
       4                 :            :  * Copyright (c) 2009-2026 by the authors listed in the file AUTHORS
       5                 :            :  * in the top-level source directory and their institutional affiliations.
       6                 :            :  * All rights reserved.  See the file COPYING in the top-level source
       7                 :            :  * directory for licensing information.
       8                 :            :  * ****************************************************************************
       9                 :            :  *
      10                 :            :  * The eager solver.
      11                 :            :  */
      12                 :            : 
      13                 :            : #include "theory/strings/eager_solver.h"
      14                 :            : 
      15                 :            : #include "options/strings_options.h"
      16                 :            : #include "theory/strings/theory_strings_utils.h"
      17                 :            : #include "util/rational.h"
      18                 :            : 
      19                 :            : using namespace cvc5::internal::kind;
      20                 :            : 
      21                 :            : namespace cvc5::internal {
      22                 :            : namespace theory {
      23                 :            : namespace strings {
      24                 :            : 
      25                 :      51016 : EagerSolver::EagerSolver(Env& env, SolverState& state)
      26                 :            :     : EnvObj(env),
      27                 :      51016 :       d_state(state),
      28                 :      51016 :       d_aent(env.getNodeManager(), env.getRewriter()),
      29                 :     102032 :       d_rent(env.getNodeManager(), env.getRewriter())
      30                 :            : {
      31                 :      51016 : }
      32                 :            : 
      33                 :     101336 : EagerSolver::~EagerSolver() {}
      34                 :            : 
      35                 :     275863 : void EagerSolver::eqNotifyNewClass(TNode t)
      36                 :            : {
      37                 :     275863 :   Kind k = t.getKind();
      38         [ +  + ]:     275863 :   if (k == Kind::STRING_LENGTH)
      39                 :            :   {
      40                 :            :     // also assume it as upper/lower bound as applicable for the equivalence
      41                 :            :     // class info of t.
      42                 :      78484 :     EqcInfo* eil = nullptr;
      43         [ +  + ]:     235452 :     for (size_t i = 0; i < 2; i++)
      44                 :            :     {
      45                 :     156968 :       Node b = getBoundForLength(t, i == 0);
      46         [ +  + ]:     156968 :       if (b.isNull())
      47                 :            :       {
      48                 :      68534 :         continue;
      49                 :            :       }
      50         [ +  + ]:      88434 :       if (eil == nullptr)
      51                 :            :       {
      52                 :      78484 :         eil = d_state.getOrMakeEqcInfo(t);
      53                 :            :       }
      54         [ +  + ]:      88434 :       if (i == 0)
      55                 :            :       {
      56                 :      78484 :         eil->d_firstBound = t;
      57                 :            :       }
      58         [ +  - ]:       9950 :       else if (i == 1)
      59                 :            :       {
      60                 :       9950 :         eil->d_secondBound = t;
      61                 :            :       }
      62         [ +  + ]:     156968 :     }
      63                 :            :   }
      64         [ +  + ]:     197379 :   else if (t.isConst())
      65                 :            :   {
      66                 :      53637 :     TypeNode tn = t.getType();
      67 [ +  + ][ +  + ]:      53637 :     if (tn.isStringLike() || tn.isInteger())
                 [ +  + ]
      68                 :            :     {
      69                 :      41491 :       EqcInfo* ei = d_state.getOrMakeEqcInfo(t);
      70                 :      41491 :       ei->d_firstBound = t;
      71                 :      41491 :       ei->d_secondBound = t;
      72                 :            :     }
      73                 :      53637 :   }
      74         [ +  + ]:     143742 :   else if (k == Kind::STRING_CONCAT)
      75                 :            :   {
      76                 :      26590 :     addEndpointsToEqcInfo(t, t, t);
      77                 :            :   }
      78                 :     275863 : }
      79                 :            : 
      80                 :    1622738 : void EagerSolver::eqNotifyMerge(EqcInfo* e1, TNode t1, EqcInfo* e2, TNode t2)
      81                 :            : {
      82 [ -  + ][ -  + ]:    1622738 :   Assert(e1 != nullptr);
                 [ -  - ]
      83 [ -  + ][ -  + ]:    1622738 :   Assert(e2 != nullptr);
                 [ -  - ]
      84                 :            :   // check for conflict
      85         [ +  + ]:    1622738 :   if (checkForMergeConflict(t1, t2, e1, e2))
      86                 :            :   {
      87                 :       1133 :     return;
      88                 :            :   }
      89                 :            : }
      90                 :            : 
      91                 :      30352 : bool EagerSolver::addEndpointsToEqcInfo(Node t, Node concat, Node eqc)
      92                 :            : {
      93 [ +  + ][ +  - ]:      30352 :   Assert(concat.getKind() == Kind::STRING_CONCAT
         [ -  + ][ -  + ]
                 [ -  - ]
      94                 :            :          || concat.getKind() == Kind::REGEXP_CONCAT);
      95                 :      30352 :   EqcInfo* ei = nullptr;
      96                 :            :   // check each side
      97         [ +  + ]:      91035 :   for (size_t r = 0; r < 2; r++)
      98                 :            :   {
      99         [ +  + ]:      60700 :     size_t index = r == 0 ? 0 : concat.getNumChildren() - 1;
     100                 :      60700 :     Node c = utils::getConstantComponent(concat[index]);
     101         [ +  + ]:      60700 :     if (!c.isNull())
     102                 :            :     {
     103         [ +  + ]:      11503 :       if (ei == nullptr)
     104                 :            :       {
     105                 :      10382 :         ei = d_state.getOrMakeEqcInfo(eqc);
     106                 :            :       }
     107         [ +  - ]:      23006 :       Trace("strings-eager-pconf-debug")
     108                 :          0 :           << "New term: " << concat << " for " << t << " with prefix " << c
     109                 :      11503 :           << " (" << (r == 1) << ")" << std::endl;
     110         [ +  + ]:      11503 :       if (addEndpointConst(ei, t, c, r == 1))
     111                 :            :       {
     112                 :         17 :         return true;
     113                 :            :       }
     114                 :            :     }
     115         [ +  + ]:      60700 :   }
     116                 :      30335 :   return false;
     117                 :            : }
     118                 :            : 
     119                 :    1622738 : bool EagerSolver::checkForMergeConflict(Node a,
     120                 :            :                                         Node b,
     121                 :            :                                         EqcInfo* ea,
     122                 :            :                                         EqcInfo* eb)
     123                 :            : {
     124 [ +  - ][ +  - ]:    1622738 :   Assert(eb != nullptr && ea != nullptr);
         [ -  + ][ -  + ]
                 [ -  - ]
     125                 :    4868214 :   AssertEqual(a.getType(), b.getType())
     126 [ -  + ][ -  + ]:    1622738 :       << "bad types for merge " << a << ", " << b;
                 [ -  - ]
     127                 :            :   // usages of isRealOrInt are only due to subtyping, where seq.nth for
     128                 :            :   // sequences of Real are merged to integer equivalence classes
     129                 :    1622738 :   Assert(a.getType().isStringLike() || a.getType().isRealOrInt());
     130                 :            :   // check prefix, suffix
     131         [ +  + ]:    4866383 :   for (size_t i = 0; i < 2; i++)
     132                 :            :   {
     133         [ +  + ]:    3244778 :     Node n = i == 0 ? eb->d_firstBound.get() : eb->d_secondBound.get();
     134         [ +  + ]:    3244778 :     if (!n.isNull())
     135                 :            :     {
     136                 :            :       bool isConflict;
     137         [ +  + ]:     967721 :       if (a.getType().isStringLike())
     138                 :            :       {
     139                 :     145727 :         isConflict = addEndpointConst(ea, n, Node::null(), i == 1);
     140                 :            :       }
     141                 :            :       else
     142                 :            :       {
     143         [ +  - ]:    1643988 :         Trace("strings-eager-aconf-debug")
     144                 :          0 :             << "addArithmeticBound " << n << " into " << a << " from " << b
     145                 :     821994 :             << std::endl;
     146                 :     821994 :         isConflict = addArithmeticBound(ea, n, i == 0);
     147                 :            :       }
     148         [ +  + ]:     967721 :       if (isConflict)
     149                 :            :       {
     150                 :       1133 :         return true;
     151                 :            :       }
     152                 :            :     }
     153         [ +  + ]:    3244778 :   }
     154                 :    1621605 :   return false;
     155                 :            : }
     156                 :            : 
     157                 :    2181904 : void EagerSolver::notifyFact(TNode atom,
     158                 :            :                              bool polarity,
     159                 :            :                              CVC5_UNUSED TNode fact,
     160                 :            :                              CVC5_UNUSED bool isInternal)
     161                 :            : {
     162         [ +  + ]:    2181904 :   if (atom.getKind() == Kind::STRING_IN_REGEXP)
     163                 :            :   {
     164 [ +  + ][ +  + ]:      11528 :     if (polarity && atom[1].getKind() == Kind::REGEXP_CONCAT)
         [ +  + ][ +  + ]
                 [ -  - ]
     165                 :            :     {
     166                 :       3762 :       eq::EqualityEngine* ee = d_state.getEqualityEngine();
     167                 :       7524 :       Node eqc = ee->getRepresentative(atom[0]);
     168                 :            :       // add prefix constraints
     169         [ +  + ]:       3762 :       if (addEndpointsToEqcInfo(atom, atom[1], eqc))
     170                 :            :       {
     171                 :            :         // conflict, we are done
     172                 :         17 :         return;
     173                 :            :       }
     174         [ +  + ]:       3745 :       else if (!options().strings.stringsEagerLenEntRegexp)
     175                 :            :       {
     176                 :            :         // do not infer length constraints if option is disabled
     177                 :       3732 :         return;
     178                 :            :       }
     179                 :            :       // also infer length constraints if the first is a variable
     180         [ +  + ]:         13 :       if (atom[0].isVar())
     181                 :            :       {
     182                 :          9 :         EqcInfo* blenEqc = nullptr;
     183         [ +  + ]:         27 :         for (size_t i = 0; i < 2; i++)
     184                 :            :         {
     185                 :         18 :           bool isLower = (i == 0);
     186                 :         18 :           Node b = d_rent.getConstantBoundLengthForRegexp(atom[1], isLower);
     187         [ +  + ]:         18 :           if (!b.isNull())
     188                 :            :           {
     189         [ +  + ]:         11 :             if (blenEqc == nullptr)
     190                 :            :             {
     191                 :            :               Node lenTerm =
     192                 :          9 :                   nodeManager()->mkNode(Kind::STRING_LENGTH, atom[0]);
     193         [ -  + ]:          9 :               if (!ee->hasTerm(lenTerm))
     194                 :            :               {
     195                 :          0 :                 break;
     196                 :            :               }
     197                 :          9 :               lenTerm = ee->getRepresentative(lenTerm);
     198                 :          9 :               blenEqc = d_state.getOrMakeEqcInfo(lenTerm);
     199         [ +  - ]:          9 :             }
     200         [ -  + ]:         11 :             if (addArithmeticBound(blenEqc, atom, isLower))
     201                 :            :             {
     202                 :          0 :               return;
     203                 :            :             }
     204                 :            :           }
     205    [ +  - ][ - ]:         18 :         }
     206                 :            :       }
     207         [ +  + ]:       3762 :     }
     208                 :            :   }
     209                 :            : }
     210                 :            : 
     211                 :     157230 : bool EagerSolver::addEndpointConst(EqcInfo* e, Node t, Node c, bool isSuf)
     212                 :            : {
     213 [ -  + ][ -  + ]:     157230 :   Assert(e != nullptr);
                 [ -  - ]
     214 [ -  + ][ -  + ]:     157230 :   Assert(!t.isNull());
                 [ -  - ]
     215                 :     314460 :   Node conf = e->addEndpointConst(t, c, isSuf);
     216         [ +  + ]:     157230 :   if (!conf.isNull())
     217                 :            :   {
     218                 :        768 :     d_state.setPendingMergeConflict(
     219                 :            :         conf, InferenceId::STRINGS_PREFIX_CONFLICT, isSuf);
     220                 :        768 :     return true;
     221                 :            :   }
     222                 :     156462 :   return false;
     223                 :     157230 : }
     224                 :            : 
     225                 :     822005 : bool EagerSolver::addArithmeticBound(EqcInfo* e, Node t, bool isLower)
     226                 :            : {
     227         [ +  - ]:    1644010 :   Trace("strings-eager-aconf-debug")
     228                 :          0 :       << "addArithmeticBound " << t << ", isLower = " << isLower << "..."
     229                 :     822005 :       << std::endl;
     230 [ -  + ][ -  + ]:     822005 :   Assert(e != nullptr);
                 [ -  - ]
     231 [ -  + ][ -  + ]:     822005 :   Assert(!t.isNull());
                 [ -  - ]
     232 [ -  + ][ +  - ]:     822005 :   Node tb = t.isConst() ? t : getBoundForLength(t, isLower);
                 [ -  - ]
     233                 :    1644010 :   Assert(!tb.isNull() && tb.isConst() && tb.getType().isRealOrInt())
     234 [ -  + ][ -  + ]:     822005 :       << "Unexpected bound " << tb << " from " << t;
                 [ -  - ]
     235                 :     822005 :   Rational br = tb.getConst<Rational>();
     236         [ +  + ]:     822005 :   Node prev = isLower ? e->d_firstBound : e->d_secondBound;
     237                 :            :   // check if subsumed
     238         [ +  + ]:     822005 :   if (!prev.isNull())
     239                 :            :   {
     240                 :            :     // convert to bound
     241 [ +  + ][ +  + ]:     818943 :     Node prevb = prev.isConst() ? prev : getBoundForLength(prev, isLower);
                 [ -  - ]
     242                 :     818943 :     Assert(!prevb.isNull() && prevb.isConst() && prevb.getType().isRealOrInt());
     243                 :     818943 :     Rational prevbr = prevb.getConst<Rational>();
     244 [ +  + ][ +  + ]:     818943 :     if (prevbr == br || (br < prevbr) == isLower)
                 [ +  + ]
     245                 :            :     {
     246                 :            :       // subsumed
     247                 :     778103 :       return false;
     248                 :            :     }
     249 [ +  + ][ +  + ]:    1597046 :   }
     250         [ +  - ]:      43902 :   Node prevo = isLower ? e->d_secondBound : e->d_firstBound;
     251         [ +  - ]:      87804 :   Trace("strings-eager-aconf-debug")
     252                 :      43902 :       << "Check conflict for bounds " << t << " " << prevo << std::endl;
     253         [ +  + ]:      43902 :   if (!prevo.isNull())
     254                 :            :   {
     255                 :            :     // are we in conflict?
     256 [ +  - ][ -  + ]:        382 :     Node prevob = prevo.isConst() ? prevo : getBoundForLength(prevo, !isLower);
                 [ -  - ]
     257                 :        382 :     Assert(!prevob.isNull() && prevob.isConst()
     258                 :            :            && prevob.getType().isRealOrInt());
     259                 :        382 :     Rational prevobr = prevob.getConst<Rational>();
     260         [ +  - ]:        764 :     Trace("strings-eager-aconf-debug")
     261                 :          0 :         << "Previous opposite bound was " << prevobr << ", current bound is "
     262                 :        382 :         << br << ", isLower = " << isLower << std::endl;
     263 [ +  - ][ +  - ]:        382 :     if (prevobr != br && (prevobr < br) == isLower)
                 [ +  - ]
     264                 :            :     {
     265                 :            :       // conflict
     266                 :        764 :       Node ret = EqcInfo::mkMergeConflict(t, prevo, true);
     267         [ +  - ]:        764 :       Trace("strings-eager-aconf")
     268                 :        382 :           << "String: eager arithmetic bound conflict: " << ret << std::endl;
     269                 :        382 :       d_state.setPendingMergeConflict(
     270                 :            :           ret, InferenceId::STRINGS_ARITH_BOUND_CONFLICT);
     271                 :        382 :       return true;
     272                 :        382 :     }
     273 [ -  + ][ -  + ]:        764 :   }
     274         [ +  - ]:      43520 :   if (isLower)
     275                 :            :   {
     276                 :      43520 :     e->d_firstBound = t;
     277                 :            :   }
     278                 :            :   else
     279                 :            :   {
     280                 :          0 :     e->d_secondBound = t;
     281                 :            :   }
     282                 :      43520 :   return false;
     283                 :     822005 : }
     284                 :            : 
     285                 :    1209440 : Node EagerSolver::getBoundForLength(Node t, bool isLower) const
     286                 :            : {
     287         [ +  + ]:    1209440 :   if (t.getKind() == Kind::STRING_IN_REGEXP)
     288                 :            :   {
     289                 :         25 :     return d_rent.getConstantBoundLengthForRegexp(t[1], isLower);
     290                 :            :   }
     291 [ -  + ][ -  + ]:    1209415 :   Assert(t.getKind() == Kind::STRING_LENGTH);
                 [ -  - ]
     292                 :            :   // it is prohibitively expensive to convert to original form and rewrite,
     293                 :            :   // since this may invoke the rewriter on lengths of complex terms. Instead,
     294                 :            :   // we convert to original term the argument, then call the utility method
     295                 :            :   // for computing the length of the argument, implicitly under an application
     296                 :            :   // of length (ArithEntail::getConstantBoundLength).
     297                 :            :   // convert to original form
     298                 :    1209415 :   Node olent = SkolemManager::getOriginalForm(t[0]);
     299                 :            :   // get the bound
     300                 :    1209415 :   Node c = d_aent.getConstantBoundLength(olent, isLower);
     301         [ +  - ]:    2418830 :   Trace("strings-eager-aconf-debug")
     302         [ -  - ]:          0 :       << "Constant " << (isLower ? "lower" : "upper") << " bound for " << t
     303                 :    1209415 :       << " is " << c << ", from original form " << olent << std::endl;
     304                 :    1209415 :   return c;
     305                 :    1209415 : }
     306                 :            : 
     307                 :            : }  // namespace strings
     308                 :            : }  // namespace theory
     309                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14