GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/proof_checker.cpp Lines: 251 296 84.8 %
Date: 2021-08-01 Branches: 645 1846 34.9 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Aina Niemetz
4
 *
5
 * This file is part of the cvc5 project.
6
 *
7
 * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
8
 * in the top-level source directory and their institutional affiliations.
9
 * All rights reserved.  See the file COPYING in the top-level source
10
 * directory for licensing information.
11
 * ****************************************************************************
12
 *
13
 * Implementation of strings proof checker.
14
 */
15
16
#include "theory/strings/proof_checker.h"
17
18
#include "expr/sequence.h"
19
#include "options/strings_options.h"
20
#include "theory/rewriter.h"
21
#include "theory/strings/core_solver.h"
22
#include "theory/strings/regexp_elim.h"
23
#include "theory/strings/regexp_operation.h"
24
#include "theory/strings/term_registry.h"
25
#include "theory/strings/theory_strings_preprocess.h"
26
#include "theory/strings/theory_strings_utils.h"
27
#include "theory/strings/word.h"
28
29
using namespace cvc5::kind;
30
31
namespace cvc5 {
32
namespace theory {
33
namespace strings {
34
35
3756
void StringProofRuleChecker::registerTo(ProofChecker* pc)
36
{
37
3756
  pc->registerChecker(PfRule::CONCAT_EQ, this);
38
3756
  pc->registerChecker(PfRule::CONCAT_UNIFY, this);
39
3756
  pc->registerChecker(PfRule::CONCAT_CONFLICT, this);
40
3756
  pc->registerChecker(PfRule::CONCAT_SPLIT, this);
41
3756
  pc->registerChecker(PfRule::CONCAT_CSPLIT, this);
42
3756
  pc->registerChecker(PfRule::CONCAT_LPROP, this);
43
3756
  pc->registerChecker(PfRule::CONCAT_CPROP, this);
44
3756
  pc->registerChecker(PfRule::STRING_DECOMPOSE, this);
45
3756
  pc->registerChecker(PfRule::STRING_LENGTH_POS, this);
46
3756
  pc->registerChecker(PfRule::STRING_LENGTH_NON_EMPTY, this);
47
3756
  pc->registerChecker(PfRule::STRING_REDUCTION, this);
48
3756
  pc->registerChecker(PfRule::STRING_EAGER_REDUCTION, this);
49
3756
  pc->registerChecker(PfRule::RE_INTER, this);
50
3756
  pc->registerChecker(PfRule::RE_UNFOLD_POS, this);
51
3756
  pc->registerChecker(PfRule::RE_UNFOLD_NEG, this);
52
3756
  pc->registerChecker(PfRule::RE_UNFOLD_NEG_CONCAT_FIXED, this);
53
3756
  pc->registerChecker(PfRule::RE_ELIM, this);
54
3756
  pc->registerChecker(PfRule::STRING_CODE_INJ, this);
55
3756
  pc->registerChecker(PfRule::STRING_SEQ_UNIT_INJ, this);
56
  // trusted rules
57
3756
  pc->registerTrustedChecker(PfRule::STRING_TRUST, this, 2);
58
3756
}
59
60
5002
Node StringProofRuleChecker::checkInternal(PfRule id,
61
                                           const std::vector<Node>& children,
62
                                           const std::vector<Node>& args)
63
{
64
5002
  NodeManager* nm = NodeManager::currentNM();
65
  // core rules for word equations
66
5002
  if (id == PfRule::CONCAT_EQ || id == PfRule::CONCAT_UNIFY
67
3397
      || id == PfRule::CONCAT_CONFLICT || id == PfRule::CONCAT_SPLIT
68
3388
      || id == PfRule::CONCAT_CSPLIT || id == PfRule::CONCAT_LPROP
69
3000
      || id == PfRule::CONCAT_CPROP)
70
  {
71
2036
    Trace("strings-pfcheck") << "Checking id " << id << std::endl;
72
2036
    Assert(children.size() >= 1);
73
2036
    Assert(args.size() == 1);
74
    // all rules have an equality
75
2036
    if (children[0].getKind() != EQUAL)
76
    {
77
      return Node::null();
78
    }
79
    // convert to concatenation form
80
4072
    std::vector<Node> tvec;
81
4072
    std::vector<Node> svec;
82
2036
    utils::getConcat(children[0][0], tvec);
83
2036
    utils::getConcat(children[0][1], svec);
84
2036
    size_t nchildt = tvec.size();
85
2036
    size_t nchilds = svec.size();
86
4072
    TypeNode stringType = children[0][0].getType();
87
    // extract the Boolean corresponding to whether the rule is reversed
88
    bool isRev;
89
2036
    if (!getBool(args[0], isRev))
90
    {
91
      return Node::null();
92
    }
93
2036
    if (id == PfRule::CONCAT_EQ)
94
    {
95
1110
      Assert(children.size() == 1);
96
1110
      size_t index = 0;
97
2220
      std::vector<Node> tremVec;
98
2220
      std::vector<Node> sremVec;
99
      // scan the concatenation until we exhaust child proofs
100
2722
      while (index < nchilds && index < nchildt)
101
      {
102
2722
        Node currT = tvec[isRev ? (nchildt - 1 - index) : index];
103
2722
        Node currS = svec[isRev ? (nchilds - 1 - index) : index];
104
1916
        if (currT != currS)
105
        {
106
1110
          if (currT.isConst() && currS.isConst())
107
          {
108
            size_t sindex;
109
            // get the equal prefix/suffix, strip and add the remainders
110
744
            Node currR = Word::splitConstant(currT, currS, sindex, isRev);
111
372
            if (!currR.isNull())
112
            {
113
              // add the constant to remainder vec
114
372
              std::vector<Node>& rem = sindex == 0 ? tremVec : sremVec;
115
372
              rem.push_back(currR);
116
              // ignore the current component
117
372
              index++;
118
              // In other words, if we have (currS,currT) = ("ab","abc"), then
119
              // we proceed to the next component and add currR = "c" to
120
              // tremVec.
121
            }
122
            // otherwise if we are not the same prefix, then both will be added
123
            // Notice that we do not add maximal prefixes, in other words,
124
            // ("abc", "abd") may be added to the remainder vectors, and not
125
            // ("c", "d").
126
          }
127
1110
          break;
128
        }
129
806
        index++;
130
      }
131
1110
      Assert(index <= nchildt);
132
1110
      Assert(index <= nchilds);
133
      // the remainders are equal
134
3330
      tremVec.insert(isRev ? tremVec.begin() : tremVec.end(),
135
2220
                     tvec.begin() + (isRev ? 0 : index),
136
5550
                     tvec.begin() + nchildt - (isRev ? index : 0));
137
3330
      sremVec.insert(isRev ? sremVec.begin() : sremVec.end(),
138
2220
                     svec.begin() + (isRev ? 0 : index),
139
5550
                     svec.begin() + nchilds - (isRev ? index : 0));
140
      // convert back to node
141
2220
      Node trem = utils::mkConcat(tremVec, stringType);
142
2220
      Node srem = utils::mkConcat(sremVec, stringType);
143
1110
      return trem.eqNode(srem);
144
    }
145
    // all remaining rules do something with the first child of each side
146
1852
    Node t0 = tvec[isRev ? nchildt - 1 : 0];
147
1852
    Node s0 = svec[isRev ? nchilds - 1 : 0];
148
926
    if (id == PfRule::CONCAT_UNIFY)
149
    {
150
495
      Assert(children.size() == 2);
151
495
      if (children[1].getKind() != EQUAL)
152
      {
153
        return Node::null();
154
      }
155
1485
      for (size_t i = 0; i < 2; i++)
156
      {
157
1008
        Node l = children[1][i];
158
990
        if (l.getKind() != STRING_LENGTH)
159
        {
160
          return Node::null();
161
        }
162
1008
        Node term = i == 0 ? t0 : s0;
163
990
        if (l[0] == term)
164
        {
165
972
          continue;
166
        }
167
        // could be a spliced constant
168
18
        bool success = false;
169
18
        if (term.isConst() && l[0].isConst())
170
        {
171
18
          size_t lenL = Word::getLength(l[0]);
172
36
          success = (isRev && l[0] == Word::suffix(term, lenL))
173
54
                    || (!isRev && l[0] == Word::prefix(term, lenL));
174
        }
175
18
        if (!success)
176
        {
177
          return Node::null();
178
        }
179
      }
180
495
      return children[1][0][0].eqNode(children[1][1][0]);
181
    }
182
431
    else if (id == PfRule::CONCAT_CONFLICT)
183
    {
184
      Assert(children.size() == 1);
185
      if (!t0.isConst() || !s0.isConst())
186
      {
187
        // not constants
188
        return Node::null();
189
      }
190
      size_t sindex;
191
      Node r0 = Word::splitConstant(t0, s0, sindex, isRev);
192
      if (!r0.isNull())
193
      {
194
        // Not a conflict due to constants, i.e. s0 is a prefix of t0 or vice
195
        // versa.
196
        return Node::null();
197
      }
198
      return nm->mkConst(false);
199
    }
200
431
    else if (id == PfRule::CONCAT_SPLIT)
201
    {
202
9
      Assert(children.size() == 2);
203
27
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
204
18
          || children[1][0][0].getKind() != STRING_LENGTH
205
18
          || children[1][0][0][0] != t0
206
18
          || children[1][0][1].getKind() != STRING_LENGTH
207
27
          || children[1][0][1][0] != s0)
208
      {
209
        return Node::null();
210
      }
211
    }
212
422
    else if (id == PfRule::CONCAT_CSPLIT)
213
    {
214
318
      Assert(children.size() == 2);
215
636
      Node zero = nm->mkConst(Rational(0));
216
636
      Node one = nm->mkConst(Rational(1));
217
954
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
218
636
          || children[1][0][0].getKind() != STRING_LENGTH
219
954
          || children[1][0][0][0] != t0 || children[1][0][1] != zero)
220
      {
221
        return Node::null();
222
      }
223
318
      if (!s0.isConst() || !s0.getType().isStringLike() || Word::isEmpty(s0))
224
      {
225
        return Node::null();
226
      }
227
    }
228
104
    else if (id == PfRule::CONCAT_LPROP)
229
    {
230
70
      Assert(children.size() == 2);
231
140
      if (children[1].getKind() != GT
232
140
          || children[1][0].getKind() != STRING_LENGTH
233
140
          || children[1][0][0] != t0
234
140
          || children[1][1].getKind() != STRING_LENGTH
235
210
          || children[1][1][0] != s0)
236
      {
237
        return Node::null();
238
      }
239
    }
240
34
    else if (id == PfRule::CONCAT_CPROP)
241
    {
242
34
      Assert(children.size() == 2);
243
68
      Node zero = nm->mkConst(Rational(0));
244
245
68
      Trace("pfcheck-strings-cprop")
246
34
          << "CONCAT_PROP, isRev=" << isRev << std::endl;
247
102
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
248
68
          || children[1][0][0].getKind() != STRING_LENGTH
249
102
          || children[1][0][0][0] != t0 || children[1][0][1] != zero)
250
      {
251
        Trace("pfcheck-strings-cprop")
252
            << "...failed pattern match" << std::endl;
253
        return Node::null();
254
      }
255
34
      if (tvec.size() <= 1)
256
      {
257
        Trace("pfcheck-strings-cprop")
258
            << "...failed adjacent constant" << std::endl;
259
        return Node::null();
260
      }
261
68
      Node w1 = tvec[isRev ? nchildt - 2 : 1];
262
34
      if (!w1.isConst() || !w1.getType().isStringLike() || Word::isEmpty(w1))
263
      {
264
        Trace("pfcheck-strings-cprop")
265
            << "...failed adjacent constant content" << std::endl;
266
        return Node::null();
267
      }
268
68
      Node w2 = s0;
269
34
      if (!w2.isConst() || !w2.getType().isStringLike() || Word::isEmpty(w2))
270
      {
271
        Trace("pfcheck-strings-cprop") << "...failed constant" << std::endl;
272
        return Node::null();
273
      }
274
      // getConclusion expects the adjacent constant to be included
275
34
      t0 = nm->mkNode(STRING_CONCAT, isRev ? w1 : t0, isRev ? t0 : w1);
276
    }
277
    // use skolem cache
278
862
    SkolemCache skc(false);
279
862
    std::vector<Node> newSkolems;
280
862
    Node conc = CoreSolver::getConclusion(t0, s0, id, isRev, &skc, newSkolems);
281
431
    return conc;
282
  }
283
2966
  else if (id == PfRule::STRING_DECOMPOSE)
284
  {
285
2
    Assert(children.size() == 1);
286
2
    Assert(args.size() == 1);
287
    bool isRev;
288
2
    if (!getBool(args[0], isRev))
289
    {
290
      return Node::null();
291
    }
292
4
    Node atom = children[0];
293
2
    if (atom.getKind() != GEQ || atom[0].getKind() != STRING_LENGTH)
294
    {
295
      return Node::null();
296
    }
297
4
    SkolemCache sc(false);
298
4
    std::vector<Node> newSkolems;
299
    Node conc = CoreSolver::getConclusion(
300
4
        atom[0][0], atom[1], id, isRev, &sc, newSkolems);
301
2
    return conc;
302
  }
303
2964
  else if (id == PfRule::STRING_REDUCTION
304
2455
           || id == PfRule::STRING_EAGER_REDUCTION
305
1642
           || id == PfRule::STRING_LENGTH_POS)
306
  {
307
2362
    Assert(children.empty());
308
2362
    Assert(args.size() >= 1);
309
    // These rules are based on calling a C++ method for returning a valid
310
    // lemma involving a single argument term.
311
    // Must convert to skolem form.
312
4724
    Node t = args[0];
313
4724
    Node ret;
314
2362
    if (id == PfRule::STRING_REDUCTION)
315
    {
316
509
      Assert(args.size() == 1);
317
      // we do not use optimizations
318
1018
      SkolemCache skc(false);
319
1018
      std::vector<Node> conj;
320
509
      ret = StringsPreprocess::reduce(t, conj, &skc);
321
509
      conj.push_back(t.eqNode(ret));
322
509
      ret = nm->mkAnd(conj);
323
    }
324
1853
    else if (id == PfRule::STRING_EAGER_REDUCTION)
325
    {
326
813
      Assert(args.size() == 1);
327
1626
      SkolemCache skc(false);
328
813
      ret = TermRegistry::eagerReduce(t, &skc);
329
    }
330
1040
    else if (id == PfRule::STRING_LENGTH_POS)
331
    {
332
1040
      Assert(args.size() == 1);
333
1040
      ret = TermRegistry::lengthPositive(t);
334
    }
335
2362
    if (ret.isNull())
336
    {
337
      return Node::null();
338
    }
339
2362
    return ret;
340
  }
341
602
  else if (id == PfRule::STRING_LENGTH_NON_EMPTY)
342
  {
343
209
    Assert(children.size() == 1);
344
209
    Assert(args.empty());
345
418
    Node nemp = children[0];
346
624
    if (nemp.getKind() != NOT || nemp[0].getKind() != EQUAL
347
613
        || !nemp[0][1].isConst() || !nemp[0][1].getType().isStringLike())
348
    {
349
23
      return Node::null();
350
    }
351
186
    if (!Word::isEmpty(nemp[0][1]))
352
    {
353
      return Node::null();
354
    }
355
372
    Node zero = nm->mkConst(Rational(0));
356
372
    Node clen = nm->mkNode(STRING_LENGTH, nemp[0][0]);
357
186
    return clen.eqNode(zero).notNode();
358
  }
359
393
  else if (id == PfRule::RE_INTER)
360
  {
361
11
    Assert(children.size() >= 1);
362
11
    Assert(args.empty());
363
22
    std::vector<Node> reis;
364
22
    Node x;
365
    // make the regular expression intersection that summarizes all
366
    // memberships in the explanation
367
31
    for (const Node& c : children)
368
    {
369
22
      bool polarity = c.getKind() != NOT;
370
42
      Node catom = polarity ? c : c[0];
371
22
      if (catom.getKind() != STRING_IN_REGEXP)
372
      {
373
        return Node::null();
374
      }
375
22
      if (x.isNull())
376
      {
377
11
        x = catom[0];
378
      }
379
11
      else if (x != catom[0])
380
      {
381
        // different LHS
382
2
        return Node::null();
383
      }
384
40
      Node xcurr = catom[0];
385
      Node rcurr =
386
40
          polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]);
387
20
      reis.push_back(rcurr);
388
    }
