Coverage Report

Created: 2026-09-14 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/regex/regex-automata/src/dfa/determinize.rs
Line
Count
Source
1
use alloc::{collections::BTreeMap, vec::Vec};
2
3
use crate::{
4
    dfa::{
5
        dense::{self, BuildError},
6
        DEAD,
7
    },
8
    nfa::thompson,
9
    util::{
10
        self,
11
        alphabet::{self, ByteSet},
12
        determinize::{State, StateBuilderEmpty, StateBuilderNFA},
13
        primitives::{PatternID, StateID},
14
        search::{Anchored, MatchKind},
15
        sparse_set::SparseSets,
16
        start::Start,
17
    },
18
};
19
20
/// A builder for configuring and running a DFA determinizer.
21
#[derive(Clone, Debug)]
22
pub(crate) struct Config {
23
    match_kind: MatchKind,
24
    quit: ByteSet,
25
    dfa_size_limit: Option<usize>,
26
    determinize_size_limit: Option<usize>,
27
}
28
29
impl Config {
30
    /// Create a new default config for a determinizer. The determinizer may be
31
    /// configured before calling `run`.
32
82.1k
    pub fn new() -> Config {
33
82.1k
        Config {
34
82.1k
            match_kind: MatchKind::LeftmostFirst,
35
82.1k
            quit: ByteSet::empty(),
36
82.1k
            dfa_size_limit: None,
37
82.1k
            determinize_size_limit: None,
38
82.1k
        }
39
82.1k
    }
40
41
    /// Run determinization on the given NFA and write the resulting DFA into
42
    /// the one given. The DFA given should be initialized but otherwise empty.
43
    /// "Initialized" means that it is setup to handle the NFA's byte classes,
44
    /// number of patterns and whether to build start states for each pattern.
45
82.1k
    pub fn run(
46
82.1k
        &self,
47
82.1k
        nfa: &thompson::NFA,
48
82.1k
        dfa: &mut dense::OwnedDFA,
49
82.1k
    ) -> Result<(), BuildError> {
50
82.1k
        let dead = State::dead();
51
82.1k
        let quit = State::dead();
52
82.1k
        let mut cache = StateMap::default();
53
        // We only insert the dead state here since its representation is
54
        // identical to the quit state. And we never want anything pointing
55
        // to the quit state other than specific transitions derived from the
56
        // determinizer's configured "quit" bytes.
57
        //
58
        // We do put the quit state into 'builder_states' below. This ensures
59
        // that a proper DFA state ID is allocated for it, and that no other
60
        // DFA state uses the "location after the DEAD state." That is, it
61
        // is assumed that the quit state is always the state immediately
62
        // following the DEAD state.
63
82.1k
        cache.insert(dead.clone(), DEAD);
64
65
82.1k
        let runner = Runner {
66
82.1k
            config: self.clone(),
67
82.1k
            nfa,
68
82.1k
            dfa,
69
82.1k
            builder_states: alloc::vec![dead, quit],
70
82.1k
            cache,
71
82.1k
            memory_usage_state: 0,
72
82.1k
            sparses: SparseSets::new(nfa.states().len()),
73
82.1k
            stack: alloc::vec![],
74
82.1k
            scratch_state_builder: StateBuilderEmpty::new(),
75
82.1k
        };
76
82.1k
        runner.run()
77
82.1k
    }
78
79
    /// The match semantics to use for determinization.
80
    ///
81
    /// MatchKind::All corresponds to the standard textbook construction.
82
    /// All possible match states are represented in the DFA.
83
    /// MatchKind::LeftmostFirst permits greediness and otherwise tries to
84
    /// simulate the match semantics of backtracking regex engines. Namely,
85
    /// only a subset of match states are built, and dead states are used to
86
    /// stop searches with an unanchored prefix.
87
    ///
88
    /// The default is MatchKind::LeftmostFirst.
89
82.1k
    pub fn match_kind(&mut self, kind: MatchKind) -> &mut Config {
90
82.1k
        self.match_kind = kind;
91
82.1k
        self
92
82.1k
    }
93
94
    /// The set of bytes to use that will cause the DFA to enter a quit state,
95
    /// stop searching and return an error. By default, this is empty.
96
82.1k
    pub fn quit(&mut self, set: ByteSet) -> &mut Config {
97
82.1k
        self.quit = set;
98
82.1k
        self
99
82.1k
    }
100
101
    /// The limit, in bytes of the heap, that the DFA is permitted to use. This
102
    /// does not include the auxiliary heap storage used by determinization.
103
82.1k
    pub fn dfa_size_limit(&mut self, bytes: Option<usize>) -> &mut Config {
104
82.1k
        self.dfa_size_limit = bytes;
105
82.1k
        self
106
82.1k
    }
107
108
    /// The limit, in bytes of the heap, that determinization itself is allowed
109
    /// to use. This does not include the size of the DFA being built.
110
82.1k
    pub fn determinize_size_limit(
111
82.1k
        &mut self,
112
82.1k
        bytes: Option<usize>,
113
82.1k
    ) -> &mut Config {
114
82.1k
        self.determinize_size_limit = bytes;
115
82.1k
        self
116
82.1k
    }
117
}
118
119
/// The actual implementation of determinization that converts an NFA to a DFA
120
/// through powerset construction.
121
///
122
/// This determinizer roughly follows the typical powerset construction, where
123
/// each DFA state is comprised of one or more NFA states. In the worst case,
124
/// there is one DFA state for every possible combination of NFA states. In
125
/// practice, this only happens in certain conditions, typically when there are
126
/// bounded repetitions.
127
///
128
/// The main differences between this implementation and typical deteminization
129
/// are that this implementation delays matches by one state and hackily makes
130
/// look-around work. Comments below attempt to explain this.
131
///
132
/// The lifetime variable `'a` refers to the lifetime of the NFA or DFA,
133
/// whichever is shorter.
134
#[derive(Debug)]
135
struct Runner<'a> {
136
    /// The configuration used to initialize determinization.
