schemurai 3.0.0 → 3.0.2
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 +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -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 +74 -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 +1087 -307
- 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 +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- 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_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- 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 +7 -22
- metadata +25 -2
|
@@ -38,6 +38,8 @@ SN_CONSTANT(TYPE_INTEGER, 32)
|
|
|
38
38
|
SN_CONSTANT(TYPE_STRING, 64)
|
|
39
39
|
SN_CONSTANT(TYPE_INTEGRAL_NUMBER, 48)
|
|
40
40
|
SN_CONSTANT(TYPE_UNSUPPORTED, 128)
|
|
41
|
+
/* Native-only scalar kind outside the JSON type masks. */
|
|
42
|
+
SN_CONSTANT(TYPE_IDENTITY_SCALAR, 256)
|
|
41
43
|
SN_CONSTANT(MAXIMUM, 1)
|
|
42
44
|
SN_CONSTANT(MINIMUM, 2)
|
|
43
45
|
SN_CONSTANT(EXCLUSIVE_MAXIMUM, 4)
|
|
@@ -50,9 +52,48 @@ SN_CONSTANT(TRACKS_DYNAMIC_SCOPE, 2)
|
|
|
50
52
|
SN_CONSTANT(VERDICT_INVALID, 0)
|
|
51
53
|
SN_CONSTANT(VERDICT_VALID, 1)
|
|
52
54
|
SN_CONSTANT(VERDICT_UNSUPPORTED, 2)
|
|
55
|
+
SN_CONSTANT(REGEXP_INVALID, 3)
|
|
56
|
+
|
|
57
|
+
/* Runtime requests handled by C only after Rust evaluation returns. */
|
|
58
|
+
SN_CONSTANT(RUNTIME_REGEXP_STRICT, 0)
|
|
59
|
+
SN_CONSTANT(RUNTIME_REGEXP_ASSERTION, 1)
|
|
60
|
+
SN_CONSTANT(RUNTIME_JSON, 2)
|
|
61
|
+
SN_CONSTANT(RUNTIME_POINTER, 3)
|
|
62
|
+
SN_CONSTANT(RUNTIME_IDENTITY_METHODS, 4)
|
|
63
|
+
SN_CONSTANT(RUNTIME_REQUIRED_MISSING, 5)
|
|
64
|
+
SN_CONSTANT(RUNTIME_DIAGNOSTIC_MESSAGE, 6)
|
|
65
|
+
SN_CONSTANT(RUNTIME_SCALAR_CONTAINS, 7)
|
|
66
|
+
/* Null numeric requests add twice the assertion bit and the decimal-mode bit. */
|
|
67
|
+
SN_CONSTANT(RUNTIME_NULL_NUMBER, 8)
|
|
68
|
+
SN_CONSTANT(RUNTIME_OBJECT_REPORT, 55)
|
|
69
|
+
SN_CONSTANT(RUNTIME_PROPERTY_MERGE, 56)
|
|
70
|
+
SN_CONSTANT(RUNTIME_PATH_POP, 57)
|
|
71
|
+
SN_CONSTANT(RUNTIME_ARRAY_REPORT, 58)
|
|
72
|
+
SN_CONSTANT(RUNTIME_NATIVE_REPORT, 59)
|
|
73
|
+
SN_CONSTANT(RUNTIME_NATIVE_CHILD, 60)
|
|
74
|
+
SN_CONSTANT(RUNTIME_OBJECT_CHILDREN, 61)
|
|
75
|
+
SN_CONSTANT(RUNTIME_ARRAY_CHILDREN, 62)
|
|
76
|
+
SN_CONSTANT(RUNTIME_CALLBACK_CHECKPOINT, 63)
|
|
77
|
+
/* Type requests add twice the type mask and the union-opcode bit. */
|
|
78
|
+
SN_CONSTANT(RUNTIME_TYPE, 64)
|
|
79
|
+
/* Numeric frame requests add 64 times the step and the required type mask. */
|
|
80
|
+
SN_CONSTANT(RUNTIME_NUMBER, 1024)
|
|
81
|
+
/* String frames add 256 times the step, required type and option flags. */
|
|
82
|
+
SN_CONSTANT(RUNTIME_STRING, 2048)
|
|
83
|
+
/* Occurrence-based Ruby value primitives, without schema evaluation. */
|
|
84
|
+
SN_CONSTANT(RUNTIME_EQUAL, 8192)
|
|
85
|
+
SN_CONSTANT(RUNTIME_ENUM, 8193)
|
|
86
|
+
SN_CONSTANT(RUNTIME_UNIQUE, 8194)
|
|
87
|
+
/* Container header frames add 64 times the step and required type mask. */
|
|
88
|
+
SN_CONSTANT(RUNTIME_ARRAY_HEADER, 16384)
|
|
89
|
+
SN_CONSTANT(RUNTIME_OBJECT_HEADER, 32768)
|
|
53
90
|
SN_CONSTANT(ADAPTER_UNSUPPORTED, 3)
|
|
54
91
|
SN_CONSTANT(STATUS_PANIC, -1)
|
|
55
92
|
SN_CONSTANT(STATUS_EXCEPTION, -2)
|
|
93
|
+
SN_CONSTANT(STATUS_STACK_ERROR, -3)
|
|
94
|
+
SN_CONSTANT(STATUS_DECIMAL_NAN, -4)
|
|
95
|
+
SN_CONSTANT(STATUS_DECIMAL_POSITIVE_INFINITY, -5)
|
|
96
|
+
SN_CONSTANT(STATUS_DECIMAL_NEGATIVE_INFINITY, -6)
|
|
56
97
|
SN_CONSTANT(OPCODE_UNKNOWN, -1)
|
|
57
98
|
SN_CONSTANT(SMALL_HASH_ORDINARY, 16)
|
|
58
99
|
SN_CONSTANT(SMALL_HASH_COUNT_MASK, 15)
|
|
@@ -61,3 +102,7 @@ SN_CONSTANT(ENCODING_OTHER, 0)
|
|
|
61
102
|
SN_CONSTANT(ENCODING_UTF8, 1)
|
|
62
103
|
SN_CONSTANT(ENCODING_ASCII, 2)
|
|
63
104
|
SN_CONSTANT(ENCODING_SEVEN_BIT, 3)
|
|
105
|
+
|
|
106
|
+
SN_CONSTANT(CALLBACK_RUNTIME, 0)
|
|
107
|
+
SN_CONSTANT(CALLBACK_BOOLEAN, 1)
|
|
108
|
+
SN_CONSTANT(CALLBACK_TYPE, 2)
|
|
@@ -19,10 +19,21 @@ pub struct Outcome {
|
|
|
19
19
|
pub(super) struct Budget {
|
|
20
20
|
pub limits: Limits,
|
|
21
21
|
pub hit: Cell<(u32, usize)>,
|
|
22
|
+
pub failure: Cell<i32>,
|
|
22
23
|
deadline: Option<Instant>,
|
|
23
24
|
checks: Cell<usize>,
|
|
24
25
|
}
|
|
25
26
|
impl Budget {
|
|
27
|
+
// Defer Ruby exception construction until all Rust frames have unwound.
|
|
28
|
+
pub fn status(&self, verdict: super::Verdict) -> i32 {
|
|
29
|
+
let failure = self.failure.get();
|
|
30
|
+
if failure != 0 {
|
|
31
|
+
failure
|
|
32
|
+
} else {
|
|
33
|
+
verdict as i32
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
pub fn new(limits: Limits) -> Self {
|
|
27
38
|
Self {
|
|
28
39
|
limits,
|
|
@@ -36,6 +47,15 @@ impl Budget {
|
|
|
36
47
|
..Self::default()
|
|
37
48
|
}
|
|
38
49
|
}
|
|
50
|
+
pub fn resume(limits: Limits, deadline: &Cell<Option<Instant>>) -> Self {
|
|
51
|
+
let mut budget = Self::new(limits);
|
|
52
|
+
if let Some(original) = deadline.get() {
|
|
53
|
+
budget.deadline = Some(original);
|
|
54
|
+
} else {
|
|
55
|
+
deadline.set(budget.deadline);
|
|
56
|
+
}
|
|
57
|
+
budget
|
|
58
|
+
}
|
|
39
59
|
pub fn timed(&self) -> bool {
|
|
40
60
|
self.limits.enabled & 2 != 0
|
|
41
61
|
}
|
|
@@ -98,4 +118,33 @@ mod tests {
|
|
|
98
118
|
.check_deadline()
|
|
99
119
|
);
|
|
100
120
|
}
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn runtime_retries_retain_the_original_native_deadline() {
|
|
124
|
+
let original = Instant::now() - Duration::from_secs(1);
|
|
125
|
+
let deadline = Cell::new(Some(original));
|
|
126
|
+
let budget = Budget::resume(
|
|
127
|
+
Limits {
|
|
128
|
+
timeout: 10.0,
|
|
129
|
+
enabled: 2,
|
|
130
|
+
..Limits::default()
|
|
131
|
+
},
|
|
132
|
+
&deadline,
|
|
133
|
+
);
|
|
134
|
+
assert!(!budget.check_deadline());
|
|
135
|
+
assert_eq!(budget.hit.get(), (2, 0));
|
|
136
|
+
assert_eq!(deadline.get(), Some(original));
|
|
137
|
+
deadline.set(None);
|
|
138
|
+
assert!(
|
|
139
|
+
Budget::resume(
|
|
140
|
+
Limits {
|
|
141
|
+
timeout: 10.0,
|
|
142
|
+
enabled: 2,
|
|
143
|
+
..Limits::default()
|
|
144
|
+
},
|
|
145
|
+
&deadline
|
|
146
|
+
)
|
|
147
|
+
.check_deadline()
|
|
148
|
+
);
|
|
149
|
+
}
|
|
101
150
|
}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
//! Ruby container primitives and native-child requests without storage loans.
|
|
2
|
+
use super::*;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug)]
|
|
5
|
+
pub(super) struct Header {
|
|
6
|
+
source: Value,
|
|
7
|
+
checks: u8,
|
|
8
|
+
object: bool,
|
|
9
|
+
native_bounds: bool,
|
|
10
|
+
native_required: bool,
|
|
11
|
+
required_count: usize,
|
|
12
|
+
native_dependencies: bool,
|
|
13
|
+
dependencies: Vec<Dependency>,
|
|
14
|
+
}
|
|
15
|
+
#[derive(Debug)]
|
|
16
|
+
struct Dependency {
|
|
17
|
+
count: usize,
|
|
18
|
+
keyword: &'static str,
|
|
19
|
+
}
|
|
20
|
+
impl Header {
|
|
21
|
+
pub(super) fn contains(
|
|
22
|
+
&self,
|
|
23
|
+
caches: &runtime::Caches,
|
|
24
|
+
required: i32,
|
|
25
|
+
value: Value,
|
|
26
|
+
child: usize,
|
|
27
|
+
scalar: (i32, i32),
|
|
28
|
+
) -> Verdict {
|
|
29
|
+
caches.contains(self.source, value, child, required, scalar)
|
|
30
|
+
}
|
|
31
|
+
pub(super) fn children(
|
|
32
|
+
&self,
|
|
33
|
+
caches: &runtime::Caches,
|
|
34
|
+
required: i32,
|
|
35
|
+
value: Value,
|
|
36
|
+
node: usize,
|
|
37
|
+
) -> Verdict {
|
|
38
|
+
caches.container_children(self.source, value, node, required, self.object)
|
|
39
|
+
}
|
|
40
|
+
pub(super) fn runtime_required(&self, caches: &runtime::Caches, kind: i32) -> bool {
|
|
41
|
+
!self.native_bounds
|
|
42
|
+
|| !self.native_required
|
|
43
|
+
|| !self.native_dependencies
|
|
44
|
+
|| kind == TYPE_UNSUPPORTED
|
|
45
|
+
|| caches.dynamic_calls()
|
|
46
|
+
}
|
|
47
|
+
pub(super) fn valid(&self, caches: &runtime::Caches, required: i32, value: Value) -> Verdict {
|
|
48
|
+
caches
|
|
49
|
+
.container(self.source, value, required, self.object)
|
|
50
|
+
.map_or(Unsupported, |frame| frame.step(0))
|
|
51
|
+
}
|
|
52
|
+
pub(super) fn failures(
|
|
53
|
+
&self,
|
|
54
|
+
caches: &runtime::Caches,
|
|
55
|
+
required: i32,
|
|
56
|
+
value: Value,
|
|
57
|
+
silent: bool,
|
|
58
|
+
mut visit: impl FnMut(&'static str, Value) -> Result<(), Verdict>,
|
|
59
|
+
) -> Result<bool, Verdict> {
|
|
60
|
+
let frame = caches.container(self.source, value, required, self.object)?;
|
|
61
|
+
if !frame.predicate(1)? {
|
|
62
|
+
if required != 0 {
|
|
63
|
+
visit("type", frame.ordinal())?;
|
|
64
|
+
}
|
|
65
|
+
return Ok(false);
|
|
66
|
+
}
|
|
67
|
+
for (bit, step, keyword) in [
|
|
68
|
+
(
|
|
69
|
+
1,
|
|
70
|
+
2,
|
|
71
|
+
if self.object {
|
|
72
|
+
"maxProperties"
|
|
73
|
+
} else {
|
|
74
|
+
"maxItems"
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
(
|
|
78
|
+
2,
|
|
79
|
+
3,
|
|
80
|
+
if self.object {
|
|
81
|
+
"minProperties"
|
|
82
|
+
} else {
|
|
83
|
+
"minItems"
|
|
84
|
+
},
|
|
85
|
+
),
|
|
86
|
+
(4, 4, "uniqueItems"),
|
|
87
|
+
] {
|
|
88
|
+
if self.checks & bit != 0 && !frame.predicate(step)? {
|
|
89
|
+
visit(keyword, frame.ordinal())?;
|
|
90
|
+
if silent {
|
|
91
|
+
return Ok(true);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if self.checks & 32 != 0 {
|
|
96
|
+
frame.predicate(7)?;
|
|
97
|
+
for index in 0..self.required_count {
|
|
98
|
+
let step = i32::try_from(index)
|
|
99
|
+
.ok()
|
|
100
|
+
.and_then(|index| index.checked_add(8))
|
|
101
|
+
.ok_or(Unsupported)?;
|
|
102
|
+
if !frame.predicate(step)? {
|
|
103
|
+
visit("required", frame.ordinal())?;
|
|
104
|
+
if silent {
|
|
105
|
+
return Ok(true);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if self.checks & 8 != 0 {
|
|
111
|
+
frame.predicate(5)?;
|
|
112
|
+
}
|
|
113
|
+
if self.checks & 16 != 0 {
|
|
114
|
+
frame.predicate(6)?;
|
|
115
|
+
}
|
|
116
|
+
let mut step = self.required_count.checked_add(8).ok_or(Unsupported)?;
|
|
117
|
+
for dependency in &self.dependencies {
|
|
118
|
+
let trigger = i32::try_from(step).map_err(|_| Unsupported)?;
|
|
119
|
+
if frame.predicate(trigger)? {
|
|
120
|
+
frame.predicate(trigger.checked_add(1).ok_or(Unsupported)?)?;
|
|
121
|
+
for index in 0..dependency.count {
|
|
122
|
+
let key = step
|
|
123
|
+
.checked_add(index)
|
|
124
|
+
.and_then(|step| step.checked_add(2))
|
|
125
|
+
.and_then(|step| i32::try_from(step).ok())
|
|
126
|
+
.ok_or(Unsupported)?;
|
|
127
|
+
if !frame.predicate(key)? {
|
|
128
|
+
visit(dependency.keyword, frame.ordinal())?;
|
|
129
|
+
if silent {
|
|
130
|
+
return Ok(true);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
step = step
|
|
136
|
+
.checked_add(dependency.count)
|
|
137
|
+
.and_then(|step| step.checked_add(2))
|
|
138
|
+
.ok_or(Unsupported)?;
|
|
139
|
+
}
|
|
140
|
+
Ok(true)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
impl Engine {
|
|
144
|
+
pub(super) fn scalar_callbacks_safe(&self, root: usize) -> bool {
|
|
145
|
+
self.callback_roots.get(root) == Some(&true)
|
|
146
|
+
}
|
|
147
|
+
pub(super) fn native_continuations_safe(&self, root: usize) -> bool {
|
|
148
|
+
self.validity_supported.get(root) == Some(&true)
|
|
149
|
+
}
|
|
150
|
+
pub(super) fn report_continuations_safe(&self, root: usize) -> bool {
|
|
151
|
+
self.report_supported.get(root) == Some(&true)
|
|
152
|
+
}
|
|
153
|
+
// Admit callbacks only when every reachable assertion has a runtime path.
|
|
154
|
+
// Otherwise a later unsupported container could replay an already invoked
|
|
155
|
+
// user block through the VM. Native continuations bound physical stack
|
|
156
|
+
// depth, so cycles need coverage propagation rather than a DAG proof.
|
|
157
|
+
pub(super) fn compile_native_validity_support(&mut self) {
|
|
158
|
+
let count = self.nodes.len();
|
|
159
|
+
self.has_references = self
|
|
160
|
+
.nodes
|
|
161
|
+
.iter()
|
|
162
|
+
.flatten()
|
|
163
|
+
.any(|op| matches!(op, Op::Ref(_)));
|
|
164
|
+
// One zero-depth dispatch vertex per name avoids repeating every
|
|
165
|
+
// possible binding edge for each dynamic reference.
|
|
166
|
+
let recursive = count + self.anchor_tables.len();
|
|
167
|
+
let mut edges = vec![vec![]; recursive + 1];
|
|
168
|
+
let mut admitted = vec![true; edges.len()];
|
|
169
|
+
let mut callbacks = vec![false; edges.len()];
|
|
170
|
+
let mut report_unsupported = vec![false; edges.len()];
|
|
171
|
+
for (name, bindings) in self.anchor_tables.iter().enumerate() {
|
|
172
|
+
edges[count + name].extend(bindings.iter().map(|&(_, target)| target));
|
|
173
|
+
}
|
|
174
|
+
edges[recursive].extend(
|
|
175
|
+
self.resources
|
|
176
|
+
.values()
|
|
177
|
+
.copied()
|
|
178
|
+
.filter(|&root| self.metadata[root].recursive_anchor),
|
|
179
|
+
);
|
|
180
|
+
for (node, ops) in self.nodes.iter().enumerate() {
|
|
181
|
+
admitted[node] = true;
|
|
182
|
+
for op in ops {
|
|
183
|
+
// Unresolved references have native failure and report paths;
|
|
184
|
+
// only instructions without an execution path block callbacks.
|
|
185
|
+
report_unsupported[node] |= matches!(op, Op::Unsupported);
|
|
186
|
+
match op {
|
|
187
|
+
Op::UnresolvedRef(_)
|
|
188
|
+
| Op::Boolean(_)
|
|
189
|
+
| Op::Type(..)
|
|
190
|
+
| Op::Number(..)
|
|
191
|
+
| Op::String(..)
|
|
192
|
+
| Op::Const(_)
|
|
193
|
+
| Op::Enum(_) => {}
|
|
194
|
+
Op::Array(_, rules) if rules.header_only() => {}
|
|
195
|
+
Op::Array(_, rules) => {
|
|
196
|
+
callbacks[node] = true;
|
|
197
|
+
edges[node].extend(
|
|
198
|
+
rules
|
|
199
|
+
.callback_children()
|
|
200
|
+
.chain(rules.unevaluated.iter())
|
|
201
|
+
.copied(),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
Op::Object(_, rules) if rules.header_only() => {}
|
|
205
|
+
Op::Object(_, rules) => {
|
|
206
|
+
callbacks[node] = true;
|
|
207
|
+
edges[node].extend(
|
|
208
|
+
rules
|
|
209
|
+
.callback_children()
|
|
210
|
+
.chain(rules.unevaluated.iter())
|
|
211
|
+
.copied(),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
Op::Ref(reference) => {
|
|
215
|
+
if let Some((name, fallback)) = reference.callback_dynamic_target() {
|
|
216
|
+
edges[node].extend([fallback, count + name]);
|
|
217
|
+
} else if let Some(fallback) = reference.recursive_target() {
|
|
218
|
+
edges[node].extend([fallback, recursive]);
|
|
219
|
+
} else {
|
|
220
|
+
edges[node].push(reference.static_target().expect("static reference"));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
Op::All(children) => edges[node].extend(children),
|
|
224
|
+
Op::Any(children) | Op::One(children) => {
|
|
225
|
+
edges[node].extend(children.iter().map(|&(child, _)| child))
|
|
226
|
+
}
|
|
227
|
+
Op::Not(child) => edges[node].push(*child),
|
|
228
|
+
Op::Conditional(condition, yes, no) => {
|
|
229
|
+
edges[node].push(*condition);
|
|
230
|
+
edges[node].extend(yes.iter().chain(no));
|
|
231
|
+
}
|
|
232
|
+
Op::Unsupported => admitted[node] = false,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let parents = graph::parents(&edges);
|
|
237
|
+
graph::propagate(&mut report_unsupported, &parents);
|
|
238
|
+
// Tracked arrays enter collection even through validity. They need the
|
|
239
|
+
// complete result path, while ordinary validity children may also use
|
|
240
|
+
// the existing object callbacks.
|
|
241
|
+
for node in 0..count {
|
|
242
|
+
if self.tracks[node] {
|
|
243
|
+
admitted[node] &= !report_unsupported[node];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
let mut unsupported: Vec<_> = admitted.into_iter().map(|allowed| !allowed).collect();
|
|
247
|
+
graph::propagate(&mut unsupported, &parents);
|
|
248
|
+
self.report_supported = report_unsupported[..count]
|
|
249
|
+
.iter()
|
|
250
|
+
.map(|&blocked| !blocked)
|
|
251
|
+
.collect();
|
|
252
|
+
graph::propagate(&mut callbacks, &parents);
|
|
253
|
+
self.validity_supported = unsupported[..count]
|
|
254
|
+
.iter()
|
|
255
|
+
.map(|&blocked| !blocked)
|
|
256
|
+
.collect();
|
|
257
|
+
self.callback_roots = callbacks[..count]
|
|
258
|
+
.iter()
|
|
259
|
+
.zip(&self.validity_supported)
|
|
260
|
+
.map(|(&callback, &supported)| callback && supported)
|
|
261
|
+
.collect();
|
|
262
|
+
}
|
|
263
|
+
pub(super) fn scalar_callback_kind(&self, child: usize) -> (i32, i32) {
|
|
264
|
+
if self.metadata[child].enters_scope {
|
|
265
|
+
return (CALLBACK_RUNTIME, 0);
|
|
266
|
+
}
|
|
267
|
+
match self.nodes[child].as_slice() {
|
|
268
|
+
[Op::Boolean(value)] => (CALLBACK_BOOLEAN, i32::from(*value)),
|
|
269
|
+
[Op::Type(mask, union)] => (CALLBACK_TYPE, mask * 2 + i32::from(*union)),
|
|
270
|
+
_ => (CALLBACK_RUNTIME, 0),
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
pub(super) fn callback_container_node(&self, node: usize) -> bool {
|
|
274
|
+
self.nodes[node].iter().any(|op| match op {
|
|
275
|
+
Op::Array(_, rules) => !rules.header_only() && self.array_callbacks(rules),
|
|
276
|
+
Op::Object(_, rules) => !rules.header_only() && self.object_callbacks(rules),
|
|
277
|
+
_ => false,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
// Runtime children inherit reference context but export validity only.
|
|
281
|
+
pub(super) fn array_callbacks(&self, rules: &Array) -> bool {
|
|
282
|
+
rules.unevaluated.is_none() && self.callback_children_safe(rules.callback_children())
|
|
283
|
+
}
|
|
284
|
+
pub(super) fn array_report_callbacks(&self, rules: &Array) -> bool {
|
|
285
|
+
rules
|
|
286
|
+
.callback_children()
|
|
287
|
+
.chain(rules.unevaluated.iter())
|
|
288
|
+
.all(|&child| self.report_supported.get(child) == Some(&true))
|
|
289
|
+
}
|
|
290
|
+
pub(super) fn object_report_callbacks(&self, rules: &Object) -> bool {
|
|
291
|
+
rules
|
|
292
|
+
.callback_children()
|
|
293
|
+
.chain(rules.unevaluated.iter())
|
|
294
|
+
.all(|&child| self.report_supported.get(child) == Some(&true))
|
|
295
|
+
}
|
|
296
|
+
pub(super) fn object_callbacks(&self, rules: &Object) -> bool {
|
|
297
|
+
rules.unevaluated.is_none() && self.callback_children_safe(rules.callback_children())
|
|
298
|
+
}
|
|
299
|
+
fn callback_children_safe<'a>(&self, mut children: impl Iterator<Item = &'a usize>) -> bool {
|
|
300
|
+
children.all(|&child| self.validity_supported.get(child) == Some(&true))
|
|
301
|
+
}
|
|
302
|
+
pub(super) fn scalar_contains(&self, rules: &Array) -> bool {
|
|
303
|
+
rules.has_no_item_schemas() && rules.contains.is_some() && self.array_callbacks(rules)
|
|
304
|
+
}
|
|
305
|
+
pub(super) fn container_header(
|
|
306
|
+
&mut self,
|
|
307
|
+
source: Value,
|
|
308
|
+
maximum: &'static [u8],
|
|
309
|
+
minimum: &'static [u8],
|
|
310
|
+
object: bool,
|
|
311
|
+
tail: u8,
|
|
312
|
+
) -> Result<Header, Value> {
|
|
313
|
+
let maximum = self.field(source, maximum)?;
|
|
314
|
+
let minimum = self.field(source, minimum)?;
|
|
315
|
+
let native_bounds = self.native_count(maximum)? && self.native_count(minimum)?;
|
|
316
|
+
let required = if object {
|
|
317
|
+
self.field(source, b"required\0")?
|
|
318
|
+
} else {
|
|
319
|
+
unsafe { sn_nil() }
|
|
320
|
+
};
|
|
321
|
+
let inspect_required = unsafe { sn_truthy(required) } != 0;
|
|
322
|
+
let required_kind = unsafe { sn_kind(required) };
|
|
323
|
+
let mut dependencies = vec![];
|
|
324
|
+
let mut native_dependencies = true;
|
|
325
|
+
if object {
|
|
326
|
+
for (field, keyword) in [
|
|
327
|
+
(b"dependencies\0".as_slice(), "dependencies"),
|
|
328
|
+
(b"dependent_required\0", "dependentRequired"),
|
|
329
|
+
] {
|
|
330
|
+
let entries = self.field(source, field)?;
|
|
331
|
+
for pair in self.values(entries) {
|
|
332
|
+
let names = unsafe { sn_entry(pair, 1) };
|
|
333
|
+
let kind = unsafe { sn_kind(names) };
|
|
334
|
+
native_dependencies &= kind == TYPE_ARRAY;
|
|
335
|
+
dependencies.push(Dependency {
|
|
336
|
+
count: if matches!(kind, TYPE_ARRAY | TYPE_OBJECT) {
|
|
337
|
+
unsafe { sn_len(names) }
|
|
338
|
+
} else {
|
|
339
|
+
0
|
|
340
|
+
},
|
|
341
|
+
keyword,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
Ok(Header {
|
|
347
|
+
source,
|
|
348
|
+
native_bounds,
|
|
349
|
+
native_required: !inspect_required || required_kind == TYPE_ARRAY,
|
|
350
|
+
native_dependencies,
|
|
351
|
+
dependencies,
|
|
352
|
+
required_count: if matches!(required_kind, TYPE_ARRAY | TYPE_OBJECT) {
|
|
353
|
+
unsafe { sn_len(required) }
|
|
354
|
+
} else {
|
|
355
|
+
0
|
|
356
|
+
},
|
|
357
|
+
checks: unsafe { sn_truthy(maximum) } as u8
|
|
358
|
+
| ((unsafe { sn_truthy(minimum) } as u8) << 1)
|
|
359
|
+
| tail
|
|
360
|
+
| if inspect_required { 32 } else { 0 }
|
|
361
|
+
| if object { 16 } else { 0 },
|
|
362
|
+
object,
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
fn native_count(&mut self, value: Value) -> Result<bool, Value> {
|
|
366
|
+
if unsafe { sn_truthy(value) } == 0 {
|
|
367
|
+
return Ok(true);
|
|
368
|
+
}
|
|
369
|
+
if unsafe { sn_fixnum(value) } != 0 {
|
|
370
|
+
return Ok(unsafe { sn_integer(value) } >= 0);
|
|
371
|
+
}
|
|
372
|
+
if unsafe { sn_kind(value) } & TYPE_INTEGER == 0 {
|
|
373
|
+
return Ok(false);
|
|
374
|
+
}
|
|
375
|
+
if unsafe { sn_is_float(value) } != 0 {
|
|
376
|
+
return Ok(unsafe { sn_float(value) } >= 0.0);
|
|
377
|
+
}
|
|
378
|
+
let negative = self.field(value, b"negative?\0")?;
|
|
379
|
+
Ok(unsafe { sn_true(negative) } == 0)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
impl Array {
|
|
383
|
+
fn callback_children(&self) -> impl Iterator<Item = &usize> {
|
|
384
|
+
self.prefix
|
|
385
|
+
.iter()
|
|
386
|
+
.chain(&self.items)
|
|
387
|
+
.chain(self.additional.iter().filter(|_| self.list))
|
|
388
|
+
.chain(self.contains.iter())
|
|
389
|
+
}
|
|
390
|
+
fn has_no_item_schemas(&self) -> bool {
|
|
391
|
+
self.prefix.is_empty()
|
|
392
|
+
&& self.items.is_empty()
|
|
393
|
+
&& (!self.list || self.additional.is_none())
|
|
394
|
+
&& self.unevaluated.is_none()
|
|
395
|
+
}
|
|
396
|
+
pub(super) fn header_only(&self) -> bool {
|
|
397
|
+
self.has_no_item_schemas() && self.contains.is_none()
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
impl Object {
|
|
401
|
+
fn callback_children(&self) -> impl Iterator<Item = &usize> {
|
|
402
|
+
self.properties
|
|
403
|
+
.iter()
|
|
404
|
+
.map(|(_, child)| child)
|
|
405
|
+
.chain(self.patterns.iter().map(|(_, child)| child))
|
|
406
|
+
.chain(self.additional.iter())
|
|
407
|
+
.chain(self.names.iter())
|
|
408
|
+
.chain(self.dependent_schemas.iter().map(|(_, child)| child))
|
|
409
|
+
}
|
|
410
|
+
pub(super) fn valid_header(
|
|
411
|
+
&self,
|
|
412
|
+
caches: &runtime::Caches,
|
|
413
|
+
value: Value,
|
|
414
|
+
undefined: Value,
|
|
415
|
+
budget: &budget::Budget,
|
|
416
|
+
) -> Verdict {
|
|
417
|
+
let status = bounds(unsafe { sn_len(value) }, self.min, self.max);
|
|
418
|
+
if status != Valid {
|
|
419
|
+
return status;
|
|
420
|
+
}
|
|
421
|
+
let status = Self::check_keys(&self.required_order, caches, value, undefined, budget);
|
|
422
|
+
if status != Valid {
|
|
423
|
+
return status;
|
|
424
|
+
}
|
|
425
|
+
self.check_dependent_required(caches, value, undefined, budget)
|
|
426
|
+
}
|
|
427
|
+
pub(super) fn header_only(&self) -> bool {
|
|
428
|
+
self.properties.is_empty()
|
|
429
|
+
&& self.patterns.is_empty()
|
|
430
|
+
&& self.additional.is_none()
|
|
431
|
+
&& self.names.is_none()
|
|
432
|
+
&& self.dependent_schemas.is_empty()
|
|
433
|
+
&& self.unevaluated.is_none()
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[cfg(test)]
|
|
438
|
+
mod tests {
|
|
439
|
+
use super::*;
|
|
440
|
+
|
|
441
|
+
#[test]
|
|
442
|
+
fn admits_unresolved_reports_but_propagates_unsupported_instructions() {
|
|
443
|
+
let mut engine = Engine {
|
|
444
|
+
nodes: vec![
|
|
445
|
+
vec![Op::UnresolvedRef(0)],
|
|
446
|
+
vec![Op::All(vec![0, 1])],
|
|
447
|
+
vec![Op::Unsupported],
|
|
448
|
+
vec![Op::All(vec![1, 2])],
|
|
449
|
+
],
|
|
450
|
+
tracks: vec![false, true, false, true],
|
|
451
|
+
metadata: vec![scope::Metadata::default(); 4],
|
|
452
|
+
..Engine::default()
|
|
453
|
+
};
|
|
454
|
+
engine.compile_native_validity_support();
|
|
455
|
+
for root in 0..4 {
|
|
456
|
+
assert_eq!(engine.native_continuations_safe(root), root < 2);
|
|
457
|
+
assert_eq!(engine.report_continuations_safe(root), root < 2);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|