Coverage Report

Created: 2026-09-06 07:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/regex-1.5.6/src/backtrack.rs
Line
Count
Source
1
// This is the backtracking matching engine. It has the same exact capability
2
// as the full NFA simulation, except it is artificially restricted to small
3
// regexes on small inputs because of its memory requirements.
4
//
5
// In particular, this is a *bounded* backtracking engine. It retains worst
6
// case linear time by keeping track of the states that it has visited (using a
7
// bitmap). Namely, once a state is visited, it is never visited again. Since a
8
// state is keyed by `(instruction index, input index)`, we have that its time
9
// complexity is `O(mn)` (i.e., linear in the size of the search text).
10
//
11
// The backtracking engine can beat out the NFA simulation on small
12
// regexes/inputs because it doesn't have to keep track of multiple copies of
13
// the capture groups. In benchmarks, the backtracking engine is roughly twice
14
// as fast as the full NFA simulation. Note though that its performance doesn't
15
// scale, even if you're willing to live with the memory requirements. Namely,
16
// the bitset has to be zeroed on each execution, which becomes quite expensive
17
// on large bitsets.
18
19
use crate::exec::ProgramCache;
20
use crate::input::{Input, InputAt};
21
use crate::prog::{InstPtr, Program};
22
use crate::re_trait::Slot;
23
24
type Bits = u32;
25
26
const BIT_SIZE: usize = 32;
27
const MAX_SIZE_BYTES: usize = 256 * (1 << 10); // 256 KB
28
29
/// Returns true iff the given regex and input should be executed by this
30
/// engine with reasonable memory usage.
31
66.6k
pub fn should_exec(num_insts: usize, text_len: usize) -> bool {
32
    // Total memory usage in bytes is determined by:
33
    //
34
    //   ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32))
35
    //
36
    // The actual limit picked is pretty much a heuristic.
37
    // See: https://github.com/rust-lang/regex/issues/215
38
66.6k
    let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4;
39
66.6k
    size <= MAX_SIZE_BYTES
40
66.6k
}
regex::backtrack::should_exec
Line
Count
Source
31
9.11k
pub fn should_exec(num_insts: usize, text_len: usize) -> bool {
32
    // Total memory usage in bytes is determined by:
33
    //
34
    //   ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32))
35
    //
36
    // The actual limit picked is pretty much a heuristic.
37
    // See: https://github.com/rust-lang/regex/issues/215
38
9.11k
    let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4;
39
9.11k
    size <= MAX_SIZE_BYTES
40
9.11k
}
regex::backtrack::should_exec
Line
Count
Source
31
57.5k
pub fn should_exec(num_insts: usize, text_len: usize) -> bool {
32
    // Total memory usage in bytes is determined by:
33
    //
34
    //   ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32))
35
    //
36
    // The actual limit picked is pretty much a heuristic.
37
    // See: https://github.com/rust-lang/regex/issues/215
38
57.5k
    let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4;
39
57.5k
    size <= MAX_SIZE_BYTES
40
57.5k
}
41
42
/// A backtracking matching engine.
43
#[derive(Debug)]
44
pub struct Bounded<'a, 'm, 'r, 's, I> {
45
    prog: &'r Program,
46
    input: I,
47
    matches: &'m mut [bool],
48
    slots: &'s mut [Slot],
49
    m: &'a mut Cache,
50
}
51
52
/// Shared cached state between multiple invocations of a backtracking engine
53
/// in the same thread.
54
#[derive(Clone, Debug)]
55
pub struct Cache {
56
    jobs: Vec<Job>,
57
    visited: Vec<Bits>,
58
}
59
60
impl Cache {
61
    /// Create new empty cache for the backtracking engine.
62
18
    pub fn new(_prog: &Program) -> Self {
63
18
        Cache { jobs: vec![], visited: vec![] }
64
18
    }
<regex::backtrack::Cache>::new
Line
Count
Source
62
9
    pub fn new(_prog: &Program) -> Self {
63
9
        Cache { jobs: vec![], visited: vec![] }
64
9
    }
<regex::backtrack::Cache>::new
Line
Count
Source
62
9
    pub fn new(_prog: &Program) -> Self {
63
9
        Cache { jobs: vec![], visited: vec![] }
64
9
    }
65
}
66
67
/// A job is an explicit unit of stack space in the backtracking engine.
68
///
69
/// The "normal" representation is a single state transition, which corresponds
70
/// to an NFA state and a character in the input. However, the backtracking
71
/// engine must keep track of old capture group values. We use the explicit
72
/// stack to do it.
73
#[derive(Clone, Copy, Debug)]
74
enum Job {
75
    Inst { ip: InstPtr, at: InputAt },
76
    SaveRestore { slot: usize, old_pos: Option<usize> },
77
}
78
79
impl<'a, 'm, 'r, 's, I: Input> Bounded<'a, 'm, 'r, 's, I> {
80
    /// Execute the backtracking matching engine.
81
    ///
82
    /// If there's a match, `exec` returns `true` and populates the given
83
    /// captures accordingly.
84
66.6k
    pub fn exec(
85
66.6k
        prog: &'r Program,
86
66.6k
        cache: &ProgramCache,
87
66.6k
        matches: &'m mut [bool],
88
66.6k
        slots: &'s mut [Slot],
89
66.6k
        input: I,
90
66.6k
        start: usize,
91
66.6k
        end: usize,
92
66.6k
    ) -> bool {
93
66.6k
        let mut cache = cache.borrow_mut();
94
66.6k
        let cache = &mut cache.backtrack;
95
66.6k
        let start = input.at(start);
96
66.6k
        let mut b = Bounded {
97
66.6k
            prog: prog,
98
66.6k
            input: input,
99
66.6k
            matches: matches,
100
66.6k
            slots: slots,
101
66.6k
            m: cache,
102
66.6k
        };
103
66.6k
        b.exec_(start, end)
104
66.6k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec
<regex::backtrack::Bounded<regex::input::CharInput>>::exec
Line
Count
Source
84
9.11k
    pub fn exec(
85
9.11k
        prog: &'r Program,
86
9.11k
        cache: &ProgramCache,
87
9.11k
        matches: &'m mut [bool],
88
9.11k
        slots: &'s mut [Slot],
89
9.11k
        input: I,
90
9.11k
        start: usize,
91
9.11k
        end: usize,
92
9.11k
    ) -> bool {
93
9.11k
        let mut cache = cache.borrow_mut();
94
9.11k
        let cache = &mut cache.backtrack;
95
9.11k
        let start = input.at(start);
96
9.11k
        let mut b = Bounded {
97
9.11k
            prog: prog,
98
9.11k
            input: input,
99
9.11k
            matches: matches,
100
9.11k
            slots: slots,
101
9.11k
            m: cache,
102
9.11k
        };
103
9.11k
        b.exec_(start, end)
104
9.11k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec
<regex::backtrack::Bounded<regex::input::CharInput>>::exec
Line
Count
Source
84
57.5k
    pub fn exec(
85
57.5k
        prog: &'r Program,
86
57.5k
        cache: &ProgramCache,
87
57.5k
        matches: &'m mut [bool],
88
57.5k
        slots: &'s mut [Slot],
89
57.5k
        input: I,
90
57.5k
        start: usize,
91
57.5k
        end: usize,
92
57.5k
    ) -> bool {
93
57.5k
        let mut cache = cache.borrow_mut();
94
57.5k
        let cache = &mut cache.backtrack;
95
57.5k
        let start = input.at(start);
96
57.5k
        let mut b = Bounded {
97
57.5k
            prog: prog,
98
57.5k
            input: input,
99
57.5k
            matches: matches,
100
57.5k
            slots: slots,
101
57.5k
            m: cache,
102
57.5k
        };
103
57.5k
        b.exec_(start, end)
104
57.5k
    }
105
106
    /// Clears the cache such that the backtracking engine can be executed
107
    /// on some input of fixed length.
108
66.6k
    fn clear(&mut self) {
109
        // Reset the job memory so that we start fresh.
110
66.6k
        self.m.jobs.clear();
111
112
        // Now we need to clear the bit state set.
113
        // We do this by figuring out how much space we need to keep track
114
        // of the states we've visited.
115
        // Then we reset all existing allocated space to 0.
116
        // Finally, we request more space if we need it.
117
        //
118
        // This is all a little circuitous, but doing this using unchecked
119
        // operations doesn't seem to have a measurable impact on performance.
120
        // (Probably because backtracking is limited to such small
121
        // inputs/regexes in the first place.)
122
66.6k
        let visited_len =
123
66.6k
            (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1)
124
66.6k
                / BIT_SIZE;
125
66.6k
        self.m.visited.truncate(visited_len);
126
3.79M
        for v in &mut self.m.visited {
127
3.72M
            *v = 0;
128
3.72M
        }
129
66.6k
        if visited_len > self.m.visited.len() {
130
19.6k
            let len = self.m.visited.len();
131
19.6k
            self.m.visited.reserve_exact(visited_len - len);
132
909k
            for _ in 0..(visited_len - len) {
133
909k
                self.m.visited.push(0);
134
909k
            }
135
46.9k
        }
136
66.6k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear
<regex::backtrack::Bounded<regex::input::CharInput>>::clear
Line
Count
Source
108
9.11k
    fn clear(&mut self) {
109
        // Reset the job memory so that we start fresh.
110
9.11k
        self.m.jobs.clear();
111
112
        // Now we need to clear the bit state set.
113
        // We do this by figuring out how much space we need to keep track
114
        // of the states we've visited.
115
        // Then we reset all existing allocated space to 0.
116
        // Finally, we request more space if we need it.
117
        //
118
        // This is all a little circuitous, but doing this using unchecked
119
        // operations doesn't seem to have a measurable impact on performance.
120
        // (Probably because backtracking is limited to such small
121
        // inputs/regexes in the first place.)
122
9.11k
        let visited_len =
123
9.11k
            (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1)
124
9.11k
                / BIT_SIZE;
125
9.11k
        self.m.visited.truncate(visited_len);
126
613k
        for v in &mut self.m.visited {
127
604k
            *v = 0;
128
604k
        }
129
9.11k
        if visited_len > self.m.visited.len() {
130
2.49k
            let len = self.m.visited.len();
131
2.49k
            self.m.visited.reserve_exact(visited_len - len);
132
158k
            for _ in 0..(visited_len - len) {
133
158k
                self.m.visited.push(0);
134
158k
            }
135
6.62k
        }
136
9.11k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::clear
<regex::backtrack::Bounded<regex::input::CharInput>>::clear
Line
Count
Source
108
57.5k
    fn clear(&mut self) {
109
        // Reset the job memory so that we start fresh.
110
57.5k
        self.m.jobs.clear();
111
112
        // Now we need to clear the bit state set.
113
        // We do this by figuring out how much space we need to keep track
114
        // of the states we've visited.
115
        // Then we reset all existing allocated space to 0.
116
        // Finally, we request more space if we need it.
117
        //
118
        // This is all a little circuitous, but doing this using unchecked
119
        // operations doesn't seem to have a measurable impact on performance.
120
        // (Probably because backtracking is limited to such small
121
        // inputs/regexes in the first place.)
122
57.5k
        let visited_len =
123
57.5k
            (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1)
124
57.5k
                / BIT_SIZE;
125
57.5k
        self.m.visited.truncate(visited_len);
126
3.17M
        for v in &mut self.m.visited {
127
3.12M
            *v = 0;
128
3.12M
        }
129
57.5k
        if visited_len > self.m.visited.len() {
130
17.1k
            let len = self.m.visited.len();
131
17.1k
            self.m.visited.reserve_exact(visited_len - len);
132
751k
            for _ in 0..(visited_len - len) {
133
751k
                self.m.visited.push(0);
134
751k
            }
135
40.3k
        }
136
57.5k
    }
137
138
    /// Start backtracking at the given position in the input, but also look
139
    /// for literal prefixes.
140
66.6k
    fn exec_(&mut self, mut at: InputAt, end: usize) -> bool {
141
66.6k
        self.clear();
142
        // If this is an anchored regex at the beginning of the input, then
143
        // we're either already done or we only need to try backtracking once.
144
66.6k
        if self.prog.is_anchored_start {
145
66.6k
            return if !at.is_start() { false } else { self.backtrack(at) };
146
0
        }
147
0
        let mut matched = false;
148
        loop {
149
0
            if !self.prog.prefixes.is_empty() {
150
0
                at = match self.input.prefix_at(&self.prog.prefixes, at) {
151
0
                    None => break,
152
0
                    Some(at) => at,
153
                };
154
0
            }
155
0
            matched = self.backtrack(at) || matched;
156
0
            if matched && self.prog.matches.len() == 1 {
157
0
                return true;
158
0
            }
159
0
            if at.pos() >= end {
160
0
                break;
161
0
            }
162
0
            at = self.input.at(at.next_pos());
163
        }
164
0
        matched
165
66.6k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_
<regex::backtrack::Bounded<regex::input::CharInput>>::exec_
Line
Count
Source
140
9.11k
    fn exec_(&mut self, mut at: InputAt, end: usize) -> bool {
141
9.11k
        self.clear();
142
        // If this is an anchored regex at the beginning of the input, then
143
        // we're either already done or we only need to try backtracking once.
144
9.11k
        if self.prog.is_anchored_start {
145
9.11k
            return if !at.is_start() { false } else { self.backtrack(at) };
146
0
        }
147
0
        let mut matched = false;
148
        loop {
149
0
            if !self.prog.prefixes.is_empty() {
150
0
                at = match self.input.prefix_at(&self.prog.prefixes, at) {
151
0
                    None => break,
152
0
                    Some(at) => at,
153
                };
154
0
            }
155
0
            matched = self.backtrack(at) || matched;
156
0
            if matched && self.prog.matches.len() == 1 {
157
0
                return true;
158
0
            }
159
0
            if at.pos() >= end {
160
0
                break;
161
0
            }
162
0
            at = self.input.at(at.next_pos());
163
        }
164
0
        matched
165
9.11k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::exec_
<regex::backtrack::Bounded<regex::input::CharInput>>::exec_
Line
Count
Source
140
57.5k
    fn exec_(&mut self, mut at: InputAt, end: usize) -> bool {
141
57.5k
        self.clear();
142
        // If this is an anchored regex at the beginning of the input, then
143
        // we're either already done or we only need to try backtracking once.
144
57.5k
        if self.prog.is_anchored_start {
145
57.5k
            return if !at.is_start() { false } else { self.backtrack(at) };
146
0
        }
147
0
        let mut matched = false;
148
        loop {
149
0
            if !self.prog.prefixes.is_empty() {
150
0
                at = match self.input.prefix_at(&self.prog.prefixes, at) {
151
0
                    None => break,
152
0
                    Some(at) => at,
153
                };
154
0
            }
155
0
            matched = self.backtrack(at) || matched;
156
0
            if matched && self.prog.matches.len() == 1 {
157
0
                return true;
158
0
            }
159
0
            if at.pos() >= end {
160
0
                break;
161
0
            }
162
0
            at = self.input.at(at.next_pos());
163
        }
164
0
        matched
165
57.5k
    }
166
167
    /// The main backtracking loop starting at the given input position.
168
66.6k
    fn backtrack(&mut self, start: InputAt) -> bool {
169
        // N.B. We use an explicit stack to avoid recursion.
170
        // To avoid excessive pushing and popping, most transitions are handled
171
        // in the `step` helper function, which only pushes to the stack when
172
        // there's a capture or a branch.
173
66.6k
        let mut matched = false;
174
66.6k
        self.m.jobs.push(Job::Inst { ip: 0, at: start });
175
771k
        while let Some(job) = self.m.jobs.pop() {
176
730k
            match job {
177
647k
                Job::Inst { ip, at } => {
178
647k
                    if self.step(ip, at) {
179
                        // Only quit if we're matching one regex.
180
                        // If we're matching a regex set, then mush on and
181
                        // try to find other matches (if we want them).
182
24.9k
                        if self.prog.matches.len() == 1 {
183
24.9k
                            return true;
184
0
                        }
185
0
                        matched = true;
186
622k
                    }
187
                }
188
82.3k
                Job::SaveRestore { slot, old_pos } => {
189
82.3k
                    if slot < self.slots.len() {
190
82.3k
                        self.slots[slot] = old_pos;
191
82.3k
                    }
192
                }
193
            }
194
        }
195
41.7k
        matched
196
66.6k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack
<regex::backtrack::Bounded<regex::input::CharInput>>::backtrack
Line
Count
Source
168
9.11k
    fn backtrack(&mut self, start: InputAt) -> bool {
169
        // N.B. We use an explicit stack to avoid recursion.
170
        // To avoid excessive pushing and popping, most transitions are handled
171
        // in the `step` helper function, which only pushes to the stack when
172
        // there's a capture or a branch.
173
9.11k
        let mut matched = false;
174
9.11k
        self.m.jobs.push(Job::Inst { ip: 0, at: start });
175
89.3k
        while let Some(job) = self.m.jobs.pop() {
176
83.5k
            match job {
177
74.9k
                Job::Inst { ip, at } => {
178
74.9k
                    if self.step(ip, at) {
179
                        // Only quit if we're matching one regex.
180
                        // If we're matching a regex set, then mush on and
181
                        // try to find other matches (if we want them).
182
3.31k
                        if self.prog.matches.len() == 1 {
183
3.31k
                            return true;
184
0
                        }
185
0
                        matched = true;
186
71.6k
                    }
187
                }
188
8.63k
                Job::SaveRestore { slot, old_pos } => {
189
8.63k
                    if slot < self.slots.len() {
190
8.63k
                        self.slots[slot] = old_pos;
191
8.63k
                    }
192
                }
193
            }
194
        }
195
5.80k
        matched
196
9.11k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::backtrack
<regex::backtrack::Bounded<regex::input::CharInput>>::backtrack
Line
Count
Source
168
57.5k
    fn backtrack(&mut self, start: InputAt) -> bool {
169
        // N.B. We use an explicit stack to avoid recursion.
170
        // To avoid excessive pushing and popping, most transitions are handled
171
        // in the `step` helper function, which only pushes to the stack when
172
        // there's a capture or a branch.
173
57.5k
        let mut matched = false;
174
57.5k
        self.m.jobs.push(Job::Inst { ip: 0, at: start });
175
682k
        while let Some(job) = self.m.jobs.pop() {
176
646k
            match job {
177
572k
                Job::Inst { ip, at } => {
178
572k
                    if self.step(ip, at) {
179
                        // Only quit if we're matching one regex.
180
                        // If we're matching a regex set, then mush on and
181
                        // try to find other matches (if we want them).
182
21.6k
                        if self.prog.matches.len() == 1 {
183
21.6k
                            return true;
184
0
                        }
185
0
                        matched = true;
186
551k
                    }
187
                }
188
73.7k
                Job::SaveRestore { slot, old_pos } => {
189
73.7k
                    if slot < self.slots.len() {
190
73.7k
                        self.slots[slot] = old_pos;
191
73.7k
                    }
192
                }
193
            }
194
        }
195
35.9k
        matched
196
57.5k
    }
197
198
647k
    fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool {
199
        use crate::prog::Inst::*;
200
        loop {
201
            // This loop is an optimization to avoid constantly pushing/popping
202
            // from the stack. Namely, if we're pushing a job only to run it
203
            // next, avoid the push and just mutate `ip` (and possibly `at`)
204
            // in place.
205
2.52M
            if self.has_visited(ip, at) {
206
11.6k
                return false;
207
2.51M
            }
208
2.51M
            match self.prog[ip] {
209
24.9k
                Match(slot) => {
210
24.9k
                    if slot < self.matches.len() {
211
24.9k
                        self.matches[slot] = true;
212
24.9k
                    }
213
24.9k
                    return true;
214
                }
215
217k
                Save(ref inst) => {
216
217k
                    if let Some(&old_pos) = self.slots.get(inst.slot) {
217
217k
                        // If this path doesn't work out, then we save the old
218
217k
                        // capture index (if one exists) in an alternate
219
217k
                        // job. If the next path fails, then the alternate
220
217k
                        // job is popped and the old capture index is restored.
221
217k
                        self.m.jobs.push(Job::SaveRestore {
222
217k
                            slot: inst.slot,
223
217k
                            old_pos: old_pos,
224
217k
                        });
225
217k
                        self.slots[inst.slot] = Some(at.pos());
226
217k
                    }
227
217k
                    ip = inst.goto;
228
                }
229
906k
                Split(ref inst) => {
230
906k
                    self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at });
231
906k
                    ip = inst.goto1;
232
906k
                }
233
121k
                EmptyLook(ref inst) => {
234
121k
                    if self.input.is_empty_match(at, inst) {
235
91.5k
                        ip = inst.goto;
236
91.5k
                    } else {
237
29.4k
                        return false;
238
                    }
239
                }
240
542k
                Char(ref inst) => {
241
542k
                    if inst.c == at.char() {
242
287k
                        ip = inst.goto;
243
287k
                        at = self.input.at(at.next_pos());
244
287k
                    } else {
245
255k
                        return false;
246
                    }
247
                }
248
704k
                Ranges(ref inst) => {
249
704k
                    if inst.matches(at.char()) {
250
378k
                        ip = inst.goto;
251
378k
                        at = self.input.at(at.next_pos());
252
378k
                    } else {
253
326k
                        return false;
254
                    }
255
                }
256
0
                Bytes(ref inst) => {
257
0
                    if let Some(b) = at.byte() {
258
0
                        if inst.matches(b) {
259
0
                            ip = inst.goto;
260
0
                            at = self.input.at(at.next_pos());
261
0
                            continue;
262
0
                        }
263
0
                    }
264
0
                    return false;
265
                }
266
            }
267
        }
268
647k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step
<regex::backtrack::Bounded<regex::input::CharInput>>::step
Line
Count
Source
198
74.9k
    fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool {
199
        use crate::prog::Inst::*;
200
        loop {
201
            // This loop is an optimization to avoid constantly pushing/popping
202
            // from the stack. Namely, if we're pushing a job only to run it
203
            // next, avoid the push and just mutate `ip` (and possibly `at`)
204
            // in place.
205
303k
            if self.has_visited(ip, at) {
206
745
                return false;
207
302k
            }
208
302k
            match self.prog[ip] {
209
3.31k
                Match(slot) => {
210
3.31k
                    if slot < self.matches.len() {
211
3.31k
                        self.matches[slot] = true;
212
3.31k
                    }
213
3.31k
                    return true;
214
                }
215
26.7k
                Save(ref inst) => {
216
26.7k
                    if let Some(&old_pos) = self.slots.get(inst.slot) {
217
26.7k
                        // If this path doesn't work out, then we save the old
218
26.7k
                        // capture index (if one exists) in an alternate
219
26.7k
                        // job. If the next path fails, then the alternate
220
26.7k
                        // job is popped and the old capture index is restored.
221
26.7k
                        self.m.jobs.push(Job::SaveRestore {
222
26.7k
                            slot: inst.slot,
223
26.7k
                            old_pos: old_pos,
224
26.7k
                        });
225
26.7k
                        self.slots[inst.slot] = Some(at.pos());
226
26.7k
                    }
227
26.7k
                    ip = inst.goto;
228
                }
229
106k
                Split(ref inst) => {
230
106k
                    self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at });
231
106k
                    ip = inst.goto1;
232
106k
                }
233
13.3k
                EmptyLook(ref inst) => {
234
13.3k
                    if self.input.is_empty_match(at, inst) {
235
12.4k
                        ip = inst.goto;
236
12.4k
                    } else {
237
879
                        return false;
238
                    }
239
                }
240
71.2k
                Char(ref inst) => {
241
71.2k
                    if inst.c == at.char() {
242
39.6k
                        ip = inst.goto;
243
39.6k
                        at = self.input.at(at.next_pos());
244
39.6k
                    } else {
245
31.6k
                        return false;
246
                    }
247
                }
248
81.5k
                Ranges(ref inst) => {
249
81.5k
                    if inst.matches(at.char()) {
250
43.1k
                        ip = inst.goto;
251
43.1k
                        at = self.input.at(at.next_pos());
252
43.1k
                    } else {
253
38.4k
                        return false;
254
                    }
255
                }
256
0
                Bytes(ref inst) => {
257
0
                    if let Some(b) = at.byte() {
258
0
                        if inst.matches(b) {
259
0
                            ip = inst.goto;
260
0
                            at = self.input.at(at.next_pos());
261
0
                            continue;
262
0
                        }
263
0
                    }
264
0
                    return false;
265
                }
266
            }
267
        }
268
74.9k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::step
<regex::backtrack::Bounded<regex::input::CharInput>>::step
Line
Count
Source
198
572k
    fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool {
199
        use crate::prog::Inst::*;
200
        loop {
201
            // This loop is an optimization to avoid constantly pushing/popping
202
            // from the stack. Namely, if we're pushing a job only to run it
203
            // next, avoid the push and just mutate `ip` (and possibly `at`)
204
            // in place.
205
2.22M
            if self.has_visited(ip, at) {
206
10.8k
                return false;
207
2.21M
            }
208
2.21M
            match self.prog[ip] {
209
21.6k
                Match(slot) => {
210
21.6k
                    if slot < self.matches.len() {
211
21.6k
                        self.matches[slot] = true;
212
21.6k
                    }
213
21.6k
                    return true;
214
                }
215
190k
                Save(ref inst) => {
216
190k
                    if let Some(&old_pos) = self.slots.get(inst.slot) {
217
190k
                        // If this path doesn't work out, then we save the old
218
190k
                        // capture index (if one exists) in an alternate
219
190k
                        // job. If the next path fails, then the alternate
220
190k
                        // job is popped and the old capture index is restored.
221
190k
                        self.m.jobs.push(Job::SaveRestore {
222
190k
                            slot: inst.slot,
223
190k
                            old_pos: old_pos,
224
190k
                        });
225
190k
                        self.slots[inst.slot] = Some(at.pos());
226
190k
                    }
227
190k
                    ip = inst.goto;
228
                }
229
799k
                Split(ref inst) => {
230
799k
                    self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at });
231
799k
                    ip = inst.goto1;
232
799k
                }
233
107k
                EmptyLook(ref inst) => {
234
107k
                    if self.input.is_empty_match(at, inst) {
235
79.1k
                        ip = inst.goto;
236
79.1k
                    } else {
237
28.5k
                        return false;
238
                    }
239
                }
240
471k
                Char(ref inst) => {
241
471k
                    if inst.c == at.char() {
242
247k
                        ip = inst.goto;
243
247k
                        at = self.input.at(at.next_pos());
244
247k
                    } else {
245
223k
                        return false;
246
                    }
247
                }
248
623k
                Ranges(ref inst) => {
249
623k
                    if inst.matches(at.char()) {
250
335k
                        ip = inst.goto;
251
335k
                        at = self.input.at(at.next_pos());
252
335k
                    } else {
253
288k
                        return false;
254
                    }
255
                }
256
0
                Bytes(ref inst) => {
257
0
                    if let Some(b) = at.byte() {
258
0
                        if inst.matches(b) {
259
0
                            ip = inst.goto;
260
0
                            at = self.input.at(at.next_pos());
261
0
                            continue;
262
0
                        }
263
0
                    }
264
0
                    return false;
265
                }
266
            }
267
        }
268
572k
    }
269
270
2.52M
    fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool {
271
2.52M
        let k = ip * (self.input.len() + 1) + at.pos();
272
2.52M
        let k1 = k / BIT_SIZE;
273
2.52M
        let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1)));
274
2.52M
        if self.m.visited[k1] & k2 == 0 {
275
2.51M
            self.m.visited[k1] |= k2;
276
2.51M
            false
277
        } else {
278
11.6k
            true
279
        }
280
2.52M
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited
<regex::backtrack::Bounded<regex::input::CharInput>>::has_visited
Line
Count
Source
270
303k
    fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool {
271
303k
        let k = ip * (self.input.len() + 1) + at.pos();
272
303k
        let k1 = k / BIT_SIZE;
273
303k
        let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1)));
274
303k
        if self.m.visited[k1] & k2 == 0 {
275
302k
            self.m.visited[k1] |= k2;
276
302k
            false
277
        } else {
278
745
            true
279
        }
280
303k
    }
