schemurai 3.0.1 → 3.1.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 +28 -7
- data/ext/schemurai_native/Cargo.lock +59 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +5 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +56 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1064 -325
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +8 -6
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/lib.rs +910 -237
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +210 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +113 -101
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_limits.rb +65 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/limited_evaluator.rb +2 -59
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +8 -78
- metadata +25 -2
|
@@ -7,13 +7,14 @@ const MAX_INDEX_BYTES: usize = 64 * 1024 * 1024;
|
|
|
7
7
|
|
|
8
8
|
#[derive(Debug)]
|
|
9
9
|
pub(super) struct Enumeration {
|
|
10
|
+
source: Value,
|
|
10
11
|
values: Vec<Value>,
|
|
11
12
|
index: Option<Index>,
|
|
12
13
|
kinds: i32,
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
impl Enumeration {
|
|
16
|
-
pub(super) fn compile(values: Vec<Value>, fixnum_flag: Value) -> Self {
|
|
17
|
+
pub(super) fn compile(source: Value, values: Vec<Value>, fixnum_flag: Value) -> Self {
|
|
17
18
|
let mut fingerprints = structural::Cache::batch(0);
|
|
18
19
|
let budget = budget::Budget::default();
|
|
19
20
|
let index = (values.len() > LINEAR_LIMIT)
|
|
@@ -31,12 +32,16 @@ impl Enumeration {
|
|
|
31
32
|
0
|
|
32
33
|
};
|
|
33
34
|
Self {
|
|
35
|
+
source,
|
|
34
36
|
values,
|
|
35
37
|
index,
|
|
36
38
|
kinds,
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
pub(super) fn runtime(&self, caches: &runtime::Caches, value: Value) -> Verdict {
|
|
43
|
+
caches.equality(self.source, value, RUNTIME_ENUM)
|
|
44
|
+
}
|
|
40
45
|
pub(super) fn eval(
|
|
41
46
|
&self,
|
|
42
47
|
value: Value,
|
|
@@ -42,7 +42,6 @@ impl BranchPolicy {
|
|
|
42
42
|
pub(super) trait BranchResult {
|
|
43
43
|
fn valid() -> Self;
|
|
44
44
|
fn status(&self) -> Verdict;
|
|
45
|
-
fn merge(&mut self, branch: Self);
|
|
46
45
|
}
|
|
47
46
|
impl BranchResult for Verdict {
|
|
48
47
|
fn valid() -> Self {
|
|
@@ -51,7 +50,6 @@ impl BranchResult for Verdict {
|
|
|
51
50
|
fn status(&self) -> Verdict {
|
|
52
51
|
*self
|
|
53
52
|
}
|
|
54
|
-
fn merge(&mut self, _: Self) {}
|
|
55
53
|
}
|
|
56
54
|
impl BranchResult for Tracked {
|
|
57
55
|
fn valid() -> Self {
|
|
@@ -60,9 +58,6 @@ impl BranchResult for Tracked {
|
|
|
60
58
|
fn status(&self) -> Verdict {
|
|
61
59
|
self.status
|
|
62
60
|
}
|
|
63
|
-
fn merge(&mut self, branch: Self) {
|
|
64
|
-
self.merge(branch);
|
|
65
|
-
}
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
pub(super) struct BranchMatches<R> {
|
|
@@ -72,33 +67,44 @@ pub(super) struct BranchMatches<R> {
|
|
|
72
67
|
pub(super) fn scan_branches<R: BranchResult>(
|
|
73
68
|
nodes: &[(usize, usize)],
|
|
74
69
|
policy: BranchPolicy,
|
|
70
|
+
separate_calls: bool,
|
|
75
71
|
mut evaluate: impl FnMut(usize) -> R,
|
|
72
|
+
mut merge: impl FnMut(&mut R, R) -> Result<(), Verdict>,
|
|
76
73
|
) -> Result<BranchMatches<R>, Verdict> {
|
|
77
74
|
let mut result = BranchMatches {
|
|
78
75
|
matches: 0,
|
|
79
76
|
coverage: R::valid(),
|
|
80
77
|
};
|
|
81
|
-
for &(node, multiplicity) in nodes {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
'groups: for &(node, multiplicity) in nodes {
|
|
79
|
+
for _ in 0..if separate_calls { multiplicity } else { 1 } {
|
|
80
|
+
let branch = evaluate(node);
|
|
81
|
+
match branch.status() {
|
|
82
|
+
Unsupported => return Err(Unsupported),
|
|
83
|
+
Invalid => {}
|
|
84
|
+
Valid => {
|
|
85
|
+
result.matches += if separate_calls { 1 } else { multiplicity };
|
|
86
|
+
merge(&mut result.coverage, branch)?;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if policy.finished(result.matches) {
|
|
90
|
+
break 'groups;
|
|
89
91
|
}
|
|
90
|
-
}
|
|
91
|
-
if policy.finished(result.matches) {
|
|
92
|
-
break;
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
Ok(result)
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
#[derive(Clone, Copy)]
|
|
98
|
+
pub(super) enum ContainsPolicy {
|
|
99
|
+
FirstMatch,
|
|
100
|
+
AllMatches,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Older dialects use any? for untracked validity; counted validity,
|
|
104
|
+
// diagnostic counts and annotations need every match.
|
|
100
105
|
pub(super) fn scan_contains(
|
|
101
106
|
len: usize,
|
|
107
|
+
policy: ContainsPolicy,
|
|
102
108
|
mut evaluate: impl FnMut(usize) -> Verdict,
|
|
103
109
|
mut record: impl FnMut(usize),
|
|
104
110
|
) -> Result<usize, Verdict> {
|
|
@@ -110,6 +116,9 @@ pub(super) fn scan_contains(
|
|
|
110
116
|
Valid => {
|
|
111
117
|
matches += 1;
|
|
112
118
|
record(index);
|
|
119
|
+
if matches!(policy, ContainsPolicy::FirstMatch) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
124
|
}
|
|
@@ -184,7 +193,14 @@ mod tests {
|
|
|
184
193
|
let nodes = [(0, 2), (1, 1)];
|
|
185
194
|
let evaluate = |node| if node == 0 { Valid } else { Unsupported };
|
|
186
195
|
for kind in [BranchKind::Any, BranchKind::One] {
|
|
187
|
-
let result = scan_branches(
|
|
196
|
+
let result = scan_branches(
|
|
197
|
+
&nodes,
|
|
198
|
+
BranchPolicy::Validity(kind),
|
|
199
|
+
false,
|
|
200
|
+
evaluate,
|
|
201
|
+
|_, _| Ok(()),
|
|
202
|
+
)
|
|
203
|
+
.unwrap();
|
|
188
204
|
assert_eq!(result.matches, 2);
|
|
189
205
|
assert_eq!(
|
|
190
206
|
kind.accepts(result.matches),
|
|
@@ -192,7 +208,9 @@ mod tests {
|
|
|
192
208
|
);
|
|
193
209
|
}
|
|
194
210
|
assert!(matches!(
|
|
195
|
-
scan_branches(&nodes, BranchPolicy::Complete, evaluate
|
|
211
|
+
scan_branches(&nodes, BranchPolicy::Complete, false, evaluate, |_, _| Ok(
|
|
212
|
+
()
|
|
213
|
+
)),
|
|
196
214
|
Err(Unsupported)
|
|
197
215
|
));
|
|
198
216
|
}
|
|
@@ -202,6 +220,7 @@ mod tests {
|
|
|
202
220
|
let mut recorded = vec![];
|
|
203
221
|
let result = scan_contains(
|
|
204
222
|
4,
|
|
223
|
+
ContainsPolicy::AllMatches,
|
|
205
224
|
|index| verdict(index % 2 == 0),
|
|
206
225
|
|index| recorded.push(index),
|
|
207
226
|
);
|
|
@@ -210,6 +229,16 @@ mod tests {
|
|
|
210
229
|
assert_eq!(
|
|
211
230
|
scan_contains(
|
|
212
231
|
2,
|
|
232
|
+
ContainsPolicy::FirstMatch,
|
|
233
|
+
|index| if index == 0 { Valid } else { Unsupported },
|
|
234
|
+
|_| {}
|
|
235
|
+
),
|
|
236
|
+
Ok(1)
|
|
237
|
+
);
|
|
238
|
+
assert_eq!(
|
|
239
|
+
scan_contains(
|
|
240
|
+
2,
|
|
241
|
+
ContainsPolicy::AllMatches,
|
|
213
242
|
|index| if index == 0 { Valid } else { Unsupported },
|
|
214
243
|
|_| {}
|
|
215
244
|
),
|
|
@@ -51,9 +51,6 @@ impl Format {
|
|
|
51
51
|
Self::Unsupported => return Unsupported,
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
|
-
pub(super) fn eval_items(&self, required: i32, values: &[Value]) -> Verdict {
|
|
55
|
-
self.visit_invalid_items(required, values, |_, _| Invalid)
|
|
56
|
-
}
|
|
57
54
|
pub(super) fn visit_invalid_items(
|
|
58
55
|
&self,
|
|
59
56
|
required: i32,
|
|
@@ -91,9 +88,14 @@ fn scan_items(
|
|
|
91
88
|
let mut data = std::ptr::null();
|
|
92
89
|
let mut len = 0;
|
|
93
90
|
let kind = unsafe { sn_format_item_view(value, &mut data, &mut len) };
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
if kind == -TYPE_UNSUPPORTED {
|
|
92
|
+
let status = invalid(index, value);
|
|
93
|
+
if status != Valid {
|
|
94
|
+
return status;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
let result = if kind < 0 {
|
|
97
99
|
verdict(required == 0)
|
|
98
100
|
} else {
|
|
99
101
|
matches(unsafe { byte_view(data, len) })
|
|
@@ -26,6 +26,34 @@ pub(super) fn postorder(edges: &[Vec<usize>], seen: &mut [bool]) -> Vec<usize> {
|
|
|
26
26
|
order
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
pub(super) fn parents(edges: &[Vec<usize>]) -> Vec<Vec<usize>> {
|
|
30
|
+
let mut parents = vec![vec![]; edges.len()];
|
|
31
|
+
for (parent, children) in edges.iter().enumerate() {
|
|
32
|
+
for &child in children {
|
|
33
|
+
parents[child].push(parent);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
parents
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Reachability flags flow from children to parents, including through cycles.
|
|
40
|
+
// Each newly marked node enters the queue once.
|
|
41
|
+
pub(super) fn propagate(flags: &mut [bool], parents: &[Vec<usize>]) {
|
|
42
|
+
let mut queue: std::collections::VecDeque<_> = flags
|
|
43
|
+
.iter()
|
|
44
|
+
.enumerate()
|
|
45
|
+
.filter_map(|(node, &set)| set.then_some(node))
|
|
46
|
+
.collect();
|
|
47
|
+
while let Some(node) = queue.pop_front() {
|
|
48
|
+
for &parent in &parents[node] {
|
|
49
|
+
if !flags[parent] {
|
|
50
|
+
flags[parent] = true;
|
|
51
|
+
queue.push_back(parent);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
#[cfg(test)]
|
|
30
58
|
mod tests {
|
|
31
59
|
use super::*;
|
|
@@ -39,6 +67,15 @@ mod tests {
|
|
|
39
67
|
assert!(postorder(&[], &mut []).is_empty());
|
|
40
68
|
}
|
|
41
69
|
|
|
70
|
+
#[test]
|
|
71
|
+
fn flags_propagate_through_cycles_without_marking_disconnected_nodes() {
|
|
72
|
+
let parents = vec![vec![2], vec![0], vec![1, 3], vec![], vec![4]];
|
|
73
|
+
let mut flags = vec![false, true, false, false, false];
|
|
74
|
+
propagate(&mut flags, &parents);
|
|
75
|
+
assert_eq!(flags, [true, true, true, true, false]);
|
|
76
|
+
propagate(&mut [], &[]);
|
|
77
|
+
}
|
|
78
|
+
|
|
42
79
|
#[test]
|
|
43
80
|
fn deep_chains_use_an_explicit_stack() {
|
|
44
81
|
let count = 100_000;
|
|
@@ -82,3 +82,93 @@ unsafe extern "C" fn entry(context: *mut c_void, _key: Value, value: Value) -> i
|
|
|
82
82
|
}))
|
|
83
83
|
.unwrap_or(VERDICT_INVALID)
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
enum Work {
|
|
87
|
+
Visit(Value),
|
|
88
|
+
Finish(Value),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
pub(super) enum Safety {
|
|
92
|
+
Ordinary,
|
|
93
|
+
Cyclic,
|
|
94
|
+
Unsupported,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A failed fingerprint may indicate either an optimization bound or Ruby
|
|
98
|
+
// behavior that native equality must not skip. Classify every reachable value
|
|
99
|
+
// before substituting direct comparisons or a stack error for hashing. Continue
|
|
100
|
+
// after detecting a cycle: any special value still requires Ruby's behavior.
|
|
101
|
+
// Unlike the memoization hint above, this rare path needs no depth/visit cap.
|
|
102
|
+
pub(super) fn ordinary_graph(value: Value, budget: &budget::Budget) -> Safety {
|
|
103
|
+
let mut work = vec![Work::Visit(value)];
|
|
104
|
+
let mut states = HashMap::new();
|
|
105
|
+
let mut cyclic = false;
|
|
106
|
+
while let Some(task) = work.pop() {
|
|
107
|
+
if !budget.checkpoint() {
|
|
108
|
+
return Safety::Unsupported;
|
|
109
|
+
}
|
|
110
|
+
match task {
|
|
111
|
+
Work::Finish(value) => {
|
|
112
|
+
states.insert(value, true);
|
|
113
|
+
}
|
|
114
|
+
Work::Visit(value) => {
|
|
115
|
+
if let Some(&done) = states.get(&value) {
|
|
116
|
+
if !done {
|
|
117
|
+
cyclic = true;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
match unsafe { sn_kind(value) } {
|
|
122
|
+
TYPE_UNSUPPORTED => return Safety::Unsupported,
|
|
123
|
+
TYPE_ARRAY => {
|
|
124
|
+
states.insert(value, false);
|
|
125
|
+
work.push(Work::Finish(value));
|
|
126
|
+
for i in (0..unsafe { sn_len(value) }).rev() {
|
|
127
|
+
work.push(Work::Visit(unsafe { sn_entry(value, i) }));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
TYPE_OBJECT => {
|
|
131
|
+
states.insert(value, false);
|
|
132
|
+
work.push(Work::Finish(value));
|
|
133
|
+
let mut context = OrdinaryWalk {
|
|
134
|
+
work: &mut work,
|
|
135
|
+
budget,
|
|
136
|
+
};
|
|
137
|
+
if unsafe {
|
|
138
|
+
sn_hash_each(
|
|
139
|
+
value,
|
|
140
|
+
(&mut context as *mut OrdinaryWalk<'_>).cast(),
|
|
141
|
+
ordinary_entry,
|
|
142
|
+
)
|
|
143
|
+
} != VERDICT_VALID
|
|
144
|
+
{
|
|
145
|
+
return Safety::Unsupported;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
_ => {}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if cyclic {
|
|
154
|
+
Safety::Cyclic
|
|
155
|
+
} else {
|
|
156
|
+
Safety::Ordinary
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
struct OrdinaryWalk<'a> {
|
|
161
|
+
work: &'a mut Vec<Work>,
|
|
162
|
+
budget: &'a budget::Budget,
|
|
163
|
+
}
|
|
164
|
+
unsafe extern "C" fn ordinary_entry(context: *mut c_void, _: Value, value: Value) -> i32 {
|
|
165
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
166
|
+
let context = unsafe { &mut *context.cast::<OrdinaryWalk<'_>>() };
|
|
167
|
+
if !context.budget.checkpoint() {
|
|
168
|
+
return VERDICT_UNSUPPORTED;
|
|
169
|
+
}
|
|
170
|
+
context.work.push(Work::Visit(value));
|
|
171
|
+
VERDICT_VALID
|
|
172
|
+
}))
|
|
173
|
+
.unwrap_or(VERDICT_UNSUPPORTED)
|
|
174
|
+
}
|