GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/proof_checker.cpp Lines: 44 295 14.9 %
Date: 2021-09-29 Branches: 30 1826 1.6 %

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
143
void StringProofRuleChecker::registerTo(ProofChecker* pc)
36
{
37
143
  pc->registerChecker(PfRule::CONCAT_EQ, this);
38
143
  pc->registerChecker(PfRule::CONCAT_UNIFY, this);
39
143
  pc->registerChecker(PfRule::CONCAT_CONFLICT, this);
40
143
  pc->registerChecker(PfRule::CONCAT_SPLIT, this);
41
143
  pc->registerChecker(PfRule::CONCAT_CSPLIT, this);
42
143
  pc->registerChecker(PfRule::CONCAT_LPROP, this);
43
143
  pc->registerChecker(PfRule::CONCAT_CPROP, this);
44
143
  pc->registerChecker(PfRule::STRING_DECOMPOSE, this);
45
143
  pc->registerChecker(PfRule::STRING_LENGTH_POS, this);
46
143
  pc->registerChecker(PfRule::STRING_LENGTH_NON_EMPTY, this);
47
143
  pc->registerChecker(PfRule::STRING_REDUCTION, this);
48
143
  pc->registerChecker(PfRule::STRING_EAGER_REDUCTION, this);
49
143
  pc->registerChecker(PfRule::RE_INTER, this);
50
143
  pc->registerChecker(PfRule::RE_UNFOLD_POS, this);
51
143
  pc->registerChecker(PfRule::RE_UNFOLD_NEG, this);
52
143
  pc->registerChecker(PfRule::RE_UNFOLD_NEG_CONCAT_FIXED, this);
53
143
  pc->registerChecker(PfRule::RE_ELIM, this);
54
143
  pc->registerChecker(PfRule::STRING_CODE_INJ, this);
55
143
  pc->registerChecker(PfRule::STRING_SEQ_UNIT_INJ, this);
56
  // trusted rule
57
143
  pc->registerTrustedChecker(PfRule::STRING_INFERENCE, this, 2);
58
143
}
59
60
4
Node StringProofRuleChecker::checkInternal(PfRule id,
61
                                           const std::vector<Node>& children,
62
                                           const std::vector<Node>& args)