Unexecuted instantiation: <regex::backtrack::Bounded<regex::input::ByteInput>>::has_visited
<regex::backtrack::Bounded<regex::input::CharInput>>::has_visited
Line
Count
Source
270
2.22M
    fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool {
271
2.22M
        let k = ip * (self.input.len() + 1) + at.pos();
272
2.22M
        let k1 = k / BIT_SIZE;
273
2.22M
        let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1)));
274
2.22M
        if self.m.visited[k1] & k2 == 0 {
275
2.21M
            self.m.visited[k1] |= k2;
276
2.21M
            false
277
        } else {
278
10.8k
            true
279
        }
280
2.22M
    }
281
}
282
283
2.52M
fn usize_to_u32(n: usize) -> u32 {
284
2.52M
    if (n as u64) > (::std::u32::MAX as u64) {
285
0
        panic!("BUG: {} is too big to fit into u32", n)
286
2.52M
    }
287
2.52M
    n as u32
288
2.52M
}
regex::backtrack::usize_to_u32
Line
Count
Source
283
303k
fn usize_to_u32(n: usize) -> u32 {
284
303k
    if (n as u64) > (::std::u32::MAX as u64) {
285
0
        panic!("BUG: {} is too big to fit into u32", n)
286
303k
    }
287
303k
    n as u32
288
303k
}
regex::backtrack::usize_to_u32
Line
Count
Source
283
2.22M
fn usize_to_u32(n: usize) -> u32 {
284
2.22M
    if (n as u64) > (::std::u32::MAX as u64) {
285
0
        panic!("BUG: {} is too big to fit into u32", n)
286
2.22M
    }
287
2.22M
    n as u32
288
2.22M
}