137
    config: Config,
138
    /// The NFA we're converting into a DFA.
139
    nfa: &'a thompson::NFA,
140
    /// The DFA we're building.
141
    dfa: &'a mut dense::OwnedDFA,
142
    /// Each DFA state being built is defined as an *ordered* set of NFA
143
    /// states, along with some meta facts about the ordered set of NFA states.
144
    ///
145
    /// This is never empty. The first state is always a dummy state such that
146
    /// a state id == 0 corresponds to a dead state. The second state is always
147
    /// the quit state.
148
    ///
149
    /// Why do we have states in both a `Vec` and in a cache map below?
150
    /// Well, they serve two different roles based on access patterns.
151
    /// `builder_states` is the canonical home of each state, and provides
152
    /// constant random access by a DFA state's ID. The cache map below, on
153
    /// the other hand, provides a quick way of searching for identical DFA
154
    /// states by using the DFA state as a key in the map. Of course, we use
155
    /// reference counting to avoid actually duplicating the state's data
156
    /// itself. (Although this has never been benchmarked.) Note that the cache
157
    /// map does not give us full minimization; it just lets us avoid some very
158
    /// obvious redundant states.
159
    ///
160
    /// Note that the index into this Vec isn't quite the DFA's state ID.
161
    /// Rather, it's just an index. To get the state ID, you have to multiply
162
    /// it by the DFA's stride. That's done by self.dfa.from_index. And the
163
    /// inverse is self.dfa.to_index.
164
    ///
165
    /// Moreover, DFA states don't usually retain the IDs assigned to them
166
    /// by their position in this Vec. After determinization completes,
167
    /// states are shuffled around to support other optimizations. See the
168
    /// sibling 'special' module for more details on that. (The reason for
169
    /// mentioning this is that if you print out the DFA for debugging during
170
    /// determinization, and then print out the final DFA after it is fully
171
    /// built, then the state IDs likely won't match up.)
172
    builder_states: Vec<State>,
173
    /// A cache of DFA states that already exist and can be easily looked up
174
    /// via ordered sets of NFA states.
175
    ///
176
    /// See `builder_states` docs for why we store states in two different
177
    /// ways.
178
    cache: StateMap,