63
{
64
4
  NodeManager* nm = NodeManager::currentNM();
65
  // core rules for word equations
66
4
  if (id == PfRule::CONCAT_EQ || id == PfRule::CONCAT_UNIFY
67
4
      || id == PfRule::CONCAT_CONFLICT || id == PfRule::CONCAT_SPLIT
68
4
      || id == PfRule::CONCAT_CSPLIT || id == PfRule::CONCAT_LPROP
69
4
      || id == PfRule::CONCAT_CPROP)
70
  {
71
    Trace("strings-pfcheck") << "Checking id " << id << std::endl;
72
    Assert(children.size() >= 1);
73
    Assert(args.size() == 1);
74
    // all rules have an equality
75
    if (children[0].getKind() != EQUAL)
76
    {
77
      return Node::null();
78
    }
79
    // convert to concatenation form
80
    std::vector<Node> tvec;
81
    std::vector<Node> svec;
82
    utils::getConcat(children[0][0], tvec);
83
    utils::getConcat(children[0][1], svec);
84
    size_t nchildt = tvec.size();
85
    size_t nchilds = svec.size();
86
    TypeNode stringType = children[0][0].getType();
87
    // extract the Boolean corresponding to whether the rule is reversed
88
    bool isRev;
89
    if (!getBool(args[0], isRev))
90
    {
91
      return Node::null();
92
    }
93
    if (id == PfRule::CONCAT_EQ)
94
    {
95
      Assert(children.size() == 1);
96
      size_t index = 0;
97
      std::vector<Node> tremVec;
98
      std::vector<Node> sremVec;
99
      // scan the concatenation until we exhaust child proofs
100
      while (index < nchilds && index < nchildt)
101
      {
102
        Node currT = tvec[isRev ? (nchildt - 1 - index) : index];
103
        Node currS = svec[isRev ? (nchilds - 1 - index) : index];
104
        if (currT != currS)
105
        {
106
          if (currT.isConst() && currS.isConst())
107
          {
108
            size_t sindex;
109
            // get the equal prefix/suffix, strip and add the remainders
110
            Node currR = Word::splitConstant(currT, currS, sindex, isRev);
111
            if (!currR.isNull())
112
            {
113
              // add the constant to remainder vec
114
              std::vector<Node>& rem = sindex == 0 ? tremVec : sremVec;
115
              rem.push_back(currR);
116
              // ignore the current component
117
              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
          break;
128
        }
129
        index++;
130
      }
131
      Assert(index <= nchildt);
132
      Assert(index <= nchilds);
133
      // the remainders are equal
134
      tremVec.insert(isRev ? tremVec.begin() : tremVec.end(),
135
                     tvec.begin() + (isRev ? 0 : index),
136
                     tvec.begin() + nchildt - (isRev ? index : 0));
137
      sremVec.insert(isRev ? sremVec.begin() : sremVec.end(),
138
                     svec.begin() + (isRev ? 0 : index),
139
                     svec.begin() + nchilds - (isRev ? index : 0));
140
      // convert back to node
141
      Node trem = utils::mkConcat(tremVec, stringType);
142
      Node srem = utils::mkConcat(sremVec, stringType);
143
      return trem.eqNode(srem);
144
    }
145
    // all remaining rules do something with the first child of each side
146
    Node t0 = tvec[isRev ? nchildt - 1 : 0];
147
    Node s0 = svec[isRev ? nchilds - 1 : 0];
148
    if (id == PfRule::CONCAT_UNIFY)
149
    {
150
      Assert(children.size() == 2);
151
      if (children[1].getKind() != EQUAL)
152
      {
153
        return Node::null();
154
      }
155
      for (size_t i = 0; i < 2; i++)
156
      {
157
        Node l = children[1][i];
158
        if (l.getKind() != STRING_LENGTH)
159
        {
160
          return Node::null();
161
        }
162
        Node term = i == 0 ? t0 : s0;
163
        if (l[0] == term)
164
        {
165
          continue;
166
        }
167
        // could be a spliced constant
168
        bool success = false;
169
        if (term.isConst() && l[0].isConst())
170
        {
171
          size_t lenL = Word::getLength(l[0]);
172
          success = (isRev && l[0] == Word::suffix(term, lenL))
173
                    || (!isRev && l[0] == Word::prefix(term, lenL));
174
        }
175
        if (!success)
176
        {
177
          return Node::null();
178
        }
179
      }
180
      return children[1][0][0].eqNode(children[1][1][0]);
181
    }
182
    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
    else if (id == PfRule::CONCAT_SPLIT)
201
    {
202
      Assert(children.size() == 2);
203
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
204
          || children[1][0][0].getKind() != STRING_LENGTH
205
          || children[1][0][0][0] != t0
206
          || children[1][0][1].getKind() != STRING_LENGTH
207
          || children[1][0][1][0] != s0)
208
      {
209
        return Node::null();
210
      }
211
    }
212
    else if (id == PfRule::CONCAT_CSPLIT)
213
    {
214
      Assert(children.size() == 2);
215
      Node zero = nm->mkConst(Rational(0));
216
      Node one = nm->mkConst(Rational(1));
217
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
218
          || children[1][0][0].getKind() != STRING_LENGTH
219
          || children[1][0][0][0] != t0 || children[1][0][1] != zero)
220
      {
221
        return Node::null();
222
      }
223
      if (!s0.isConst() || !s0.getType().isStringLike() || Word::isEmpty(s0))
224
      {
225
        return Node::null();
226
      }
227
    }
228
    else if (id == PfRule::CONCAT_LPROP)
229
    {
230
      Assert(children.size() == 2);
231
      if (children[1].getKind() != GT
232
          || children[1][0].getKind() != STRING_LENGTH
233
          || children[1][0][0] != t0
234
          || children[1][1].getKind() != STRING_LENGTH
235
          || children[1][1][0] != s0)
236
      {
237
        return Node::null();
238
      }
239
    }
240
    else if (id == PfRule::CONCAT_CPROP)
241
    {
242
      Assert(children.size() == 2);
243
      Node zero = nm->mkConst(Rational(0));
244
245
      Trace("pfcheck-strings-cprop")
246
          << "CONCAT_PROP, isRev=" << isRev << std::endl;
247
      if (children[1].getKind() != NOT || children[1][0].getKind() != EQUAL
248
          || children[1][0][0].getKind() != STRING_LENGTH
249
          || 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
      if (tvec.size() <= 1)
256
      {
257
        Trace("pfcheck-strings-cprop")
258
            << "...failed adjacent constant" << std::endl;
259
        return Node::null();
260
      }
261
      Node w1 = tvec[isRev ? nchildt - 2 : 1];
262
      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
      Node w2 = s0;
269
      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
      t0 = nm->mkNode(STRING_CONCAT, isRev ? w1 : t0, isRev ? t0 : w1);
276
    }
277
    // use skolem cache
278
    SkolemCache skc(false);
279
    std::vector<Node> newSkolems;
280
    Node conc = CoreSolver::getConclusion(t0, s0, id, isRev, &skc, newSkolems);
281
    return conc;
282
  }
283
4
  else if (id == PfRule::STRING_DECOMPOSE)
284
  {
285
    Assert(children.size() == 1);
286
    Assert(args.size() == 1);
287
    bool isRev;
288
    if (!getBool(args[0], isRev))
289
    {
290
      return Node::null();
291
    }
292
    Node atom = children[0];
293
    if (atom.getKind() != GEQ || atom[0].getKind() != STRING_LENGTH)
294
    {
295
      return Node::null();
296
    }
297
    SkolemCache sc(false);
298
    std::vector<Node> newSkolems;
299
    Node conc = CoreSolver::getConclusion(
300
        atom[0][0], atom[1], id, isRev, &sc, newSkolems);
301
    return conc;
302
  }
303
4
  else if (id == PfRule::STRING_REDUCTION
304
4
           || id == PfRule::STRING_EAGER_REDUCTION
305
4
           || id == PfRule::STRING_LENGTH_POS)
306
  {
307
4
    Assert(children.empty());
308
4
    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
8
    Node t = args[0];
313
8
    Node ret;
314
4
    if (id == PfRule::STRING_REDUCTION)
315
    {
316
      Assert(args.size() == 1);
317
      // we do not use optimizations
318
      SkolemCache skc(false);
319
      std::vector<Node> conj;
320
      ret = StringsPreprocess::reduce(t, conj, &skc);
321
      conj.push_back(t.eqNode(ret));
322
      ret = nm->mkAnd(conj);
323
    }
324
4
    else if (id == PfRule::STRING_EAGER_REDUCTION)
325
    {
326
      Assert(args.size() == 1);
327
      SkolemCache skc(false);
328
      ret = TermRegistry::eagerReduce(t, &skc);
329
    }
330
4
    else if (id == PfRule::STRING_LENGTH_POS)
331
    {
332
4
      Assert(args.size() == 1);
333
4
      ret = TermRegistry::lengthPositive(t);
334
    }
335
4
    if (ret.isNull())
336
    {
337
      return Node::null();
338
    }
339
4
    return ret;
340
  }
341
  else if (id == PfRule::STRING_LENGTH_NON_EMPTY)
342
  {
343
    Assert(children.size() == 1);
344
    Assert(args.empty());
345
    Node nemp = children[0];
346
    if (nemp.getKind() != NOT || nemp[0].getKind() != EQUAL
347
        || !nemp[0][1].isConst() || !nemp[0][1].getType().isStringLike())
348
    {
349
      return Node::null();
350
    }
351
    if (!Word::isEmpty(nemp[0][1]))
352
    {
353
      return Node::null();
354
    }
355
    Node zero = nm->mkConst(Rational(0));
356
    Node clen = nm->mkNode(STRING_LENGTH, nemp[0][0]);
357
    return clen.eqNode(zero).notNode();
358
  }
359
  else if (id == PfRule::RE_INTER)
360
  {
361
    Assert(children.size() >= 1);
362
    Assert(args.empty());
363
    std::vector<Node> reis;
364
    Node x;
365
    // make the regular expression intersection that summarizes all
366
    // memberships in the explanation
367
    for (const Node& c : children)
368
    {
369
      bool polarity = c.getKind() != NOT;
370
      Node catom = polarity ? c : c[0];
371
      if (catom.getKind() != STRING_IN_REGEXP)
372
      {
373
        return Node::null();
374
      }
375
      if (x.isNull())
376
      {
377
        x = catom[0];
378
      }
379
      else if (x != catom[0])
380
      {
381
        // different LHS
382
        return Node::null();
383
      }
384
      Node xcurr = catom[0];
385
      Node rcurr =
386
          polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]);
387
      reis.push_back(rcurr);
388
    }
389
    Node rei = reis.size() == 1 ? reis[0] : nm->mkNode(REGEXP_INTER, reis);
390
    return nm->mkNode(STRING_IN_REGEXP, x, rei);
391
  }
