Coverage Report

Created: 2026-10-28 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hobbes/lib/hobbes/lang/pat/regex.C
Line
Count
Source
1
2
#include <hobbes/eval/cc.H>
3
#include <hobbes/lang/pat/regex.H>
4
#include <hobbes/util/array.H>
5
#include <hobbes/util/str.H>
6
#include <hobbes/util/rmap.H>
7
8
#include <algorithm>
9
#include <limits>
10
#include <memory>
11
#include <tuple>
12
#include <unordered_map>
13
#include <unordered_set>
14
15
namespace hobbes {
16
17
/******************
18
 * parse regular expressions into an AST
19
 ******************/
20
21
using rchar_t = uint8_t;
22
23
// epsilon / the empty string
24
struct REps : public Regex {
25
1.29k
  void show(std::ostream& out) const override { out << "`"; }
26
};
27
28
// a range of chars [b,e] (inclusive)
29
struct RCharRange : public Regex {
30
  rchar_t b, e;
31
1.08M
  RCharRange(rchar_t b, rchar_t e) : b(b), e(e) { }
32
33
8.31k
  void show(std::ostream& out) const override {
34
8.31k
    if (this->b == this->e) {
35
7.46k
      out << this->b;
36
7.46k
    } else if (this->b == rchar_t(0) && this->e == rchar_t(255)) {
37
129
      out << ".";
38
725
    } else {
39
725
      out << this->b << "-" << this->e;
40
725
    }
41
8.31k
  }
42
};
43
44
// E* (zero or more instances of the regex E)
45
struct RStar : public Regex {
46
  RegexPtr v;
47
18.2k
  RStar(const RegexPtr& v) : v(v) { }
48
49
500
  void show(std::ostream& out) const override { this->v->show(out); out << "*"; }
50
};
51
52
// A|B (either match A, or match B)
53
struct REither : public Regex {
54
  RegexPtr lhs;
55
  RegexPtr rhs;
56
149k
  REither(const RegexPtr& lhs, const RegexPtr& rhs) : lhs(lhs), rhs(rhs) { }
57
58
1.99k
  void show(std::ostream& out) const override { this->lhs->show(out); out << "|"; this->rhs->show(out); }
59
};
60
61
// AB (match A, then match B)
62
struct RSeq : public Regex {
63
  RegexPtr lhs;
64
  RegexPtr rhs;
65
1.01M
  RSeq(const RegexPtr& lhs, const RegexPtr& rhs) : lhs(lhs), rhs(rhs) { }
66
67
7.40k
  void show(std::ostream& out) const override { this->lhs->show(out); this->rhs->show(out); }
68
};
69
70
// (?<x>E) (match E, bind the substring to a variable "x")
71
struct RBind : public Regex {
72
  std::string var;
73
  RegexPtr    def;
74
7.88k
  RBind(const std::string& var, const RegexPtr& def) : var(var), def(def) { }
75
76
198
  void show(std::ostream& out) const override {
77
198
    out << "(?<" << this->var << ">";
78
198
    this->def->show(out);
79
198
    out << ")";
80
198
  }
81
};
82
83
template <typename T>
84
  struct switchRegex {
85
    virtual T with(const REps*)       const = 0;
86
    virtual T with(const RCharRange*) const = 0;
87
    virtual T with(const RStar*)      const = 0;
88
    virtual T with(const REither*)    const = 0;
89
    virtual T with(const RSeq*)       const = 0;
90
    virtual T with(const RBind*)      const = 0;
91
  };
92
93
template <typename T>
94
44.9M
  T switchOf(const RegexPtr& p, const switchRegex<T>& f) {
95
44.9M
    if (const REps* x = dynamic_cast<REps*>(p.get())) {
96
2.21M
      return f.with(x);
97
42.7M
    } else if (const RCharRange* x = dynamic_cast<RCharRange*>(p.get())) {
98
20.0M
      return f.with(x);
99
22.7M
    } else if (const RStar* x = dynamic_cast<RStar*>(p.get())) {
100
776k
      return f.with(x);
101
21.9M
    } else if (const REither* x = dynamic_cast<REither*>(p.get())) {
102
2.06M
      return f.with(x);
103
19.8M
    } else if (const RSeq* x = dynamic_cast<RSeq*>(p.get())) {
104
19.7M
      return f.with(x);
105
19.7M
    } else if (const RBind* x = dynamic_cast<RBind*>(p.get())) {
106
87.0k
      return f.with(x);
107
87.0k
    } else {
108
0
      throw std::runtime_error("Internal error, not a valid regex case");
109
0
    }
110
44.9M
  }
unsigned long hobbes::switchOf<unsigned long>(std::shared_ptr<hobbes::Regex> const&, hobbes::switchRegex<unsigned long> const&)
Line
Count
Source
94
4.65M
  T switchOf(const RegexPtr& p, const switchRegex<T>& f) {
95
4.65M
    if (const REps* x = dynamic_cast<REps*>(p.get())) {
96
379k
      return f.with(x);
97
4.27M
    } else if (const RCharRange* x = dynamic_cast<RCharRange*>(p.get())) {
98
1.98M
      return f.with(x);
99
2.28M
    } else if (const RStar* x = dynamic_cast<RStar*>(p.get())) {
100
47.0k
      return f.with(x);
101
2.24M
    } else if (const REither* x = dynamic_cast<REither*>(p.get())) {
102
390k
      return f.with(x);
103
1.85M
    } else if (const RSeq* x = dynamic_cast<RSeq*>(p.get())) {
104
1.83M
      return f.with(x);
105
1.83M
    } else if (const RBind* x = dynamic_cast<RBind*>(p.get())) {
106
20.1k
      return f.with(x);
107
20.1k
    } else {
108
0
      throw std::runtime_error("Internal error, not a valid regex case");
109
0
    }
110
4.65M
  }
unsigned char hobbes::switchOf<unsigned char>(std::shared_ptr<hobbes::Regex> const&, hobbes::switchRegex<unsigned char> const&)
Line
Count
Source
94
40.3M
  T switchOf(const RegexPtr& p, const switchRegex<T>& f) {
95
40.3M
    if (const REps* x = dynamic_cast<REps*>(p.get())) {
96
1.83M
      return f.with(x);
97
38.4M
    } else if (const RCharRange* x = dynamic_cast<RCharRange*>(p.get())) {
98
18.0M
      return f.with(x);
99
20.4M
    } else if (const RStar* x = dynamic_cast<RStar*>(p.get())) {
100
729k
      return f.with(x);
101
19.7M
    } else if (const REither* x = dynamic_cast<REither*>(p.get())) {
102
1.67M
      return f.with(x);
103
18.0M
    } else if (const RSeq* x = dynamic_cast<RSeq*>(p.get())) {
104
17.9M
      return f.with(x);
105
17.9M
    } else if (const RBind* x = dynamic_cast<RBind*>(p.get())) {
106
66.9k
      return f.with(x);
107
66.9k
    } else {
108
0
      throw std::runtime_error("Internal error, not a valid regex case");
109
0
    }
110
40.3M
  }
111
112
0
std::string show(const Regex& rgx) {
113
0
  std::ostringstream ss;
114
0
  rgx.show(ss);
115
0
  return ss.str();
116
0
}
117
0
std::string show(const RegexPtr& rgx) { return show(*rgx); }
118
119
// shorthand constructors for regex AST forms
120
192k
RegexPtr epsilon() {
121
192k
  return RegexPtr(new REps());
122
192k
}
123
124
884k
RegexPtr charLit(rchar_t c) {
125
884k
  return RegexPtr(new RCharRange(c, c));
126
884k
}
127
128
197k
RegexPtr charRange(rchar_t b, rchar_t e) {
129
197k
  return RegexPtr(new RCharRange(b, e));
130
197k
}
131
132
18.2k
RegexPtr zeroOrMore(const RegexPtr& p) {
133
18.2k
  return RegexPtr(new RStar(p));
134
18.2k
}
135
136
149k
RegexPtr either(const RegexPtr& p0, const RegexPtr& p1) {
137
149k
  return RegexPtr(new REither(p0, p1));
138
149k
}
139
140
17.2k
RegexPtr bindTo(const std::string& name, const RegexPtr& p) {
141
17.2k
  if (name.empty()) {
142
9.41k
    return p;
143
9.41k
  } else {
144
7.88k
    return RegexPtr(new RBind(name, p));
145
7.88k
  }
146
17.2k
}
147
148
1.01M
RegexPtr sequence(const RegexPtr& p0, const RegexPtr& p1) {
149
1.01M
  return RegexPtr(new RSeq(p0, p1));
150
1.01M
}
151
152
100k
RegexPtr anyOf(const Regexes& rs) {
153
100k
  if (rs.empty()) {
154
461
    return epsilon();
155
100k
  } else {
156
100k
    RegexPtr a = rs[0];
157
197k
    for (size_t i = 1; i < rs.size(); ++i) {
158
97.2k
      a = either(a, rs[i]);
159
97.2k
    }
160
100k
    return a;
161
100k
  }
162
100k
}
163
164
using CharRange = std::pair<rchar_t, rchar_t>;
165
using CharRanges = std::vector<CharRange>;
166
167
100k
CharRanges toRanges(const std::set<rchar_t>& cs) {
168
100k
  if (cs.empty()) {
169
461
    return CharRanges();
170
100k
  } else {
171
100k
    auto ci = cs.begin();
172
100k
    rchar_t b = *ci;
173
100k
    rchar_t e = b;
174
175
100k
    CharRanges r;
176
4.90M
    do {
177
4.90M
      ++ci;
178
4.90M
      if (ci == cs.end()) {
179
100k
        r.push_back(std::make_pair(b, e));
180
4.80M
      } else if (e+1 == *ci) {
181
4.71M
        e = *ci;
182
4.71M
      } else {
183
97.2k
        r.push_back(std::make_pair(b, e));
184
97.2k
        b = e = *ci;
185
97.2k
      }
186
4.90M
    } while (ci != cs.end());
187
100k
    return r;
188
100k
  }
189
100k
}
190
191
100k
RegexPtr anyOf(const std::set<rchar_t>& cs) {
192
100k
  Regexes rs;
193
197k
  for (const auto& p : toRanges(cs)) {
194
197k
    rs.push_back(charRange(p.first, p.second));
195
197k
  }
196
100k
  return anyOf(rs);
197
100k
}
198
199
80.4k
void unescapeInto(rchar_t e, std::set<rchar_t>* out) {
200
80.4k
  switch (e) {
201
1.32k
  case 't': out->insert('\t'); break;
202
1.27k
  case 'r': out->insert('\r'); break;
203
1.31k
  case 'n': out->insert('\n'); break;
204
76.5k
  default:  out->insert(e);    break;
205
80.4k
  }
206
80.4k
}
207
208
75.1k
RegexPtr unescapePatChar(rchar_t x) {
209
75.1k
  std::set<rchar_t> cs;
210
75.1k
  unescapeInto(x, &cs);
211
75.1k
  return anyOf(cs);
212
75.1k
}
213
214
// read char sets
215
using DCharset = std::pair<size_t, std::set<rchar_t>>;
216
217
9.69k
void charRange(rchar_t i, rchar_t e, std::set<rchar_t>* out) {
218
112k
  for (auto x = static_cast<size_t>(i); x <= static_cast<size_t>(e); ++x) {
219
102k
    out->insert(static_cast<rchar_t>(x));
220
102k
  }
221
9.69k
}
222
223
18.3k
const std::set<rchar_t>& anyChars() {
224
18.3k
  static std::set<rchar_t> r;
225
18.3k
  if (r.empty()) {
226
2
    charRange(0x00, 0xff, &r);
227
2
  }
228
18.3k
  return r;
229
18.3k
}
230
231
10.2k
DCharset readCharset(const std::string& x, size_t i) {
232
10.2k
  DCharset r;
233
10.2k
  r.first = i;
234
235
  // lookahead by one char so that we can catch ranges and escapes
236
648k
  while (r.first < x.size()) {
237
640k
    rchar_t c0 = x[r.first];
238
640k
    ++r.first;
239
240
640k
    rchar_t c1 = (r.first == x.size()) ? '\0' : x[r.first];
241
640k
    if (c0 == ']') {
242
2.39k
      return r;
243
638k
    } else if (c0 == '\\') {
244
      // c1 is the escaped character, and the increment below steps over it --
245
      // but when the backslash is the last character there is no c1 (the read
246
      // above substituted a nul for it), and stepping over it lands one past
247
      // the end. diffRegex rejects a trailing escape outside a charset; do the
248
      // same within one rather than returning a position that cannot be read.
249
5.28k
      if (r.first == x.size()) {
250
0
        throw std::runtime_error("Unexpected end in regex (expecting escape code)");
251
0
      }
252
5.28k
      unescapeInto(c1, &r.second);
253
5.28k
      ++r.first;
254
633k
    } else if (c1 == '-' && (r.first+1) < x.size()) {
255
9.69k
      charRange(c0, x[r.first+1], &r.second);
256
9.69k
      ++r.first;
257
9.69k
      ++r.first;
258
623k
    } else {
259
623k
      r.second.insert(c0);
260
623k
    }
261
640k
  }
262
7.86k
  return r;
263
10.2k
}
264
265
// parse a complete regex
266
using DRegex = std::pair<size_t, RegexPtr>;
267
268
// every term in a regex is a level of nesting in the tree it parses to, and
269
// that tree is built and consumed by recursive functions -- the parser here,
270
// the NFA translation in linkStateF, bindingNames, and the tree's own
271
// destructor. so the term count of a regex literal is stack depth several
272
// times over, and a regex only as exotic as a few thousand characters can run
273
// the stack out where it is read. bound it once, here, where every regex has
274
// to pass, rather than in each of those walks.
275
const size_t maxRegexTerms = 1000;
276
277
// the term budget above counts the distinct nodes a regex parses to, but a
278
// quantified group shares its sub-tree rather than copying it: 'E+' desugars
279
// to 'E E*', and the two E's are one node pointed at twice (see returnR). So a
280
// literal with quantifiers nested k deep has a term count linear in k while the
281
// tree those terms stand for, walked as a tree, has 2^k nodes -- and every walk
282
// here (bindingNames, the cache key in regexFnKey, the NFA translation in
283
// linkStateF) does walk it as a tree. The term budget is the wrong quantity to
284
// stop that: it is comfortably under 1000 while the walks take exponential time.
285
// Bound the size of the tree once it is unshared instead -- the count of nodes
286
// counting each shared node once per path to it, which is what those walks cost
287
// and what the NFA state count comes to. A regex a person writes is a small
288
// multiple of its term count here (a group is expanded at most once per '+'
289
// applied to it); nothing legitimate approaches this bound, and a regex that
290
// does could not compile in reasonable time even if it were allowed to.
291
const size_t maxRegexExpandedSize = 100000;
292
293
struct TermBudget {
294
  size_t taken = 0;
295
296
1.07M
  void take() {
297
1.07M
    if (++this->taken > maxRegexTerms) {
298
209
      throw std::runtime_error("regex is too complex to compile (more than " + str::from(maxRegexTerms) + " terms)");
299
209
    }
300
1.07M
  }
301
};
302
303
DRegex diffRegex(const RegexPtr& lhs, const std::string& x, size_t i, TermBudget* tb);
304
305
1.02M
DRegex returnR(const std::string& x, size_t k, const RegexPtr& r) {
306
1.02M
  switch (k==x.size() ? '\0' : x[k]) {
307
9.92k
  case '?': return DRegex(k+1, either(epsilon(), r));
308
3.93k
  case '*': return DRegex(k+1, zeroOrMore(r));
309
14.3k
  case '+': return DRegex(k+1, sequence(r, zeroOrMore(r)));
310
995k
  default:  return DRegex(k, r);
311
1.02M
  }
312
1.02M
}
313
314
984k
DRegex seqR(const RegexPtr& lhs, const RegexPtr& c, const std::string& x, size_t k, TermBudget* tb) {
315
984k
  DRegex cm = returnR(x, k, c);
316
984k
  return diffRegex(sequence(lhs, cm.second), x, cm.first, tb);
317
984k
}
318
319
1.20M
DRegex diffRegex(const RegexPtr& lhs, const std::string& x, size_t i, TermBudget* tb) {
320
  // a position past the end is a step this function's own cases got wrong, not
321
  // something an input can ask for -- each of them advances at most one past
322
  // the character it consumed. Nothing downstream rechecks, though: reading
323
  // from out there does not stop, it walks further off the end for as many
324
  // terms as the budget allows (OSS-Fuzz testcase 4831270021693440 read ~1,000
325
  // bytes past a 3-byte buffer that way). Two steps did get it wrong -- an
326
  // unterminated capturing group name below, and a trailing escape in
327
  // readCharset -- and both now reject instead. This is what keeps a third
328
  // from being an out-of-bounds read rather than an error.
329
1.20M
  if (i > x.size()) {
330
0
    throw std::runtime_error("Internal error: regex read position is past the end of the regex");
331
0
  }
332
333
1.20M
  if (i == x.size()) {
334
129k
    return DRegex(i, lhs);
335
1.07M
  } else {
336
    // one term of the regex is read below, whichever case it takes
337
1.07M
    tb->take();
338
339
1.07M
    rchar_t n = (i+1==x.size()) ? '\0' : x[i+1];
340
341
1.07M
    switch (x[i]) {
342
    // a group ends here; a quantifier after it belongs to the whole group, and
343
    // the '(' case applies it once the body has been read (reading it here
344
    // would attach it to the alternative being read at the time, so that
345
    // '(a|b)*' meant 'a|b*')
346
28.3k
    case ')': return DRegex(i+1, lhs);
347
17.9k
    case '(': {
348
      // maybe read a binding name for this group
349
      // (according to typical accepted regex syntax)
350
17.9k
      std::string b;
351
17.9k
      if (n == '?') {
352
7.99k
        size_t k = i+2;
353
7.99k
        if (k < x.size() && x[k] == 'P') ++k;
354
7.99k
        rchar_t gd = (k < x.size() ? x[k] : '\0');
355
7.99k
        ++k;
356
7.99k
        rchar_t ed;
357
7.99k
        if (gd == '<') {
358
7.97k
          ed = '>';
359
7.97k
        } else if (gd == '\'') {
360
0
          ed = '\'';
361
17
        } else {
362
17
          throw std::runtime_error("Expected capturing group name delimited as 'name' or <name>");
363
17
        }
364
7.97k
        size_t j = k;
365
109k
        while (j < x.size() && x[j] != ed) { ++j; }
366
367
        // the scan above stops either on the closing delimiter or on the end of
368
        // the regex. Only the first is a group name: the second means there is
369
        // no delimiter to resume after, and reading on from it steps past the
370
        // end (`i` becomes x.size(), and the recursion below starts at i+1).
371
7.97k
        if (j == x.size()) {
372
13
          throw std::runtime_error(std::string("Unexpected end in regex (expecting '") + static_cast<char>(ed) + "' to close a capturing group name)");
373
13
        }
374
375
7.96k
        b = x.substr(k, j-k);
376
7.96k
        i = j;
377
7.96k
      }
378
379
      // now the group body just matches as if inline, with any quantifier
380
      // after the group applied to the body as a whole
381
      // (but we may bind to the group match result)
382
17.8k
      DRegex g = diffRegex(epsilon(), x, i+1, tb);
383
17.8k
      DRegex q = returnR(x, g.first, g.second);
384
17.8k
      return diffRegex(sequence(lhs, bindTo(b, q.second)), x, q.first, tb);
385
17.9k
    }
386
44.6k
    case '|': {
387
44.6k
      DRegex n = diffRegex(epsilon(), x, i+1, tb);
388
44.6k
      return DRegex(n.first, either(lhs, n.second));
389
17.9k
    }
390
15.2k
    case '.': {
391
15.2k
      return seqR(lhs, anyOf(anyChars()), x, i+1, tb);
392
17.9k
    }
393
10.3k
    case '[': {
394
10.3k
      if (i+1 < x.size()) {
395
10.2k
        bool invert = x[i+1] == '^';
396
10.2k
        auto p      = readCharset(x, invert ? (i+2) : (i+1));
397
10.2k
        auto cs     = invert ? setDifference(anyChars(), p.second) : p.second;
398
399
10.2k
        return seqR(lhs, anyOf(cs), x, p.first, tb);
400
10.2k
      } else {
401
54
        throw std::runtime_error("Unexpected end in regex (expecting ']')");
402
54
      }
403
10.3k
    }
404
75.1k
    case '\\': {
405
75.1k
      if (i+1 < x.size()) {
406
75.1k
        return seqR(lhs, unescapePatChar(x[i+1]), x, i+2, tb);
407
75.1k
      } else {
408
0
        throw std::runtime_error("Unexpected end in regex (expecting escape code)");
409
0
      }
410
75.1k
    }
411
884k
    default: {
412
884k
      return seqR(lhs, charLit(x[i]), x, i+1, tb);
413
75.1k
    }}
414
1.07M
  }
415
1.20M
}
416
417
119k
RegexPtr parseRegex(const std::string& x) {
418
119k
  TermBudget tb;
419
119k
  DRegex dr = diffRegex(epsilon(), x, 0, &tb);
420
141k
  while (dr.first < x.size()) {
421
    // only an unmatched ')' stops the read early; it closes an implicit group
422
    // around everything read so far, so a quantifier after it applies to all
423
    // of that (as it did when the ')' case read the quantifier itself)
424
21.5k
    dr = returnR(x, dr.first, dr.second);
425
21.5k
    dr = diffRegex(dr.second, x, dr.first, &tb);
426
21.5k
  }
427
119k
  return dr.second;
428
119k
}
429
430
// the set of names a regex binds. A quantified group shares its sub-tree
431
// between two parents (see the note on maxRegexExpandedSize), so a plain
432
// recursion over it visits shared nodes once per path -- exponentially often
433
// when groups nest. The names are a set, and a node contributes the same names
434
// however many paths reach it, so each distinct node is visited once here: the
435
// walk skips a node it has already seen. This is what keeps bindingNames linear
436
// in the distinct nodes rather than in the unshared tree.
437
struct bnamesF : public switchRegex<UnitV> {
438
  str::set* bnames;
439
  std::unordered_set<const Regex*>* seen;
440
441
80.3k
  bnamesF(str::set* bnames, std::unordered_set<const Regex*>* seen) : bnames(bnames), seen(seen) { }
442
443
4.72M
  UnitV visit(const RegexPtr& p) const {
444
4.72M
    if (this->seen->insert(p.get()).second) {
445
4.67M
      switchOf(p, *this);
446
4.67M
    }
447
4.72M
    return unitv;
448
4.72M
  }
449
450
317k
  UnitV with(const REps*) const override { return unitv; }
451
1.99M
  UnitV with(const RCharRange*) const override { return unitv; }
452
65.9k
  UnitV with(const RStar* x) const override { return visit(x->v); }
453
454
391k
  UnitV with(const REither* x) const override {
455
391k
    visit(x->lhs);
456
391k
    return visit(x->rhs);
457
391k
  }
458
459
1.88M
  UnitV with(const RSeq* x) const override {
460
1.88M
    visit(x->lhs);
461
1.88M
    return visit(x->rhs);
462
1.88M
  }
463
464
17.6k
  UnitV with(const RBind* x) const override {
465
17.6k
    this->bnames->insert(x->var);
466
17.6k
    return visit(x->def);
467
17.6k
  }
468
};
469
470
80.3k
str::seq bindingNames(const RegexPtr& rgx) {
471
80.3k
  str::set ns;
472
80.3k
  std::unordered_set<const Regex*> seen;
473
80.3k
  bnamesF(&ns, &seen).visit(rgx);
474
80.3k
  return str::seq(ns.begin(), ns.end());
475
80.3k
}
476
477
// the number of nodes in a regex's tree once its shared sub-trees are unshared
478
// -- the size the tree-walking passes over it (bindingNames, regexFnKey, the
479
// NFA translation) actually see. A quantified group shares one sub-tree between
480
// its two uses, so this can be exponentially larger than the term count; it is
481
// computed here in one pass over the distinct nodes by remembering each node's
482
// size the first time it is reached, and saturated at size_t's max so the sum
483
// cannot itself overflow on the way to being rejected.
484
struct expandedSizeF : public switchRegex<size_t> {
485
  std::unordered_map<const Regex*, size_t>* memo;
486
181k
  explicit expandedSizeF(std::unordered_map<const Regex*, size_t>* memo) : memo(memo) { }
487
488
4.51M
  static size_t satAdd(size_t a, size_t b) {
489
4.51M
    size_t s = a + b;
490
4.51M
    return s < a ? std::numeric_limits<size_t>::max() : s;
491
4.51M
  }
492
493
4.69M
  size_t sizeOf(const RegexPtr& p) const {
494
4.69M
    auto i = this->memo->find(p.get());
495
4.69M
    if (i != this->memo->end()) {
496
36.0k
      return i->second;
497
36.0k
    }
498
4.65M
    size_t r = switchOf(p, *this);
499
4.65M
    (*this->memo)[p.get()] = r;
500
4.65M
    return r;
501
4.69M
  }
502
503
379k
  size_t with(const REps*)            const override { return 1; }
504
1.98M
  size_t with(const RCharRange*)      const override { return 1; }
505
47.0k
  size_t with(const RStar* x)         const override { return satAdd(1, sizeOf(x->v)); }
506
390k
  size_t with(const REither* x)       const override { return satAdd(1, satAdd(sizeOf(x->lhs), sizeOf(x->rhs))); }
507
1.83M
  size_t with(const RSeq* x)          const override { return satAdd(1, satAdd(sizeOf(x->lhs), sizeOf(x->rhs))); }
508
20.1k
  size_t with(const RBind* x)         const override { return satAdd(1, sizeOf(x->def)); }
509
};
510
511
181k
size_t expandedRegexSize(const RegexPtr& rgx) {
512
181k
  std::unordered_map<const Regex*, size_t> memo;
513
181k
  return expandedSizeF(&memo).sizeOf(rgx);
514
181k
}
515
516
181k
void rejectOversizedRegex(const RegexPtr& rgx) {
517
181k
  if (expandedRegexSize(rgx) > maxRegexExpandedSize) {
518
48
    throw std::runtime_error("regex is too complex to compile (expands to more than " + str::from(maxRegexExpandedSize) + " terms)");
519
48
  }
520
181k
}
521
522
/******************************
523
 * translate the regex AST to an NFA
524
 ******************************/
525
using state = uint32_t;
526
using stateset = std::set<state>;
527
528
using result = uint32_t;
529
static const result nullResult = static_cast<result>(-1);
530
531
struct char_range_ord {
532
714M
  static bool lt(rchar_t lhs, rchar_t rhs) {
533
714M
    return lhs < rhs;
534
714M
  }
535
5.75M
  static rchar_t pred(rchar_t x) {
536
5.75M
    return x-1;
537
5.75M
  }
538
20.2M
  static rchar_t succ(rchar_t x) {
539
20.2M
    return x+1;
540
20.2M
  }
541
254k
  static void copyRange(rchar_t t0, rchar_t t1, std::set<rchar_t>* out) {
542
254k
    auto t = t0;
543
254k
    auto e = t1;
544
280k
    while (true) {
545
280k
      out->insert(t);
546
280k
      if (t == e) {
547
254k
        break;
548
254k
      } else {
549
26.5k
        ++t;
550
26.5k
      }
551
280k
    }
552
254k
  }
553
};
554
555
using ntransitions = range_map<rchar_t, stateset, char_range_ord>;
556
557
using srcmarkers = std::map<result, str::set>;
558
559
struct NFAState {
560
  // transitions to successor states
561
  stateset     eps;
562
  ntransitions chars;
563
564
  // if defined, the accept value for this state
565
  result acc = nullResult;
566
567
  // markers to begin and end recording subranges
568
  srcmarkers begins, ends;
569
7.95k
  void beginMark(result r, const std::string& m) { this->begins[r].insert(m); }
570
7.95k
  void endMark  (result r, const std::string& m) {   this->ends[r].insert(m); }
571
};
572
using NFA = std::vector<NFAState>;
573
574
// for a given set of NFA states, find the set of non-overlapping char ranges
575
2.11M
CharRanges usedCharRanges(const NFA& nfa, const stateset& ss) {
576
2.11M
  CharRanges rs;
577
27.6M
  for (auto s : ss) {
578
27.6M
    rs = nfa[s].chars.disjointRanges(rs);
579
27.6M
  }
580
2.11M
  return rs;
581
2.11M
}
582
583
// accumulate a regex match into an NFA
584
state regexBefore(const RegexPtr&, state cont, result, NFA*);
585
586
struct linkStateF : public switchRegex<UnitV> {
587
  NFA*   nfa;
588
  state  self;
589
  state  succ;
590
  result id;
591
592
5.76M
  linkStateF(NFA* nfa, state self, state succ, result id) : nfa(nfa), self(self), succ(succ), id(id) {
593
5.76M
  }
594
595
3.39M
  NFAState& s(state k) const { return (*this->nfa)[k]; }
596
3.38M
  NFAState& s()        const { return s(this->self); }
597
598
3.04M
  state ins(const RegexPtr& p) const { return regexBefore(p, this->succ, this->id, this->nfa); }
599
600
245k
  UnitV with(const REps*) const override {
601
245k
    s().eps.insert(this->succ);
602
245k
    return unitv;
603
245k
  }
604
605
2.58M
  UnitV with(const RCharRange* x) const override {
606
2.58M
    s().chars.mergeRange(x->b, x->e, [&](stateset& ss){ ss.insert(this->succ); });
607
2.58M
    return unitv;
608
2.58M
  }
609
610
110k
  UnitV with(const RStar* x) const override {
611
110k
    switchOf(x->v, linkStateF(this->nfa, this->self, this->self, this->id));
612
110k
    s().eps.insert(this->succ);
613
110k
    return unitv;
614
110k
  }
615
616
217k
  UnitV with(const REither* x) const override {
617
217k
    state lstate = ins(x->lhs);
618
217k
    state rstate = ins(x->rhs);
619
217k
    s().eps.insert(lstate);
620
217k
    s().eps.insert(rstate);
621
217k
    return unitv;
622
217k
  }
623
624
2.60M
  UnitV with(const RSeq* x) const override {
625
2.60M
    state rstate = ins(x->rhs);
626
2.60M
    switchOf(x->lhs, linkStateF(this->nfa, this->self, rstate, this->id));
627
2.60M
    return unitv;
628
2.60M
  }
629
630
7.95k
  UnitV with(const RBind* x) const override {
631
7.95k
    s().beginMark(this->id, x->var);
632
7.95k
    switchOf(x->def, *this);
633
7.95k
    s(this->succ).endMark(this->id, x->var);
634
7.95k
    return unitv;
635
7.95k
  }
636
};
637
638
3.04M
state regexBefore(const RegexPtr& p, state cont, result x, NFA* nfa) {
639
3.04M
  state rstate = nfa->size();
640
3.04M
  nfa->resize(rstate+1);
641
3.04M
  switchOf(p, linkStateF(nfa, rstate, cont, x));
642
3.04M
  return rstate;
643
3.04M
}
644
645
6.37k
state accumRegex(const RegexPtr& p, result x, NFA* nfa) {
646
6.37k
  state rstate = nfa->size();
647
6.37k
  nfa->resize(rstate+1);
648
6.37k
  (*nfa)[rstate].acc = x;
649
650
6.37k
  return regexBefore(p, rstate, x, nfa);
651
6.37k
}
652
653
0
std::set<rchar_t> usedChars(const NFA& nfa) {
654
0
  std::set<rchar_t> cs;
655
0
  for (const auto& s : nfa) {
656
0
    s.chars.keys(&cs);
657
0
  }
658
0
  return cs;
659
0
}
660
661
/*****************************
662
 * print an NFA
663
 *****************************/
664
0
std::string descStates(const stateset& ss) {
665
0
  std::ostringstream out;
666
0
  if (!ss.empty()) {
667
0
    auto s = ss.begin();
668
0
    out << *s;
669
0
    ++s;
670
0
    for (; s != ss.end(); ++s) {
671
0
      out << "/" << *s;
672
0
    }
673
0
  }
674
0
  return out.str();
675
0
}
676
677
0
void print(std::ostream& out, const NFA& nfa) {
678
  // make columns for state, eps, and acc
679
0
  str::seq state;
680
0
  state.push_back("");
681
682
0
  str::seq eps;
683
0
  eps.push_back("eps");
684
685
0
  str::seq acc;
686
0
  acc.push_back("acc");
687
688
0
  size_t si = 0;
689
0
  for (const auto& s : nfa) {
690
0
    state.push_back(str::from(si++) + ":");
691
0
    eps.push_back(descStates(s.eps));
692
0
    acc.push_back(s.acc == nullResult ? "" : str::from(s.acc));
693
0
  }
694
695
  // make columns for used chars
696
0
  str::seqs ccols;
697
0
  for (auto c : usedChars(nfa)) {
698
0
    ccols.push_back(str::seq());
699
0
    str::seq& ccol = ccols.back();
700
701
0
    ccol.push_back(str::from(c));
702
0
    for (const auto& s : nfa) {
703
0
      if (const auto* ts = s.chars.lookup(c)) {
704
0
        ccol.push_back(descStates(*ts));
705
0
      } else {
706
0
        ccol.push_back("");
707
0
      }
708
0
    }
709
0
  }
710
711
  // make columns for begin/end capture markers
712
0
  str::seq mbegin;
713
0
  mbegin.push_back("mbegin");
714
715
0
  str::seq mend;
716
0
  mend.push_back("mend");
717
718
0
  for (const auto& s : nfa) {
719
0
    std::ostringstream mb;
720
0
    for (const auto& b : s.begins) {
721
0
      mb << "{" << b.first << ":";
722
0
      for (const auto& v : b.second) {
723
0
        mb << " " << v;
724
0
      }
725
0
      mb << "}";
726
0
    }
727
0
    mbegin.push_back(mb.str());
728
729
0
    std::ostringstream me;
730
0
    for (const auto& e : s.ends) {
731
0
      me << "{" << e.first << ":";
732
0
      for (const auto& v : e.second) {
733
0
        me << " " << v;
734
0
      }
735
0
      me << "}";
736
0
    }
737
0
    mend.push_back(me.str());
738
0
  }
739
740
  // print a table out of all of this
741
0
  str::seqs tbl;
742
0
  tbl.push_back(state);
743
0
  tbl.push_back(eps);
744
0
  tbl.insert(tbl.end(), ccols.begin(), ccols.end());
745
0
  tbl.push_back(acc);
746
0
  tbl.push_back(mbegin);
747
0
  tbl.push_back(mend);
748
0
  str::printRightAlignedTable(out, tbl);
749
0
}
750
751
/**********************
752
 * find eps* for an NFA
753
 **********************/
754
using EpsClosure = std::map<state, stateset>;
755
756
using statemarks = std::vector<bool>;
757
758
// eps* is kept as a set per NFA state, so what it costs is the sum of those
759
// sets' sizes -- and that is the square of the NFA's size for a regex whose
760
// eps edges make a chain, because then every state's closure is the whole
761
// chain below it. `(a?a?...a?)+` is exactly that regex: 300 of the `a?` under
762
// five nested '+' expand to 28,867 NFA states, every one of them closing over
763
// all the others, for 83M states held at once and ~4.9GB (OSS-Fuzz 557561539
764
// reported it as an out-of-memory against a 2560MB limit; the same shape one
765
// '+' shallower reaches 2.7GB).
766
//
767
// None of the bounds around this one reaches it. maxRegexTerms bounds the
768
// regex as parsed and maxRegexExpandedSize the tree it expands to, but a tree
769
// of 100,000 terms is a hundredth of what its closures can cost; the DFA
770
// state cap and maxDisambiguationSteps bound the subset construction, which
771
// runs after eps* is already built. So the states carried into closures are
772
// counted here and the regex rejected past a budget, the same way an
773
// oversized DFA or an overlong determinization is. The margin is measured,
774
// not guessed: across the whole test suite the costliest regex holds 6,105
775
// states this way (the deliberately-huge Matching/
776
// hugeRegexDFACompilesWithoutQuadraticBlowup one, whose 4,911-state NFA
777
// closes over 1.24 states per state on average), and ordinary regexes hold
778
// tens, so a budget of two million leaves legitimate regexes -- including
779
// wide alternations, whose closures really are large -- a factor of a few
780
// hundred of room while holding this phase to a couple of hundred MB.
781
static const size_t maxEpsClosureStates = 2000000;
782
783
// the walk is iterative rather than one frame per eps edge: the descent below
784
// reaches the end of a chain of eps edges before it closes over anything, so
785
// a budget on what the closures hold cannot speak until the deepest frame is
786
// already on the stack, and each frame carries a set of its own. The NFA can
787
// have as many states as maxRegexExpandedSize allows, which is far more chain
788
// than a stack holds. (The DFA walk in disambiguate() was made iterative for
789
// the same reason.)
790
//
791
// Order is preserved exactly, and that matters because eps edges can form
792
// cycles: a state on a cycle is combined while a state that reaches it is
793
// still in progress, so neither this walk nor the recursion it replaces
794
// computes a complete closure over a cycle -- what a closure ends up holding
795
// depends on which of its descendants happen to be finished by the time it is
796
// combined. The recursion descended into successors in the ascending order
797
// std::set gave them and combined a state after returning from all of them,
798
// so this pushes successors in reverse to pop them in that same order, and
799
// combines a state only after the ones it descended into. The two therefore
800
// arrive at the same incomplete closure rather than at two different ones.
801
2.46M
void findEpsClosure(const NFA& nfa, state s0, statemarks* sms, EpsClosure* ec, size_t* held) {
802
  // false: visit this state and schedule its successors; true: combine it
803
2.46M
  std::vector<std::pair<state, bool>> walk(1, std::make_pair(s0, false));
804
805
8.46M
  while (!walk.empty()) {
806
5.99M
    const state s = walk.back().first;
807
808
5.99M
    if (!walk.back().second) {
809
2.99M
      if ((*sms)[s]) {
810
0
        walk.pop_back();
811
0
        continue;
812
0
      }
813
2.99M
      (*sms)[s] = true;
814
2.99M
      walk.back().second = true;
815
816
2.99M
      const stateset& ets = nfa[s].eps;
817
3.74M
      for (auto et = ets.rbegin(); et != ets.rend(); ++et) {
818
744k
        if (!(*sms)[*et]) {
819
534k
          walk.push_back(std::make_pair(*et, false));
820
534k
        }
821
744k
      }
822
2.99M
    } else {
823
2.99M
      walk.pop_back();
824
825
2.99M
      stateset stes = (*ec)[s];
826
2.99M
      stes.insert(s);
827
2.99M
      for (auto et : nfa[s].eps) {
828
740k
        stes.insert(et);
829
830
740k
        const stateset& rstes = (*ec)[et];
831
740k
        stes.insert(rstes.begin(), rstes.end());
832
740k
      }
833
834
2.99M
      *held += stes.size();
835
2.99M
      if (*held > maxEpsClosureStates) {
836
6
        throw std::runtime_error("regex is too complex to compile (needs more than " + str::from(maxEpsClosureStates) + " epsilon-closure states)");
837
6
      }
838
2.99M
      (*ec)[s] = std::move(stes);
839
2.99M
    }
840
5.99M
  }
841
2.46M
}
842
843
3.69k
void findEpsClosure(const NFA& nfa, EpsClosure* ec) {
844
3.69k
  statemarks ms(nfa.size(), false);
845
3.69k
  size_t     held = 0;
846
847
2.99M
  for (state s = 0; s < nfa.size(); ++s) {
848
2.98M
    if (!ms[s]) {
849
2.46M
      findEpsClosure(nfa, s, &ms, ec, &held);
850
2.46M
    }
851
2.98M
  }
852
3.69k
}
853
854
47.5M
const stateset& epsState(const EpsClosure& ec, state s) {
855
47.5M
  auto eci = ec.find(s);
856
47.5M
  if (eci != ec.end()) {
857
47.5M
    return eci->second;
858
47.5M
  } else {
859
0
    throw std::runtime_error("Internal error, NFA state not in eps*");
860
0
  }
861
47.5M
}
862
863
0
stateset epsState(const EpsClosure& ec, const stateset& ss) {
864
0
  stateset r;
865
0
  for (state s : ss) {
866
0
    auto nss = epsState(ec, s);
867
0
    r.insert(nss.begin(), nss.end());
868
0
  }
869
0
  return r;
870
0
}
871
872
0
void print(std::ostream& out, const stateset& ss) {
873
0
  out << "{";
874
0
  auto s = ss.begin();
875
0
  if (s != ss.end()) {
876
0
    out << *s;
877
0
    ++s;
878
0
    for (; s != ss.end(); ++s) {
879
0
      out << ", " << *s;
880
0
    }
881
0
  }
882
0
  out << "}";
883
0
}
884
885
0
void print(std::ostream& out, const EpsClosure& ec) {
886
0
  for (const auto& eset : ec) {
887
0
    out << eset.first << " -> ";
888
0
    print(out, eset.second);
889
0
    out << std::endl;
890
0
  }
891
0
}
892
893
/**********************
894
 * convert an NFA to a DFA
895
 **********************/
896
using dtransitions = range_map<rchar_t, state, char_range_ord>;
897
struct DFAState {
898
  dtransitions chars;
899
900
  // if defined, the accept value for this state
901
  result acc = nullResult;
902
903
  // markers to begin and end recording subranges
904
  srcmarkers begins, ends;
905
};
906
using DFA = std::vector<DFAState>;
907
908
47.5M
void insert(stateset* o, const stateset& i) {
909
47.5M
  o->insert(i.begin(), i.end());
910
47.5M
}
911
912
// find the set of NFA states we'd transition to from a char from within a set of states.
913
// *steps counts every state carried into the union; disambiguate() bounds the
914
// whole walk by that count.
915
6.76M
stateset nfaTransition(const NFA& nfa, const EpsClosure& ec, const stateset& ss, const CharRange& cr, size_t* steps) {
916
6.76M
  stateset result;
917
216M
  for (state s : ss) {
918
216M
    if (const auto* tss = nfa[s].chars.lookupRangeSubset(cr)) {
919
47.5M
      for (state t : *tss) {
920
47.5M
        const stateset& cts = epsState(ec, t);
921
47.5M
        *steps += cts.size();
922
47.5M
        insert(&result, cts);
923
47.5M
      }
924
47.5M
    }
925
216M
  }
926
6.76M
  return result;
927
6.76M
}
928
929
// sets of NFA states are mapped to distinct DFA states
930
using Nss2Ds = std::map<stateset, state>;
931
932
// the DFA state for a set of NFA states: the one already made for it, or a
933
// new one, allocated here and left on the worklist for its transitions to be
934
// filled in
935
6.76M
state dfaState(const cc* c, const NFA& nfa, Nss2Ds* nss2ds, DFA* dfa, std::vector<std::pair<state, stateset>>* pending, const stateset& ss) {
936
  // did we already make this state?  if so, just return it
937
6.76M
  auto didIt = nss2ds->find(ss);
938
6.76M
  if (didIt != nss2ds->end()) {
939
4.58M
    return didIt->second;
940
4.58M
  }
941
942
  // we need to make this state -- allocate it and remember it
943
2.18M
  state result = dfa->size();
944
2.18M
  dfa->resize(dfa->size() + 1);
945
946
  // determinizing is exponential in the worst case, and it doesn't take an
947
  // exotic regex to hit it: ' *...........,...............' is 29 characters
948
  // that determinize to over 50k states. cap the construction so that a regex
949
  // literal can't hang the compiler (or the parser -- regex literals are
950
  // compiled where they're read).
951
2.18M
  if (dfa->size() > c->regexMaxDFAStates()) {
952
68
    throw std::runtime_error("regex is too complex to compile (needs more than " + str::from(c->regexMaxDFAStates()) + " DFA states)");
953
68
  }
954
955
2.18M
  if (c->throwOnHugeRegexDFA() and c->regexDFAOverNFAMaxRatio() > 0 and (dfa->size() / nfa.size() > size_t(c->regexDFAOverNFAMaxRatio()))) {
956
0
    throw std::runtime_error("regexes DFA over NFA Max ratio was breached");
957
0
  }
958
959
2.18M
  (*nss2ds)[ss] = result;
960
2.18M
  pending->push_back(std::make_pair(result, ss));
961
2.18M
  return result;
962
2.18M
}
963
964
// the walk over the DFA costs a product per state made -- the NFA states in
965
// its set, the char ranges they use, and the eps-closed successors unioned
966
// per range -- and the state cap in dfaState() bounds only how many states
967
// there are. 143 bytes of regex (OSS-Fuzz 554287846) held every other factor
968
// high at once: ~35M of the steps counted below and minutes of instrumented
969
// time before the state cap finally spoke, a minute and a half in. So the
970
// steps are counted where they are taken and the regex is rejected past a
971
// budget, the same way an oversized DFA is, while the answer still costs
972
// seconds rather than a fuzzer's whole time budget. The margin is measured, not guessed: the largest
973
// deliberately-huge regex in the test suite (Matching/
974
// hugeRegexDFACompilesWithoutQuadraticBlowup) costs 1.32M steps and ordinary
975
// regexes cost thousands, so triple the former still rejects the reported
976
// input at an eighth of its budgeted run.
977
static const size_t maxDisambiguationSteps = 4000000;
978
979
3.69k
void disambiguate(const cc* c, const NFA& nfa, DFA* dfa, RStates* rstates) {
980
  // determine eps* for this NFA
981
3.69k
  EpsClosure ec;
982
3.69k
  findEpsClosure(nfa, &ec);
983
984
  // starting from the eps* start state, follow non-eps transitions to eps*
985
  // successor states, making a DFA state for each set of NFA states reached.
986
  //
987
  // this is a walk over the DFA as it is built, and it is done with an
988
  // explicit worklist rather than by recursing into each successor as it is
989
  // found: the walk can be as deep as the DFA has states -- a chain of
990
  // transitions with no repeats is one frame per state -- and the cap on
991
  // state count above bounds how many there are, not how deep a chain of them
992
  // goes. A regex well inside that cap ran the stack out this way once its
993
  // frames were made large by instrumentation (each carried the sets of NFA
994
  // states it was visiting). The order states are made in is not significant
995
  // to anything downstream, only that each set of NFA states gets one.
996
3.69k
  Nss2Ds nss2ds;
997
3.69k
  std::vector<std::pair<state, stateset>> pending;
998
3.69k
  dfaState(c, nfa, &nss2ds, dfa, &pending, epsState(ec, 0));
999
1000
3.69k
  size_t steps = 0;
1001
1002
2.11M
  while (!pending.empty()) {
1003
2.11M
    const state    result = pending.back().first;
1004
2.11M
    const stateset ss     = pending.back().second;
1005
2.11M
    pending.pop_back();
1006
1007
    // ok, how can we transition out of here?
1008
    // for each case, we'll go to a set of NFA states
1009
2.11M
    const CharRanges crs = usedCharRanges(nfa, ss);
1010
2.11M
    steps += ss.size() * (crs.size() + 1);
1011
2.11M
    if (steps > maxDisambiguationSteps) {
1012
22
      throw std::runtime_error("regex is too complex to compile (needs more than " + str::from(maxDisambiguationSteps) + " determinization steps)");
1013
22
    }
1014
6.76M
    for (const auto& cr : crs) {
1015
6.76M
      stateset nss = nfaTransition(nfa, ec, ss, cr, &steps);
1016
6.76M
      auto ns = dfaState(c, nfa, &nss2ds, dfa, &pending, nss);
1017
6.76M
      (*dfa)[result].chars.insert(cr, ns);
1018
6.76M
    }
1019
1020
    // our DFA state accepts if any of its NFA states accept
1021
    // we may have multiple potential matches here, so we should
1022
    // keep track of every such set so that outer match compilation
1023
    // can choose the right one
1024
27.6M
    for (state s : ss) {
1025
27.6M
      auto nr = nfa[s].acc;
1026
27.6M
      if (nr != nullResult) {
1027
59.2k
        (*dfa)[result].acc = result;
1028
59.2k
        (*rstates)[result].insert(nr);
1029
59.2k
      }
1030
27.6M
    }
1031
1032
    // our DFA state begins/ends subrange recording for each collapsed NFA state
1033
27.6M
    for (state s : ss) {
1034
27.6M
      for (const auto& b : nfa[s].begins) {
1035
26.5k
        (*dfa)[result].begins[b.first].insert(b.second.begin(), b.second.end());
1036
26.5k
      }
1037
27.6M
      for (const auto& e : nfa[s].ends) {
1038
124k
        (*dfa)[result].ends[e.first].insert(e.second.begin(), e.second.end());
1039
124k
      }
1040
27.6M
    }
1041
2.11M
  }
1042
3.69k
}
1043
1044
/*****************************
1045
 * print a DFA
1046
 *****************************/
1047
0
std::set<rchar_t> usedChars(const DFA& dfa) {
1048
0
  std::set<rchar_t> cs;
1049
0
  for (const auto& s : dfa) {
1050
0
    s.chars.keys(&cs);
1051
0
  }
1052
0
  return cs;
1053
0
}
1054
1055
0
void print(std::ostream& out, const DFA& dfa) {
1056
  // make columns for state, eps, and acc
1057
0
  str::seq state;
1058
0
  state.push_back("");
1059
1060
0
  str::seq acc;
1061
0
  acc.push_back("acc");
1062
1063
0
  str::seq mbegin;
1064
0
  mbegin.push_back("mbegin");
1065
1066
0
  str::seq mend;
1067
0
  mend.push_back("mend");
1068
1069
0
  size_t si = 0;
1070
0
  for (const auto& s : dfa) {
1071
0
    state.push_back(str::from(si++) + ":");
1072
0
    acc.push_back(s.acc == nullResult ? "" : str::from(s.acc));
1073
1074
0
    std::ostringstream mb;
1075
0
    for (const auto& b : s.begins) {
1076
0
      mb << "{" << b.first << ":";
1077
0
      for (const auto& v : b.second) {
1078
0
        mb << " " << v;
1079
0
      }
1080
0
      mb << "}";
1081
0
    }
1082
0
    mbegin.push_back(mb.str());
1083
1084
0
    std::ostringstream me;
1085
0
    for (const auto& e : s.ends) {
1086
0
      me << "{" << e.first << ":";
1087
0
      for (const auto& v : e.second) {
1088
0
        me << " " << v;
1089
0
      }
1090
0
      me << "}";
1091
0
    }
1092
0
    mend.push_back(me.str());
1093
0
  }
1094
1095
  // make columns for used chars
1096
0
  str::seqs ccols;
1097
0
  for (auto c : usedChars(dfa)) {
1098
0
    ccols.push_back(str::seq());
1099
0
    str::seq& ccol = ccols.back();
1100
1101
0
    ccol.push_back(str::from(c));
1102
0
    for (const auto& s : dfa) {
1103
0
      if (const auto* st = s.chars.lookup(c)) {
1104
0
        ccol.push_back(str::from(*st));
1105
0
      } else {
1106
0
        ccol.push_back("");
1107
0
      }
1108
0
    }
1109
0
  }
1110
1111
  // print a table out of all of this
1112
0
  str::seqs tbl;
1113
0
  tbl.push_back(state);
1114
0
  tbl.insert(tbl.end(), ccols.begin(), ccols.end());
1115
0
  tbl.push_back(acc);
1116
0
  tbl.push_back(mbegin);
1117
0
  tbl.push_back(mend);
1118
0
  str::printRightAlignedTable(out, tbl);
1119
0
}
1120
1121
/**********************
1122
 * convert a DFA to an equivalent function
1123
 **********************/
1124
978k
static ExprPtr transitionToState(const std::string& fname, int s, const LexicalAnnotation& rootLA) {
1125
  // produce an expression fragment to jump to the given state
1126
978k
  return fncall(var(fname, rootLA), list(var("cap", rootLA), var("cs", rootLA), var("n", rootLA), var("e", rootLA), constant(s, rootLA)), rootLA);
1127
978k
}
1128
1129
162k
static ExprPtr transitionAsCharSwitch(const std::string& fname, const DFAState& s, const ExprPtr& charExpr, const ExprPtr& defaultResult, const LexicalAnnotation& rootLA) {
1130
162k
  Switch::Bindings cs;
1131
162k
  std::set<rchar_t> tcs;
1132
162k
  s.chars.keys(&tcs);
1133
280k
  for (auto tc : tcs) {
1134
280k
    cs.push_back(Switch::Binding(PrimitivePtr(new Char(tc, rootLA)),
1135
280k
      transitionToState(fname, *s.chars.lookup(tc), rootLA)
1136
280k
    ));
1137
280k
  }
1138
162k
  return
1139
162k
    switchE(
1140
162k
      charExpr,
1141
162k
      cs,
1142
162k
      defaultResult,
1143
162k
      rootLA
1144
162k
    );
1145
162k
}
1146
1147
691k
static ExprPtr charInRange(const ExprPtr& c, const std::pair<rchar_t, rchar_t>& crange, const LexicalAnnotation& rootLA) {
1148
691k
  if (crange.first == 0 && crange.second == 255) {
1149
0
    return constant(true, rootLA);
1150
691k
  } else if (crange.first == 0) {
1151
95.0k
    return fncall(var("blte", rootLA), list(c, constant(static_cast<uint8_t>(crange.second), rootLA)), rootLA);
1152
596k
  } else if (crange.first == crange.second) {
1153
335k
    return fncall(var("beq", rootLA), list(c, constant(static_cast<uint8_t>(crange.first), rootLA)), rootLA);
1154
335k
  } else {
1155
260k
    return
1156
260k
      fncall(
1157
260k
        var("and", rootLA),
1158
260k
        list(
1159
260k
          fncall(var("blte", rootLA), list(constant(static_cast<uint8_t>(crange.first), rootLA), c), rootLA),
1160
260k
          fncall(var("blte", rootLA), list(c, constant(static_cast<uint8_t>(crange.second), rootLA)), rootLA)
1161
260k
        ),
1162
260k
        rootLA
1163
260k
      );
1164
260k
  }
1165
691k
}
1166
1167
95.3k
static ExprPtr transitionAsRangeChecks(const std::string& fname, const std::vector<std::pair<std::pair<rchar_t,rchar_t>, state>>& ranges, const ExprPtr& charExpr, const ExprPtr& defaultResult, const LexicalAnnotation& rootLA) {
1168
95.3k
  std::string n = freshName();
1169
95.3k
  ExprPtr cvar = var(n, rootLA);
1170
1171
95.3k
  ExprPtr ifvn = var("if", rootLA);
1172
1173
95.3k
  ExprPtr result = defaultResult;
1174
786k
  for (auto r = ranges.rbegin(); r != ranges.rend(); ++r) {
1175
691k
    result =
1176
691k
      fncall(
1177
691k
        ifvn,
1178
691k
        list(
1179
691k
          charInRange(cvar, r->first, rootLA),
1180
691k
          transitionToState(fname, r->second, rootLA),
1181
691k
          result
1182
691k
        ),
1183
691k
        rootLA
1184
691k
      );
1185
691k
  }
1186
95.3k
  return
1187
95.3k
    let(n, assume(fncall(var("unsafeCast", rootLA), list(charExpr), rootLA), primty("byte"), rootLA),
1188
95.3k
      result,
1189
95.3k
      rootLA
1190
95.3k
    );
1191
95.3k
}
1192
1193
271k
static ExprPtr transitionMapping(const std::string& fname, const DFAState& s, const ExprPtr& charExpr, const ExprPtr& defaultResult, const LexicalAnnotation& rootLA) {
1194
  // for the full 'range -> state' transition map at this state
1195
  //   are we mostly looking at single character transitions?
1196
  //   if so, we're better off using a switch
1197
  //   else use a sequence of range tests
1198
271k
  auto rtns = s.chars.mapping();
1199
1200
271k
  if (rtns.empty()) {
1201
    // shouldn't happen, but it's the right thing to do
1202
7.19k
    return defaultResult;
1203
263k
  } else if (rtns.size() == 1 && rtns[0].first.first == 0 && rtns[0].first.second == 255) {
1204
    // a catch-all case, don't need any special logic
1205
6.31k
    return transitionToState(fname, rtns[0].second, rootLA);
1206
6.31k
  }
1207
1208
257k
  double sd = 0.0;
1209
945k
  for (const auto& rtn : rtns) {
1210
945k
    sd += 1 + rtn.first.second - rtn.first.first;
1211
945k
  }
1212
257k
  double avgs = sd / static_cast<double>(rtns.size());
1213
1214
257k
  if (avgs <= 2.0) {
1215
162k
    return transitionAsCharSwitch(fname, s, charExpr, defaultResult, rootLA);
1216
162k
  } else {
1217
95.3k
    return transitionAsRangeChecks(fname, rtns, charExpr, defaultResult, rootLA);
1218
95.3k
  }
1219
257k
}
1220
1221
3.29k
void makeExprDFAFunc(cc* c, const std::string& fname, const MonoTypePtr& captureTy, const DFA& dfa, const LexicalAnnotation& rootLA) {
1222
  // F(cap,cs,i,e,s) =
1223
  //   switch (s) {
1224
  //   ...
1225
  //   case S:
1226
  //     cap.N.beginX <- i;
1227
  //     ... etc for every begin marker
1228
  //     cap.N.endX <- i;
1229
  //     ... etc for every end marker
1230
  //
1231
  //     if (i == e) then
1232
  //       ACC(S)
1233
  //     else
1234
  //       switch (c) {
1235
  //       ...
1236
  //       case C:
1237
  //         F(cs, i+1, e, SUCC(S,C))
1238
  //       ...
1239
  //   ...
1240
3.29k
  Switch::Bindings bs;
1241
3.29k
  MonoTypePtr arrT = freshTypeVar();
1242
3.29k
  QualTypePtr qarrElemTy = qualtype(list(std::make_shared<Constraint>("Array", list(arrT, primty("char")))), functy(list(arrT, primty("long")), primty("char")));
1243
3.29k
  QualTypePtr qarrT = qualtype(list(std::make_shared<Constraint>("Array", list(arrT, primty("char")))), arrT);
1244
1245
274k
  for (size_t s = 0; s < dfa.size(); ++s) {
1246
    // all transitions out of this state
1247
271k
    ExprPtr dispatchExpr =
1248
271k
      transitionMapping(
1249
271k
        fname,
1250
271k
        dfa[s],
1251
271k
        fncall(var("elem", rootLA), list(var("cs", rootLA), var("i", rootLA)), rootLA),
1252
271k
        constant(static_cast<int>(-1), rootLA),
1253
271k
        rootLA
1254
271k
      );
1255
1256
    // evaluate the input char and transition
1257
271k
    ExprPtr evalChar =
1258
271k
      fncall(
1259
271k
        var("if", rootLA),
1260
271k
        list(
1261
271k
          fncall(var("leq", rootLA), list(var("i", rootLA), var("e", rootLA)), rootLA),
1262
271k
          constant(static_cast<int>(dfa[s].acc), rootLA),
1263
271k
          dispatchExpr
1264
271k
        ),
1265
271k
        rootLA
1266
271k
      );
1267
1268
    // mark begin/end capture ranges as appropriate
1269
271k
    for (const auto& b : dfa[s].begins) {
1270
8.69k
      for (const auto& v : b.second) {
1271
        // cap.N.beginvar <- i
1272
        // ...
1273
8.69k
        evalChar =
1274
8.69k
          let("_", assign(proj(var("cap", rootLA), str::from(b.first) + "_begin_" + v, rootLA), var("i", rootLA), rootLA),
1275
8.69k
            evalChar,
1276
8.69k
            rootLA
1277
8.69k
          );
1278
8.69k
      }
1279
8.53k
    }
1280
1281
271k
    for (const auto& e : dfa[s].ends) {
1282
70.6k
      for (const auto& v : e.second) {
1283
        // cap.N.endvar <- i
1284
        // ...
1285
70.6k
        evalChar =
1286
70.6k
          let("_", assign(proj(var("cap", rootLA), str::from(e.first) + "_end_" + v, rootLA), var("i", rootLA), rootLA),
1287
70.6k
            evalChar,
1288
70.6k
            rootLA
1289
70.6k
          );
1290
70.6k
      }
1291
59.4k
    }
1292
1293
    // do all of this when in this state
1294
271k
    bs.push_back(Switch::Binding(PrimitivePtr(new Int(static_cast<int>(s), rootLA)), evalChar));
1295
271k
  }
1296
1297
3.29k
  ExprPtr fndef =
1298
3.29k
    fn(str::strings("cap", "cs", "i", "e", "s"),
1299
3.29k
      let("n", fncall(var("ladd", rootLA), list(var("i", rootLA), constant(static_cast<long>(1), rootLA)), rootLA),
1300
3.29k
      let("elem", assume(var("element", rootLA), qarrElemTy, rootLA),
1301
3.29k
        switchE(
1302
3.29k
          var("s", rootLA),
1303
3.29k
          bs,
1304
3.29k
          constant(static_cast<int>(-1), rootLA),
1305
3.29k
          rootLA
1306
3.29k
        ),
1307
3.29k
      rootLA),rootLA),
1308
3.29k
      rootLA
1309
3.29k
    );
1310
1311
3.29k
  c->define(fname, assume(fndef, qualtype(qarrT->constraints(), functy(list(captureTy, arrT, primty("long"), primty("long"), primty("int")), primty("int"))), rootLA));
1312
3.29k
}
1313
1314
using CRange = std::pair<char, char>;
1315
using CTransition = std::pair<CRange, int>;
1316
using CTransitions = array<CTransition>;
1317
1318
DEFINE_STRUCT(
1319
  DFAStateRep,
1320
  (CTransitions*, transitions),
1321
  (int,           acc)
1322
);
1323
1324
286
array<DFAStateRep>* makeDFARep(cc* c, const DFA& dfa) {
1325
286
  auto *result = c->makeArray<DFAStateRep>(dfa.size());
1326
822k
  for (size_t i = 0; i < dfa.size(); ++i) {
1327
822k
    DFAStateRep& s = result->data[i];
1328
822k
    auto ctnm = dfa[i].chars.mapping();
1329
822k
    s.transitions = c->makeArray<CTransition>(ctnm.size());
1330
3.72M
    for (size_t j = 0; j < ctnm.size(); ++j) {
1331
2.90M
      s.transitions->data[j] = ctnm[j];
1332
2.90M
    }
1333
822k
    s.transitions->size = ctnm.size();
1334
822k
    s.acc = dfa[i].acc;
1335
822k
  }
1336
286
  result->size = dfa.size();
1337
286
  return result;
1338
286
}
1339
1340
286
void makeInterpDFAFunc(cc* c, const std::string& fname, const MonoTypePtr& captureTy, const DFA& dfa, const LexicalAnnotation& rootLA) {
1341
286
  MonoTypePtr arrT = freshTypeVar();
1342
286
  QualTypePtr qarrElemTy = qualtype(list(std::make_shared<Constraint>("Array", list(arrT, primty("char")))), functy(list(arrT, primty("long")), primty("char")));
1343
286
  QualTypePtr qarrT = qualtype(list(std::make_shared<Constraint>("Array", list(arrT, primty("char")))), arrT);
1344
1345
286
  std::string regexDFADef = ".regexDFA." + freshName();
1346
286
  c->bind(regexDFADef, makeDFARep(c, dfa));
1347
1348
286
  ExprPtr fndef =
1349
286
    fn(str::strings("cap", "cs", "i", "e", "s"),
1350
286
      fncall(var("runRegexDFA", rootLA), list(var("cs", rootLA), var("i", rootLA), var("e", rootLA), var("s", rootLA), var(regexDFADef, rootLA)), rootLA),
1351
286
      rootLA
1352
286
    );
1353
1354
286
  c->define(fname, assume(fndef, qualtype(qarrT->constraints(), functy(list(captureTy, arrT, primty("long"), primty("long"), primty("int")), primty("int"))), rootLA));
1355
286
}
1356
1357
// how many range tests the expression form of a DFA would spell out
1358
3.32k
static size_t dfaTransitions(const DFA& dfa) {
1359
3.32k
  size_t n = 0;
1360
316k
  for (const auto& s : dfa) {
1361
316k
    n += s.chars.size();
1362
316k
  }
1363
3.32k
  return n;
1364
3.32k
}
1365
1366
// The expression form of a DFA (makeExprDFAFunc) is a switch with a case per
1367
// state and a range test per transition, and the compiler types and compiles
1368
// it like any other expression: measured in an optimized build, that costs
1369
// about 270us per state and 50-100us per transition, so a DFA of a few
1370
// thousand states and a few tens of thousands of transitions is seconds of
1371
// compile time for one regex literal. The interpreter (makeInterpDFAFunc) is
1372
// free of that, since its table is built directly, but it knows nothing of
1373
// the markers that record where a capture begins and ends -- so a regex with
1374
// capture groups used to take the expression form at any size, and was
1375
// bounded only by the DFA state cap. OSS-Fuzz 557846266 is a 709 byte
1376
// captured regex that determinizes to 4,906 states and 38,354 transitions:
1377
// 4-6s of type inference in an optimized build, and past the fuzzer's
1378
// minute under ASan.
1379
//
1380
// Both figures are budgeted, because the DFA state cap alone does not bound
1381
// the expression: a state can carry up to 256 disjoint byte ranges, and a
1382
// 257-state DFA with 63 per state (16,191 transitions) took longer to type
1383
// than one with 2,049 states and three each. Past either budget a regex
1384
// without captures is interpreted, as it was past the state cap before, and
1385
// one with captures is rejected. The bounds are cc settings, so a caller that
1386
// wants a larger captured regex compiled, and will wait for it, can raise
1387
// them.
1388
3.59k
void makeDFAFunc(cc* c, const std::string& fname, const MonoTypePtr& captureTy, const DFA& dfa, const LexicalAnnotation& rootLA) {
1389
3.59k
  const size_t maxStates      = c->regexMaxExprDFASize();
1390
3.59k
  const size_t maxTransitions = c->regexMaxExprDFATransitions();
1391
1392
  // the transitions are counted once, and only where the count decides
1393
  // something (a DFA under the state cap) or is reported (one with captures)
1394
3.59k
  const bool   underStates = dfa.size() < maxStates;
1395
3.59k
  const size_t transitions = (underStates || !isUnit(captureTy)) ? dfaTransitions(dfa) : 0;
1396
1397
3.59k
  if (underStates && transitions < maxTransitions) {
1398
3.29k
    makeExprDFAFunc(c, fname, captureTy, dfa, rootLA);
1399
3.29k
  } else if (isUnit(captureTy)) {
1400
286
    makeInterpDFAFunc(c, fname, captureTy, dfa, rootLA);
1401
286
  } else {
1402
17
    throw std::runtime_error(
1403
17
      "regex is too complex to compile (its capture groups require it to be compiled as an expression, "
1404
17
      "but its DFA has " + str::from(dfa.size()) + " states and " + str::from(transitions) + " transitions, "
1405
17
      "past the " + str::from(maxStates) + " states or " + str::from(maxTransitions) + " transitions "
1406
17
      "an expression may hold)"
1407
17
    );
1408
17
  }
1409
3.59k
}
1410
1411
// merge char-range mappings where possible and conflate duplicate result states
1412
3.59k
void mergeCharRangesAndEqResults(DFA* dfa, const RStates& fstates, RStates* rstates) {
1413
3.59k
  std::map<RegexIdxs, size_t> results;
1414
1415
1.44M
  for (auto& s : *dfa) {
1416
1.44M
    s.chars.compact();
1417
1418
1.44M
    if (s.acc != nullResult) {
1419
30.4k
      auto fsi = fstates.find(s.acc);
1420
30.4k
      if (fsi == fstates.end()) { throw std::runtime_error("Internal error, invalid regex state mapping determined"); }
1421
1422
30.4k
      auto si = results.find(fsi->second);
1423
30.4k
      if (si != results.end()) {
1424
22.0k
        s.acc = si->second;
1425
22.0k
      } else {
1426
8.49k
        size_t ns = results.size();
1427
1428
8.49k
        results[fsi->second] = ns;
1429
8.49k
        (*rstates)[ns]       = fsi->second;
1430
8.49k
        s.acc                = ns;
1431
8.49k
      }
1432
30.4k
    }
1433
1.44M
  }
1434
3.59k
}
1435
1436
/**************************
1437
 * compress a DFA by merging equivalent states
1438
 **************************/
1439
using EqStates = std::map<state, state>;
1440
1441
// a union-find over DFA states where a class is always named by its least
1442
// member (removeEquivStates relies on a representative never being merged away
1443
// itself, and on state 0 staying the start state)
1444
class StatePartition {
1445
public:
1446
3.59k
  explicit StatePartition(size_t states) : reps(states) {
1447
1.45M
    for (size_t s = 0; s < states; ++s) {
1448
1.44M
      this->reps[s] = static_cast<state>(s);
1449
1.44M
    }
1450
3.59k
  }
1451
1452
91.6M
  state find(state s) {
1453
105M
    while (this->reps[s] != s) {
1454
14.0M
      this->reps[s] = this->reps[this->reps[s]];
1455
14.0M
      s = this->reps[s];
1456
14.0M
    }
1457
91.6M
    return s;
1458
91.6M
  }
1459
1460
314k
  bool merge(state s0, state s1) {
1461
314k
    state r0 = find(s0);
1462
314k
    state r1 = find(s1);
1463
314k
    if (r0 == r1) {
1464
0
      return false;
1465
0
    }
1466
314k
    this->reps[std::max(r0, r1)] = std::min(r0, r1);
1467
314k
    return true;
1468
314k
  }
1469
private:
1470
  std::vector<state> reps;
1471
};
1472
1473
// everything about a state that merging can never change
1474
using Observation = std::tuple<result, srcmarkers, srcmarkers>;
1475
1476
197M
void appendKey(std::string* k, uint64_t x) {
1477
1.78G
  for (size_t b = 0; b < sizeof(x); ++b) {
1478
1.58G
    k->push_back(static_cast<char>((x >> (8 * b)) & 0xff));
1479
1.58G
  }
1480
197M
}
1481
1482
// how a state behaves: what it observes, plus the class each of its char ranges
1483
// leads to (which shifts as states are merged, so this must be recomputed)
1484
24.4M
std::string stateKey(size_t obs, const dtransitions::Mapping& ts, StatePartition* p) {
1485
24.4M
  std::string k;
1486
24.4M
  k.reserve(sizeof(uint64_t) * (1 + (3 * ts.size())));
1487
24.4M
  appendKey(&k, obs);
1488
57.8M
  for (const auto& t : ts) {
1489
57.8M
    appendKey(&k, t.first.first);
1490
57.8M
    appendKey(&k, t.first.second);
1491
57.8M
    appendKey(&k, p->find(t.second));
1492
57.8M
  }
1493
24.4M
  return k;
1494
24.4M
}
1495
1496
3.59k
EqStates findEquivStates(const DFA& dfa) {
1497
3.59k
  StatePartition p(dfa.size());
1498
1499
  // acceptance and capture marking are fixed for the life of a state, so reduce
1500
  // them to a single comparable number once rather than per pass
1501
3.59k
  std::map<Observation, size_t> obsIdx;
1502
3.59k
  std::vector<size_t> obs(dfa.size());
1503
3.59k
  std::vector<dtransitions::Mapping> mappings(dfa.size());
1504
1.45M
  for (size_t s = 0; s < dfa.size(); ++s) {
1505
1.44M
    size_t nid = obsIdx.size();
1506
1.44M
    obs[s]      = obsIdx.insert(std::make_pair(Observation(dfa[s].acc, dfa[s].begins, dfa[s].ends), nid)).first->second;
1507
1.44M
    mappings[s] = dfa[s].chars.mapping();
1508
1.44M
  }
1509
1510
  // two states are equivalent when they observe the same thing and their
1511
  // transitions agree on the classes established so far. merging states can
1512
  // bring more states into agreement, so keep refining until a pass finds
1513
  // nothing new -- a pass that stops short only leaves the DFA larger, never
1514
  // wrong, so the states left unmerged in a cycle are safe to keep.
1515
3.59k
  bool merging = true;
1516
25.8k
  while (merging) {
1517
22.2k
    merging = false;
1518
1519
22.2k
    std::unordered_map<std::string, state> keys;
1520
22.2k
    std::vector<std::pair<state, state>> merges;
1521
31.7M
    for (state s = 0; s < dfa.size(); ++s) {
1522
31.7M
      if (p.find(s) != s) continue; // one key per class, not per state
1523
1524
24.4M
      auto k = keys.insert(std::make_pair(stateKey(obs[s], mappings[s], &p), s));
1525
24.4M
      if (!k.second) {
1526
314k
        merges.push_back(std::make_pair(k.first->second, s));
1527
314k
      }
1528
24.4M
    }
1529
1530
    // deferred so that every key in a pass is read against the same classes
1531
314k
    for (const auto& m : merges) {
1532
314k
      merging = p.merge(m.first, m.second) || merging;
1533
314k
    }
1534
22.2k
  }
1535
1536
3.59k
  EqStates result;
1537
1.45M
  for (state s = 0; s < dfa.size(); ++s) {
1538
1.44M
    state r = p.find(s);
1539
1.44M
    if (r != s) {
1540
314k
      result[s] = r;
1541
314k
    }
1542
1.44M
  }
1543
3.59k
  return result;
1544
3.59k
}
1545
1546
3.59k
DFA removeEquivStates(const DFA& dfa, const EqStates& eqs) {
1547
  // after removing states, some states will need to be renumbered
1548
  // we can infer this mapping from the eqstate mapping
1549
3.59k
  std::map<state, state> shifted;
1550
3.59k
  size_t accShift = 0;
1551
318k
  for (auto eq = eqs.begin(); eq != eqs.end();) {
1552
314k
    state s0 = eq->first+1;
1553
314k
    ++eq;
1554
314k
    state s1 = (eq == eqs.end()) ? dfa.size() : eq->first;
1555
314k
    ++accShift;
1556
1557
1.02M
    for (state s = s0; s < s1; ++s) {
1558
706k
      shifted[s] = s - accShift;
1559
706k
    }
1560
314k
  }
1561
1562
  // now make a new DFA with eq states removed
1563
  // all references to states need to be
1564
  // patched up to account for merging/shifting
1565
3.59k
  DFA result;
1566
3.59k
  result.reserve(dfa.size()-eqs.size());
1567
1568
1.45M
  for (size_t s = 0; s < dfa.size(); ++s) {
1569
1.44M
    const auto& sd = dfa[s];
1570
1571
    // don't include eliminated states
1572
1.44M
    if (eqs.find(s) != eqs.end()) continue;
1573
1574
    // bring this state over to the result
1575
1.13M
    result.push_back(dfa[s]);
1576
1.13M
    auto& rsd = result.back();
1577
1578
    // patch its transitions to account for merged and shifted states
1579
3.94M
    for (const auto& m : sd.chars.mapping()) {
1580
3.94M
      auto  eq    = eqs.find(m.second);
1581
3.94M
      state tgt   = (eq == eqs.end()) ? m.second : eq->second;
1582
3.94M
      auto  shift = shifted.find(tgt);
1583
3.94M
      state stgt  = (shift == shifted.end()) ? tgt : shift->second;
1584
1585
3.94M
      if (stgt != m.second) {
1586
2.03M
        rsd.chars.insert(m.first, stgt);
1587
2.03M
      }
1588
3.94M
    }
1589
1.13M
  }
1590
3.59k
  return result;
1591
3.59k
}
1592
1593
/**************************
1594
 * make an expression to allocate capture group data for a set of regular expressions
1595
 **************************/
1596
31.5k
MonoTypePtr regexCaptureBufferType(const Regexes& regexes) {
1597
31.5k
  Record::Members ms;
1598
68.5k
  for (size_t r = 0; r < regexes.size(); ++r) {
1599
36.9k
    for (const auto& b : bindingNames(regexes[r])) {
1600
9.19k
      ms.push_back(Record::Member(str::from(r) + "_begin_" + b, primty("long")));
1601
9.19k
      ms.push_back(Record::Member(str::from(r) + "_end_"   + b, primty("long")));
1602
9.19k
    }
1603
36.9k
  }
1604
31.5k
  return ms.empty() ? primty("unit") : MonoTypePtr(Record::make(ms));
1605
31.5k
}
1606
1607
27.9k
ExprPtr makeRegexCaptureBuffer(const Regexes& regexes, const LexicalAnnotation& rootLA) {
1608
27.9k
  return
1609
27.9k
    assume(
1610
27.9k
      fncall(
1611
27.9k
        var("newPrimZ", rootLA),
1612
27.9k
        list(mktunit(rootLA)),
1613
27.9k
        rootLA
1614
27.9k
      ),
1615
27.9k
      regexCaptureBufferType(regexes),
1616
27.9k
      rootLA
1617
27.9k
    );
1618
27.9k
}
1619
1620
/**************************
1621
 * make a function to determine which among the input regexes here a later string matches
1622
 **************************/
1623
// an encoding of a regex that two regexes share only if they are the same
1624
// regex: every node is tagged, and every name is length-prefixed, so the
1625
// encodings of different trees cannot run together. (show() is not that: it
1626
// prints characters raw, so 'a|b' and the three literals a, |, b look alike.)
1627
struct regexKeyF : public switchRegex<UnitV> {
1628
  std::string* out;
1629
213k
  explicit regexKeyF(std::string* out) : out(out) { }
1630
1631
1.27M
  UnitV with(const REps*) const override { *this->out += "e;"; return unitv; }
1632
13.4M
  UnitV with(const RCharRange* x) const override {
1633
13.4M
    *this->out += "r" + str::from(static_cast<int>(x->b)) + "," + str::from(static_cast<int>(x->e)) + ";";
1634
13.4M
    return unitv;
1635
13.4M
  }
1636
553k
  UnitV with(const RStar* x) const override {
1637
553k
    *this->out += "*(";
1638
553k
    switchOf(x->v, *this);
1639
553k
    *this->out += ")";
1640
553k
    return unitv;
1641
553k
  }
1642
1.06M
  UnitV with(const REither* x) const override {
1643
1.06M
    *this->out += "|(";
1644
1.06M
    switchOf(x->lhs, *this);
1645
1.06M
    *this->out += ",";
1646
1.06M
    switchOf(x->rhs, *this);
1647
1.06M
    *this->out += ")";
1648
1.06M
    return unitv;
1649
1.06M
  }
1650
13.4M
  UnitV with(const RSeq* x) const override {
1651
13.4M
    *this->out += ".(";
1652
13.4M
    switchOf(x->lhs, *this);
1653
13.4M
    *this->out += ",";
1654
13.4M
    switchOf(x->rhs, *this);
1655
13.4M
    *this->out += ")";
1656
13.4M
    return unitv;
1657
13.4M
  }
1658
41.4k
  UnitV with(const RBind* x) const override {
1659
41.4k
    *this->out += "b" + str::from(x->var.size()) + ":" + x->var + "(";
1660
41.4k
    switchOf(x->def, *this);
1661
41.4k
    *this->out += ")";
1662
41.4k
    return unitv;
1663
41.4k
  }
1664
};
1665
1666
213k
std::string regexKey(const RegexPtr& r) {
1667
213k
  std::string k;
1668
213k
  switchOf(r, regexKeyF(&k));
1669
213k
  return k;
1670
213k
}
1671
1672
27.9k
std::string regexFnKey(const Regexes& regexes) {
1673
27.9k
  std::string k;
1674
30.6k
  for (const auto& r : regexes) {
1675
30.6k
    k += regexKey(r);
1676
30.6k
    k += "\n";
1677
30.6k
  }
1678
27.9k
  return k;
1679
27.9k
}
1680
1681
27.9k
CRegexes makeRegexFn(cc* c, const Regexes& regexes, const LexicalAnnotation& rootLA) {
1682
27.9k
  CRegexes result;
1683
1684
  // reject a regex whose tree is too large before anything walks it: every pass
1685
  // below (the capture buffer's bindingNames, the cache key, the NFA build) is
1686
  // linear in the unshared tree size, which a quantified group can blow up far
1687
  // past the term count. Checked here, once, ahead of the first of those walks.
1688
30.6k
  for (const auto& r : regexes) {
1689
30.6k
    rejectOversizedRegex(r);
1690
30.6k
  }
1691
1692
  // save capturing-group settings
1693
27.9k
  result.captureBuffer = makeRegexCaptureBuffer(regexes, rootLA);
1694
58.6k
  for (size_t i = 0; i < regexes.size(); ++i) {
1695
30.6k
    result.captureVarsAt[i] = bindingNames(regexes[i]);
1696
30.6k
  }
1697
1698
  // if these regexes have been compiled into this compiler before, the
1699
  // function and the result mapping made then serve now: the function is
1700
  // pure in its input and the mapping is a property of the regexes, so
1701
  // neither depends on where the match that uses them is. (The capture
1702
  // buffer expression above is remade with this match's annotation, and the
1703
  // binding names recomputed, because they are cheap and carry a location.)
1704
27.9k
  const std::string key = regexFnKey(regexes);
1705
27.9k
  auto cached = c->regexFnCache.find(key);
1706
27.9k
  if (cached != c->regexFnCache.end()) {
1707
24.2k
    result.fname   = cached->second.fname;
1708
24.2k
    result.rstates = cached->second.rstates;
1709
24.2k
    return result;
1710
24.2k
  }
1711
1712
  // our NFA will non-deterministically jump to every possible start state
1713
3.69k
  NFA nfa;
1714
3.69k
  nfa.resize(1);
1715
10.0k
  for (size_t i = 0; i < regexes.size(); ++i) {
1716
6.37k
    auto s = accumRegex(regexes[i], i, &nfa);
1717
6.37k
    nfa[0].eps.insert(s);
1718
6.37k
  }
1719
1720
  // now map this NFA to a DFA
1721
3.69k
  DFA dfa;
1722
3.69k
  RStates fstates;
1723
3.69k
  disambiguate(c, nfa, &dfa, &fstates);
1724
1725
  // make all char ranges compact and minimize the results to avoid redundant work in the caller
1726
3.69k
  mergeCharRangesAndEqResults(&dfa, fstates, &result.rstates);
1727
1728
  // in case of blowup, minimize the size of this DFA
1729
3.69k
  dfa = removeEquivStates(dfa, findEquivStates(dfa));
1730
1731
  // translate this DFA to a function
1732
3.69k
  std::string fname = ".regex." + freshName();
1733
3.69k
  makeDFAFunc(c, fname, regexCaptureBufferType(regexes), dfa, rootLA);
1734
1735
  // and that's the function that the outer match logic should use
1736
3.69k
  result.fname = fname;
1737
1738
  // remember the function and the result mapping for the next match on these
1739
  // regexes; not the capture buffer expression, which carries this match's
1740
  // source location and would pin it for the life of the compiler
1741
3.69k
  CRegexes& kept = c->regexFnCache[key];
1742
3.69k
  kept.fname   = result.fname;
1743
3.69k
  kept.rstates = result.rstates;
1744
3.69k
  return result;
1745
27.9k
}
1746
1747
/**************************
1748
 * produce code to load capture vars out of a buffer for a given source regex
1749
 **************************/
1750
53.1k
CVarDefs unpackCaptureVars(const std::string& strVar, const std::string& bufferVar, const CRegexes& crgxs, size_t regex, const LexicalAnnotation& rootLA) {
1751
53.1k
  CVarDefs result;
1752
53.1k
  auto cvars = crgxs.captureVarsAt.find(regex);
1753
53.1k
  if (cvars == crgxs.captureVarsAt.end()) return result;
1754
1755
53.1k
  for (const auto& vn : cvars->second) {
1756
27.4k
    result.push_back(CVarDef(vn,
1757
27.4k
      fncall(
1758
27.4k
        var("slice", rootLA),
1759
27.4k
        list(
1760
27.4k
          var(strVar, rootLA),
1761
27.4k
          proj(var(bufferVar, rootLA), str::from(regex) + "_begin_" + vn, rootLA),
1762
27.4k
          proj(var(bufferVar, rootLA), str::from(regex) + "_end_"   + vn, rootLA)
1763
27.4k
        ),
1764
27.4k
        rootLA
1765
27.4k
      )
1766
27.4k
    ));
1767
27.4k
  }
1768
53.1k
  return result;
1769
53.1k
}
1770
1771
}