179
    /// The memory usage, in bytes, used by builder_states and cache. We track
180
    /// this as new states are added since states use a variable amount of
181
    /// heap. Tracking this as we add states makes it possible to compute the
182
    /// total amount of memory used by the determinizer in constant time.
183
    memory_usage_state: usize,
184
    /// A pair of sparse sets for tracking ordered sets of NFA state IDs.
185
    /// These are reused throughout determinization. A bounded sparse set
186
    /// gives us constant time insertion, membership testing and clearing.
187
    sparses: SparseSets,
188
    /// Scratch space for a stack of NFA states to visit, for depth first
189
    /// visiting without recursion.
190
    stack: Vec<StateID>,
191
    /// Scratch space for storing an ordered sequence of NFA states, for
192
    /// amortizing allocation. This is principally useful for when we avoid
193
    /// adding a new DFA state since it already exists. In order to detect this
194
    /// case though, we still need an ordered set of NFA state IDs. So we use
195
    /// this space to stage that ordered set before we know whether we need to
196
    /// create a new DFA state or not.
197
    scratch_state_builder: StateBuilderEmpty,
198
}
199
200
/// A map from states to state identifiers. When using std, we use a standard
201
/// hashmap, since it's a bit faster for this use case. (Other maps, like
202
/// one's based on FNV, have not yet been benchmarked.)
203
///
204
/// The main purpose of this map is to reuse states where possible. This won't
205
/// fully minimize the DFA, but it works well in a lot of cases.
206
#[cfg(feature = "std")]
207
type StateMap = std::collections::HashMap<State, StateID>;
208
#[cfg(not(feature = "std"))]
209
type StateMap = BTreeMap<State, StateID>;
210
211
impl<'a> Runner<'a> {
212
    /// Build the DFA. If there was a problem constructing the DFA (e.g., if
213
    /// the chosen state identifier representation is too small), then an error
214
    /// is returned.
215
82.1k
    fn run(mut self) -> Result<(), BuildError> {
216
82.1k
        if self.nfa.look_set_any().contains_word_unicode()
217
28.4k
            && !self.config.quit.contains_range(0x80, 0xFF)
218
        {
219
0
            return Err(BuildError::unsupported_dfa_word_boundary_unicode());
220
82.1k
        }
221
222
        // A sequence of "representative" bytes drawn from each equivalence
223
        // class. These representative bytes are fed to the NFA to compute
224
        // state transitions. This allows us to avoid re-computing state
225
        // transitions for bytes that are guaranteed to produce identical
226
        // results. Since computing the representatives needs to do a little
227
        // work, we do it once here because we'll be iterating over them a lot.
228
82.1k
        let representatives: Vec<alphabet::Unit> =
229
82.1k
            self.dfa.byte_classes().representatives(..).collect();
230
        // The set of all DFA state IDs that still need to have their
231
        // transitions set. We start by seeding this with all starting states.
232
82.1k
        let mut uncompiled = alloc::vec![];
233
82.1k
        self.add_all_starts(&mut uncompiled)?;
234
915k
        while let Some(dfa_id) = uncompiled.pop() {
235
17.4M
            for &unit in &representatives {
236
16.6M
                if unit.as_u8().map_or(false, |b| self.config.quit.contains(b))
237
                {
238
1.32M
                    continue;
239
15.2M
                }
240
                // In many cases, the state we transition to has already been
241
                // computed. 'cached_state' will do the minimal amount of work
242
                // to check this, and if it exists, immediately return an
243
                // already existing state ID.
244
15.2M
                let (next_dfa_id, is_new) = self.cached_state(dfa_id, unit)?;
245
15.2M
                self.dfa.set_transition(dfa_id, unit, next_dfa_id);
246
                // If the state ID we got back is newly created, then we need
247
                // to compile it, so add it to our uncompiled frontier.
248
15.2M
                if is_new {
249
680k
                    uncompiled.push(next_dfa_id);
250
14.6M
                }
251
            }
252
        }
253
80.2k
        debug!(
254
0
            "determinization complete, memory usage: {}, \
255
0
             dense DFA size: {}, \
256
0
             is reverse? {}",
257
0
            self.memory_usage(),
258
0
            self.dfa.memory_usage(),
259
0
            self.nfa.is_reverse(),
260
        );
261
262
        // A map from DFA state ID to one or more NFA match IDs. Each NFA match
263
        // ID corresponds to a distinct regex pattern that matches in the state
264
        // corresponding to the key.
265
80.2k
        let mut matches: BTreeMap<StateID, Vec<PatternID>> = BTreeMap::new();
266
80.2k
        self.cache.clear();
267
        #[cfg(feature = "logging")]
268
80.2k
        let mut total_pat_len = 0;
269
931k
        for (i, state) in self.builder_states.into_iter().enumerate() {
270
931k
            if let Some(pat_ids) = state.match_pattern_ids() {
271
109k
                let id = self.dfa.to_state_id(i);
272
109k
                log! {
273
109k
                    total_pat_len += pat_ids.len();
274
109k
                }
275
109k
                matches.insert(id, pat_ids);
276
821k
            }
277
        }
278
80.2k
        log! {
279
            use core::mem::size_of;
280
80.2k
            let per_elem = size_of::<StateID>() + size_of::<Vec<PatternID>>();
281
80.2k
            let pats = total_pat_len * size_of::<PatternID>();
282
80.2k
            let mem = (matches.len() * per_elem) + pats;
283
80.2k
            log::debug!("matches map built, memory usage: {mem}");
284
        }
285
        // At this point, we shuffle the "special" states in the final DFA.
286
        // This permits a DFA's match loop to detect a match condition (among
287
        // other things) by merely inspecting the current state's identifier,
288
        // and avoids the need for any additional auxiliary storage.
289
80.2k
        self.dfa.shuffle(matches)?;
290
80.2k
        Ok(())
291
82.1k
    }
292
293
    /// Return the identifier for the next DFA state given an existing DFA
294
    /// state and an input byte. If the next DFA state already exists, then
295
    /// return its identifier from the cache. Otherwise, build the state, cache
296
    /// it and return its identifier.
297
    ///
298
    /// This routine returns a boolean indicating whether a new state was
299
    /// built. If a new state is built, then the caller needs to add it to its
300
    /// frontier of uncompiled DFA states to compute transitions for.
301
15.2M
    fn cached_state(
302
15.2M
        &mut self,
303
15.2M
        dfa_id: StateID,
304
15.2M
        unit: alphabet::Unit,
305
15.2M
    ) -> Result<(StateID, bool), BuildError> {
306
        // Compute the set of all reachable NFA states, including epsilons.
307
15.2M
        let empty_builder = self.get_state_builder();
308
15.2M
        let builder = util::determinize::next(
309
15.2M
            self.nfa,
310
15.2M
            self.config.match_kind,
311
15.2M
            &mut self.sparses,
312
15.2M
            &mut self.stack,
313
15.2M
            &self.builder_states[self.dfa.to_index(dfa_id)],
314
15.2M
            unit,
315
15.2M
            empty_builder,
316
        );
317
15.2M
        self.maybe_add_state(builder)
318
15.2M
    }
319
320
    /// Compute the set of DFA start states and add their identifiers in
321
    /// 'dfa_state_ids' (no duplicates are added).
322
82.1k
    fn add_all_starts(
323
82.1k
        &mut self,
324
82.1k
        dfa_state_ids: &mut Vec<StateID>,
325
82.1k
    ) -> Result<(), BuildError> {
326
        // These should be the first states added.
327
82.1k
        assert!(dfa_state_ids.is_empty());
328
        // We only want to add (un)anchored starting states that is consistent
329
        // with our DFA's configuration. Unconditionally adding both (although
330
        // it is the default) can make DFAs quite a bit bigger.
331
82.1k
        if self.dfa.start_kind().has_unanchored() {
332
40.4k
            self.add_start_group(Anchored::No, dfa_state_ids)?;
333
41.7k
        }
334
82.1k
        if self.dfa.start_kind().has_anchored() {
335
82.1k
            self.add_start_group(Anchored::Yes, dfa_state_ids)?;
336
0
        }
337
        // I previously has an 'assert' here checking that either
338
        // 'dfa_state_ids' was non-empty, or the NFA had zero patterns. But it
339
        // turns out this isn't always true. For example, the NFA might have
340
        // one or more patterns but where all such patterns are just 'fail'
341
        // states. These will ultimately just compile down to DFA dead states,
342
        // and since the dead state was added earlier, no new DFA states are
343
        // added. And thus, it is valid and okay for 'dfa_state_ids' to be
344
        // empty even if there are a non-zero number of patterns in the NFA.
345
346
        // We only need to compute anchored start states for each pattern if it
347
        // was requested to do so.
348
82.1k
        if self.dfa.starts_for_each_pattern() {
349
79.1k
            for pid in self.nfa.patterns() {
350
79.1k
                self.add_start_group(Anchored::Pattern(pid), dfa_state_ids)?;
351
            }
352
3.00k
        }
353
82.1k
        Ok(())
354
82.1k
    }
355
356
    /// Add a group of start states for the given match pattern ID. Any new
357
    /// DFA states added are pushed on to 'dfa_state_ids'. (No duplicates are
358
    /// pushed.)
359
    ///
360
    /// When pattern_id is None, then this will compile a group of unanchored
361
    /// start states (if the DFA is unanchored). When the pattern_id is
362
    /// present, then this will compile a group of anchored start states that
363
    /// only match the given pattern.
364
    ///
365
    /// This panics if `anchored` corresponds to an invalid pattern ID.
366
201k
    fn add_start_group(
367
201k
        &mut self,
368
201k
        anchored: Anchored,
369
201k
        dfa_state_ids: &mut Vec<StateID>,
370
201k
    ) -> Result<(), BuildError> {
371
201k
        let nfa_start = match anchored {
372
40.4k
            Anchored::No => self.nfa.start_unanchored(),
373
82.1k
            Anchored::Yes => self.nfa.start_anchored(),
374
79.1k
            Anchored::Pattern(pid) => {
375
79.1k
                self.nfa.start_pattern(pid).expect("valid pattern ID")
376
            }
377
        };
378
379
        // When compiling start states, we're careful not to build additional
380
        // states that aren't necessary. For example, if the NFA has no word
381
        // boundary assertion, then there's no reason to have distinct start
382
        // states for 'NonWordByte' and 'WordByte' starting configurations.
383
        // Instead, the 'WordByte' starting configuration can just point
384
        // directly to the start state for the 'NonWordByte' config.
385
        //
386
        // Note though that we only need to care about assertions in the prefix
387
        // of an NFA since this only concerns the starting states. (Actually,
388
        // the most precisely thing we could do it is look at the prefix
389
        // assertions of each pattern when 'anchored == Anchored::Pattern',
390
        // and then only compile extra states if the prefix is non-empty.) But
391
        // we settle for simplicity here instead of absolute minimalism. It is
392
        // somewhat rare, after all, for multiple patterns in the same regex to
393
        // have different prefix look-arounds.
394
395
201k
        let (id, is_new) =
396
201k
            self.add_one_start(nfa_start, Start::NonWordByte)?;
397
201k
        self.dfa.set_start_state(anchored, Start::NonWordByte, id);
398
201k
        if is_new {
399
121k
            dfa_state_ids.push(id);
400
121k
        }
401
402
201k
        if !self.nfa.look_set_prefix_any().contains_word() {
403
157k
            self.dfa.set_start_state(anchored, Start::WordByte, id);
404
157k
        } else {
405
44.6k
            let (id, is_new) =
406
44.6k
                self.add_one_start(nfa_start, Start::WordByte)?;
407
44.6k
            self.dfa.set_start_state(anchored, Start::WordByte, id);
408
44.6k
            if is_new {
409
25.6k
                dfa_state_ids.push(id);
410
25.6k
            }
411
        }
412
201k
        if !self.nfa.look_set_prefix_any().contains_anchor() {
413
175k
            self.dfa.set_start_state(anchored, Start::Text, id);
414
175k
            self.dfa.set_start_state(anchored, Start::LineLF, id);
415
175k
            self.dfa.set_start_state(anchored, Start::LineCR, id);
416
175k
            self.dfa.set_start_state(
417
175k
                anchored,
418
175k
                Start::CustomLineTerminator,
419
175k
                id,
420
175k
            );
421
175k
        } else {
422
26.4k
            let (id, is_new) = self.add_one_start(nfa_start, Start::Text)?;
423
26.4k
            self.dfa.set_start_state(anchored, Start::Text, id);
424
26.4k
            if is_new {
425
14.5k
                dfa_state_ids.push(id);
426
14.5k
            }
427
428
26.4k
            let (id, is_new) = self.add_one_start(nfa_start, Start::LineLF)?;
429
26.4k
            self.dfa.set_start_state(anchored, Start::LineLF, id);
430
26.4k
            if is_new {
431
2.27k
                dfa_state_ids.push(id);
432
24.1k
            }
433
434
26.4k
            let (id, is_new) = self.add_one_start(nfa_start, Start::LineCR)?;
435
26.4k
            self.dfa.set_start_state(anchored, Start::LineCR, id);
436
26.4k
            if is_new {
437
2.57k
                dfa_state_ids.push(id);
438
23.8k
            }
439
440
26.4k
            let (id, is_new) =
441
26.4k
                self.add_one_start(nfa_start, Start::CustomLineTerminator)?;
442
26.4k
            self.dfa.set_start_state(
443
26.4k
                anchored,
444
26.4k
                Start::CustomLineTerminator,
445
26.4k
                id,
446
            );
447
26.4k
            if is_new {
448
4.34k
                dfa_state_ids.push(id);
449
22.0k
            }
450
        }
451
452
201k
        Ok(())
453
201k
    }
454
455
    /// Add a new DFA start state corresponding to the given starting NFA
456
    /// state, and the starting search configuration. (The starting search
457
    /// configuration essentially tells us which look-behind assertions are
458
    /// true for this particular state.)
459
    ///
460
    /// The boolean returned indicates whether the state ID returned is a newly
461
    /// created state, or a previously cached state.
462
351k
    fn add_one_start(
463
351k
        &mut self,
464
351k
        nfa_start: StateID,
465
351k
        start: Start,
466
351k
    ) -> Result<(StateID, bool), BuildError> {
467
        // Compute the look-behind assertions that are true in this starting
468
        // configuration, and the determine the epsilon closure. While
469
        // computing the epsilon closure, we only follow conditional epsilon
470
        // transitions that satisfy the look-behind assertions in 'look_have'.
471
351k
        let mut builder_matches = self.get_state_builder().into_matches();
472
351k
        util::determinize::set_lookbehind_from_start(
473
351k
            self.nfa,
474
351k
            &start,
475
351k
            &mut builder_matches,
476
        );
477
351k
        self.sparses.set1.clear();
478
351k
        util::determinize::epsilon_closure(
479
351k
            self.nfa,
480
351k
            nfa_start,
481
351k
            builder_matches.look_have(),
482
351k
            &mut self.stack,
483
351k
            &mut self.sparses.set1,
484
        );
485
351k
        let mut builder = builder_matches.into_nfa();
486
351k
        util::determinize::add_nfa_states(
487
351k
            &self.nfa,
488
351k
            &self.sparses.set1,
489
351k
            &mut builder,
490
        );
491
351k
        self.maybe_add_state(builder)
492
351k
    }
493
494
    /// Adds the given state to the DFA being built depending on whether it
495
    /// already exists in this determinizer's cache.
496
    ///
497
    /// If it does exist, then the memory used by 'state' is put back into the
498
    /// determinizer and the previously created state's ID is returned. (Along
499
    /// with 'false', indicating that no new state was added.)
500
    ///
501
    /// If it does not exist, then the state is added to the DFA being built
502
    /// and a fresh ID is allocated (if ID allocation fails, then an error is
503
    /// returned) and returned. (Along with 'true', indicating that a new state
504
    /// was added.)
505
15.6M
    fn maybe_add_state(
506
15.6M
        &mut self,
507
15.6M
        builder: StateBuilderNFA,
508
15.6M
    ) -> Result<(StateID, bool), BuildError> {
509
15.6M
        if let Some(&cached_id) = self.cache.get(builder.as_bytes()) {
510
            // Since we have a cached state, put the constructed state's
511
            // memory back into our scratch space, so that it can be reused.
512
14.7M
            self.put_state_builder(builder);
513
14.7M
            return Ok((cached_id, false));
514
852k
        }
515
852k
        self.add_state(builder).map(|sid| (sid, true))
516
15.6M
    }
517
518
    /// Add the given state to the DFA and make it available in the cache.
519
    ///
520
    /// The state initially has no transitions. That is, it transitions to the
521
    /// dead state for all possible inputs, and transitions to the quit state
522
    /// for all quit bytes.
523
    ///
524
    /// If adding the state would exceed the maximum value for StateID, then an
525
    /// error is returned.
526
852k
    fn add_state(
527
852k
        &mut self,
528
852k
        builder: StateBuilderNFA,
529
852k
    ) -> Result<StateID, BuildError> {
530
852k
        let id = self.dfa.add_empty_state()?;
531
852k
        if !self.config.quit.is_empty() {
532
23.3M
            for b in self.config.quit.iter() {
533
23.3M
                self.dfa.set_transition(
534
23.3M
                    id,
535
23.3M
                    alphabet::Unit::u8(b),
536
23.3M
                    self.dfa.quit_id(),
537
23.3M
                );
538
23.3M
            }
539
670k
        }
540
852k
        let state = builder.to_state();
541
        // States use reference counting internally, so we only need to count
542
        // their memory usage once.
543
852k
        self.memory_usage_state += state.memory_usage();
544
852k
        self.builder_states.push(state.clone());
545
852k
        self.cache.insert(state, id);
546
852k
        self.put_state_builder(builder);
547
852k
        if let Some(limit) = self.config.dfa_size_limit {
548
852k
            if self.dfa.memory_usage() > limit {
549
1.76k
                return Err(BuildError::dfa_exceeded_size_limit(limit));
550
850k
            }
551
0
        }
552
850k
        if let Some(limit) = self.config.determinize_size_limit {
553
850k
            if self.memory_usage() > limit {
554
65
                return Err(BuildError::determinize_exceeded_size_limit(
555
65
                    limit,
556
65
                ));
557
850k
            }
558
0
        }
559
850k
        Ok(id)
560
852k
    }
561
562
    /// Returns a state builder from this determinizer that might have existing
563
    /// capacity. This helps avoid allocs in cases where a state is built that
564
    /// turns out to already be cached.
565
    ///
566
    /// Callers must put the state builder back with 'put_state_builder',
567
    /// otherwise the allocation reuse won't work.
568
15.6M
    fn get_state_builder(&mut self) -> StateBuilderEmpty {
569
15.6M
        core::mem::replace(
570
15.6M
            &mut self.scratch_state_builder,
571
15.6M
            StateBuilderEmpty::new(),
572
        )
573
15.6M
    }
574
575
    /// Puts the given state builder back into this determinizer for reuse.
576
    ///
577
    /// Note that building a 'State' from a builder always creates a new
578
    /// alloc, so callers should always put the builder back.
579
15.6M
    fn put_state_builder(&mut self, builder: StateBuilderNFA) {
580
15.6M
        let _ = core::mem::replace(
581
15.6M
            &mut self.scratch_state_builder,
582
15.6M
            builder.clear(),
583
15.6M
        );
584
15.6M
    }
585
586
    /// Return the memory usage, in bytes, of this determinizer at the current
587
    /// point in time. This does not include memory used by the NFA or the
588
    /// dense DFA itself.
589
850k
    fn memory_usage(&self) -> usize {
590
        use core::mem::size_of;
591
592
850k
        self.builder_states.len() * size_of::<State>()
593
850k
        // Maps likely use more memory than this, but it's probably close.
594
850k
        + self.cache.len() * (size_of::<State>() + size_of::<StateID>())
595
850k
        + self.memory_usage_state
596
850k
        + self.stack.capacity() * size_of::<StateID>()
597
850k
        + self.scratch_state_builder.capacity()
598
850k
    }
599
}