392
  else if (id == PfRule::RE_UNFOLD_POS || id == PfRule::RE_UNFOLD_NEG
393
           || id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
394
  {
395
    Assert(children.size() == 1);
396
    Assert(args.empty());
397
    Node skChild = children[0];
398
    if (id == PfRule::RE_UNFOLD_NEG || id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
399
    {
400
      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
    else if (skChild.getKind() != STRING_IN_REGEXP)
407
    {
408
      Trace("strings-pfcheck") << "...fail, non-pos member" << std::endl;
409
      return Node::null();
410
    }
411
    Node conc;
412
    if (id == PfRule::RE_UNFOLD_POS)
413
    {
414
      std::vector<Node> newSkolems;
415
      SkolemCache sc;
416
      conc = RegExpOpr::reduceRegExpPos(skChild, &sc, newSkolems);
417
    }
418
    else if (id == PfRule::RE_UNFOLD_NEG)
419
    {
420
      conc = RegExpOpr::reduceRegExpNeg(skChild);
421
    }
422
    else if (id == PfRule::RE_UNFOLD_NEG_CONCAT_FIXED)
423
    {
424
      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
      Node reLen = RegExpOpr::getRegExpConcatFixed(skChild[0][1], index);
431
      if (reLen.isNull())
432
      {
433
        Trace("strings-pfcheck") << "...fail, non-fixed lengths" << std::endl;
434
        return Node::null();
435
      }
436
      conc = RegExpOpr::reduceRegExpNegConcatFixed(skChild, reLen, index);
437
    }
438
    return conc;
439
  }
440
  else if (id == PfRule::RE_ELIM)
441
  {
442
    Assert(children.empty());
443
    Assert(args.size() == 2);
444
    bool isAgg;
445
    if (!getBool(args[1], isAgg))
446
    {
447
      return Node::null();
448
    }
449
    Node ea = RegExpElimination::eliminate(args[0], isAgg);
450
    // if we didn't eliminate, then this trivially proves the reflexive equality
451
    if (ea.isNull())
452
    {
453
      ea = args[0];
454
    }
455
    return args[0].eqNode(ea);
456
  }
457
  else if (id == PfRule::STRING_CODE_INJ)
458
  {
459
    Assert(children.empty());
460
    Assert(args.size() == 2);
461
    Assert(args[0].getType().isStringLike()
462
           && args[1].getType().isStringLike());
463
    Node c1 = nm->mkNode(STRING_TO_CODE, args[0]);
464
    Node c2 = nm->mkNode(STRING_TO_CODE, args[1]);
465
    Node eqNegOne = c1.eqNode(nm->mkConst(Rational(-1)));
466
    Node deq = c1.eqNode(c2).negate();
467
    Node eqn = args[0].eqNode(args[1]);
468
    return nm->mkNode(kind::OR, eqNegOne, deq, eqn);
469
  }
470
  else if (id == PfRule::STRING_SEQ_UNIT_INJ)
471
  {
472
    Assert(children.size() == 1);
473
    Assert(args.empty());
474
    if (children[0].getKind() != EQUAL)
475
    {
476
      return Node::null();
477
    }
478
    Node t[2];
479
    for (size_t i = 0; i < 2; i++)
480
    {
481
      if (children[0][i].getKind() == SEQ_UNIT)
482
      {
483
        t[i] = children[0][i][0];
484
      }
485
      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
        const Sequence& sx = children[0][i].getConst<Sequence>();
491
        const std::vector<Node>& vec = sx.getVec();
492
        if (vec.size() == 1)
493
        {
494
          // the character of the single character sequence
495
          t[i] = vec[0];
496
        }
497
      }
498
      if (t[i].isNull())
499
      {
500
        return Node::null();
501
      }
502
    }
503
    Trace("strings-pfcheck-debug")
504
        << "STRING_SEQ_UNIT_INJ: " << children[0] << " => " << t[0]
505
        << " == " << t[1] << std::endl;
506
    AlwaysAssert(t[0].getType() == t[1].getType());
507
    return t[0].eqNode(t[1]);
508
  }
509
  else if (id == PfRule::STRING_INFERENCE)
510
  {
511
    Assert(args.size() >= 3);
512
    return args[0];
513
  }
514
  return Node::null();
515
}
516
517
}  // namespace strings
518
}  // namespace theory
519
22746
}  // namespace cvc5