389
18
    Node rei = reis.size() == 1 ? reis[0] : nm->mkNode(REGEXP_INTER, reis);
390
9
    return nm->mkNode(STRING_IN_REGEXP, x, rei);
391
  }
392
382
  else if (id == PfRule::RE_UNFOLD_POS || id == PfRule::RE_UNFOLD_NEG
393
268
           || id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
394
  {
395
132
    Assert(children.size() == 1);
396
132
    Assert(args.empty());
397
264
    Node skChild = children[0];
398
132
    if (id == PfRule::RE_UNFOLD_NEG || id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
399
    {
400
48
      if (skChild.getKind() != NOT || skChild[0].getKind() != STRING_IN_REGEXP)
401
      {
402
        Trace("strings-pfcheck") << "...fail, non-neg member" << std::endl;
403
        return Node::null();
404
      }
405
    }
406
108
    else if (skChild.getKind() != STRING_IN_REGEXP)
407
    {
408
      Trace("strings-pfcheck") << "...fail, non-pos member" << std::endl;
409
      return Node::null();
410
    }
411
264
    Node conc;
412
132
    if (id == PfRule::RE_UNFOLD_POS)
413
    {
414
216
      std::vector<Node> newSkolems;
415
216
      SkolemCache sc;
416
108
      conc = RegExpOpr::reduceRegExpPos(skChild, &sc, newSkolems);
417
    }
418
24
    else if (id == PfRule::RE_UNFOLD_NEG)
419
    {
420
6
      conc = RegExpOpr::reduceRegExpNeg(skChild);
421
    }
422
18
    else if (id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
423
    {
424
18
      if (skChild[0][1].getKind() != REGEXP_CONCAT)
425
      {
426
        Trace("strings-pfcheck") << "...fail, no concat regexp" << std::endl;
427
        return Node::null();
428
      }
429
      size_t index;
430
36
      Node reLen = RegExpOpr::getRegExpConcatFixed(skChild[0][1], index);
431
18
      if (reLen.isNull())
432
      {
433
        Trace("strings-pfcheck") << "...fail, non-fixed lengths" << std::endl;
434
        return Node::null();
435
      }
436
18
      conc = RegExpOpr::reduceRegExpNegConcatFixed(skChild, reLen, index);
437
    }
438
132
    return conc;
439
  }
440
250
  else if (id == PfRule::RE_ELIM)
441
  {
442
4
    Assert(children.empty());
443
4
    Assert(args.size() == 2);
444
    bool isAgg;
445
4
    if (!getBool(args[1], isAgg))
446
    {
447
      return Node::null();
448
    }
449
8
    Node ea = RegExpElimination::eliminate(args[0], isAgg);
450
    // if we didn't eliminate, then this trivially proves the reflexive equality
451
4
    if (ea.isNull())
452
    {
453
      ea = args[0];
454
    }
455
4
    return args[0].eqNode(ea);
456
  }
457
246
  else if (id == PfRule::STRING_CODE_INJ)
458
  {
459
163
    Assert(children.empty());
460
163
    Assert(args.size() == 2);
461
163
    Assert(args[0].getType().isStringLike()
462
           && args[1].getType().isStringLike());
463
326
    Node c1 = nm->mkNode(STRING_TO_CODE, args[0]);
464
326
    Node c2 = nm->mkNode(STRING_TO_CODE, args[1]);
465
326
    Node eqNegOne = c1.eqNode(nm->mkConst(Rational(-1)));
466
326
    Node deq = c1.eqNode(c2).negate();
467
326
    Node eqn = args[0].eqNode(args[1]);
468
163
    return nm->mkNode(kind::OR, eqNegOne, deq, eqn);
469
  }
470
83
  else if (id == PfRule::STRING_SEQ_UNIT_INJ)
471
  {
472
16
    Assert(children.size() == 1);
473
16
    Assert(args.empty());
474
16
    if (children[0].getKind() != EQUAL)
475
    {
476
      return Node::null();
477
    }
478
32
    Node t[2];
479
48
    for (size_t i = 0; i < 2; i++)
480
    {
481
32
      if (children[0][i].getKind() == SEQ_UNIT)
482
      {
483
28
        t[i] = children[0][i][0];
484
      }
485
4
      else if (children[0][i].isConst())
486
      {
487
        // notice that Word::getChars is not the right call here, since it
488
        // gets a vector of sequences of length one. We actually need to
489
        // extract the character, which is a sequence-specific operation.
490
4
        const Sequence& sx = children[0][i].getConst<Sequence>();
491
4
        const std::vector<Node>& vec = sx.getVec();
492
4
        if (vec.size() == 1)
493
        {
494
          // the character of the single character sequence
495
4
          t[i] = vec[0];
496
        }
497
      }
498
32
      if (t[i].isNull())
499
      {
500
        return Node::null();
501
      }
502
    }
503
32
    Trace("strings-pfcheck-debug")
504
16
        << "STRING_SEQ_UNIT_INJ: " << children[0] << " => " << t[0]
505
16
        << " == " << t[1] << std::endl;
506
16
    AlwaysAssert(t[0].getType() == t[1].getType());
507
16
    return t[0].eqNode(t[1]);
508
  }
509
67
  else if (id == PfRule::STRING_TRUST)
510
  {
511
    // "trusted" rules
512
67
    Assert(!args.empty());
513
67
    Assert(args[0].getType().isBoolean());
514
67
    return args[0];
515
  }
516
  return Node::null();
517
}
518
519
}  // namespace strings
520
}  // namespace theory
521
29280
}  // namespace cvc5