schemurai 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -4
- data/ext/schemurai_native/Cargo.lock +1 -1
- data/ext/schemurai_native/Cargo.toml +1 -1
- data/ext/schemurai_native/glue.c +155 -14
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/diagnostics.rs +395 -225
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/graph.rs +50 -0
- data/ext/schemurai_native/src/instance_graph.rs +84 -0
- data/ext/schemurai_native/src/lib.rs +947 -184
- data/ext/schemurai_native/src/memo.rs +1149 -0
- data/ext/schemurai_native/src/memo_scope.rs +240 -0
- data/ext/schemurai_native/src/number.rs +7 -8
- data/ext/schemurai_native/src/pattern.rs +157 -14
- data/ext/schemurai_native/src/scope.rs +243 -33
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +245 -90
- data/lib/schemurai/backend.rb +1 -0
- data/lib/schemurai/evaluator.rb +45 -3
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +100 -0
- data/lib/schemurai/native/evaluator.rb +32 -5
- data/lib/schemurai/schema_domain.rb +12 -7
- data/lib/schemurai/schema_graph.rb +10 -1
- data/lib/schemurai/unique_items.rb +2 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +7 -3
- data/lib/schemurai/vm/evaluator.rb +50 -3
- data/lib/schemurai/vm/limited_evaluator.rb +97 -0
- data/lib/schemurai.rb +74 -27
- data/sig/schemurai.rbs +20 -12
- metadata +16 -4
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
//! Exact reachable scope dependencies, shared across schema components.
|
|
2
|
+
use super::*;
|
|
3
|
+
use std::borrow::Cow;
|
|
4
|
+
use std::collections::HashSet;
|
|
5
|
+
use std::sync::Arc;
|
|
6
|
+
|
|
7
|
+
const MAX_NAMES: usize = 65536;
|
|
8
|
+
|
|
9
|
+
#[derive(Default)]
|
|
10
|
+
pub(super) struct Projection {
|
|
11
|
+
names: Vec<Option<Arc<[usize]>>>,
|
|
12
|
+
// If materialized closures exceed the budget, retain the linear dependency
|
|
13
|
+
// graph and compute those exact projections on demand, never all names.
|
|
14
|
+
graph: Option<Vec<Vec<usize>>>,
|
|
15
|
+
}
|
|
16
|
+
impl Projection {
|
|
17
|
+
pub(super) fn compile(edges: &[Vec<usize>], parents: &[Vec<usize>], count: usize) -> Self {
|
|
18
|
+
Self::bounded(edges, parents, count, MAX_NAMES)
|
|
19
|
+
}
|
|
20
|
+
fn bounded(edges: &[Vec<usize>], parents: &[Vec<usize>], count: usize, limit: usize) -> Self {
|
|
21
|
+
let mut seen = vec![false; edges.len()];
|
|
22
|
+
let order = graph::postorder(edges, &mut seen);
|
|
23
|
+
let mut component = vec![usize::MAX; edges.len()];
|
|
24
|
+
let mut members = vec![];
|
|
25
|
+
for root in order.into_iter().rev() {
|
|
26
|
+
if component[root] != usize::MAX {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
let id = members.len();
|
|
30
|
+
let mut nodes = vec![];
|
|
31
|
+
let mut stack = vec![root];
|
|
32
|
+
component[root] = id;
|
|
33
|
+
while let Some(node) = stack.pop() {
|
|
34
|
+
nodes.push(node);
|
|
35
|
+
for &parent in &parents[node] {
|
|
36
|
+
if component[parent] == usize::MAX {
|
|
37
|
+
component[parent] = id;
|
|
38
|
+
stack.push(parent);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
members.push(nodes);
|
|
43
|
+
}
|
|
44
|
+
// Kosaraju components are ordered from sources to sinks. Every member
|
|
45
|
+
// of a cycle has the same reachable reference names; solve it once.
|
|
46
|
+
let mut closures: Vec<Option<Arc<[usize]>>> = vec![None; members.len()];
|
|
47
|
+
let mut interned: HashSet<Arc<[usize]>> = HashSet::new();
|
|
48
|
+
let mut slots = 0;
|
|
49
|
+
for (id, nodes) in members.iter().enumerate().rev() {
|
|
50
|
+
let mut names = vec![];
|
|
51
|
+
let mut children = HashSet::new();
|
|
52
|
+
let mut dependencies = HashSet::new();
|
|
53
|
+
let mut complete = true;
|
|
54
|
+
for &node in nodes {
|
|
55
|
+
if node >= count {
|
|
56
|
+
names.push(node - count);
|
|
57
|
+
}
|
|
58
|
+
for &child in &edges[node] {
|
|
59
|
+
let child = component[child];
|
|
60
|
+
if child != id && children.insert(child) {
|
|
61
|
+
match &closures[child] {
|
|
62
|
+
Some(dependency) => {
|
|
63
|
+
if dependencies.insert(dependency.as_ptr()) {
|
|
64
|
+
names.extend(dependency.iter().copied());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
None => complete = false,
|
|
68
|
+
}
|
|
69
|
+
if names.len() > limit {
|
|
70
|
+
names.sort_unstable();
|
|
71
|
+
names.dedup();
|
|
72
|
+
if names.len() > limit {
|
|
73
|
+
complete = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Bound temporary concatenation too. Deduplicate overlapping
|
|
80
|
+
// dependencies before choosing the on-demand representation.
|
|
81
|
+
if names.len() > limit {
|
|
82
|
+
names.sort_unstable();
|
|
83
|
+
names.dedup();
|
|
84
|
+
if names.len() > limit {
|
|
85
|
+
complete = false;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if !complete {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
names.sort_unstable();
|
|
94
|
+
names.dedup();
|
|
95
|
+
if let Some(existing) = interned.get(names.as_slice()) {
|
|
96
|
+
closures[id] = Some(existing.clone());
|
|
97
|
+
} else if names.len() <= limit - slots {
|
|
98
|
+
slots += names.len();
|
|
99
|
+
let names: Arc<[usize]> = names.into();
|
|
100
|
+
interned.insert(names.clone());
|
|
101
|
+
closures[id] = Some(names);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
let names: Vec<_> = component[..count]
|
|
105
|
+
.iter()
|
|
106
|
+
.map(|&id| closures[id].clone())
|
|
107
|
+
.collect();
|
|
108
|
+
let graph = names.iter().any(Option::is_none).then(|| edges.to_vec());
|
|
109
|
+
Self { names, graph }
|
|
110
|
+
}
|
|
111
|
+
pub(super) fn names(
|
|
112
|
+
&self,
|
|
113
|
+
node: usize,
|
|
114
|
+
count: usize,
|
|
115
|
+
anchors: usize,
|
|
116
|
+
guarded: bool,
|
|
117
|
+
budget: &budget::Budget,
|
|
118
|
+
) -> Option<Cow<'_, [usize]>> {
|
|
119
|
+
if self.names.is_empty() {
|
|
120
|
+
// Hand-built engines in key tests have no compiled dependency graph.
|
|
121
|
+
return Some(Cow::Owned((0..anchors + usize::from(guarded)).collect()));
|
|
122
|
+
}
|
|
123
|
+
if let Some(names) = &self.names[node] {
|
|
124
|
+
return Some(Cow::Borrowed(names));
|
|
125
|
+
}
|
|
126
|
+
let graph = self.graph.as_ref()?;
|
|
127
|
+
let mut seen = vec![false; graph.len()];
|
|
128
|
+
let mut stack = vec![node];
|
|
129
|
+
let mut names = vec![];
|
|
130
|
+
seen[node] = true;
|
|
131
|
+
while let Some(node) = stack.pop() {
|
|
132
|
+
if !budget.checkpoint() {
|
|
133
|
+
return None;
|
|
134
|
+
}
|
|
135
|
+
if node >= count {
|
|
136
|
+
names.push(node - count);
|
|
137
|
+
}
|
|
138
|
+
for &child in &graph[node] {
|
|
139
|
+
if !seen[child] {
|
|
140
|
+
seen[child] = true;
|
|
141
|
+
stack.push(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
names.sort_unstable();
|
|
146
|
+
Some(Cow::Owned(names))
|
|
147
|
+
}
|
|
148
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
149
|
+
let mut seen = HashSet::new();
|
|
150
|
+
self.names.capacity() * std::mem::size_of::<Option<Arc<[usize]>>>()
|
|
151
|
+
+ self
|
|
152
|
+
.names
|
|
153
|
+
.iter()
|
|
154
|
+
.flatten()
|
|
155
|
+
.filter(|names| seen.insert(names.as_ptr()))
|
|
156
|
+
.map(|names| (names.len() + 2) * std::mem::size_of::<usize>())
|
|
157
|
+
.sum::<usize>()
|
|
158
|
+
+ self.graph.as_ref().map_or(0, |graph| {
|
|
159
|
+
graph.capacity() * std::mem::size_of::<Vec<usize>>()
|
|
160
|
+
+ graph
|
|
161
|
+
.iter()
|
|
162
|
+
.map(|row| row.capacity() * std::mem::size_of::<usize>())
|
|
163
|
+
.sum::<usize>()
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
mod tests {
|
|
170
|
+
use super::*;
|
|
171
|
+
|
|
172
|
+
fn parents(edges: &[Vec<usize>]) -> Vec<Vec<usize>> {
|
|
173
|
+
let mut parents = vec![vec![]; edges.len()];
|
|
174
|
+
for (node, children) in edges.iter().enumerate() {
|
|
175
|
+
for &child in children {
|
|
176
|
+
parents[child].push(node);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
parents
|
|
180
|
+
}
|
|
181
|
+
#[test]
|
|
182
|
+
fn projects_cycles_and_transitive_dynamic_dispatch_targets() {
|
|
183
|
+
// Four nodes, two dynamic names and the recursive dispatch vertex.
|
|
184
|
+
let edges = vec![
|
|
185
|
+
vec![1],
|
|
186
|
+
vec![0, 4],
|
|
187
|
+
vec![5],
|
|
188
|
+
vec![],
|
|
189
|
+
vec![2],
|
|
190
|
+
vec![3],
|
|
191
|
+
vec![0],
|
|
192
|
+
];
|
|
193
|
+
for limit in [0, 1, MAX_NAMES] {
|
|
194
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, limit);
|
|
195
|
+
let names = |node| {
|
|
196
|
+
projection
|
|
197
|
+
.names(node, 4, 2, true, &budget::Budget::default())
|
|
198
|
+
.unwrap()
|
|
199
|
+
.into_owned()
|
|
200
|
+
};
|
|
201
|
+
assert_eq!(names(0), [0, 1]);
|
|
202
|
+
assert_eq!(names(1), [0, 1]);
|
|
203
|
+
assert_eq!(names(2), [1]);
|
|
204
|
+
assert!(names(3).is_empty());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
#[test]
|
|
208
|
+
fn retains_recursive_dispatch_only_where_reachable() {
|
|
209
|
+
let edges = vec![vec![1], vec![4], vec![3], vec![], vec![0]];
|
|
210
|
+
let projection = Projection::compile(&edges, &parents(&edges), 3);
|
|
211
|
+
let names = |node| {
|
|
212
|
+
projection
|
|
213
|
+
.names(node, 3, 1, true, &budget::Budget::default())
|
|
214
|
+
.unwrap()
|
|
215
|
+
.into_owned()
|
|
216
|
+
};
|
|
217
|
+
assert_eq!(names(0), [1]);
|
|
218
|
+
assert_eq!(names(1), [1]);
|
|
219
|
+
assert_eq!(names(2), [0]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn shares_equal_closures_and_retains_exact_projection_after_budget_exhaustion() {
|
|
224
|
+
let edges = vec![vec![1, 2], vec![3], vec![3], vec![4], vec![], vec![]];
|
|
225
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, MAX_NAMES);
|
|
226
|
+
assert!(projection.graph.is_none());
|
|
227
|
+
assert!(Arc::ptr_eq(
|
|
228
|
+
projection.names[0].as_ref().unwrap(),
|
|
229
|
+
projection.names[3].as_ref().unwrap()
|
|
230
|
+
));
|
|
231
|
+
let projection = Projection::bounded(&edges, &parents(&edges), 4, 0);
|
|
232
|
+
assert!(projection.graph.is_some());
|
|
233
|
+
assert_eq!(
|
|
234
|
+
&*projection
|
|
235
|
+
.names(0, 4, 1, false, &budget::Budget::default())
|
|
236
|
+
.unwrap(),
|
|
237
|
+
&[0]
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -96,14 +96,13 @@ pub(super) fn integer_fingerprint(integer: Value) -> u64 {
|
|
|
96
96
|
let mut words = [0_u64; 16];
|
|
97
97
|
let sign = unsafe { sn_integer1024(integer, words.as_mut_ptr()) };
|
|
98
98
|
if sign.abs() > 1 {
|
|
99
|
-
// No finite Float can equal this magnitude
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return integer_bucket(&words[..len], sign, unsafe { sn_integer_bytes(integer) });
|
|
99
|
+
// No finite Float can equal this magnitude, so the full native digit
|
|
100
|
+
// hash needs no Float normalization. Sampling only low digits makes
|
|
101
|
+
// mixed/nested uniqueItems quadratic for differences above bit 1024
|
|
102
|
+
// and collapses large enum indexes into a single candidate bucket.
|
|
103
|
+
// The adapter hashes the original Bignum without Ruby dispatch or
|
|
104
|
+
// allocation; exact equality still decides every hash collision.
|
|
105
|
+
return mix(big_only_fingerprint(integer).unwrap() ^ TYPE_NUMBER as u64);
|
|
107
106
|
}
|
|
108
107
|
exact_integer_fingerprint(&words, sign)
|
|
109
108
|
}
|
|
@@ -4,29 +4,150 @@
|
|
|
4
4
|
use super::*;
|
|
5
5
|
use regex_automata::{
|
|
6
6
|
Anchored, Input, MatchKind,
|
|
7
|
-
dfa::{Automaton, StartKind, dense},
|
|
8
|
-
meta::Regex,
|
|
7
|
+
dfa::{Automaton, OverlappingState, StartKind, dense},
|
|
8
|
+
meta::{Cache, Regex},
|
|
9
9
|
nfa::thompson,
|
|
10
10
|
};
|
|
11
11
|
use regex_syntax::ast::{Ast, RepetitionKind, RepetitionRange};
|
|
12
12
|
|
|
13
|
+
// Each validator owns its search caches. Compiled regex/DFA data stays shared.
|
|
14
|
+
#[derive(Default)]
|
|
15
|
+
pub struct Caches(std::cell::RefCell<Vec<Option<Box<Cache>>>>);
|
|
16
|
+
impl Caches {
|
|
17
|
+
fn is_match(&self, index: usize, regex: &Regex, bytes: &[u8]) -> bool {
|
|
18
|
+
// Each runner is tied to one engine, whose regex indices are fixed at
|
|
19
|
+
// compilation. Allocate only the caches this validator actually uses.
|
|
20
|
+
let mut caches = self.0.borrow_mut();
|
|
21
|
+
if caches.len() <= index {
|
|
22
|
+
caches.resize_with(index + 1, || None);
|
|
23
|
+
}
|
|
24
|
+
let cache = caches[index].get_or_insert_with(|| Box::new(regex.create_cache()));
|
|
25
|
+
regex
|
|
26
|
+
.search_half_with(cache, &Input::new(bytes).earliest(true))
|
|
27
|
+
.is_some()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
#[unsafe(no_mangle)]
|
|
31
|
+
pub extern "C" fn sn_caches_new() -> *mut Caches {
|
|
32
|
+
Box::into_raw(Box::new(Caches::default()))
|
|
33
|
+
}
|
|
34
|
+
/// # Safety
|
|
35
|
+
/// Caches must be owned, returned by sn_caches_new, and freed exactly once.
|
|
36
|
+
#[unsafe(no_mangle)]
|
|
37
|
+
pub unsafe extern "C" fn sn_caches_free(caches: *mut Caches) {
|
|
38
|
+
drop(unsafe { Box::from_raw(caches) });
|
|
39
|
+
}
|
|
40
|
+
/// # Safety
|
|
41
|
+
/// Caches must be live; call under the GVL or while all Ractors are stopped.
|
|
42
|
+
#[unsafe(no_mangle)]
|
|
43
|
+
pub unsafe extern "C" fn sn_caches_size(caches: *const Caches) -> usize {
|
|
44
|
+
// GC may inspect a stopped runner while its search has borrowed a cache.
|
|
45
|
+
let Ok(caches) = (unsafe { &*caches }).0.try_borrow() else {
|
|
46
|
+
return std::mem::size_of::<Caches>();
|
|
47
|
+
};
|
|
48
|
+
std::mem::size_of::<Caches>()
|
|
49
|
+
+ caches.capacity() * std::mem::size_of::<Option<Box<Cache>>>()
|
|
50
|
+
+ caches
|
|
51
|
+
.iter()
|
|
52
|
+
.flatten()
|
|
53
|
+
.map(|cache| std::mem::size_of::<Cache>() + cache.memory_usage())
|
|
54
|
+
.sum::<usize>()
|
|
55
|
+
}
|
|
56
|
+
|
|
13
57
|
#[derive(Debug)]
|
|
14
58
|
pub(super) enum Pattern {
|
|
15
59
|
Unsupported,
|
|
16
60
|
Dfa(Box<dense::DFA<Vec<u32>>>),
|
|
17
|
-
Compiled
|
|
61
|
+
Compiled { regex: Regex, index: usize },
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Preserve every overlapping match while scanning a key with one automaton.
|
|
65
|
+
// Keep individual matchers for unsupported compilation/search configurations.
|
|
66
|
+
#[derive(Debug)]
|
|
67
|
+
pub(super) struct PatternGroup {
|
|
68
|
+
dfa: dense::DFA<Vec<u32>>,
|
|
69
|
+
all: u64,
|
|
70
|
+
}
|
|
71
|
+
impl PatternGroup {
|
|
72
|
+
pub(super) fn compile(names: &[Value], patterns: &[(Pattern, usize)]) -> Option<Self> {
|
|
73
|
+
if !(2..=64).contains(&patterns.len())
|
|
74
|
+
|| !patterns
|
|
75
|
+
.iter()
|
|
76
|
+
.all(|(pattern, _)| matches!(pattern, Pattern::Dfa(_)))
|
|
77
|
+
{
|
|
78
|
+
return None;
|
|
79
|
+
}
|
|
80
|
+
// Existing DFA compilation already proved syntax, encoding, compatibility
|
|
81
|
+
// and full start anchoring. Reuse exactly the same translation.
|
|
82
|
+
let sources: Option<Vec<_>> = names
|
|
83
|
+
.iter()
|
|
84
|
+
.map(|&name| {
|
|
85
|
+
std::str::from_utf8(unsafe { string_bytes(name) })
|
|
86
|
+
.ok()
|
|
87
|
+
.and_then(translate)
|
|
88
|
+
})
|
|
89
|
+
.collect();
|
|
90
|
+
let dfa = dense::Builder::new()
|
|
91
|
+
.configure(
|
|
92
|
+
dense::Config::new()
|
|
93
|
+
.match_kind(MatchKind::All)
|
|
94
|
+
.start_kind(StartKind::Anchored)
|
|
95
|
+
.dfa_size_limit(Some(1 << 20))
|
|
96
|
+
.determinize_size_limit(Some(1 << 22)),
|
|
97
|
+
)
|
|
98
|
+
.thompson(thompson::Config::new().nfa_size_limit(Some(1 << 20)))
|
|
99
|
+
.build_many(&sources?)
|
|
100
|
+
.ok()?;
|
|
101
|
+
let all = u64::MAX >> (64 - patterns.len());
|
|
102
|
+
Some(Self { dfa, all })
|
|
103
|
+
}
|
|
104
|
+
pub(super) fn matches(&self, value: Value) -> Option<u64> {
|
|
105
|
+
let mut data = std::ptr::null();
|
|
106
|
+
let mut len = 0;
|
|
107
|
+
let encoding = unsafe { sn_pattern_view(value, &mut data, &mut len) };
|
|
108
|
+
if encoding == ENCODING_OTHER {
|
|
109
|
+
return None;
|
|
110
|
+
}
|
|
111
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
112
|
+
if encoding != ENCODING_SEVEN_BIT
|
|
113
|
+
&& ((encoding == ENCODING_ASCII && !bytes.is_ascii())
|
|
114
|
+
|| std::str::from_utf8(bytes).is_err())
|
|
115
|
+
{
|
|
116
|
+
return None;
|
|
117
|
+
}
|
|
118
|
+
let input = Input::new(bytes).anchored(Anchored::Yes);
|
|
119
|
+
let mut state = OverlappingState::start();
|
|
120
|
+
let mut matches = 0;
|
|
121
|
+
loop {
|
|
122
|
+
self.dfa
|
|
123
|
+
.try_search_overlapping_fwd(&input, &mut state)
|
|
124
|
+
.ok()?;
|
|
125
|
+
let Some(found) = state.get_match() else {
|
|
126
|
+
break;
|
|
127
|
+
};
|
|
128
|
+
matches |= 1_u64 << found.pattern().as_usize();
|
|
129
|
+
if matches == self.all {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
Some(matches)
|
|
134
|
+
}
|
|
135
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
136
|
+
self.dfa.memory_usage()
|
|
137
|
+
}
|
|
18
138
|
}
|
|
19
139
|
|
|
20
140
|
impl Pattern {
|
|
21
141
|
// Select the crate matcher once for a homogeneous array. The borrowed
|
|
22
142
|
// Ruby slices are used synchronously; classification and encoding are
|
|
23
143
|
// still checked on every element, including non-string instances.
|
|
24
|
-
pub(super) fn eval_items(&self, required: i32, values: &[Value]) -> Verdict {
|
|
25
|
-
self.visit_invalid_items(required, values, |_, _| Invalid)
|
|
144
|
+
pub(super) fn eval_items(&self, caches: &Caches, required: i32, values: &[Value]) -> Verdict {
|
|
145
|
+
self.visit_invalid_items(caches, required, values, |_, _| Invalid)
|
|
26
146
|
}
|
|
27
147
|
|
|
28
148
|
pub(super) fn visit_invalid_items(
|
|
29
149
|
&self,
|
|
150
|
+
caches: &Caches,
|
|
30
151
|
required: i32,
|
|
31
152
|
values: &[Value],
|
|
32
153
|
invalid: impl FnMut(usize, Value) -> Verdict,
|
|
@@ -39,8 +160,8 @@ impl Pattern {
|
|
|
39
160
|
Err(_) => Unsupported,
|
|
40
161
|
}
|
|
41
162
|
}),
|
|
42
|
-
Self::Compiled
|
|
43
|
-
verdict(
|
|
163
|
+
Self::Compiled { regex, index } => scan_items(required, values, invalid, |bytes| {
|
|
164
|
+
verdict(caches.is_match(*index, regex, bytes))
|
|
44
165
|
}),
|
|
45
166
|
Self::Unsupported => scan_items(required, values, invalid, |_| Unsupported),
|
|
46
167
|
}
|
|
@@ -49,12 +170,12 @@ impl Pattern {
|
|
|
49
170
|
pub(super) fn memory_usage(&self) -> usize {
|
|
50
171
|
match self {
|
|
51
172
|
Self::Dfa(dfa) => std::mem::size_of_val(&**dfa) + dfa.memory_usage(),
|
|
52
|
-
Self::Compiled
|
|
173
|
+
Self::Compiled { regex, .. } => regex.memory_usage(),
|
|
53
174
|
Self::Unsupported => 0,
|
|
54
175
|
}
|
|
55
176
|
}
|
|
56
177
|
|
|
57
|
-
pub(super) fn compile(value: Value) -> Self {
|
|
178
|
+
pub(super) fn compile(value: Value, cache_count: &mut usize) -> Self {
|
|
58
179
|
if unsafe { sn_kind(value) } != TYPE_STRING {
|
|
59
180
|
return Self::Unsupported;
|
|
60
181
|
}
|
|
@@ -105,11 +226,15 @@ impl Pattern {
|
|
|
105
226
|
.hybrid_cache_capacity(1 << 20),
|
|
106
227
|
)
|
|
107
228
|
.build(&source)
|
|
108
|
-
.map(
|
|
229
|
+
.map(|regex| {
|
|
230
|
+
let index = *cache_count;
|
|
231
|
+
*cache_count += 1;
|
|
232
|
+
Self::Compiled { regex, index }
|
|
233
|
+
})
|
|
109
234
|
.unwrap_or(Self::Unsupported)
|
|
110
235
|
}
|
|
111
236
|
|
|
112
|
-
pub(super) fn eval(&self, value: Value) -> Verdict {
|
|
237
|
+
pub(super) fn eval(&self, caches: &Caches, value: Value) -> Verdict {
|
|
113
238
|
if matches!(self, Self::Unsupported) {
|
|
114
239
|
return Unsupported;
|
|
115
240
|
}
|
|
@@ -120,10 +245,10 @@ impl Pattern {
|
|
|
120
245
|
return Unsupported;
|
|
121
246
|
}
|
|
122
247
|
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
123
|
-
self.eval_bytes(bytes, encoding)
|
|
248
|
+
self.eval_bytes(caches, bytes, encoding)
|
|
124
249
|
}
|
|
125
250
|
|
|
126
|
-
pub(super) fn eval_bytes(&self, bytes: &[u8], encoding: i32) -> Verdict {
|
|
251
|
+
pub(super) fn eval_bytes(&self, caches: &Caches, bytes: &[u8], encoding: i32) -> Verdict {
|
|
127
252
|
if encoding == ENCODING_OTHER || matches!(self, Self::Unsupported) {
|
|
128
253
|
return Unsupported;
|
|
129
254
|
}
|
|
@@ -140,7 +265,7 @@ impl Pattern {
|
|
|
140
265
|
Ok(matched) => matched.is_some(),
|
|
141
266
|
Err(_) => return Unsupported,
|
|
142
267
|
},
|
|
143
|
-
Self::Compiled
|
|
268
|
+
Self::Compiled { regex, index } => caches.is_match(*index, regex, bytes),
|
|
144
269
|
Self::Unsupported => return Unsupported,
|
|
145
270
|
};
|
|
146
271
|
if matched { Valid } else { Invalid }
|
|
@@ -286,3 +411,21 @@ fn compatible(ast: &Ast) -> bool {
|
|
|
286
411
|
_ => true,
|
|
287
412
|
}
|
|
288
413
|
}
|
|
414
|
+
|
|
415
|
+
#[cfg(test)]
|
|
416
|
+
mod tests {
|
|
417
|
+
use super::*;
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn reports_memory_when_a_search_cache_is_borrowed() {
|
|
421
|
+
let caches = Caches::default();
|
|
422
|
+
let regex = Regex::new("value[0-9]+").unwrap();
|
|
423
|
+
assert!(caches.is_match(0, ®ex, b"value1"));
|
|
424
|
+
assert!(unsafe { sn_caches_size(&caches) } > std::mem::size_of::<Caches>());
|
|
425
|
+
let _search = caches.0.borrow_mut();
|
|
426
|
+
assert_eq!(
|
|
427
|
+
unsafe { sn_caches_size(&caches) },
|
|
428
|
+
std::mem::size_of::<Caches>()
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|