schemurai 2.2.3 → 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 +81 -7
- data/ext/schemurai_native/Cargo.lock +99 -0
- data/ext/schemurai_native/Cargo.toml +21 -0
- data/ext/schemurai_native/build.rs +35 -0
- data/ext/schemurai_native/extconf.rb +63 -0
- data/ext/schemurai_native/glue.c +780 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +1135 -0
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/format.rs +357 -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 +3139 -0
- 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 +1054 -0
- data/ext/schemurai_native/src/pattern.rs +431 -0
- data/ext/schemurai_native/src/scope.rs +420 -0
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +687 -0
- data/lib/schemurai/backend.rb +3 -1
- 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/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +59 -0
- 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 +81 -33
- data/sig/schemurai.rbs +21 -13
- metadata +36 -6
|
@@ -0,0 +1,3139 @@
|
|
|
1
|
+
//! A VM over borrowed CRuby VALUE handles. No instance decoding or serialization.
|
|
2
|
+
//! Ruby runs with the GVL held. Evaluation calls only non-allocating C adapters;
|
|
3
|
+
//! Ruby callbacks during compilation are protected on the C side.
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use std::ffi::{c_char, c_void};
|
|
6
|
+
use std::hash::{BuildHasherDefault, Hasher};
|
|
7
|
+
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
8
|
+
mod budget;
|
|
9
|
+
mod content;
|
|
10
|
+
mod diagnostics;
|
|
11
|
+
mod enumeration;
|
|
12
|
+
mod evaluation;
|
|
13
|
+
mod format;
|
|
14
|
+
mod graph;
|
|
15
|
+
mod instance_graph;
|
|
16
|
+
mod memo;
|
|
17
|
+
mod memo_scope;
|
|
18
|
+
mod number;
|
|
19
|
+
mod pattern;
|
|
20
|
+
// Some shared constants are consumed only by C or Ruby.
|
|
21
|
+
#[allow(dead_code)]
|
|
22
|
+
mod protocol {
|
|
23
|
+
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
|
|
24
|
+
}
|
|
25
|
+
use protocol::*;
|
|
26
|
+
mod scope;
|
|
27
|
+
mod structural;
|
|
28
|
+
mod tracking;
|
|
29
|
+
type Value = usize;
|
|
30
|
+
unsafe extern "C" {
|
|
31
|
+
fn sn_pin(value: Value);
|
|
32
|
+
fn sn_nil() -> Value;
|
|
33
|
+
fn sn_undefined() -> Value;
|
|
34
|
+
fn sn_kind(value: Value) -> i32;
|
|
35
|
+
fn sn_shallow_kind(value: Value) -> i32;
|
|
36
|
+
fn sn_fingerprint_view(
|
|
37
|
+
value: Value,
|
|
38
|
+
data: *mut *const c_void,
|
|
39
|
+
length: *mut usize,
|
|
40
|
+
number: *mut f64,
|
|
41
|
+
) -> i32;
|
|
42
|
+
fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
|
|
43
|
+
fn sn_true(value: Value) -> i32;
|
|
44
|
+
fn sn_fixnum(value: Value) -> i32;
|
|
45
|
+
fn sn_fixnum_flag() -> Value;
|
|
46
|
+
fn sn_array_data(value: Value) -> *const Value;
|
|
47
|
+
fn sn_integer(value: Value) -> i64;
|
|
48
|
+
fn sn_integer128(value: Value, words: *mut u64) -> i32;
|
|
49
|
+
fn sn_integer_bounds128(value: Value, words: *mut u64) -> i32;
|
|
50
|
+
fn sn_number_bounds_view(
|
|
51
|
+
value: Value,
|
|
52
|
+
words: *mut u64,
|
|
53
|
+
kind: *mut i32,
|
|
54
|
+
floating: *mut f64,
|
|
55
|
+
required: i32,
|
|
56
|
+
) -> i32;
|
|
57
|
+
fn sn_integer1024(value: Value, words: *mut u64) -> i32;
|
|
58
|
+
fn sn_is_bignum(value: Value) -> i32;
|
|
59
|
+
fn sn_big256(value: Value, words: *mut u64) -> i32;
|
|
60
|
+
fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
|
|
61
|
+
fn sn_big_low_word(value: Value) -> u64;
|
|
62
|
+
fn sn_big_low64_word(value: Value, word: *mut u64) -> i32;
|
|
63
|
+
fn sn_big_equal(big: Value, other: Value) -> i32;
|
|
64
|
+
fn sn_big_order(left: Value, right: Value) -> i32;
|
|
65
|
+
fn sn_wide_integer_compare(value: Value, bound: Value) -> i32;
|
|
66
|
+
fn sn_float(value: Value) -> f64;
|
|
67
|
+
fn sn_is_float(value: Value) -> i32;
|
|
68
|
+
fn sn_is_rational(value: Value) -> i32;
|
|
69
|
+
fn sn_len(value: Value) -> usize;
|
|
70
|
+
fn sn_entry(value: Value, index: usize) -> Value;
|
|
71
|
+
fn sn_lookup(hash: Value, key: Value) -> Value;
|
|
72
|
+
fn sn_missing(value: Value) -> i32;
|
|
73
|
+
fn sn_same(a: Value, b: Value) -> i32;
|
|
74
|
+
fn sn_same_class(a: Value, b: Value) -> i32;
|
|
75
|
+
fn sn_string_data(value: Value) -> *const u8;
|
|
76
|
+
fn sn_string_bytes(value: Value) -> usize;
|
|
77
|
+
fn sn_string_equal(a: Value, b: Value) -> i32;
|
|
78
|
+
fn sn_string_length_guarded(value: Value, length: *mut usize) -> i32;
|
|
79
|
+
fn sn_record_key_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
80
|
+
fn sn_string_raw_view(value: Value, data: *mut *const u8, length: *mut usize);
|
|
81
|
+
fn sn_string_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
82
|
+
fn sn_pattern_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
83
|
+
fn sn_pattern_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
84
|
+
fn sn_format_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
85
|
+
fn sn_json_encoding(value: Value) -> i32;
|
|
86
|
+
fn sn_json_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
87
|
+
fn sn_resolve(compiler: Value, program: Value, reference: Value, result: *mut Value) -> i32;
|
|
88
|
+
fn sn_resolution_error(error: Value) -> i32;
|
|
89
|
+
fn sn_resource_program(compiler: Value, resource: Value, result: *mut Value) -> i32;
|
|
90
|
+
fn sn_anchor_count(resource: Value) -> usize;
|
|
91
|
+
fn sn_anchor_names(compiler: Value, resource: Value, result: *mut Value) -> i32;
|
|
92
|
+
fn sn_anchor_program(compiler: Value, resource: Value, name: Value, result: *mut Value) -> i32;
|
|
93
|
+
fn sn_hash_each(
|
|
94
|
+
value: Value,
|
|
95
|
+
context: *mut c_void,
|
|
96
|
+
callback: unsafe extern "C" fn(*mut c_void, Value, Value) -> i32,
|
|
97
|
+
) -> i32;
|
|
98
|
+
fn sn_hash_each_key_view(
|
|
99
|
+
value: Value,
|
|
100
|
+
context: *mut c_void,
|
|
101
|
+
callback: unsafe extern "C" fn(*mut c_void, *const u8, usize, Value) -> i32,
|
|
102
|
+
) -> i32;
|
|
103
|
+
fn sn_field(object: Value, name: *const c_char, value: *mut Value) -> i32;
|
|
104
|
+
fn sn_pairs(hash: Value, value: *mut Value) -> i32;
|
|
105
|
+
fn sn_key_copy(key: Value, value: *mut Value) -> i32;
|
|
106
|
+
fn sn_opcode(symbol: Value) -> i32;
|
|
107
|
+
fn sn_object_each(value: Value, context: *mut c_void) -> i32;
|
|
108
|
+
}
|
|
109
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
110
|
+
#[repr(i32)]
|
|
111
|
+
enum Verdict {
|
|
112
|
+
Invalid = VERDICT_INVALID,
|
|
113
|
+
Valid = VERDICT_VALID,
|
|
114
|
+
Unsupported = VERDICT_UNSUPPORTED,
|
|
115
|
+
}
|
|
116
|
+
use Verdict::*;
|
|
117
|
+
const MAX_EVALUATION_DEPTH: usize = 256;
|
|
118
|
+
const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
|
|
119
|
+
#[derive(Debug)]
|
|
120
|
+
enum Op {
|
|
121
|
+
Unsupported,
|
|
122
|
+
Boolean(bool),
|
|
123
|
+
Ref(scope::Reference),
|
|
124
|
+
Type(i32),
|
|
125
|
+
Const(Value),
|
|
126
|
+
Enum(Box<enumeration::Enumeration>),
|
|
127
|
+
All(Vec<usize>),
|
|
128
|
+
Any(Vec<(usize, usize)>),
|
|
129
|
+
One(Vec<(usize, usize)>),
|
|
130
|
+
Not(usize),
|
|
131
|
+
Conditional(usize, Option<usize>, Option<usize>),
|
|
132
|
+
Number(i32, Box<number::Rules>),
|
|
133
|
+
String(i32, Box<StringRules>),
|
|
134
|
+
Array(i32, Box<Array>),
|
|
135
|
+
Object(i32, Box<Object>),
|
|
136
|
+
}
|
|
137
|
+
#[derive(Debug)]
|
|
138
|
+
struct Array {
|
|
139
|
+
min: Option<usize>,
|
|
140
|
+
max: Option<usize>,
|
|
141
|
+
unique: bool,
|
|
142
|
+
prefix: Vec<usize>,
|
|
143
|
+
items: Vec<usize>,
|
|
144
|
+
list: bool,
|
|
145
|
+
additional: Option<usize>,
|
|
146
|
+
contains: Option<usize>,
|
|
147
|
+
min_contains: usize,
|
|
148
|
+
max_contains: Option<usize>,
|
|
149
|
+
unevaluated: Option<usize>,
|
|
150
|
+
}
|
|
151
|
+
#[derive(Debug)]
|
|
152
|
+
struct StringRules {
|
|
153
|
+
min: Option<usize>,
|
|
154
|
+
max: Option<usize>,
|
|
155
|
+
pattern: Option<pattern::Pattern>,
|
|
156
|
+
format: Option<format::Format>,
|
|
157
|
+
content: Option<content::Content>,
|
|
158
|
+
}
|
|
159
|
+
impl StringRules {
|
|
160
|
+
fn content_only(&self) -> Option<&content::Content> {
|
|
161
|
+
if self.min.is_none()
|
|
162
|
+
&& self.max.is_none()
|
|
163
|
+
&& self.pattern.is_none()
|
|
164
|
+
&& self.format.is_none()
|
|
165
|
+
{
|
|
166
|
+
self.content.as_ref()
|
|
167
|
+
} else {
|
|
168
|
+
None
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
fn format_only(&self) -> Option<&format::Format> {
|
|
172
|
+
if self.min.is_none()
|
|
173
|
+
&& self.max.is_none()
|
|
174
|
+
&& self.pattern.is_none()
|
|
175
|
+
&& self.content.is_none()
|
|
176
|
+
{
|
|
177
|
+
self.format.as_ref()
|
|
178
|
+
} else {
|
|
179
|
+
None
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
fn pattern_only(&self) -> Option<&pattern::Pattern> {
|
|
183
|
+
if self.min.is_none()
|
|
184
|
+
&& self.max.is_none()
|
|
185
|
+
&& self.format.is_none()
|
|
186
|
+
&& self.content.is_none()
|
|
187
|
+
{
|
|
188
|
+
self.pattern.as_ref()
|
|
189
|
+
} else {
|
|
190
|
+
None
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
fn length_only(&self) -> bool {
|
|
194
|
+
(self.min.is_some() || self.max.is_some())
|
|
195
|
+
&& self.pattern.is_none()
|
|
196
|
+
&& self.format.is_none()
|
|
197
|
+
&& self.content.is_none()
|
|
198
|
+
}
|
|
199
|
+
fn eval_length_guarded(&self, required: i32, value: Value) -> Verdict {
|
|
200
|
+
length_guarded(value, required, self.min, self.max)
|
|
201
|
+
}
|
|
202
|
+
fn eval_guarded(&self, caches: &pattern::Caches, required: i32, value: Value) -> Verdict {
|
|
203
|
+
let mut data = std::ptr::null();
|
|
204
|
+
let mut len = 0;
|
|
205
|
+
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
206
|
+
if encoding == -TYPE_UNSUPPORTED {
|
|
207
|
+
Unsupported
|
|
208
|
+
} else if encoding < 0 {
|
|
209
|
+
verdict(required == 0)
|
|
210
|
+
} else {
|
|
211
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
212
|
+
self.eval_with_view::<true>(caches, value, Some((bytes, encoding)))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
fn eval(&self, caches: &pattern::Caches, value: Value) -> Verdict {
|
|
216
|
+
self.eval_with_view::<false>(caches, value, None)
|
|
217
|
+
}
|
|
218
|
+
fn eval_with_view<const BORROWED: bool>(
|
|
219
|
+
&self,
|
|
220
|
+
caches: &pattern::Caches,
|
|
221
|
+
value: Value,
|
|
222
|
+
view: Option<(&[u8], i32)>,
|
|
223
|
+
) -> Verdict {
|
|
224
|
+
// Specialize the standalone path so diagnostics and ordinary string
|
|
225
|
+
// checks do not dispatch through borrowed-view branches at runtime.
|
|
226
|
+
let view = if BORROWED { view } else { None };
|
|
227
|
+
if (self.min.is_some() || self.max.is_some())
|
|
228
|
+
&& bounds(unsafe { sn_len(value) }, self.min, self.max) == Invalid
|
|
229
|
+
{
|
|
230
|
+
return Invalid;
|
|
231
|
+
}
|
|
232
|
+
if let Some(pattern) = &self.pattern {
|
|
233
|
+
let result = match view {
|
|
234
|
+
Some((bytes, encoding)) => pattern.eval_bytes(caches, bytes, encoding),
|
|
235
|
+
None => pattern.eval(caches, value),
|
|
236
|
+
};
|
|
237
|
+
if result != Valid {
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if let Some(format) = &self.format {
|
|
242
|
+
let result = match view {
|
|
243
|
+
Some((bytes, _)) => format.eval_bytes(bytes),
|
|
244
|
+
None => format.eval(value),
|
|
245
|
+
};
|
|
246
|
+
if result != Valid {
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
self.content.as_ref().map_or(Valid, |content| match view {
|
|
251
|
+
Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
|
|
252
|
+
None => content.eval(value),
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
#[derive(Debug)]
|
|
257
|
+
enum DiagnosticDependency {
|
|
258
|
+
Required(Value, Vec<Value>, &'static str),
|
|
259
|
+
Schema(Value, usize, &'static str),
|
|
260
|
+
}
|
|
261
|
+
#[derive(Clone, Copy, Debug)]
|
|
262
|
+
struct PropertyTarget {
|
|
263
|
+
name: Value,
|
|
264
|
+
child: usize,
|
|
265
|
+
index: usize,
|
|
266
|
+
}
|
|
267
|
+
#[derive(Debug)]
|
|
268
|
+
struct Object {
|
|
269
|
+
record: Option<RecordObject>,
|
|
270
|
+
min: Option<usize>,
|
|
271
|
+
max: Option<usize>,
|
|
272
|
+
required: Vec<Value>,
|
|
273
|
+
required_order: Vec<Value>,
|
|
274
|
+
properties: Vec<(Value, usize)>,
|
|
275
|
+
property_targets: HashMap<tracking::JsonKey, PropertyTarget, BuildHasherDefault<BucketHasher>>,
|
|
276
|
+
patterns: Vec<(pattern::Pattern, usize)>,
|
|
277
|
+
pattern_names: Vec<Value>,
|
|
278
|
+
pattern_group: Option<pattern::PatternGroup>,
|
|
279
|
+
required_properties: Vec<bool>,
|
|
280
|
+
required_property_indices: Vec<usize>,
|
|
281
|
+
property_keys: tracking::KeySet,
|
|
282
|
+
property_indices: Vec<usize>,
|
|
283
|
+
additional: Option<usize>,
|
|
284
|
+
names: Option<usize>,
|
|
285
|
+
dependent_required: Vec<(Value, Vec<Value>)>,
|
|
286
|
+
dependent_schemas: Vec<(Value, usize)>,
|
|
287
|
+
diagnostic_dependencies: Vec<DiagnosticDependency>,
|
|
288
|
+
unevaluated: Option<usize>,
|
|
289
|
+
}
|
|
290
|
+
#[derive(Clone, Copy, Debug)]
|
|
291
|
+
struct RecordField {
|
|
292
|
+
key: Value,
|
|
293
|
+
name: Value,
|
|
294
|
+
short_key: ShortRecordKey,
|
|
295
|
+
child: Option<usize>,
|
|
296
|
+
bit: u64,
|
|
297
|
+
check: Option<RecordCheck>,
|
|
298
|
+
}
|
|
299
|
+
#[derive(Clone, Copy, Debug)]
|
|
300
|
+
enum RecordCheck {
|
|
301
|
+
IntegerBounds(i64, i64),
|
|
302
|
+
StringLength(i32, Option<usize>, Option<usize>),
|
|
303
|
+
}
|
|
304
|
+
#[derive(Debug)]
|
|
305
|
+
struct RecordObject {
|
|
306
|
+
fields: Vec<RecordField>,
|
|
307
|
+
short_index: Option<RecordShortIndex>,
|
|
308
|
+
// Immutable schema handle, used only to choose a lookup strategy.
|
|
309
|
+
identity_hint: Value,
|
|
310
|
+
required: u64,
|
|
311
|
+
}
|
|
312
|
+
#[derive(Debug)]
|
|
313
|
+
struct RecordShortIndex {
|
|
314
|
+
slots: [u8; 16],
|
|
315
|
+
shift: u8,
|
|
316
|
+
}
|
|
317
|
+
impl RecordShortIndex {
|
|
318
|
+
fn compile(fields: &[RecordField]) -> Option<Self> {
|
|
319
|
+
if !(4..=8).contains(&fields.len()) || fields.iter().any(|field| field.short_key.tag == 0) {
|
|
320
|
+
return None;
|
|
321
|
+
}
|
|
322
|
+
for shift in 0..=60 {
|
|
323
|
+
// Use a four-bit window only if every compiled key owns a distinct
|
|
324
|
+
// slot. Unknown input keys still undergo full equality checks.
|
|
325
|
+
let mut index = Self {
|
|
326
|
+
slots: [0; 16],
|
|
327
|
+
shift,
|
|
328
|
+
};
|
|
329
|
+
let mut unique = true;
|
|
330
|
+
for (at, field) in fields.iter().enumerate() {
|
|
331
|
+
let slot = index.slot(field.short_key);
|
|
332
|
+
if index.slots[slot] != 0 {
|
|
333
|
+
unique = false;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
index.slots[slot] = at as u8 + 1;
|
|
337
|
+
}
|
|
338
|
+
if unique {
|
|
339
|
+
return Some(index);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
None
|
|
343
|
+
}
|
|
344
|
+
fn slot(&self, key: ShortRecordKey) -> usize {
|
|
345
|
+
// Encoding is excluded: equal ASCII keys may have compatible but
|
|
346
|
+
// different encodings. Exact byte/length/encoding checks follow lookup.
|
|
347
|
+
let word = key.word ^ (key.tag & 15).wrapping_mul(0x9e3779b97f4a7c15);
|
|
348
|
+
((word >> self.shift) & 15) as usize
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
impl Object {
|
|
352
|
+
// Keep these calls at their original evaluation positions: property
|
|
353
|
+
// failures and unsupported children must retain their ordering.
|
|
354
|
+
#[inline]
|
|
355
|
+
fn check_required(&self, value: Value, undefined: Value, budget: &budget::Budget) -> Verdict {
|
|
356
|
+
Self::check_keys(&self.required, value, undefined, budget)
|
|
357
|
+
}
|
|
358
|
+
#[inline]
|
|
359
|
+
fn check_keys(
|
|
360
|
+
keys: &[Value],
|
|
361
|
+
value: Value,
|
|
362
|
+
undefined: Value,
|
|
363
|
+
budget: &budget::Budget,
|
|
364
|
+
) -> Verdict {
|
|
365
|
+
for &key in keys {
|
|
366
|
+
if !budget.checkpoint() {
|
|
367
|
+
return Unsupported;
|
|
368
|
+
}
|
|
369
|
+
if missing(value, key, undefined) {
|
|
370
|
+
return Invalid;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
Valid
|
|
374
|
+
}
|
|
375
|
+
#[inline]
|
|
376
|
+
fn check_dependent_required(
|
|
377
|
+
&self,
|
|
378
|
+
value: Value,
|
|
379
|
+
undefined: Value,
|
|
380
|
+
budget: &budget::Budget,
|
|
381
|
+
) -> Verdict {
|
|
382
|
+
for (name, required) in &self.dependent_required {
|
|
383
|
+
if !budget.checkpoint() {
|
|
384
|
+
return Unsupported;
|
|
385
|
+
}
|
|
386
|
+
if !missing(value, *name, undefined) {
|
|
387
|
+
let status = Self::check_keys(required, value, undefined, budget);
|
|
388
|
+
if status != Valid {
|
|
389
|
+
return status;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
Valid
|
|
394
|
+
}
|
|
395
|
+
fn visit_properties(
|
|
396
|
+
&self,
|
|
397
|
+
value: Value,
|
|
398
|
+
len: usize,
|
|
399
|
+
undefined: Value,
|
|
400
|
+
budget: &budget::Budget,
|
|
401
|
+
mut visit: impl FnMut(usize, usize, Value) -> Verdict,
|
|
402
|
+
) -> Result<usize, Verdict> {
|
|
403
|
+
let mut found = 0;
|
|
404
|
+
if self.properties.len() > len.saturating_mul(4).max(8)
|
|
405
|
+
&& self.property_targets.len() == self.properties.len()
|
|
406
|
+
{
|
|
407
|
+
// Discover only present schema fields, then retain schema order for
|
|
408
|
+
// validity, including required failures and unsupported children.
|
|
409
|
+
let mut candidates = PropertyCandidates {
|
|
410
|
+
rules: self,
|
|
411
|
+
budget,
|
|
412
|
+
indices: Vec::with_capacity(len.min(self.properties.len())),
|
|
413
|
+
};
|
|
414
|
+
let status = decode(unsafe {
|
|
415
|
+
sn_hash_each(
|
|
416
|
+
value,
|
|
417
|
+
(&mut candidates as *mut PropertyCandidates).cast(),
|
|
418
|
+
property_candidate,
|
|
419
|
+
)
|
|
420
|
+
});
|
|
421
|
+
if status != Valid {
|
|
422
|
+
return Err(status);
|
|
423
|
+
}
|
|
424
|
+
candidates.indices.sort_unstable();
|
|
425
|
+
candidates.indices.dedup();
|
|
426
|
+
let mut missing_required = None;
|
|
427
|
+
for &index in &self.required_property_indices {
|
|
428
|
+
if !budget.checkpoint() {
|
|
429
|
+
return Err(Unsupported);
|
|
430
|
+
}
|
|
431
|
+
if missing(value, self.properties[index].0, undefined) {
|
|
432
|
+
missing_required = Some(index);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
for index in candidates.indices {
|
|
437
|
+
if !budget.checkpoint() {
|
|
438
|
+
return Err(Unsupported);
|
|
439
|
+
}
|
|
440
|
+
if missing_required.is_some_and(|missing| missing <= index) {
|
|
441
|
+
return Err(Invalid);
|
|
442
|
+
}
|
|
443
|
+
let (key, child) = self.properties[index];
|
|
444
|
+
// Use the schema handle for the final lookup to retain identity
|
|
445
|
+
// Hash semantics, even when an equal input key found the index.
|
|
446
|
+
let item = unsafe { sn_lookup(value, key) };
|
|
447
|
+
if item == undefined {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
found += 1;
|
|
451
|
+
match visit(index, child, item) {
|
|
452
|
+
Valid => {}
|
|
453
|
+
status => return Err(status),
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if missing_required.is_some() {
|
|
457
|
+
return Err(Invalid);
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
for (index, &(key, child)) in self.properties.iter().enumerate() {
|
|
461
|
+
if !budget.checkpoint() {
|
|
462
|
+
return Err(Unsupported);
|
|
463
|
+
}
|
|
464
|
+
let item = unsafe { sn_lookup(value, key) };
|
|
465
|
+
if item == undefined {
|
|
466
|
+
if self.required_properties[index] {
|
|
467
|
+
return Err(Invalid);
|
|
468
|
+
}
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
found += 1;
|
|
472
|
+
match visit(index, child, item) {
|
|
473
|
+
Valid => {}
|
|
474
|
+
status => return Err(status),
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
Ok(found)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// All execution policies use the same property/pattern/additional routing.
|
|
482
|
+
// Schema-order validity scans have already visited declared properties;
|
|
483
|
+
// input-order diagnostic scans visit them here as well.
|
|
484
|
+
fn visit_value_rules<const PROPERTIES: bool>(
|
|
485
|
+
&self,
|
|
486
|
+
caches: &pattern::Caches,
|
|
487
|
+
budget: &budget::Budget,
|
|
488
|
+
key: Value,
|
|
489
|
+
location: Option<tracking::JsonKey>,
|
|
490
|
+
mut visit: impl FnMut(&'static str, Option<Value>, usize) -> Result<(), Verdict>,
|
|
491
|
+
) -> Result<bool, Verdict> {
|
|
492
|
+
// Tracked scans already fingerprinted this key for location lookup.
|
|
493
|
+
// Reuse that result, and skip hashing for pattern-only object rules.
|
|
494
|
+
let mut matched = if self.properties.is_empty() {
|
|
495
|
+
false
|
|
496
|
+
} else if PROPERTIES {
|
|
497
|
+
if let Some(target) = self
|
|
498
|
+
.property_targets
|
|
499
|
+
.get(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
|
|
500
|
+
{
|
|
501
|
+
visit("properties", Some(target.name), target.child)?;
|
|
502
|
+
true
|
|
503
|
+
} else {
|
|
504
|
+
false
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
self.property_keys
|
|
508
|
+
.contains(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
|
|
509
|
+
};
|
|
510
|
+
if let Some(mut matches) = self
|
|
511
|
+
.pattern_group
|
|
512
|
+
.as_ref()
|
|
513
|
+
.and_then(|group| group.matches(key))
|
|
514
|
+
{
|
|
515
|
+
while matches != 0 {
|
|
516
|
+
if !budget.checkpoint() {
|
|
517
|
+
return Err(Unsupported);
|
|
518
|
+
}
|
|
519
|
+
let index = matches.trailing_zeros() as usize;
|
|
520
|
+
matches &= matches - 1;
|
|
521
|
+
matched = true;
|
|
522
|
+
visit(
|
|
523
|
+
"patternProperties",
|
|
524
|
+
Some(self.pattern_names[index]),
|
|
525
|
+
self.patterns[index].1,
|
|
526
|
+
)?;
|
|
527
|
+
}
|
|
528
|
+
} else {
|
|
529
|
+
for ((pattern, child), &name) in self.patterns.iter().zip(&self.pattern_names) {
|
|
530
|
+
if !budget.checkpoint() {
|
|
531
|
+
return Err(Unsupported);
|
|
532
|
+
}
|
|
533
|
+
match pattern.eval(caches, key) {
|
|
534
|
+
Unsupported => return Err(Unsupported),
|
|
535
|
+
Invalid => {}
|
|
536
|
+
Valid => {
|
|
537
|
+
matched = true;
|
|
538
|
+
visit("patternProperties", Some(name), *child)?;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if !matched && let Some(child) = self.additional {
|
|
544
|
+
visit("additionalProperties", None, child)?;
|
|
545
|
+
}
|
|
546
|
+
Ok(matched || self.additional.is_some())
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fn with_record(mut self) -> Self {
|
|
550
|
+
if self.required_order.len() > 64
|
|
551
|
+
|| !self.patterns.is_empty()
|
|
552
|
+
|| self.names.is_some()
|
|
553
|
+
|| !self.diagnostic_dependencies.is_empty()
|
|
554
|
+
|| self.unevaluated.is_some()
|
|
555
|
+
{
|
|
556
|
+
return self;
|
|
557
|
+
}
|
|
558
|
+
let mut fields: HashMap<_, _, BuildHasherDefault<BucketHasher>> = self
|
|
559
|
+
.properties
|
|
560
|
+
.iter()
|
|
561
|
+
.map(|&(name, child)| {
|
|
562
|
+
(
|
|
563
|
+
tracking::JsonKey::new(name),
|
|
564
|
+
RecordField {
|
|
565
|
+
key: name,
|
|
566
|
+
name,
|
|
567
|
+
short_key: short_record_key(name),
|
|
568
|
+
child: Some(child),
|
|
569
|
+
bit: 0,
|
|
570
|
+
check: None,
|
|
571
|
+
},
|
|
572
|
+
)
|
|
573
|
+
})
|
|
574
|
+
.collect();
|
|
575
|
+
for &name in &self.required_order {
|
|
576
|
+
fields
|
|
577
|
+
.entry(tracking::JsonKey::new(name))
|
|
578
|
+
.or_insert(RecordField {
|
|
579
|
+
key: name,
|
|
580
|
+
name,
|
|
581
|
+
short_key: short_record_key(name),
|
|
582
|
+
child: None,
|
|
583
|
+
bit: 0,
|
|
584
|
+
check: None,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
if fields.len() > 8 {
|
|
588
|
+
return self;
|
|
589
|
+
}
|
|
590
|
+
for (index, field) in fields.values_mut().enumerate() {
|
|
591
|
+
field.bit = 1_u64 << index;
|
|
592
|
+
}
|
|
593
|
+
// Required names share the field bits already used for duplicate-key
|
|
594
|
+
// detection. Repeated required names therefore need no extra state.
|
|
595
|
+
let required = self.required_order.iter().fold(0, |bits, &name| {
|
|
596
|
+
bits | fields[&tracking::JsonKey::new(name)].bit
|
|
597
|
+
});
|
|
598
|
+
let fields: Vec<_> = fields.into_values().collect();
|
|
599
|
+
let short_index = RecordShortIndex::compile(&fields);
|
|
600
|
+
self.record = Some(RecordObject {
|
|
601
|
+
identity_hint: self.properties.first().map_or(0, |&(key, _)| key),
|
|
602
|
+
fields,
|
|
603
|
+
short_index,
|
|
604
|
+
required,
|
|
605
|
+
});
|
|
606
|
+
self
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
#[derive(Default)]
|
|
610
|
+
pub struct Roots {
|
|
611
|
+
values: Vec<Value>,
|
|
612
|
+
binding_names: Vec<Value>,
|
|
613
|
+
}
|
|
614
|
+
#[derive(Default)]
|
|
615
|
+
pub struct Engine {
|
|
616
|
+
nodes: Vec<Vec<Op>>,
|
|
617
|
+
pattern_cache_count: usize,
|
|
618
|
+
error_operands: Vec<Vec<(i32, Value)>>,
|
|
619
|
+
tracks: Vec<bool>,
|
|
620
|
+
memoizable: Vec<bool>,
|
|
621
|
+
memo_contextual: Vec<bool>,
|
|
622
|
+
memo_guarded: Vec<bool>,
|
|
623
|
+
memo_guard_sources: Vec<bool>,
|
|
624
|
+
memo_guard_same_sources: Vec<bool>,
|
|
625
|
+
memo_guard_scalar_sources: Vec<bool>,
|
|
626
|
+
memo_guard_scalar_amplification: Vec<bool>,
|
|
627
|
+
memo_scope_projection: memo_scope::Projection,
|
|
628
|
+
metadata: Vec<scope::Metadata>,
|
|
629
|
+
resources: HashMap<Value, usize>,
|
|
630
|
+
dynamic_bindings: HashMap<(Value, usize), usize>,
|
|
631
|
+
anchor_names: Vec<tracking::JsonKey>,
|
|
632
|
+
anchor_ids: HashMap<tracking::JsonKey, usize, BuildHasherDefault<BucketHasher>>,
|
|
633
|
+
anchor_tables: Vec<Vec<(Value, usize)>>,
|
|
634
|
+
property_ids: HashMap<tracking::JsonKey, usize, BuildHasherDefault<BucketHasher>>,
|
|
635
|
+
indices: HashMap<Value, usize>,
|
|
636
|
+
roots: *mut Roots,
|
|
637
|
+
root: usize,
|
|
638
|
+
fixnum_flag: Value,
|
|
639
|
+
compiler: Value,
|
|
640
|
+
validate_format: bool,
|
|
641
|
+
validate_content: bool,
|
|
642
|
+
undefined: Value,
|
|
643
|
+
}
|
|
644
|
+
impl Engine {
|
|
645
|
+
fn retain(&mut self, value: Value) -> Value {
|
|
646
|
+
// A separate allocation lets GC mark roots without aliasing the
|
|
647
|
+
// compiler's exclusive Engine reference. This borrow ends before any
|
|
648
|
+
// allocating Ruby call; Vec growth uses the Rust allocator only.
|
|
649
|
+
unsafe { &mut *self.roots }.values.push(value);
|
|
650
|
+
value
|
|
651
|
+
}
|
|
652
|
+
fn field(&mut self, value: Value, name: &'static [u8]) -> Result<Value, Value> {
|
|
653
|
+
let mut result = 0;
|
|
654
|
+
if unsafe { sn_field(value, name.as_ptr().cast(), &mut result) } != 0 {
|
|
655
|
+
return Err(result);
|
|
656
|
+
}
|
|
657
|
+
Ok(self.retain(result))
|
|
658
|
+
}
|
|
659
|
+
fn values(&mut self, value: Value) -> Vec<Value> {
|
|
660
|
+
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
661
|
+
return vec![];
|
|
662
|
+
}
|
|
663
|
+
(0..unsafe { sn_len(value) })
|
|
664
|
+
.map(|i| {
|
|
665
|
+
let item = unsafe { sn_entry(value, i) };
|
|
666
|
+
self.retain(item)
|
|
667
|
+
})
|
|
668
|
+
.collect()
|
|
669
|
+
}
|
|
670
|
+
fn map(&mut self, value: Value) -> Result<Vec<(Value, Value)>, Value> {
|
|
671
|
+
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
672
|
+
return Ok(vec![]);
|
|
673
|
+
}
|
|
674
|
+
let mut pairs = 0;
|
|
675
|
+
if unsafe { sn_pairs(value, &mut pairs) } != 0 {
|
|
676
|
+
return Err(pairs);
|
|
677
|
+
}
|
|
678
|
+
self.retain(pairs);
|
|
679
|
+
Ok(self
|
|
680
|
+
.values(pairs)
|
|
681
|
+
.as_chunks::<2>()
|
|
682
|
+
.0
|
|
683
|
+
.iter()
|
|
684
|
+
.map(|p| (p[0], p[1]))
|
|
685
|
+
.collect())
|
|
686
|
+
}
|
|
687
|
+
fn size_field(&mut self, value: Value, name: &'static [u8]) -> Result<Option<usize>, Value> {
|
|
688
|
+
let n = self.field(value, name)?;
|
|
689
|
+
Ok(if unsafe { sn_same(n, sn_nil()) } != 0 {
|
|
690
|
+
None
|
|
691
|
+
} else if unsafe { sn_fixnum(n) } != 0 {
|
|
692
|
+
Some(unsafe { sn_integer(n) } as usize)
|
|
693
|
+
} else if unsafe { sn_is_float(n) } != 0 {
|
|
694
|
+
// Count keywords allow integral JSON numbers such as 2.0.
|
|
695
|
+
// Saturation is exact for comparisons against representable lengths.
|
|
696
|
+
Some(unsafe { sn_float(n) } as usize)
|
|
697
|
+
} else if unsafe { sn_kind(n) } & TYPE_INTEGER != 0 {
|
|
698
|
+
Some(usize::MAX)
|
|
699
|
+
} else {
|
|
700
|
+
None // The only non-integral bound is maxContains' +Infinity.
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
fn child(&mut self, value: Value, name: &'static [u8]) -> Result<Option<usize>, Value> {
|
|
704
|
+
let child = self.field(value, name)?;
|
|
705
|
+
if unsafe { sn_same(child, sn_nil()) } != 0 {
|
|
706
|
+
Ok(None)
|
|
707
|
+
} else {
|
|
708
|
+
self.compile(child).map(Some)
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
fn children(&mut self, value: Value) -> Result<Vec<usize>, Value> {
|
|
712
|
+
self.values(value)
|
|
713
|
+
.into_iter()
|
|
714
|
+
.map(|c| self.compile(c))
|
|
715
|
+
.collect()
|
|
716
|
+
}
|
|
717
|
+
fn branches(&mut self, value: Value) -> Result<Vec<(usize, usize)>, Value> {
|
|
718
|
+
let nodes = self.children(value)?;
|
|
719
|
+
let mut groups: Vec<(usize, usize)> = Vec::with_capacity(nodes.len());
|
|
720
|
+
for node in nodes {
|
|
721
|
+
if let Some(group) = groups
|
|
722
|
+
.last_mut()
|
|
723
|
+
.filter(|(prior, _)| *prior == node || self.same_simple_object(*prior, node))
|
|
724
|
+
{
|
|
725
|
+
group.1 += 1;
|
|
726
|
+
} else {
|
|
727
|
+
groups.push((node, 1));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
Ok(groups)
|
|
731
|
+
}
|
|
732
|
+
fn same_simple_object(&self, a: usize, b: usize) -> bool {
|
|
733
|
+
let ([Op::Object(required_a, a)], [Op::Object(required_b, b)]) =
|
|
734
|
+
(self.nodes[a].as_slice(), self.nodes[b].as_slice())
|
|
735
|
+
else {
|
|
736
|
+
return false;
|
|
737
|
+
};
|
|
738
|
+
let simple = |r: &Object| {
|
|
739
|
+
r.patterns.is_empty()
|
|
740
|
+
&& r.additional.is_none()
|
|
741
|
+
&& r.names.is_none()
|
|
742
|
+
&& r.dependent_required.is_empty()
|
|
743
|
+
&& r.dependent_schemas.is_empty()
|
|
744
|
+
&& r.unevaluated.is_none()
|
|
745
|
+
};
|
|
746
|
+
required_a == required_b
|
|
747
|
+
&& simple(a)
|
|
748
|
+
&& simple(b)
|
|
749
|
+
&& a.min == b.min
|
|
750
|
+
&& a.max == b.max
|
|
751
|
+
&& a.required == b.required
|
|
752
|
+
&& a.required_properties == b.required_properties
|
|
753
|
+
&& a.properties.len() == b.properties.len()
|
|
754
|
+
&& a.properties
|
|
755
|
+
.iter()
|
|
756
|
+
.zip(&b.properties)
|
|
757
|
+
.all(|(&(ka, ca), &(kb, cb))| {
|
|
758
|
+
// Exact key handles also preserve identity-Hash lookups. Only
|
|
759
|
+
// shape-independent leaves can ignore resource/path metadata.
|
|
760
|
+
ka == kb
|
|
761
|
+
&& match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
|
|
762
|
+
([Op::Type(a)], [Op::Type(b)]) => a == b,
|
|
763
|
+
([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
|
|
764
|
+
_ => false,
|
|
765
|
+
}
|
|
766
|
+
})
|
|
767
|
+
}
|
|
768
|
+
fn compile_map(&mut self, value: Value) -> Result<Vec<(Value, usize)>, Value> {
|
|
769
|
+
self.map(value)?
|
|
770
|
+
.into_iter()
|
|
771
|
+
.map(|(k, c)| Ok((k, self.compile(c)?)))
|
|
772
|
+
.collect()
|
|
773
|
+
}
|
|
774
|
+
fn compile(&mut self, program: Value) -> Result<usize, Value> {
|
|
775
|
+
if let Some(&i) = self.indices.get(&program) {
|
|
776
|
+
return Ok(i);
|
|
777
|
+
}
|
|
778
|
+
self.retain(program);
|
|
779
|
+
let index = self.nodes.len();
|
|
780
|
+
self.indices.insert(program, index);
|
|
781
|
+
self.nodes.push(vec![]);
|
|
782
|
+
self.error_operands.push(vec![]);
|
|
783
|
+
self.tracks.push(false);
|
|
784
|
+
self.metadata.push(scope::Metadata::default());
|
|
785
|
+
let code = self.field(program, b"code\0")?;
|
|
786
|
+
let flags = unsafe { sn_integer(sn_entry(code, 0)) };
|
|
787
|
+
self.tracks[index] = flags & i64::from(TRACKS_EVALUATION) != 0;
|
|
788
|
+
self.compile_scope(program, index, flags)?;
|
|
789
|
+
let mut ops = vec![];
|
|
790
|
+
let mut error_operands = vec![];
|
|
791
|
+
for i in (1..unsafe { sn_len(code) }).step_by(2) {
|
|
792
|
+
let operand = self.retain(unsafe { sn_entry(code, i + 1) });
|
|
793
|
+
let opcode = unsafe { sn_opcode(sn_entry(code, i)) };
|
|
794
|
+
error_operands.push((opcode, operand));
|
|
795
|
+
let op = match opcode {
|
|
796
|
+
OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
|
|
797
|
+
OP_TYPE_NULL => Op::Type(TYPE_NULL),
|
|
798
|
+
OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
|
|
799
|
+
OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
|
|
800
|
+
OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
|
|
801
|
+
OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
|
|
802
|
+
OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
|
|
803
|
+
OP_TYPE_STRING => Op::Type(TYPE_STRING),
|
|
804
|
+
OP_TYPES => {
|
|
805
|
+
let mask = self.field(operand, b"mask\0")?;
|
|
806
|
+
Op::Type(unsafe { sn_integer(mask) } as i32)
|
|
807
|
+
}
|
|
808
|
+
OP_CONST => Op::Const(operand),
|
|
809
|
+
OP_ENUM => Op::Enum(Box::new(enumeration::Enumeration::compile(
|
|
810
|
+
self.values(operand),
|
|
811
|
+
self.fixnum_flag,
|
|
812
|
+
))),
|
|
813
|
+
OP_ALL_OF => Op::All(self.children(operand)?),
|
|
814
|
+
OP_ANY_OF => Op::Any(self.branches(operand)?),
|
|
815
|
+
OP_ONE_OF => Op::One(self.branches(operand)?),
|
|
816
|
+
OP_NOT => Op::Not(self.compile(operand)?),
|
|
817
|
+
OP_CONDITIONAL => {
|
|
818
|
+
let condition = self.field(operand, b"condition\0")?;
|
|
819
|
+
Op::Conditional(
|
|
820
|
+
self.compile(condition)?,
|
|
821
|
+
self.child(operand, b"then_branch\0")?,
|
|
822
|
+
self.child(operand, b"else_branch\0")?,
|
|
823
|
+
)
|
|
824
|
+
}
|
|
825
|
+
OP_NUMBER | OP_TYPED_NUMBER | OP_TYPED_INTEGER => {
|
|
826
|
+
if let Some(rules) = self.compile_number(operand)? {
|
|
827
|
+
Op::Number(
|
|
828
|
+
if opcode == OP_NUMBER {
|
|
829
|
+
0
|
|
830
|
+
} else if opcode == OP_TYPED_NUMBER {
|
|
831
|
+
TYPE_NUMBER
|
|
832
|
+
} else {
|
|
833
|
+
TYPE_INTEGER
|
|
834
|
+
},
|
|
835
|
+
Box::new(rules),
|
|
836
|
+
)
|
|
837
|
+
} else {
|
|
838
|
+
Op::Unsupported
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
OP_STRING | OP_TYPED_STRING => {
|
|
842
|
+
let pattern = self.field(operand, b"pattern\0")?;
|
|
843
|
+
let format = self.field(operand, b"format\0")?;
|
|
844
|
+
let base64 = self.field(operand, b"decode_base64\0")?;
|
|
845
|
+
let json = self.field(operand, b"parse_json\0")?;
|
|
846
|
+
{
|
|
847
|
+
let content = if self.validate_content
|
|
848
|
+
&& unsafe { sn_true(base64) | sn_true(json) } != 0
|
|
849
|
+
{
|
|
850
|
+
Some(content::Content {
|
|
851
|
+
base64: unsafe { sn_true(base64) } != 0,
|
|
852
|
+
json: unsafe { sn_true(json) } != 0,
|
|
853
|
+
})
|
|
854
|
+
} else {
|
|
855
|
+
None
|
|
856
|
+
};
|
|
857
|
+
let assertion = self.field(operand, b"format_assertion\0")?;
|
|
858
|
+
let format = if (self.validate_format || unsafe { sn_true(assertion) } != 0)
|
|
859
|
+
&& unsafe { sn_same(format, sn_nil()) } == 0
|
|
860
|
+
{
|
|
861
|
+
let name = self.field(format, b"name\0")?;
|
|
862
|
+
if unsafe { sn_same(name, sn_nil()) } != 0 {
|
|
863
|
+
None
|
|
864
|
+
} else {
|
|
865
|
+
Some(format::Format::compile(name))
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
None
|
|
869
|
+
};
|
|
870
|
+
Op::String(
|
|
871
|
+
if opcode == OP_TYPED_STRING {
|
|
872
|
+
TYPE_STRING
|
|
873
|
+
} else {
|
|
874
|
+
0
|
|
875
|
+
},
|
|
876
|
+
Box::new(StringRules {
|
|
877
|
+
format,
|
|
878
|
+
content,
|
|
879
|
+
min: self.size_field(operand, b"min_length\0")?,
|
|
880
|
+
max: self.size_field(operand, b"max_length\0")?,
|
|
881
|
+
pattern: if unsafe { sn_same(pattern, sn_nil()) } != 0 {
|
|
882
|
+
None
|
|
883
|
+
} else {
|
|
884
|
+
Some(pattern::Pattern::compile(
|
|
885
|
+
pattern,
|
|
886
|
+
&mut self.pattern_cache_count,
|
|
887
|
+
))
|
|
888
|
+
},
|
|
889
|
+
}),
|
|
890
|
+
)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
OP_ARRAY | OP_TYPED_ARRAY => {
|
|
894
|
+
let unique = self.field(operand, b"unique\0")?;
|
|
895
|
+
let prefix = self.field(operand, b"prefix_items\0")?;
|
|
896
|
+
let prefix = self.children(prefix)?;
|
|
897
|
+
let items = self.field(operand, b"items\0")?;
|
|
898
|
+
let list = self.field(operand, b"items_list\0")?;
|
|
899
|
+
let list = unsafe { sn_true(list) } != 0;
|
|
900
|
+
let items = if list {
|
|
901
|
+
self.children(items)?
|
|
902
|
+
} else if unsafe { sn_same(items, sn_nil()) } != 0 {
|
|
903
|
+
vec![]
|
|
904
|
+
} else {
|
|
905
|
+
vec![self.compile(items)?]
|
|
906
|
+
};
|
|
907
|
+
Op::Array(
|
|
908
|
+
if opcode == OP_TYPED_ARRAY {
|
|
909
|
+
TYPE_ARRAY
|
|
910
|
+
} else {
|
|
911
|
+
0
|
|
912
|
+
},
|
|
913
|
+
Box::new(Array {
|
|
914
|
+
unique: unsafe { sn_true(unique) } != 0,
|
|
915
|
+
min: self.size_field(operand, b"min_items\0")?,
|
|
916
|
+
max: self.size_field(operand, b"max_items\0")?,
|
|
917
|
+
prefix,
|
|
918
|
+
items,
|
|
919
|
+
list,
|
|
920
|
+
additional: self.child(operand, b"additional\0")?,
|
|
921
|
+
contains: self.child(operand, b"contains\0")?,
|
|
922
|
+
min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
|
|
923
|
+
max_contains: self.size_field(operand, b"max_contains\0")?,
|
|
924
|
+
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
925
|
+
}),
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
OP_OBJECT | OP_TYPED_OBJECT => {
|
|
929
|
+
let patterns = self.field(operand, b"patterns\0")?;
|
|
930
|
+
let dependencies = self.field(operand, b"dependencies\0")?;
|
|
931
|
+
{
|
|
932
|
+
let patterns = self.compile_map(patterns)?;
|
|
933
|
+
let pattern_names: Vec<_> = patterns.iter().map(|&(key, _)| key).collect();
|
|
934
|
+
let patterns: Vec<_> = patterns
|
|
935
|
+
.into_iter()
|
|
936
|
+
.map(|(key, child)| {
|
|
937
|
+
(
|
|
938
|
+
pattern::Pattern::compile(key, &mut self.pattern_cache_count),
|
|
939
|
+
child,
|
|
940
|
+
)
|
|
941
|
+
})
|
|
942
|
+
.collect();
|
|
943
|
+
let pattern_group =
|
|
944
|
+
pattern::PatternGroup::compile(&pattern_names, &patterns);
|
|
945
|
+
let required = self.field(operand, b"required\0")?;
|
|
946
|
+
let required = self.values(required);
|
|
947
|
+
let required_order = required.clone();
|
|
948
|
+
let properties = self.field(operand, b"properties\0")?;
|
|
949
|
+
let properties = self.compile_map(properties)?;
|
|
950
|
+
if required
|
|
951
|
+
.iter()
|
|
952
|
+
.chain(properties.iter().map(|(key, _)| key))
|
|
953
|
+
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
954
|
+
{
|
|
955
|
+
ops.push(Op::Unsupported);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
let property_targets: HashMap<_, _, BuildHasherDefault<BucketHasher>> =
|
|
959
|
+
properties
|
|
960
|
+
.iter()
|
|
961
|
+
.enumerate()
|
|
962
|
+
.map(|(index, &(name, child))| {
|
|
963
|
+
(
|
|
964
|
+
tracking::JsonKey::new(name),
|
|
965
|
+
PropertyTarget { name, child, index },
|
|
966
|
+
)
|
|
967
|
+
})
|
|
968
|
+
.collect();
|
|
969
|
+
let property_locations: Vec<_> = properties
|
|
970
|
+
.iter()
|
|
971
|
+
.map(|&(key, _)| tracking::JsonKey::new(key))
|
|
972
|
+
.collect();
|
|
973
|
+
let property_keys = property_locations.iter().copied().collect();
|
|
974
|
+
let property_indices = property_locations
|
|
975
|
+
.iter()
|
|
976
|
+
.map(|&key| {
|
|
977
|
+
let next = self.property_ids.len();
|
|
978
|
+
*self.property_ids.entry(key).or_insert(next)
|
|
979
|
+
})
|
|
980
|
+
.collect();
|
|
981
|
+
// Keep tiny schemas allocation-cheap; larger joins use
|
|
982
|
+
// content-keyed sets and the already compiled property index.
|
|
983
|
+
let indexed_required = properties.len().saturating_mul(required.len()) > 64;
|
|
984
|
+
let required_keys: Option<tracking::KeySet> = indexed_required.then(|| {
|
|
985
|
+
required
|
|
986
|
+
.iter()
|
|
987
|
+
.map(|&name| tracking::JsonKey::new(name))
|
|
988
|
+
.collect()
|
|
989
|
+
});
|
|
990
|
+
let required_properties: Vec<bool> = property_locations
|
|
991
|
+
.iter()
|
|
992
|
+
.map(|key| {
|
|
993
|
+
required_keys.as_ref().map_or_else(
|
|
994
|
+
|| {
|
|
995
|
+
required.iter().any(
|
|
996
|
+
|&name| unsafe { sn_string_equal(key.0, name) } != 0,
|
|
997
|
+
)
|
|
998
|
+
},
|
|
999
|
+
|names| names.contains(key),
|
|
1000
|
+
)
|
|
1001
|
+
})
|
|
1002
|
+
.collect();
|
|
1003
|
+
let required_property_indices = required_properties
|
|
1004
|
+
.iter()
|
|
1005
|
+
.enumerate()
|
|
1006
|
+
.filter_map(|(index, &required)| required.then_some(index))
|
|
1007
|
+
.collect();
|
|
1008
|
+
let required = required
|
|
1009
|
+
.into_iter()
|
|
1010
|
+
.filter(|&name| {
|
|
1011
|
+
if indexed_required {
|
|
1012
|
+
!property_targets.contains_key(&tracking::JsonKey::new(name))
|
|
1013
|
+
} else {
|
|
1014
|
+
!properties
|
|
1015
|
+
.iter()
|
|
1016
|
+
.any(|&(key, _)| unsafe { sn_string_equal(key, name) } != 0)
|
|
1017
|
+
}
|
|
1018
|
+
})
|
|
1019
|
+
.collect();
|
|
1020
|
+
let dependent = self.field(operand, b"dependent_required\0")?;
|
|
1021
|
+
let mut dependent_required = vec![];
|
|
1022
|
+
let mut dependent_schemas = vec![];
|
|
1023
|
+
let mut diagnostic_dependencies = vec![];
|
|
1024
|
+
for pair in self.values(dependencies) {
|
|
1025
|
+
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1026
|
+
let dependency = self.retain(unsafe { sn_entry(pair, 1) });
|
|
1027
|
+
if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
|
|
1028
|
+
let names = self.values(dependency);
|
|
1029
|
+
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1030
|
+
name,
|
|
1031
|
+
names.clone(),
|
|
1032
|
+
"dependencies",
|
|
1033
|
+
));
|
|
1034
|
+
dependent_required.push((name, names));
|
|
1035
|
+
} else {
|
|
1036
|
+
let child = self.compile(dependency)?;
|
|
1037
|
+
diagnostic_dependencies.push(DiagnosticDependency::Schema(
|
|
1038
|
+
name,
|
|
1039
|
+
child,
|
|
1040
|
+
"dependencies",
|
|
1041
|
+
));
|
|
1042
|
+
dependent_schemas.push((name, child));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
for pair in self.values(dependent) {
|
|
1046
|
+
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1047
|
+
let names = self.values(unsafe { sn_entry(pair, 1) });
|
|
1048
|
+
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1049
|
+
name,
|
|
1050
|
+
names.clone(),
|
|
1051
|
+
"dependentRequired",
|
|
1052
|
+
));
|
|
1053
|
+
dependent_required.push((name, names));
|
|
1054
|
+
}
|
|
1055
|
+
let dependent = self.field(operand, b"dependent_schemas\0")?;
|
|
1056
|
+
let schemas = self.compile_map(dependent)?;
|
|
1057
|
+
diagnostic_dependencies.extend(schemas.iter().map(|&(name, child)| {
|
|
1058
|
+
DiagnosticDependency::Schema(name, child, "dependentSchemas")
|
|
1059
|
+
}));
|
|
1060
|
+
dependent_schemas.extend(schemas);
|
|
1061
|
+
if dependent_required.iter().any(|(name, keys)| {
|
|
1062
|
+
(unsafe { sn_kind(*name) }) != TYPE_STRING
|
|
1063
|
+
|| keys
|
|
1064
|
+
.iter()
|
|
1065
|
+
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1066
|
+
}) || dependent_schemas
|
|
1067
|
+
.iter()
|
|
1068
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1069
|
+
{
|
|
1070
|
+
ops.push(Op::Unsupported);
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
let mut object = Object {
|
|
1074
|
+
record: None,
|
|
1075
|
+
min: self.size_field(operand, b"min_properties\0")?,
|
|
1076
|
+
max: self.size_field(operand, b"max_properties\0")?,
|
|
1077
|
+
required,
|
|
1078
|
+
required_order,
|
|
1079
|
+
properties,
|
|
1080
|
+
property_targets,
|
|
1081
|
+
patterns,
|
|
1082
|
+
pattern_names,
|
|
1083
|
+
pattern_group,
|
|
1084
|
+
required_properties,
|
|
1085
|
+
required_property_indices,
|
|
1086
|
+
property_keys,
|
|
1087
|
+
property_indices,
|
|
1088
|
+
additional: self.child(operand, b"additional\0")?,
|
|
1089
|
+
names: self.child(operand, b"property_names\0")?,
|
|
1090
|
+
dependent_required,
|
|
1091
|
+
dependent_schemas,
|
|
1092
|
+
diagnostic_dependencies,
|
|
1093
|
+
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
1094
|
+
}
|
|
1095
|
+
.with_record();
|
|
1096
|
+
if let Some(record) = &mut object.record {
|
|
1097
|
+
for field in &mut record.fields {
|
|
1098
|
+
field.check = field.child.and_then(|child| {
|
|
1099
|
+
match self.nodes[child].as_slice() {
|
|
1100
|
+
[Op::Number(_, rules)] => {
|
|
1101
|
+
rules.fast_limits.as_ref().map(|limits| {
|
|
1102
|
+
let (min, max) = limits.iter().fold(
|
|
1103
|
+
(i64::MIN, i64::MAX),
|
|
1104
|
+
|(min, max), &(bit, limit)| match bit {
|
|
1105
|
+
MAXIMUM => (min, max.min(limit)),
|
|
1106
|
+
MINIMUM => (min.max(limit), max),
|
|
1107
|
+
EXCLUSIVE_MAXIMUM => {
|
|
1108
|
+
(min, max.min(limit.saturating_sub(1)))
|
|
1109
|
+
}
|
|
1110
|
+
_ => {
|
|
1111
|
+
(min.max(limit.saturating_add(1)), max)
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
);
|
|
1115
|
+
RecordCheck::IntegerBounds(min, max)
|
|
1116
|
+
})
|
|
1117
|
+
}
|
|
1118
|
+
[Op::String(required, rules)] if rules.length_only() => {
|
|
1119
|
+
Some(RecordCheck::StringLength(
|
|
1120
|
+
*required, rules.min, rules.max,
|
|
1121
|
+
))
|
|
1122
|
+
}
|
|
1123
|
+
_ => None,
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
let mut key = 0;
|
|
1127
|
+
if unsafe { sn_key_copy(field.name, &mut key) } != 0 {
|
|
1128
|
+
return Err(key);
|
|
1129
|
+
}
|
|
1130
|
+
// Distinct schema-only lookup handles also work
|
|
1131
|
+
// when VM keys and input keys share an fstring.
|
|
1132
|
+
field.name = self.retain(key);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
Op::Object(
|
|
1136
|
+
if opcode == OP_TYPED_OBJECT {
|
|
1137
|
+
TYPE_OBJECT
|
|
1138
|
+
} else {
|
|
1139
|
+
0
|
|
1140
|
+
},
|
|
1141
|
+
Box::new(object),
|
|
1142
|
+
)
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
OP_REF | OP_RECURSIVE_REF | OP_DYNAMIC_REF => {
|
|
1146
|
+
let reference = self.field(operand, b"value\0")?;
|
|
1147
|
+
let mut target = 0;
|
|
1148
|
+
if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
|
|
1149
|
+
if unsafe { sn_resolution_error(target) } != 0 {
|
|
1150
|
+
Op::Unsupported
|
|
1151
|
+
} else {
|
|
1152
|
+
return Err(target);
|
|
1153
|
+
}
|
|
1154
|
+
} else {
|
|
1155
|
+
self.retain(target);
|
|
1156
|
+
let target = self.compile(target)?;
|
|
1157
|
+
self.compile_reference(operand, opcode, target)?
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
_ => Op::Unsupported,
|
|
1161
|
+
};
|
|
1162
|
+
ops.push(op);
|
|
1163
|
+
}
|
|
1164
|
+
self.nodes[index] = ops;
|
|
1165
|
+
self.error_operands[index] = error_operands;
|
|
1166
|
+
Ok(index)
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// A view of immutable compiled data and validator-owned regex search caches.
|
|
1171
|
+
struct Execution<'a> {
|
|
1172
|
+
engine: &'a Engine,
|
|
1173
|
+
caches: &'a pattern::Caches,
|
|
1174
|
+
memo: memo::Memo,
|
|
1175
|
+
budget: budget::Budget,
|
|
1176
|
+
}
|
|
1177
|
+
impl std::ops::Deref for Execution<'_> {
|
|
1178
|
+
type Target = Engine;
|
|
1179
|
+
fn deref(&self) -> &Engine {
|
|
1180
|
+
self.engine
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
impl Execution<'_> {
|
|
1184
|
+
fn eval(&self, node: usize, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
1185
|
+
self.eval_with_kind(node, value, depth, active, None)
|
|
1186
|
+
}
|
|
1187
|
+
// Same-location applicators borrow an immutable instance under the GVL.
|
|
1188
|
+
// A full key guard can be reused; descendants at other locations recheck.
|
|
1189
|
+
fn eval_with_kind(
|
|
1190
|
+
&self,
|
|
1191
|
+
node: usize,
|
|
1192
|
+
value: Value,
|
|
1193
|
+
depth: usize,
|
|
1194
|
+
active: scope::Context<'_>,
|
|
1195
|
+
known_kind: Option<i32>,
|
|
1196
|
+
) -> Verdict {
|
|
1197
|
+
if !self.budget.checkpoint() {
|
|
1198
|
+
return Unsupported;
|
|
1199
|
+
}
|
|
1200
|
+
let Some(key) = self
|
|
1201
|
+
.memo
|
|
1202
|
+
.key(self, node, value, depth, active, &self.budget)
|
|
1203
|
+
else {
|
|
1204
|
+
if self.budget.hit.get().0 != 0 {
|
|
1205
|
+
return Unsupported;
|
|
1206
|
+
}
|
|
1207
|
+
return self.eval_uncached(node, value, depth, active, known_kind);
|
|
1208
|
+
};
|
|
1209
|
+
if self.budget.hit.get().0 != 0 {
|
|
1210
|
+
return Unsupported;
|
|
1211
|
+
}
|
|
1212
|
+
if let Some(status) = self.memo.verdict(key) {
|
|
1213
|
+
return status;
|
|
1214
|
+
}
|
|
1215
|
+
let status = self.eval_uncached(node, value, depth, active, known_kind);
|
|
1216
|
+
self.memo.record_verdict(key, status);
|
|
1217
|
+
status
|
|
1218
|
+
}
|
|
1219
|
+
fn eval_uncached(
|
|
1220
|
+
&self,
|
|
1221
|
+
node: usize,
|
|
1222
|
+
value: Value,
|
|
1223
|
+
depth: usize,
|
|
1224
|
+
active: scope::Context<'_>,
|
|
1225
|
+
known_kind: Option<i32>,
|
|
1226
|
+
) -> Verdict {
|
|
1227
|
+
let frame = scope::ScopeFrame {
|
|
1228
|
+
resource: self.metadata[node].resource,
|
|
1229
|
+
parent: active.scope,
|
|
1230
|
+
primary_binding: active
|
|
1231
|
+
.scope
|
|
1232
|
+
.and_then(|frame| frame.primary_binding)
|
|
1233
|
+
.or(self.metadata[node].primary_binding),
|
|
1234
|
+
};
|
|
1235
|
+
let active = self.enter_scope(node, active, &frame);
|
|
1236
|
+
if self.tracks[node] {
|
|
1237
|
+
return match known_kind {
|
|
1238
|
+
Some(kind) => {
|
|
1239
|
+
self.eval_tracked_kind(node, value, depth, active, kind)
|
|
1240
|
+
.status
|
|
1241
|
+
}
|
|
1242
|
+
None => self.eval_tracked(node, value, depth, active).status,
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
1246
|
+
return Unsupported;
|
|
1247
|
+
}
|
|
1248
|
+
if let [Op::String(required, rules)] = self.nodes[node].as_slice()
|
|
1249
|
+
&& rules.pattern.is_some()
|
|
1250
|
+
{
|
|
1251
|
+
return rules.eval_guarded(self.caches, *required, value);
|
|
1252
|
+
}
|
|
1253
|
+
let kind = known_kind.unwrap_or_else(|| {
|
|
1254
|
+
if value & self.fixnum_flag != 0 {
|
|
1255
|
+
TYPE_INTEGRAL_NUMBER
|
|
1256
|
+
} else {
|
|
1257
|
+
self.instance_kind(node, value)
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
if kind == TYPE_UNSUPPORTED {
|
|
1261
|
+
return Unsupported;
|
|
1262
|
+
}
|
|
1263
|
+
for op in &self.nodes[node] {
|
|
1264
|
+
if !self.budget.checkpoint() {
|
|
1265
|
+
return Unsupported;
|
|
1266
|
+
}
|
|
1267
|
+
let outcome = match op {
|
|
1268
|
+
Op::Ref(target) => {
|
|
1269
|
+
if RefFrame::contains(active.active, node, value) {
|
|
1270
|
+
Valid
|
|
1271
|
+
} else {
|
|
1272
|
+
let frame = RefFrame {
|
|
1273
|
+
source: node,
|
|
1274
|
+
value,
|
|
1275
|
+
parent: active.active,
|
|
1276
|
+
};
|
|
1277
|
+
self.eval_with_kind(
|
|
1278
|
+
self.reference_target(*target, active),
|
|
1279
|
+
value,
|
|
1280
|
+
depth + 1,
|
|
1281
|
+
scope::Context {
|
|
1282
|
+
active: Some(&frame),
|
|
1283
|
+
..active
|
|
1284
|
+
},
|
|
1285
|
+
Some(kind),
|
|
1286
|
+
)
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
Op::All(nodes) => {
|
|
1290
|
+
let mut outcome = Valid;
|
|
1291
|
+
for &n in nodes {
|
|
1292
|
+
if !self.budget.checkpoint() {
|
|
1293
|
+
return Unsupported;
|
|
1294
|
+
}
|
|
1295
|
+
outcome = self.eval_with_kind(n, value, depth + 1, active, Some(kind));
|
|
1296
|
+
if outcome != Valid {
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
outcome
|
|
1301
|
+
}
|
|
1302
|
+
Op::Any(nodes) | Op::One(nodes) => {
|
|
1303
|
+
let branch_kind = if matches!(op, Op::Any(_)) {
|
|
1304
|
+
evaluation::BranchKind::Any
|
|
1305
|
+
} else {
|
|
1306
|
+
evaluation::BranchKind::One
|
|
1307
|
+
};
|
|
1308
|
+
match evaluation::scan_branches(
|
|
1309
|
+
nodes,
|
|
1310
|
+
evaluation::BranchPolicy::Validity(branch_kind),
|
|
1311
|
+
|child| self.eval_with_kind(child, value, depth + 1, active, Some(kind)),
|
|
1312
|
+
) {
|
|
1313
|
+
Ok(result) => verdict(branch_kind.accepts(result.matches)),
|
|
1314
|
+
Err(status) => status,
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
Op::Not(n) => match self.eval_with_kind(*n, value, depth + 1, active, Some(kind)) {
|
|
1318
|
+
Valid => Invalid,
|
|
1319
|
+
Invalid => Valid,
|
|
1320
|
+
Unsupported => Unsupported,
|
|
1321
|
+
},
|
|
1322
|
+
Op::Conditional(c, yes, no) => {
|
|
1323
|
+
match self.eval_with_kind(*c, value, depth + 1, active, Some(kind)) {
|
|
1324
|
+
Unsupported => Unsupported,
|
|
1325
|
+
result => match if result == Valid { yes } else { no } {
|
|
1326
|
+
Some(n) => {
|
|
1327
|
+
self.eval_with_kind(*n, value, depth + 1, active, Some(kind))
|
|
1328
|
+
}
|
|
1329
|
+
None => Valid,
|
|
1330
|
+
},
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
Op::Array(required, rules) => {
|
|
1334
|
+
if *required != 0 && kind != TYPE_ARRAY {
|
|
1335
|
+
Invalid
|
|
1336
|
+
} else if kind != TYPE_ARRAY {
|
|
1337
|
+
Valid
|
|
1338
|
+
} else {
|
|
1339
|
+
self.array(rules, value, depth + 1, active)
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
Op::Object(required, rules) => {
|
|
1343
|
+
if *required != 0 && kind != TYPE_OBJECT {
|
|
1344
|
+
Invalid
|
|
1345
|
+
} else if kind != TYPE_OBJECT {
|
|
1346
|
+
Valid
|
|
1347
|
+
} else {
|
|
1348
|
+
self.object(rules, value, depth + 1, active)
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
_ => self.leaf(op, kind, value, depth),
|
|
1352
|
+
};
|
|
1353
|
+
if outcome != Valid {
|
|
1354
|
+
return outcome;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
Valid
|
|
1358
|
+
}
|
|
1359
|
+
fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
|
|
1360
|
+
match op {
|
|
1361
|
+
Op::Unsupported => Unsupported,
|
|
1362
|
+
Op::Boolean(v) => verdict(*v),
|
|
1363
|
+
Op::Type(mask) => verdict(kind & mask != 0),
|
|
1364
|
+
Op::Const(v) => equal_budgeted(*v, value, depth, &self.budget),
|
|
1365
|
+
Op::Enum(values) => values.eval(value, kind, depth, self.fixnum_flag, &self.budget),
|
|
1366
|
+
Op::Number(required, rules) => {
|
|
1367
|
+
if *required != 0 && kind & required == 0 {
|
|
1368
|
+
Invalid
|
|
1369
|
+
} else if kind & TYPE_NUMBER == 0 {
|
|
1370
|
+
Valid
|
|
1371
|
+
} else {
|
|
1372
|
+
rules.eval(value)
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
Op::String(required, rules) => {
|
|
1376
|
+
if *required != 0 && kind != TYPE_STRING {
|
|
1377
|
+
Invalid
|
|
1378
|
+
} else if kind != TYPE_STRING {
|
|
1379
|
+
Valid
|
|
1380
|
+
} else {
|
|
1381
|
+
rules.eval(self.caches, value)
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
_ => unreachable!("applicators must be evaluated with their context"),
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
1388
|
+
if !self.budget.checkpoint() {
|
|
1389
|
+
return Unsupported;
|
|
1390
|
+
}
|
|
1391
|
+
let len = unsafe { sn_len(value) };
|
|
1392
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
1393
|
+
return Invalid;
|
|
1394
|
+
}
|
|
1395
|
+
if r.unique {
|
|
1396
|
+
let result = unique(value, depth, self.fixnum_flag, &self.budget);
|
|
1397
|
+
if result != Valid {
|
|
1398
|
+
return result;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
for (i, &child) in r.prefix.iter().take(len).enumerate() {
|
|
1402
|
+
if !self.budget.checkpoint() {
|
|
1403
|
+
return Unsupported;
|
|
1404
|
+
}
|
|
1405
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1406
|
+
if result != Valid {
|
|
1407
|
+
return result;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if r.list {
|
|
1411
|
+
for (i, &child) in r.items.iter().take(len).enumerate() {
|
|
1412
|
+
if !self.budget.checkpoint() {
|
|
1413
|
+
return Unsupported;
|
|
1414
|
+
}
|
|
1415
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1416
|
+
if result != Valid {
|
|
1417
|
+
return result;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
if let Some(child) = r.additional {
|
|
1421
|
+
for i in r.items.len()..len {
|
|
1422
|
+
if !self.budget.checkpoint() {
|
|
1423
|
+
return Unsupported;
|
|
1424
|
+
}
|
|
1425
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1426
|
+
if result != Valid {
|
|
1427
|
+
return result;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
} else if let Some(&child) = r.items.first() {
|
|
1432
|
+
// Borrow CRuby's storage only for this Ruby-allocation-free traversal.
|
|
1433
|
+
// No Ruby callback or Ruby allocation occurs while it is live.
|
|
1434
|
+
let values = if len == 0 {
|
|
1435
|
+
&[]
|
|
1436
|
+
} else {
|
|
1437
|
+
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
1438
|
+
};
|
|
1439
|
+
let primitive = match self.nodes[child].as_slice() {
|
|
1440
|
+
[Op::Type(mask)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
|
|
1441
|
+
Some(&[][..])
|
|
1442
|
+
}
|
|
1443
|
+
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
1444
|
+
_ => None,
|
|
1445
|
+
};
|
|
1446
|
+
let number = match self.nodes[child].as_slice() {
|
|
1447
|
+
[Op::Number(required, rules)]
|
|
1448
|
+
if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
|
|
1449
|
+
{
|
|
1450
|
+
Some((*required, rules))
|
|
1451
|
+
}
|
|
1452
|
+
_ => None,
|
|
1453
|
+
};
|
|
1454
|
+
let string = match self.nodes[child].as_slice() {
|
|
1455
|
+
[Op::String(required, rules)] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1456
|
+
Some((*required, rules))
|
|
1457
|
+
}
|
|
1458
|
+
_ => None,
|
|
1459
|
+
};
|
|
1460
|
+
let object = match self.nodes[child].as_slice() {
|
|
1461
|
+
[Op::Object(required, rules)]
|
|
1462
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1463
|
+
&& !self.tracks[child]
|
|
1464
|
+
&& rules.record.is_some() =>
|
|
1465
|
+
{
|
|
1466
|
+
Some((*required, rules))
|
|
1467
|
+
}
|
|
1468
|
+
_ => None,
|
|
1469
|
+
};
|
|
1470
|
+
// Sibling items enter the same schema resource. Reuse the immutable
|
|
1471
|
+
// scope frame while their child evaluations remain synchronous.
|
|
1472
|
+
let frame = scope::ScopeFrame {
|
|
1473
|
+
resource: self.metadata[child].resource,
|
|
1474
|
+
parent: active.scope,
|
|
1475
|
+
primary_binding: active
|
|
1476
|
+
.scope
|
|
1477
|
+
.and_then(|frame| frame.primary_binding)
|
|
1478
|
+
.or(self.metadata[child].primary_binding),
|
|
1479
|
+
};
|
|
1480
|
+
let object_active = self.enter_scope(child, active, &frame);
|
|
1481
|
+
let pattern = string.and_then(|(required, rules)| {
|
|
1482
|
+
rules
|
|
1483
|
+
.pattern_only()
|
|
1484
|
+
.map(|pattern| (required, pattern))
|
|
1485
|
+
.filter(|_| !self.budget.timed())
|
|
1486
|
+
});
|
|
1487
|
+
let format = string.and_then(|(required, rules)| {
|
|
1488
|
+
rules
|
|
1489
|
+
.format_only()
|
|
1490
|
+
.map(|format| (required, format))
|
|
1491
|
+
.filter(|_| !self.budget.timed())
|
|
1492
|
+
});
|
|
1493
|
+
let content = string.and_then(|(required, rules)| {
|
|
1494
|
+
rules
|
|
1495
|
+
.content_only()
|
|
1496
|
+
.map(|content| (required, content))
|
|
1497
|
+
.filter(|_| !self.budget.timed())
|
|
1498
|
+
});
|
|
1499
|
+
if let Some((required, pattern)) = pattern {
|
|
1500
|
+
let result = pattern.eval_items(
|
|
1501
|
+
self.caches,
|
|
1502
|
+
required,
|
|
1503
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1504
|
+
);
|
|
1505
|
+
if result != Valid {
|
|
1506
|
+
return result;
|
|
1507
|
+
}
|
|
1508
|
+
} else if let Some((required, format)) = format {
|
|
1509
|
+
let result =
|
|
1510
|
+
format.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
|
|
1511
|
+
if result != Valid {
|
|
1512
|
+
return result;
|
|
1513
|
+
}
|
|
1514
|
+
} else if let Some((required, content)) = content {
|
|
1515
|
+
let result =
|
|
1516
|
+
content.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
|
|
1517
|
+
if result != Valid {
|
|
1518
|
+
return result;
|
|
1519
|
+
}
|
|
1520
|
+
} else if let Some((required, rules)) = object {
|
|
1521
|
+
let result = self.record_items(
|
|
1522
|
+
rules,
|
|
1523
|
+
required,
|
|
1524
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1525
|
+
depth + 1,
|
|
1526
|
+
object_active,
|
|
1527
|
+
);
|
|
1528
|
+
if result != Valid {
|
|
1529
|
+
return result;
|
|
1530
|
+
}
|
|
1531
|
+
} else if let Some(limits) = primitive {
|
|
1532
|
+
let result = self.integer_items(
|
|
1533
|
+
limits,
|
|
1534
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1535
|
+
child,
|
|
1536
|
+
depth,
|
|
1537
|
+
active,
|
|
1538
|
+
);
|
|
1539
|
+
if result != Valid {
|
|
1540
|
+
return result;
|
|
1541
|
+
}
|
|
1542
|
+
} else {
|
|
1543
|
+
for &item in values.iter().skip(r.prefix.len()) {
|
|
1544
|
+
if !self.budget.checkpoint() {
|
|
1545
|
+
return Unsupported;
|
|
1546
|
+
}
|
|
1547
|
+
let result = if let Some((required, rules)) = number {
|
|
1548
|
+
rules.eval_guarded(required, item)
|
|
1549
|
+
} else if let Some((required, rules)) = string {
|
|
1550
|
+
if rules.pattern.is_some() {
|
|
1551
|
+
rules.eval_guarded(self.caches, required, item)
|
|
1552
|
+
} else if rules.length_only() {
|
|
1553
|
+
rules.eval_length_guarded(required, item)
|
|
1554
|
+
} else {
|
|
1555
|
+
let kind = unsafe { sn_kind(item) };
|
|
1556
|
+
if kind == TYPE_UNSUPPORTED {
|
|
1557
|
+
Unsupported
|
|
1558
|
+
} else if kind != TYPE_STRING {
|
|
1559
|
+
verdict(required == 0)
|
|
1560
|
+
} else {
|
|
1561
|
+
rules.eval(self.caches, item)
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
} else {
|
|
1565
|
+
self.eval(child, item, depth, active)
|
|
1566
|
+
};
|
|
1567
|
+
if result != Valid {
|
|
1568
|
+
return result;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if let Some(child) = r.contains {
|
|
1574
|
+
if let Some(status) = self.fixnum_contains(r, child, value, len, depth) {
|
|
1575
|
+
return status;
|
|
1576
|
+
}
|
|
1577
|
+
let matches = match evaluation::scan_contains(
|
|
1578
|
+
len,
|
|
1579
|
+
|index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
|
|
1580
|
+
|_| {},
|
|
1581
|
+
) {
|
|
1582
|
+
Ok(matches) => matches,
|
|
1583
|
+
Err(status) => return status,
|
|
1584
|
+
};
|
|
1585
|
+
if bounds(matches, Some(r.min_contains), r.max_contains) == Invalid {
|
|
1586
|
+
return Invalid;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
Valid
|
|
1590
|
+
}
|
|
1591
|
+
fn fixnum_contains(
|
|
1592
|
+
&self,
|
|
1593
|
+
rules: &Array,
|
|
1594
|
+
child: usize,
|
|
1595
|
+
value: Value,
|
|
1596
|
+
len: usize,
|
|
1597
|
+
depth: usize,
|
|
1598
|
+
) -> Option<Verdict> {
|
|
1599
|
+
if depth > MAX_EVALUATION_DEPTH || self.tracks[child] || self.budget.timed() {
|
|
1600
|
+
return None;
|
|
1601
|
+
}
|
|
1602
|
+
let [op] = self.nodes[child].as_slice() else {
|
|
1603
|
+
return None;
|
|
1604
|
+
};
|
|
1605
|
+
let total = match op {
|
|
1606
|
+
Op::Type(_) | Op::Boolean(_) => true,
|
|
1607
|
+
Op::Const(expected) => expected & self.fixnum_flag != 0,
|
|
1608
|
+
Op::Number(_, rules) => rules.fast_limits.is_some(),
|
|
1609
|
+
_ => false,
|
|
1610
|
+
};
|
|
1611
|
+
if !total {
|
|
1612
|
+
return None;
|
|
1613
|
+
}
|
|
1614
|
+
let values = if len == 0 {
|
|
1615
|
+
&[]
|
|
1616
|
+
} else {
|
|
1617
|
+
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
1618
|
+
};
|
|
1619
|
+
// Preserve later Unsupported/fallback results: no predicate work may
|
|
1620
|
+
// be skipped until all values are proven to be supported Fixnums.
|
|
1621
|
+
// This guard stays linear, but is far cheaper than full node evaluation.
|
|
1622
|
+
if !values.iter().all(|&item| item & self.fixnum_flag != 0) {
|
|
1623
|
+
return None;
|
|
1624
|
+
}
|
|
1625
|
+
Some(evaluation::contains_validity(
|
|
1626
|
+
len,
|
|
1627
|
+
rules.min_contains,
|
|
1628
|
+
rules.max_contains,
|
|
1629
|
+
|index| self.leaf(op, TYPE_INTEGRAL_NUMBER, values[index], depth) == Valid,
|
|
1630
|
+
))
|
|
1631
|
+
}
|
|
1632
|
+
#[inline(never)]
|
|
1633
|
+
fn integer_items(
|
|
1634
|
+
&self,
|
|
1635
|
+
limits: &[(i32, i64)],
|
|
1636
|
+
values: &[Value],
|
|
1637
|
+
child: usize,
|
|
1638
|
+
depth: usize,
|
|
1639
|
+
active: scope::Context<'_>,
|
|
1640
|
+
) -> Verdict {
|
|
1641
|
+
// Interpret exact limits once; only header-validated immediate integers
|
|
1642
|
+
// use this interval. Other values retain the original guarded evaluator.
|
|
1643
|
+
let (min, max) =
|
|
1644
|
+
limits.iter().fold(
|
|
1645
|
+
(i64::MIN, i64::MAX),
|
|
1646
|
+
|(min, max), &(bit, limit)| match bit {
|
|
1647
|
+
MAXIMUM => (min, max.min(limit)),
|
|
1648
|
+
MINIMUM => (min.max(limit), max),
|
|
1649
|
+
EXCLUSIVE_MAXIMUM => (min, max.min(limit.saturating_sub(1))),
|
|
1650
|
+
_ => (min.max(limit.saturating_add(1)), max),
|
|
1651
|
+
},
|
|
1652
|
+
);
|
|
1653
|
+
let number = match self.nodes[child].as_slice() {
|
|
1654
|
+
[Op::Number(required, rules)]
|
|
1655
|
+
if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
|
|
1656
|
+
{
|
|
1657
|
+
Some((*required, rules))
|
|
1658
|
+
}
|
|
1659
|
+
_ => None,
|
|
1660
|
+
};
|
|
1661
|
+
for &value in values {
|
|
1662
|
+
if !self.budget.checkpoint() {
|
|
1663
|
+
return Unsupported;
|
|
1664
|
+
}
|
|
1665
|
+
let result = if value & self.fixnum_flag != 0 {
|
|
1666
|
+
let n = immediate_integer(value);
|
|
1667
|
+
verdict(n >= min && n <= max)
|
|
1668
|
+
} else if let Some((required, rules)) = number {
|
|
1669
|
+
rules.eval_guarded(required, value)
|
|
1670
|
+
} else {
|
|
1671
|
+
self.eval(child, value, depth, active)
|
|
1672
|
+
};
|
|
1673
|
+
if result != Valid {
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
Valid
|
|
1678
|
+
}
|
|
1679
|
+
#[inline(never)]
|
|
1680
|
+
fn record_items(
|
|
1681
|
+
&self,
|
|
1682
|
+
rules: &Object,
|
|
1683
|
+
required: i32,
|
|
1684
|
+
values: &[Value],
|
|
1685
|
+
depth: usize,
|
|
1686
|
+
active: scope::Context<'_>,
|
|
1687
|
+
) -> Verdict {
|
|
1688
|
+
for &value in values {
|
|
1689
|
+
if !self.budget.checkpoint() {
|
|
1690
|
+
return Unsupported;
|
|
1691
|
+
}
|
|
1692
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
1693
|
+
TYPE_INTEGRAL_NUMBER
|
|
1694
|
+
} else {
|
|
1695
|
+
unsafe { sn_shallow_kind(value) }
|
|
1696
|
+
};
|
|
1697
|
+
let result = if kind == TYPE_UNSUPPORTED {
|
|
1698
|
+
Unsupported
|
|
1699
|
+
} else if kind != TYPE_OBJECT {
|
|
1700
|
+
verdict(required == 0)
|
|
1701
|
+
} else {
|
|
1702
|
+
self.object(rules, value, depth, active)
|
|
1703
|
+
};
|
|
1704
|
+
if result != Valid {
|
|
1705
|
+
return result;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
Valid
|
|
1709
|
+
}
|
|
1710
|
+
fn object(
|
|
1711
|
+
&self,
|
|
1712
|
+
r: &Object,
|
|
1713
|
+
value: Value,
|
|
1714
|
+
depth: usize,
|
|
1715
|
+
active: scope::Context<'_>,
|
|
1716
|
+
) -> Verdict {
|
|
1717
|
+
if !self.budget.checkpoint() {
|
|
1718
|
+
return Unsupported;
|
|
1719
|
+
}
|
|
1720
|
+
if let Some(record) = &r.record {
|
|
1721
|
+
// C initializes only the written key/value pairs. The untouched
|
|
1722
|
+
// tail is never read, including empty and unsupported records.
|
|
1723
|
+
let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
|
|
1724
|
+
let len = unsafe { sn_small_hash(value, buffer.as_mut_ptr().cast()) };
|
|
1725
|
+
if len == SMALL_HASH_TOO_LARGE {
|
|
1726
|
+
return if unsafe { sn_kind(value) } == TYPE_OBJECT {
|
|
1727
|
+
self.object_general(r, value, depth, active)
|
|
1728
|
+
} else {
|
|
1729
|
+
Unsupported
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
if len < 0 {
|
|
1733
|
+
return Unsupported;
|
|
1734
|
+
}
|
|
1735
|
+
let ordinary = len & SMALL_HASH_ORDINARY != 0;
|
|
1736
|
+
let len = len & SMALL_HASH_COUNT_MASK;
|
|
1737
|
+
let status = bounds(len as usize, r.min, r.max);
|
|
1738
|
+
if status != Valid {
|
|
1739
|
+
return status;
|
|
1740
|
+
}
|
|
1741
|
+
// Successful calls wrote exactly len pairs, bounded by eight;
|
|
1742
|
+
// no Ruby callback or allocation can change the Hash during C's walk.
|
|
1743
|
+
let entries = unsafe {
|
|
1744
|
+
std::slice::from_raw_parts(buffer.as_ptr().cast::<Value>(), 2 * len as usize)
|
|
1745
|
+
};
|
|
1746
|
+
let mut context = RecordContext {
|
|
1747
|
+
engine: self,
|
|
1748
|
+
rules: r,
|
|
1749
|
+
record,
|
|
1750
|
+
// Choose for this record only. Both lookup strategies verify
|
|
1751
|
+
// every actual key; mixed layouts cannot change the verdict.
|
|
1752
|
+
indexed: record.short_index.is_some()
|
|
1753
|
+
&& entries.first().is_some_and(|key| {
|
|
1754
|
+
record.identity_hint != *key
|
|
1755
|
+
&& !record.fields.iter().any(|field| field.key == *key)
|
|
1756
|
+
}),
|
|
1757
|
+
depth,
|
|
1758
|
+
active,
|
|
1759
|
+
visited: 0,
|
|
1760
|
+
// A proven ordinary Hash never uses this lookup handle.
|
|
1761
|
+
// Seed its unused slot to avoid collecting a distinct key.
|
|
1762
|
+
probe: ordinary.then_some(self.undefined),
|
|
1763
|
+
status,
|
|
1764
|
+
};
|
|
1765
|
+
for entry in entries.as_chunks::<2>().0 {
|
|
1766
|
+
if !self.budget.checkpoint() {
|
|
1767
|
+
return Unsupported;
|
|
1768
|
+
}
|
|
1769
|
+
if record_entry(&mut context, entry[0], entry[1]) != Valid {
|
|
1770
|
+
return Unsupported;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
if !ordinary && let Some(name) = context.probe {
|
|
1774
|
+
// All keys have now been checked. A successful lookup by
|
|
1775
|
+
// a distinct equal schema key proves ordinary Hash key
|
|
1776
|
+
// semantics, without calling a Ruby identity predicate.
|
|
1777
|
+
if missing(value, name, self.undefined) {
|
|
1778
|
+
return Unsupported;
|
|
1779
|
+
}
|
|
1780
|
+
} else if !ordinary && context.visited != 0 {
|
|
1781
|
+
return Unsupported;
|
|
1782
|
+
}
|
|
1783
|
+
return if context.status != Valid {
|
|
1784
|
+
context.status
|
|
1785
|
+
} else {
|
|
1786
|
+
verdict(context.visited & record.required == record.required)
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
self.object_general(r, value, depth, active)
|
|
1790
|
+
}
|
|
1791
|
+
fn object_general(
|
|
1792
|
+
&self,
|
|
1793
|
+
r: &Object,
|
|
1794
|
+
value: Value,
|
|
1795
|
+
depth: usize,
|
|
1796
|
+
active: scope::Context<'_>,
|
|
1797
|
+
) -> Verdict {
|
|
1798
|
+
let len = unsafe { sn_len(value) };
|
|
1799
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
1800
|
+
return Invalid;
|
|
1801
|
+
}
|
|
1802
|
+
let status = r.check_required(value, self.undefined, &self.budget);
|
|
1803
|
+
if status != Valid {
|
|
1804
|
+
return status;
|
|
1805
|
+
}
|
|
1806
|
+
let found = match r.visit_properties(
|
|
1807
|
+
value,
|
|
1808
|
+
len,
|
|
1809
|
+
self.undefined,
|
|
1810
|
+
&self.budget,
|
|
1811
|
+
|_, child, item| self.eval(child, item, depth, active),
|
|
1812
|
+
) {
|
|
1813
|
+
Ok(found) => found,
|
|
1814
|
+
Err(status) => return status,
|
|
1815
|
+
};
|
|
1816
|
+
if r.names.is_some() || !r.patterns.is_empty() || (r.additional.is_some() && found < len) {
|
|
1817
|
+
let mut context = ObjectContext {
|
|
1818
|
+
engine: self,
|
|
1819
|
+
rules: r,
|
|
1820
|
+
depth,
|
|
1821
|
+
active,
|
|
1822
|
+
};
|
|
1823
|
+
let result =
|
|
1824
|
+
unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
|
|
1825
|
+
if result != VERDICT_VALID {
|
|
1826
|
+
return if result == 0 { Invalid } else { Unsupported };
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
let status = r.check_dependent_required(value, self.undefined, &self.budget);
|
|
1830
|
+
if status != Valid {
|
|
1831
|
+
return status;
|
|
1832
|
+
}
|
|
1833
|
+
for &(name, child) in &r.dependent_schemas {
|
|
1834
|
+
if !self.budget.checkpoint() {
|
|
1835
|
+
return Unsupported;
|
|
1836
|
+
}
|
|
1837
|
+
if !missing(value, name, self.undefined) {
|
|
1838
|
+
let result = self.eval(child, value, depth, active);
|
|
1839
|
+
if result != Valid {
|
|
1840
|
+
return result;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
Valid
|
|
1845
|
+
}
|
|
1846
|
+
fn instance_kind(&self, node: usize, value: Value) -> i32 {
|
|
1847
|
+
if matches!(self.nodes[node].as_slice(), [Op::Object(_, rules)] if rules.record.is_some()) {
|
|
1848
|
+
unsafe { sn_shallow_kind(value) }
|
|
1849
|
+
} else {
|
|
1850
|
+
unsafe { sn_kind(value) }
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
fn record_value(
|
|
1854
|
+
&self,
|
|
1855
|
+
node: usize,
|
|
1856
|
+
value: Value,
|
|
1857
|
+
depth: usize,
|
|
1858
|
+
active: scope::Context<'_>,
|
|
1859
|
+
) -> Verdict {
|
|
1860
|
+
if !self.budget.checkpoint() {
|
|
1861
|
+
return Unsupported;
|
|
1862
|
+
}
|
|
1863
|
+
match self.nodes[node].as_slice() {
|
|
1864
|
+
[Op::String(required, rules)]
|
|
1865
|
+
if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
|
|
1866
|
+
{
|
|
1867
|
+
rules.eval_length_guarded(*required, value)
|
|
1868
|
+
}
|
|
1869
|
+
[Op::String(required, rules)]
|
|
1870
|
+
if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
|
|
1871
|
+
{
|
|
1872
|
+
rules.eval_guarded(self.caches, *required, value)
|
|
1873
|
+
}
|
|
1874
|
+
[
|
|
1875
|
+
op @ (Op::Type(_)
|
|
1876
|
+
| Op::Boolean(_)
|
|
1877
|
+
| Op::Number(..)
|
|
1878
|
+
| Op::String(..)
|
|
1879
|
+
| Op::Const(_)
|
|
1880
|
+
| Op::Enum(_)),
|
|
1881
|
+
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1882
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
1883
|
+
TYPE_INTEGRAL_NUMBER
|
|
1884
|
+
} else {
|
|
1885
|
+
unsafe { sn_kind(value) }
|
|
1886
|
+
};
|
|
1887
|
+
if kind == TYPE_UNSUPPORTED {
|
|
1888
|
+
Unsupported
|
|
1889
|
+
} else {
|
|
1890
|
+
self.leaf(op, kind, value, depth)
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
_ => self.eval(node, value, depth, active),
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
struct RecordContext<'a, 'b> {
|
|
1898
|
+
engine: &'a Execution<'a>,
|
|
1899
|
+
rules: &'a Object,
|
|
1900
|
+
record: &'a RecordObject,
|
|
1901
|
+
indexed: bool,
|
|
1902
|
+
depth: usize,
|
|
1903
|
+
active: scope::Context<'b>,
|
|
1904
|
+
visited: u64,
|
|
1905
|
+
probe: Option<Value>,
|
|
1906
|
+
status: Verdict,
|
|
1907
|
+
}
|
|
1908
|
+
fn indexed_record_field(record: &RecordObject, key: Value) -> Option<&RecordField> {
|
|
1909
|
+
let index = record.short_index.as_ref()?;
|
|
1910
|
+
let short = short_record_key(key);
|
|
1911
|
+
if short.tag == 0 {
|
|
1912
|
+
return None;
|
|
1913
|
+
}
|
|
1914
|
+
index.slots[index.slot(short)]
|
|
1915
|
+
.checked_sub(1)
|
|
1916
|
+
.and_then(|at| record.fields.get(at as usize))
|
|
1917
|
+
.filter(|field| {
|
|
1918
|
+
field.short_key.tag & 15 == short.tag & 15
|
|
1919
|
+
&& field.short_key.word == short.word
|
|
1920
|
+
&& (field.short_key.tag == short.tag
|
|
1921
|
+
|| unsafe { sn_string_equal(field.key, key) } != 0)
|
|
1922
|
+
})
|
|
1923
|
+
}
|
|
1924
|
+
fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verdict {
|
|
1925
|
+
let field = if c.indexed {
|
|
1926
|
+
indexed_record_field(c.record, key)
|
|
1927
|
+
} else {
|
|
1928
|
+
c.record
|
|
1929
|
+
.fields
|
|
1930
|
+
.iter()
|
|
1931
|
+
.find(|field| field.key == key)
|
|
1932
|
+
.or_else(|| {
|
|
1933
|
+
if c.record.short_index.is_some() {
|
|
1934
|
+
return indexed_record_field(c.record, key);
|
|
1935
|
+
}
|
|
1936
|
+
let short = (c.record.fields.len() >= 4).then(|| short_record_key(key));
|
|
1937
|
+
c.record.fields.iter().find(|field| {
|
|
1938
|
+
if let Some(short) = short.filter(|short| short.tag != 0) {
|
|
1939
|
+
field.short_key.tag & 15 == short.tag & 15
|
|
1940
|
+
&& field.short_key.word == short.word
|
|
1941
|
+
&& (field.short_key.tag == short.tag
|
|
1942
|
+
|| unsafe { sn_string_equal(field.key, key) } != 0)
|
|
1943
|
+
} else {
|
|
1944
|
+
(unsafe { sn_string_equal(field.key, key) }) != 0
|
|
1945
|
+
}
|
|
1946
|
+
})
|
|
1947
|
+
})
|
|
1948
|
+
};
|
|
1949
|
+
let child = if let Some(field) = field {
|
|
1950
|
+
if c.visited & field.bit != 0 {
|
|
1951
|
+
return Unsupported;
|
|
1952
|
+
}
|
|
1953
|
+
c.visited |= field.bit;
|
|
1954
|
+
if c.probe.is_none() && field.name != key {
|
|
1955
|
+
c.probe = Some(field.name);
|
|
1956
|
+
}
|
|
1957
|
+
field.child.or(c.rules.additional)
|
|
1958
|
+
} else {
|
|
1959
|
+
c.rules.additional
|
|
1960
|
+
};
|
|
1961
|
+
if c.status == Valid
|
|
1962
|
+
&& let Some(child) = child
|
|
1963
|
+
{
|
|
1964
|
+
c.status = if c.depth <= MAX_EVALUATION_DEPTH {
|
|
1965
|
+
match field.and_then(|field| field.check) {
|
|
1966
|
+
Some(RecordCheck::IntegerBounds(min, max)) if value & c.engine.fixnum_flag != 0 => {
|
|
1967
|
+
let n = immediate_integer(value);
|
|
1968
|
+
verdict(n >= min && n <= max)
|
|
1969
|
+
}
|
|
1970
|
+
Some(RecordCheck::StringLength(required, min, max)) => {
|
|
1971
|
+
length_guarded(value, required, min, max)
|
|
1972
|
+
}
|
|
1973
|
+
_ => c.engine.record_value(child, value, c.depth, c.active),
|
|
1974
|
+
}
|
|
1975
|
+
} else {
|
|
1976
|
+
c.engine.record_value(child, value, c.depth, c.active)
|
|
1977
|
+
};
|
|
1978
|
+
}
|
|
1979
|
+
// Keep recording matches after failure: duplicate equal keys indicate
|
|
1980
|
+
// identity semantics, so their speculative child results cannot be used.
|
|
1981
|
+
Valid
|
|
1982
|
+
}
|
|
1983
|
+
struct RefFrame<'a> {
|
|
1984
|
+
source: usize,
|
|
1985
|
+
value: Value,
|
|
1986
|
+
parent: Option<&'a RefFrame<'a>>,
|
|
1987
|
+
}
|
|
1988
|
+
impl RefFrame<'_> {
|
|
1989
|
+
fn contains(mut active: Option<&Self>, source: usize, value: Value) -> bool {
|
|
1990
|
+
while let Some(frame) = active {
|
|
1991
|
+
if frame.source == source && frame.value == value {
|
|
1992
|
+
return true;
|
|
1993
|
+
}
|
|
1994
|
+
active = frame.parent;
|
|
1995
|
+
}
|
|
1996
|
+
false
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
fn missing(hash: Value, key: Value, undefined: Value) -> bool {
|
|
2000
|
+
unsafe { sn_lookup(hash, key) == undefined }
|
|
2001
|
+
}
|
|
2002
|
+
fn verdict(valid: bool) -> Verdict {
|
|
2003
|
+
if valid { Valid } else { Invalid }
|
|
2004
|
+
}
|
|
2005
|
+
fn bounds(len: usize, min: Option<usize>, max: Option<usize>) -> Verdict {
|
|
2006
|
+
verdict(!min.is_some_and(|m| len < m) && !max.is_some_and(|m| len > m))
|
|
2007
|
+
}
|
|
2008
|
+
fn length_guarded(value: Value, required: i32, min: Option<usize>, max: Option<usize>) -> Verdict {
|
|
2009
|
+
let mut len = 0;
|
|
2010
|
+
match unsafe { sn_string_length_guarded(value, &mut len) } {
|
|
2011
|
+
TYPE_UNSUPPORTED => Unsupported,
|
|
2012
|
+
TYPE_STRING => bounds(len, min, max),
|
|
2013
|
+
_ => verdict(required == 0),
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
// Call only after value & the header-validated fixnum_flag is nonzero.
|
|
2017
|
+
// Rust's signed shift is arithmetic; other encodings retain the C adapters.
|
|
2018
|
+
fn immediate_integer(value: Value) -> i64 {
|
|
2019
|
+
(value as isize >> 1) as i64
|
|
2020
|
+
}
|
|
2021
|
+
fn equal_budgeted(a: Value, b: Value, depth: usize, budget: &budget::Budget) -> Verdict {
|
|
2022
|
+
equal_cached(a, b, depth, &mut structural::Cache::new(depth), budget)
|
|
2023
|
+
}
|
|
2024
|
+
fn equal_cached(
|
|
2025
|
+
a: Value,
|
|
2026
|
+
b: Value,
|
|
2027
|
+
depth: usize,
|
|
2028
|
+
memo: &mut structural::Cache<(Value, Value, usize), Verdict>,
|
|
2029
|
+
budget: &budget::Budget,
|
|
2030
|
+
) -> Verdict {
|
|
2031
|
+
if !budget.checkpoint() {
|
|
2032
|
+
return Unsupported;
|
|
2033
|
+
}
|
|
2034
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
2035
|
+
return Unsupported;
|
|
2036
|
+
}
|
|
2037
|
+
let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
|
|
2038
|
+
if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
|
|
2039
|
+
return Unsupported;
|
|
2040
|
+
}
|
|
2041
|
+
if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
|
|
2042
|
+
let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
|
|
2043
|
+
let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
|
|
2044
|
+
if ia && ib {
|
|
2045
|
+
return verdict(unsafe { sn_integer(a) == sn_integer(b) });
|
|
2046
|
+
}
|
|
2047
|
+
if fa && fb {
|
|
2048
|
+
return verdict(unsafe { sn_float(a) == sn_float(b) });
|
|
2049
|
+
}
|
|
2050
|
+
if (ia && fb) || (fa && ib) {
|
|
2051
|
+
let (n, f) = unsafe {
|
|
2052
|
+
if ia {
|
|
2053
|
+
(sn_integer(a), sn_float(b))
|
|
2054
|
+
} else {
|
|
2055
|
+
(sn_integer(b), sn_float(a))
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
// Convert the float to an integer, never the integer to a rounded float.
|
|
2059
|
+
// Fixnums are strictly inside i64's bounds on supported CRuby targets.
|
|
2060
|
+
return verdict(
|
|
2061
|
+
f.is_finite()
|
|
2062
|
+
&& f.trunc() == f
|
|
2063
|
+
&& f >= i64::MIN as f64
|
|
2064
|
+
&& f < i64::MAX as f64
|
|
2065
|
+
&& f as i64 == n,
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
if !fa && !fb {
|
|
2069
|
+
return verdict(
|
|
2070
|
+
unsafe {
|
|
2071
|
+
if ia {
|
|
2072
|
+
sn_big_equal(b, a)
|
|
2073
|
+
} else {
|
|
2074
|
+
sn_big_equal(a, b)
|
|
2075
|
+
}
|
|
2076
|
+
} != 0,
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
return if fa {
|
|
2080
|
+
number::integer_float_equal(b, unsafe { sn_float(a) })
|
|
2081
|
+
} else {
|
|
2082
|
+
number::integer_float_equal(a, unsafe { sn_float(b) })
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
// JSON container equality visits every value, even for the same object:
|
|
2086
|
+
// nested NaN values are unequal and unsupported values need VM evaluation.
|
|
2087
|
+
if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
|
|
2088
|
+
return Valid;
|
|
2089
|
+
}
|
|
2090
|
+
if ka != kb {
|
|
2091
|
+
return Invalid;
|
|
2092
|
+
}
|
|
2093
|
+
if unsafe { sn_same_class(a, b) } == 0 {
|
|
2094
|
+
return Invalid;
|
|
2095
|
+
}
|
|
2096
|
+
let container = ka == TYPE_ARRAY || ka == TYPE_OBJECT;
|
|
2097
|
+
let key = (a, b, depth);
|
|
2098
|
+
if container
|
|
2099
|
+
&& memo.eligible(depth)
|
|
2100
|
+
&& let Some(result) = memo.get(key)
|
|
2101
|
+
{
|
|
2102
|
+
return result;
|
|
2103
|
+
}
|
|
2104
|
+
let result = match ka {
|
|
2105
|
+
TYPE_STRING => verdict(unsafe { sn_string_equal(a, b) } != 0),
|
|
2106
|
+
TYPE_ARRAY => {
|
|
2107
|
+
let len = unsafe { sn_len(a) };
|
|
2108
|
+
if len != unsafe { sn_len(b) } {
|
|
2109
|
+
return Invalid;
|
|
2110
|
+
}
|
|
2111
|
+
for i in 0..len {
|
|
2112
|
+
let result = equal_cached(
|
|
2113
|
+
unsafe { sn_entry(a, i) },
|
|
2114
|
+
unsafe { sn_entry(b, i) },
|
|
2115
|
+
depth + 1,
|
|
2116
|
+
memo,
|
|
2117
|
+
budget,
|
|
2118
|
+
);
|
|
2119
|
+
if result != Valid {
|
|
2120
|
+
return result;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
Valid
|
|
2124
|
+
}
|
|
2125
|
+
TYPE_OBJECT => {
|
|
2126
|
+
if unsafe { sn_len(a) != sn_len(b) } {
|
|
2127
|
+
return Invalid;
|
|
2128
|
+
}
|
|
2129
|
+
let mut context = EqualityContext {
|
|
2130
|
+
other: b,
|
|
2131
|
+
depth: depth + 1,
|
|
2132
|
+
memo,
|
|
2133
|
+
budget,
|
|
2134
|
+
};
|
|
2135
|
+
decode(unsafe {
|
|
2136
|
+
sn_hash_each(
|
|
2137
|
+
a,
|
|
2138
|
+
(&mut context as *mut EqualityContext<'_>).cast(),
|
|
2139
|
+
equal_entry,
|
|
2140
|
+
)
|
|
2141
|
+
})
|
|
2142
|
+
}
|
|
2143
|
+
TYPE_UNSUPPORTED => Unsupported,
|
|
2144
|
+
_ => Invalid,
|
|
2145
|
+
};
|
|
2146
|
+
if container && memo.eligible(depth) {
|
|
2147
|
+
memo.record(key, result);
|
|
2148
|
+
}
|
|
2149
|
+
result
|
|
2150
|
+
}
|
|
2151
|
+
fn decode(result: i32) -> Verdict {
|
|
2152
|
+
match result {
|
|
2153
|
+
VERDICT_INVALID => Invalid,
|
|
2154
|
+
VERDICT_VALID => Valid,
|
|
2155
|
+
_ => Unsupported,
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
struct EqualityContext<'a> {
|
|
2159
|
+
other: Value,
|
|
2160
|
+
depth: usize,
|
|
2161
|
+
memo: &'a mut structural::Cache<(Value, Value, usize), Verdict>,
|
|
2162
|
+
budget: &'a budget::Budget,
|
|
2163
|
+
}
|
|
2164
|
+
unsafe extern "C" fn equal_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
2165
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2166
|
+
let c = unsafe { &mut *context.cast::<EqualityContext<'_>>() };
|
|
2167
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2168
|
+
return Unsupported as i32;
|
|
2169
|
+
}
|
|
2170
|
+
if !c.budget.checkpoint() {
|
|
2171
|
+
return Unsupported as i32;
|
|
2172
|
+
}
|
|
2173
|
+
let other = unsafe { sn_lookup(c.other, key) };
|
|
2174
|
+
if unsafe { sn_missing(other) } != 0 {
|
|
2175
|
+
Invalid as i32
|
|
2176
|
+
} else {
|
|
2177
|
+
equal_cached(value, other, c.depth, c.memo, c.budget) as i32
|
|
2178
|
+
}
|
|
2179
|
+
}))
|
|
2180
|
+
.unwrap_or(Unsupported as i32)
|
|
2181
|
+
}
|
|
2182
|
+
fn mix(n: u64) -> u64 {
|
|
2183
|
+
let n = (n ^ (n >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
|
|
2184
|
+
let n = (n ^ (n >> 27)).wrapping_mul(0x94d049bb133111eb);
|
|
2185
|
+
n ^ (n >> 31)
|
|
2186
|
+
}
|
|
2187
|
+
fn string_fingerprint(value: Value) -> u64 {
|
|
2188
|
+
bytes_fingerprint(unsafe { string_bytes(value) })
|
|
2189
|
+
}
|
|
2190
|
+
#[derive(Clone, Copy, Debug)]
|
|
2191
|
+
struct ShortRecordKey {
|
|
2192
|
+
word: u64,
|
|
2193
|
+
// Low four bits encode length + 1; zero means a longer key. The remaining
|
|
2194
|
+
// bits hold the public CRuby encoding index, not a borrowed pointer.
|
|
2195
|
+
tag: u64,
|
|
2196
|
+
}
|
|
2197
|
+
fn short_record_key(value: Value) -> ShortRecordKey {
|
|
2198
|
+
let mut data = std::ptr::null();
|
|
2199
|
+
let mut len = 0;
|
|
2200
|
+
let encoding = unsafe { sn_record_key_view(value, &mut data, &mut len) };
|
|
2201
|
+
if len > 8 {
|
|
2202
|
+
return ShortRecordKey { word: 0, tag: 0 };
|
|
2203
|
+
}
|
|
2204
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
2205
|
+
ShortRecordKey {
|
|
2206
|
+
word: short_word(bytes),
|
|
2207
|
+
tag: ((encoding as u64) << 4) | (len as u64 + 1),
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
// Borrow an already classified String under the GVL. The caller must not
|
|
2211
|
+
// allocate Ruby objects or run Ruby callbacks while the returned view is live.
|
|
2212
|
+
unsafe fn string_bytes<'a>(value: Value) -> &'a [u8] {
|
|
2213
|
+
let mut data = std::ptr::null();
|
|
2214
|
+
let mut len = 0;
|
|
2215
|
+
unsafe { sn_string_raw_view(value, &mut data, &mut len) };
|
|
2216
|
+
unsafe { byte_view(data, len) }
|
|
2217
|
+
}
|
|
2218
|
+
// The caller must keep the original Ruby string alive and unchanged under
|
|
2219
|
+
// the GVL for the entire lifetime of the view, without allocating in Ruby.
|
|
2220
|
+
unsafe fn byte_view<'a>(data: *const u8, len: usize) -> &'a [u8] {
|
|
2221
|
+
if len == 0 {
|
|
2222
|
+
&[]
|
|
2223
|
+
} else {
|
|
2224
|
+
unsafe { std::slice::from_raw_parts(data, len) }
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
// For at most eight bytes, the first/last scalar words cover every byte.
|
|
2228
|
+
fn short_word(bytes: &[u8]) -> u64 {
|
|
2229
|
+
let len = bytes.len();
|
|
2230
|
+
match bytes.len() {
|
|
2231
|
+
0 => 0,
|
|
2232
|
+
1 => bytes[0] as u64,
|
|
2233
|
+
2 => bytes[0] as u64 | (bytes[1] as u64) << 8,
|
|
2234
|
+
3 => bytes[0] as u64 | (bytes[1] as u64) << 8 | (bytes[2] as u64) << 16,
|
|
2235
|
+
_ => {
|
|
2236
|
+
let first = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as u64;
|
|
2237
|
+
let last = u32::from_le_bytes(bytes[len - 4..].try_into().unwrap()) as u64;
|
|
2238
|
+
first | last << 32
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
fn bytes_fingerprint(bytes: &[u8]) -> u64 {
|
|
2243
|
+
// Short keys and values fit in one scalar. Overlapping first/last words
|
|
2244
|
+
// cover every byte without a byte-at-a-time multiplication chain.
|
|
2245
|
+
let len = bytes.len();
|
|
2246
|
+
if len <= 8 {
|
|
2247
|
+
let word = short_word(bytes);
|
|
2248
|
+
return mix(word ^ (len as u64).wrapping_mul(0x100000001b3) ^ TYPE_STRING as u64);
|
|
2249
|
+
}
|
|
2250
|
+
let mut result = 0xcbf29ce484222325_u64;
|
|
2251
|
+
for &byte in bytes {
|
|
2252
|
+
result = (result ^ byte as u64).wrapping_mul(0x100000001b3);
|
|
2253
|
+
}
|
|
2254
|
+
mix(result ^ TYPE_STRING as u64)
|
|
2255
|
+
}
|
|
2256
|
+
fn fingerprint_budgeted(
|
|
2257
|
+
value: Value,
|
|
2258
|
+
depth: usize,
|
|
2259
|
+
fixnum_flag: Value,
|
|
2260
|
+
big_hint: bool,
|
|
2261
|
+
budget: &budget::Budget,
|
|
2262
|
+
) -> Option<u64> {
|
|
2263
|
+
fingerprint_cached(
|
|
2264
|
+
value,
|
|
2265
|
+
depth,
|
|
2266
|
+
fixnum_flag,
|
|
2267
|
+
big_hint,
|
|
2268
|
+
&mut structural::Cache::new(depth),
|
|
2269
|
+
budget,
|
|
2270
|
+
)
|
|
2271
|
+
}
|
|
2272
|
+
fn fingerprint_cached(
|
|
2273
|
+
value: Value,
|
|
2274
|
+
depth: usize,
|
|
2275
|
+
fixnum_flag: Value,
|
|
2276
|
+
big_hint: bool,
|
|
2277
|
+
memo: &mut structural::Cache<(Value, usize), Option<u64>>,
|
|
2278
|
+
budget: &budget::Budget,
|
|
2279
|
+
) -> Option<u64> {
|
|
2280
|
+
if !budget.checkpoint() {
|
|
2281
|
+
return None;
|
|
2282
|
+
}
|
|
2283
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
2284
|
+
return None;
|
|
2285
|
+
}
|
|
2286
|
+
if value & fixnum_flag != 0 {
|
|
2287
|
+
// sn_fixnum_flag enables this hint only after the C headers confirm
|
|
2288
|
+
// the signed immediate encoding. Rust's signed shift is arithmetic.
|
|
2289
|
+
let integer = immediate_integer(value);
|
|
2290
|
+
return Some(mix(integer as u64 ^ TYPE_NUMBER as u64));
|
|
2291
|
+
}
|
|
2292
|
+
if big_hint && let Some(hash) = number::big_fingerprint(value) {
|
|
2293
|
+
return Some(hash);
|
|
2294
|
+
}
|
|
2295
|
+
// The C key-view iterator validates every key without Ruby lookup.
|
|
2296
|
+
// Avoid a separate preliminary walk of the same keys.
|
|
2297
|
+
let mut data = std::mem::MaybeUninit::<*const c_void>::uninit();
|
|
2298
|
+
let mut len = std::mem::MaybeUninit::<usize>::uninit();
|
|
2299
|
+
let mut n = std::mem::MaybeUninit::<f64>::uninit();
|
|
2300
|
+
// Read only the fields the C adapter initialized for the returned kind.
|
|
2301
|
+
let kind =
|
|
2302
|
+
unsafe { sn_fingerprint_view(value, data.as_mut_ptr(), len.as_mut_ptr(), n.as_mut_ptr()) };
|
|
2303
|
+
if kind == TYPE_NUMBER {
|
|
2304
|
+
let n = unsafe { n.assume_init() };
|
|
2305
|
+
// Normalize integral floats without rounding distinct integers. Exact
|
|
2306
|
+
// JSON equality still resolves every collision before declaring duplicates.
|
|
2307
|
+
if n.is_finite() && n.trunc() == n && n >= i64::MIN as f64 && n < i64::MAX as f64 {
|
|
2308
|
+
return Some(mix(n as i64 as u64 ^ TYPE_NUMBER as u64));
|
|
2309
|
+
}
|
|
2310
|
+
return Some(mix(n.to_bits() ^ TYPE_NUMBER as u64));
|
|
2311
|
+
}
|
|
2312
|
+
let container = kind == TYPE_ARRAY || kind == TYPE_OBJECT;
|
|
2313
|
+
// Batch reuse pays for wide shared containers immediately. Small shallow
|
|
2314
|
+
// records keep the original cheap traversal, expanding at most four levels
|
|
2315
|
+
// before the depth-based cache handles deeper shared graphs.
|
|
2316
|
+
let cacheable = container && memo.eligible_container(depth, unsafe { len.assume_init() });
|
|
2317
|
+
let key = (value, depth);
|
|
2318
|
+
if cacheable
|
|
2319
|
+
&& let Some(result) = memo.get(key)
|
|
2320
|
+
{
|
|
2321
|
+
return result;
|
|
2322
|
+
}
|
|
2323
|
+
let result = match kind {
|
|
2324
|
+
TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
|
|
2325
|
+
TYPE_NULL => Some(mix(TYPE_NULL as u64)),
|
|
2326
|
+
TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
|
|
2327
|
+
2
|
|
2328
|
+
} else {
|
|
2329
|
+
3
|
|
2330
|
+
})),
|
|
2331
|
+
TYPE_STRING => Some(bytes_fingerprint(unsafe {
|
|
2332
|
+
byte_view(data.assume_init().cast(), len.assume_init())
|
|
2333
|
+
})),
|
|
2334
|
+
TYPE_ARRAY => {
|
|
2335
|
+
let mut hash = mix(TYPE_ARRAY as u64);
|
|
2336
|
+
let len = unsafe { len.assume_init() };
|
|
2337
|
+
let values = if len == 0 {
|
|
2338
|
+
&[]
|
|
2339
|
+
} else {
|
|
2340
|
+
unsafe { std::slice::from_raw_parts(data.assume_init().cast::<Value>(), len) }
|
|
2341
|
+
};
|
|
2342
|
+
// All descendants are read under the GVL without allocating Ruby
|
|
2343
|
+
// objects or invoking Ruby callbacks, so the borrowed view is stable.
|
|
2344
|
+
for &item in values {
|
|
2345
|
+
hash =
|
|
2346
|
+
mix(hash
|
|
2347
|
+
^ fingerprint_cached(item, depth + 1, fixnum_flag, false, memo, budget)?);
|
|
2348
|
+
}
|
|
2349
|
+
Some(hash)
|
|
2350
|
+
}
|
|
2351
|
+
TYPE_OBJECT => {
|
|
2352
|
+
let len = unsafe { len.assume_init() };
|
|
2353
|
+
let mut c = FingerprintContext {
|
|
2354
|
+
hash: 0,
|
|
2355
|
+
depth: depth + 1,
|
|
2356
|
+
fixnum_flag,
|
|
2357
|
+
memo,
|
|
2358
|
+
budget,
|
|
2359
|
+
};
|
|
2360
|
+
let status = unsafe {
|
|
2361
|
+
sn_hash_each_key_view(
|
|
2362
|
+
value,
|
|
2363
|
+
(&mut c as *mut FingerprintContext<'_>).cast(),
|
|
2364
|
+
fingerprint_entry,
|
|
2365
|
+
)
|
|
2366
|
+
};
|
|
2367
|
+
(status == VERDICT_VALID).then(|| mix(c.hash ^ len as u64 ^ TYPE_OBJECT as u64))
|
|
2368
|
+
}
|
|
2369
|
+
_ => None,
|
|
2370
|
+
};
|
|
2371
|
+
if cacheable {
|
|
2372
|
+
memo.record(key, result);
|
|
2373
|
+
}
|
|
2374
|
+
result
|
|
2375
|
+
}
|
|
2376
|
+
struct FingerprintContext<'a> {
|
|
2377
|
+
hash: u64,
|
|
2378
|
+
depth: usize,
|
|
2379
|
+
fixnum_flag: Value,
|
|
2380
|
+
memo: &'a mut structural::Cache<(Value, usize), Option<u64>>,
|
|
2381
|
+
budget: &'a budget::Budget,
|
|
2382
|
+
}
|
|
2383
|
+
unsafe extern "C" fn fingerprint_entry(
|
|
2384
|
+
context: *mut c_void,
|
|
2385
|
+
data: *const u8,
|
|
2386
|
+
len: usize,
|
|
2387
|
+
value: Value,
|
|
2388
|
+
) -> i32 {
|
|
2389
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2390
|
+
let c = unsafe { &mut *context.cast::<FingerprintContext<'_>>() };
|
|
2391
|
+
if !c.budget.checkpoint() {
|
|
2392
|
+
return Unsupported as i32;
|
|
2393
|
+
}
|
|
2394
|
+
let Some(hash) = fingerprint_cached(value, c.depth, c.fixnum_flag, false, c.memo, c.budget)
|
|
2395
|
+
else {
|
|
2396
|
+
return Unsupported as i32;
|
|
2397
|
+
};
|
|
2398
|
+
c.hash ^= mix(bytes_fingerprint(unsafe { byte_view(data, len) }) ^ hash.rotate_left(17));
|
|
2399
|
+
Valid as i32
|
|
2400
|
+
}))
|
|
2401
|
+
.unwrap_or(Unsupported as i32)
|
|
2402
|
+
}
|
|
2403
|
+
// Fingerprints are already mixed uniformly. Rehashing each u64 with SipHash
|
|
2404
|
+
// adds work without changing collision resolution, which always uses JSON equality.
|
|
2405
|
+
#[derive(Default)]
|
|
2406
|
+
struct BucketHasher(u64);
|
|
2407
|
+
impl Hasher for BucketHasher {
|
|
2408
|
+
fn finish(&self) -> u64 {
|
|
2409
|
+
self.0
|
|
2410
|
+
}
|
|
2411
|
+
fn write_u64(&mut self, value: u64) {
|
|
2412
|
+
self.0 = value;
|
|
2413
|
+
}
|
|
2414
|
+
fn write(&mut self, bytes: &[u8]) {
|
|
2415
|
+
for &byte in bytes {
|
|
2416
|
+
self.0 = mix(self.0 ^ byte as u64);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budget) -> Verdict {
|
|
2421
|
+
let len = unsafe { sn_len(value) };
|
|
2422
|
+
if !budget.checkpoint() {
|
|
2423
|
+
return Unsupported;
|
|
2424
|
+
}
|
|
2425
|
+
if len <= 16 {
|
|
2426
|
+
for i in 0..len {
|
|
2427
|
+
for previous in 0..i {
|
|
2428
|
+
match equal_budgeted(
|
|
2429
|
+
unsafe { sn_entry(value, previous) },
|
|
2430
|
+
unsafe { sn_entry(value, i) },
|
|
2431
|
+
depth,
|
|
2432
|
+
budget,
|
|
2433
|
+
) {
|
|
2434
|
+
Valid => return Invalid,
|
|
2435
|
+
Unsupported => return Unsupported,
|
|
2436
|
+
Invalid => {}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
return Valid;
|
|
2441
|
+
}
|
|
2442
|
+
let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
|
|
2443
|
+
// This is a checked optimization hint, not an instance shape assumption.
|
|
2444
|
+
let big_hint = unsafe { sn_is_bignum(values[0]) } != 0;
|
|
2445
|
+
if big_hint
|
|
2446
|
+
&& !budget.timed()
|
|
2447
|
+
&& depth <= MAX_EVALUATION_DEPTH
|
|
2448
|
+
&& let Some(result) = number::ordered_big_unique(values)
|
|
2449
|
+
{
|
|
2450
|
+
return result;
|
|
2451
|
+
}
|
|
2452
|
+
if big_hint && depth <= MAX_EVALUATION_DEPTH {
|
|
2453
|
+
let result = unordered_big_unique(values, budget);
|
|
2454
|
+
if result != Unsupported {
|
|
2455
|
+
return result;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
let mut fingerprints = structural::Cache::batch(depth);
|
|
2459
|
+
indexed_unique(
|
|
2460
|
+
values,
|
|
2461
|
+
|item| fingerprint_cached(item, depth, fixnum_flag, big_hint, &mut fingerprints, budget),
|
|
2462
|
+
|a, b| equal_budgeted(a, b, depth, budget),
|
|
2463
|
+
)
|
|
2464
|
+
}
|
|
2465
|
+
// Keep Big-only sampling and retry state out of the common scalar path.
|
|
2466
|
+
#[inline(never)]
|
|
2467
|
+
fn unordered_big_unique(values: &[Value], budget: &budget::Budget) -> Verdict {
|
|
2468
|
+
let mut all_big = true;
|
|
2469
|
+
let result = indexed_unique_short(
|
|
2470
|
+
values,
|
|
2471
|
+
|item| {
|
|
2472
|
+
if !budget.checkpoint() {
|
|
2473
|
+
return None;
|
|
2474
|
+
}
|
|
2475
|
+
let hash = number::big_low_fingerprint(item);
|
|
2476
|
+
if hash.is_none() {
|
|
2477
|
+
all_big = false;
|
|
2478
|
+
}
|
|
2479
|
+
hash
|
|
2480
|
+
},
|
|
2481
|
+
|a, b| {
|
|
2482
|
+
if !budget.checkpoint() {
|
|
2483
|
+
return Unsupported;
|
|
2484
|
+
}
|
|
2485
|
+
// Distinct integers sharing the short sample need the wider pass.
|
|
2486
|
+
if unsafe { sn_big_equal(a, b) } != 0 {
|
|
2487
|
+
Valid
|
|
2488
|
+
} else {
|
|
2489
|
+
Unsupported
|
|
2490
|
+
}
|
|
2491
|
+
},
|
|
2492
|
+
);
|
|
2493
|
+
if result != Unsupported {
|
|
2494
|
+
return result;
|
|
2495
|
+
}
|
|
2496
|
+
if all_big {
|
|
2497
|
+
let result = indexed_unique_impl::<true>(
|
|
2498
|
+
values,
|
|
2499
|
+
|item| {
|
|
2500
|
+
if !budget.checkpoint() {
|
|
2501
|
+
None
|
|
2502
|
+
} else {
|
|
2503
|
+
number::big_low64_fingerprint(item)
|
|
2504
|
+
}
|
|
2505
|
+
},
|
|
2506
|
+
|a, b| {
|
|
2507
|
+
if !budget.checkpoint() {
|
|
2508
|
+
return Unsupported;
|
|
2509
|
+
}
|
|
2510
|
+
verdict(unsafe { sn_big_equal(a, b) } != 0)
|
|
2511
|
+
},
|
|
2512
|
+
);
|
|
2513
|
+
if result != Unsupported {
|
|
2514
|
+
return result;
|
|
2515
|
+
}
|
|
2516
|
+
let result = indexed_unique(
|
|
2517
|
+
values,
|
|
2518
|
+
|item| {
|
|
2519
|
+
if !budget.checkpoint() {
|
|
2520
|
+
None
|
|
2521
|
+
} else {
|
|
2522
|
+
number::big_only_fingerprint(item)
|
|
2523
|
+
}
|
|
2524
|
+
},
|
|
2525
|
+
|a, b| {
|
|
2526
|
+
if !budget.checkpoint() {
|
|
2527
|
+
return Unsupported;
|
|
2528
|
+
}
|
|
2529
|
+
verdict(unsafe { sn_big_equal(a, b) } != 0)
|
|
2530
|
+
},
|
|
2531
|
+
);
|
|
2532
|
+
if result != Unsupported {
|
|
2533
|
+
return result;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
// A non-Bignum invalidates both Big-only hash conventions. Restart
|
|
2537
|
+
// from the original array so equal Integer/Float pairs share a bucket.
|
|
2538
|
+
Unsupported
|
|
2539
|
+
}
|
|
2540
|
+
fn indexed_unique(
|
|
2541
|
+
values: &[Value],
|
|
2542
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2543
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2544
|
+
) -> Verdict {
|
|
2545
|
+
indexed_unique_impl::<false>(values, fingerprint, equal)
|
|
2546
|
+
}
|
|
2547
|
+
fn indexed_unique_short(
|
|
2548
|
+
values: &[Value],
|
|
2549
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2550
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2551
|
+
) -> Verdict {
|
|
2552
|
+
// Index zero means empty. Choose a larger slot beyond the 16-bit limit
|
|
2553
|
+
// so the short sample remains available without truncating an index.
|
|
2554
|
+
if values.len() > u32::MAX as usize {
|
|
2555
|
+
return Unsupported;
|
|
2556
|
+
}
|
|
2557
|
+
let Some(capacity) = values
|
|
2558
|
+
.len()
|
|
2559
|
+
.checked_mul(2)
|
|
2560
|
+
.and_then(usize::checked_next_power_of_two)
|
|
2561
|
+
else {
|
|
2562
|
+
return Unsupported;
|
|
2563
|
+
};
|
|
2564
|
+
if values.len() <= u16::MAX as usize {
|
|
2565
|
+
indexed_unique_slots::<ShortUniqueSlot, true>(values, capacity, fingerprint, equal)
|
|
2566
|
+
} else {
|
|
2567
|
+
indexed_unique_slots::<ShortWideUniqueSlot, true>(values, capacity, fingerprint, equal)
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
fn indexed_unique_impl<const BOUNDED: bool>(
|
|
2571
|
+
values: &[Value],
|
|
2572
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2573
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2574
|
+
) -> Verdict {
|
|
2575
|
+
let Some(capacity) = values
|
|
2576
|
+
.len()
|
|
2577
|
+
.checked_mul(2)
|
|
2578
|
+
.and_then(usize::checked_next_power_of_two)
|
|
2579
|
+
else {
|
|
2580
|
+
return Unsupported;
|
|
2581
|
+
};
|
|
2582
|
+
if values.len() <= u16::MAX as usize {
|
|
2583
|
+
indexed_unique_slots::<PackedUniqueSlot, BOUNDED>(values, capacity, fingerprint, equal)
|
|
2584
|
+
} else {
|
|
2585
|
+
indexed_unique_slots::<FullUniqueSlot, BOUNDED>(values, capacity, fingerprint, equal)
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
trait UniqueSlot: Copy {
|
|
2589
|
+
const EMPTY: Self;
|
|
2590
|
+
fn new(hash: u64, index: usize) -> Self;
|
|
2591
|
+
fn index(self) -> Option<usize>;
|
|
2592
|
+
fn matches(self, hash: u64) -> bool;
|
|
2593
|
+
}
|
|
2594
|
+
#[derive(Clone, Copy)]
|
|
2595
|
+
struct ShortUniqueSlot(u32);
|
|
2596
|
+
impl UniqueSlot for ShortUniqueSlot {
|
|
2597
|
+
const EMPTY: Self = Self(0);
|
|
2598
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2599
|
+
debug_assert!(index < u16::MAX as usize);
|
|
2600
|
+
Self((hash as u32 & !0xffff) | (index + 1) as u32)
|
|
2601
|
+
}
|
|
2602
|
+
fn index(self) -> Option<usize> {
|
|
2603
|
+
let index = self.0 & 0xffff;
|
|
2604
|
+
(index != 0).then(|| index as usize - 1)
|
|
2605
|
+
}
|
|
2606
|
+
fn matches(self, hash: u64) -> bool {
|
|
2607
|
+
// Truncated filters only select an exact comparison or wider retry.
|
|
2608
|
+
self.0 & !0xffff == hash as u32 & !0xffff
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
#[derive(Clone, Copy)]
|
|
2612
|
+
struct ShortWideUniqueSlot(u64);
|
|
2613
|
+
impl UniqueSlot for ShortWideUniqueSlot {
|
|
2614
|
+
const EMPTY: Self = Self(0);
|
|
2615
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2616
|
+
debug_assert!(index < u32::MAX as usize);
|
|
2617
|
+
Self((u64::from(hash as u32) << 32) | (index + 1) as u64)
|
|
2618
|
+
}
|
|
2619
|
+
fn index(self) -> Option<usize> {
|
|
2620
|
+
let index = self.0 as u32;
|
|
2621
|
+
(index != 0).then(|| index as usize - 1)
|
|
2622
|
+
}
|
|
2623
|
+
fn matches(self, hash: u64) -> bool {
|
|
2624
|
+
self.0 >> 32 == u64::from(hash as u32)
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
#[derive(Clone, Copy)]
|
|
2628
|
+
struct PackedUniqueSlot(u64);
|
|
2629
|
+
impl UniqueSlot for PackedUniqueSlot {
|
|
2630
|
+
const EMPTY: Self = Self(0);
|
|
2631
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2632
|
+
// Selected only for at most 65535 entries. Zero is the empty marker;
|
|
2633
|
+
// the remaining 48 hash bits only filter exact JSON comparisons.
|
|
2634
|
+
debug_assert!(index < u16::MAX as usize);
|
|
2635
|
+
Self((hash & !0xffff) | (index + 1) as u64)
|
|
2636
|
+
}
|
|
2637
|
+
fn index(self) -> Option<usize> {
|
|
2638
|
+
let index = self.0 & 0xffff;
|
|
2639
|
+
(index != 0).then(|| index as usize - 1)
|
|
2640
|
+
}
|
|
2641
|
+
fn matches(self, hash: u64) -> bool {
|
|
2642
|
+
self.0 & !0xffff == hash & !0xffff
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
#[derive(Clone, Copy)]
|
|
2646
|
+
struct FullUniqueSlot(u64, usize);
|
|
2647
|
+
impl UniqueSlot for FullUniqueSlot {
|
|
2648
|
+
const EMPTY: Self = Self(0, usize::MAX);
|
|
2649
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2650
|
+
Self(hash, index)
|
|
2651
|
+
}
|
|
2652
|
+
fn index(self) -> Option<usize> {
|
|
2653
|
+
(self.1 != usize::MAX).then_some(self.1)
|
|
2654
|
+
}
|
|
2655
|
+
fn matches(self, hash: u64) -> bool {
|
|
2656
|
+
self.0 == hash
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
fn indexed_unique_slots<S: UniqueSlot, const BOUNDED: bool>(
|
|
2660
|
+
values: &[Value],
|
|
2661
|
+
capacity: usize,
|
|
2662
|
+
mut fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2663
|
+
mut equal: impl FnMut(Value, Value) -> Verdict,
|
|
2664
|
+
) -> Verdict {
|
|
2665
|
+
// Keep at least half the slots empty, so every probe terminates. Slots
|
|
2666
|
+
// retain only a fingerprint and an index into the original Ruby array.
|
|
2667
|
+
// Equal fingerprints continue probing after exact inequality; neither
|
|
2668
|
+
// table-position collisions nor fingerprint collisions imply duplicates.
|
|
2669
|
+
let Some(mut slots) = allocate_unique_slots::<S>(capacity) else {
|
|
2670
|
+
return Unsupported;
|
|
2671
|
+
};
|
|
2672
|
+
let mask = capacity - 1;
|
|
2673
|
+
let mut collisions = 0;
|
|
2674
|
+
let mut probes = 0;
|
|
2675
|
+
for (i, &item) in values.iter().enumerate() {
|
|
2676
|
+
let Some(hash) = fingerprint(item) else {
|
|
2677
|
+
return Unsupported;
|
|
2678
|
+
};
|
|
2679
|
+
let mut at = hash as usize & mask;
|
|
2680
|
+
loop {
|
|
2681
|
+
let slot = slots[at];
|
|
2682
|
+
let Some(previous) = slot.index() else {
|
|
2683
|
+
slots[at] = S::new(hash, i);
|
|
2684
|
+
break;
|
|
2685
|
+
};
|
|
2686
|
+
// A full fingerprint can still have adversarial low table bits.
|
|
2687
|
+
// Bound probing independently of exact fingerprint collisions.
|
|
2688
|
+
probes += 1;
|
|
2689
|
+
if probes > values.len().saturating_mul(2) {
|
|
2690
|
+
if BOUNDED {
|
|
2691
|
+
return Unsupported;
|
|
2692
|
+
}
|
|
2693
|
+
// Release the hash table before allocating the flat index.
|
|
2694
|
+
drop(slots);
|
|
2695
|
+
return sorted_unique(values, fingerprint, equal);
|
|
2696
|
+
}
|
|
2697
|
+
if slot.matches(hash) {
|
|
2698
|
+
match equal(values[previous], item) {
|
|
2699
|
+
Valid => return Invalid,
|
|
2700
|
+
Unsupported => return Unsupported,
|
|
2701
|
+
Invalid => {
|
|
2702
|
+
if BOUNDED {
|
|
2703
|
+
collisions += 1;
|
|
2704
|
+
if collisions >= 16 {
|
|
2705
|
+
return Unsupported;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
at = (at + 1) & mask;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
Valid
|
|
2715
|
+
}
|
|
2716
|
+
fn sorted_unique(
|
|
2717
|
+
values: &[Value],
|
|
2718
|
+
mut fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2719
|
+
mut equal: impl FnMut(Value, Value) -> Verdict,
|
|
2720
|
+
) -> Verdict {
|
|
2721
|
+
let Some(bytes) = values
|
|
2722
|
+
.len()
|
|
2723
|
+
.checked_mul(std::mem::size_of::<(u64, usize)>())
|
|
2724
|
+
else {
|
|
2725
|
+
return Unsupported;
|
|
2726
|
+
};
|
|
2727
|
+
if bytes > MAX_UNIQUE_INDEX_BYTES {
|
|
2728
|
+
return Unsupported;
|
|
2729
|
+
}
|
|
2730
|
+
let mut entries = Vec::new();
|
|
2731
|
+
if entries.try_reserve_exact(values.len()).is_err() {
|
|
2732
|
+
return Unsupported;
|
|
2733
|
+
}
|
|
2734
|
+
for (index, &value) in values.iter().enumerate() {
|
|
2735
|
+
let Some(hash) = fingerprint(value) else {
|
|
2736
|
+
break;
|
|
2737
|
+
};
|
|
2738
|
+
entries.push((hash, index));
|
|
2739
|
+
}
|
|
2740
|
+
let complete = entries.len() == values.len();
|
|
2741
|
+
// O(n log n) sorting replaces quadratic slot probing. Exact equality is
|
|
2742
|
+
// needed only within identical full fingerprints, in original array order.
|
|
2743
|
+
entries.sort_unstable();
|
|
2744
|
+
let mut decisive: Option<(usize, Verdict)> = None;
|
|
2745
|
+
for group in entries.chunk_by(|a, b| a.0 == b.0) {
|
|
2746
|
+
for at in 1..group.len() {
|
|
2747
|
+
let index = group[at].1;
|
|
2748
|
+
if decisive.is_some_and(|(earlier, _)| earlier <= index) {
|
|
2749
|
+
break;
|
|
2750
|
+
}
|
|
2751
|
+
for &(_, previous) in &group[..at] {
|
|
2752
|
+
let status = equal(values[previous], values[index]);
|
|
2753
|
+
if status != Invalid {
|
|
2754
|
+
decisive = Some((index, status));
|
|
2755
|
+
break;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
// A duplicate in the successfully fingerprinted prefix must precede a
|
|
2761
|
+
// later unavailable fingerprint, just as it does in the streaming index.
|
|
2762
|
+
decisive.map_or(if complete { Valid } else { Unsupported }, |(_, status)| {
|
|
2763
|
+
if status == Valid { Invalid } else { status }
|
|
2764
|
+
})
|
|
2765
|
+
}
|
|
2766
|
+
fn allocate_unique_slots<S: UniqueSlot>(capacity: usize) -> Option<Vec<S>> {
|
|
2767
|
+
let bytes = capacity.checked_mul(std::mem::size_of::<S>())?;
|
|
2768
|
+
if bytes > MAX_UNIQUE_INDEX_BYTES {
|
|
2769
|
+
return None;
|
|
2770
|
+
}
|
|
2771
|
+
let mut slots = Vec::new();
|
|
2772
|
+
slots.try_reserve_exact(capacity).ok()?;
|
|
2773
|
+
slots.resize(capacity, S::EMPTY);
|
|
2774
|
+
Some(slots)
|
|
2775
|
+
}
|
|
2776
|
+
struct PropertyCandidates<'a> {
|
|
2777
|
+
rules: &'a Object,
|
|
2778
|
+
indices: Vec<usize>,
|
|
2779
|
+
budget: &'a budget::Budget,
|
|
2780
|
+
}
|
|
2781
|
+
unsafe extern "C" fn property_candidate(context: *mut c_void, key: Value, _: Value) -> i32 {
|
|
2782
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2783
|
+
let c = unsafe { &mut *context.cast::<PropertyCandidates<'_>>() };
|
|
2784
|
+
if !c.budget.checkpoint() {
|
|
2785
|
+
return Unsupported as i32;
|
|
2786
|
+
}
|
|
2787
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2788
|
+
return Unsupported as i32;
|
|
2789
|
+
}
|
|
2790
|
+
if let Some(target) = c.rules.property_targets.get(&tracking::JsonKey::new(key)) {
|
|
2791
|
+
c.indices.push(target.index);
|
|
2792
|
+
}
|
|
2793
|
+
Valid as i32
|
|
2794
|
+
}))
|
|
2795
|
+
.unwrap_or(Unsupported as i32)
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
struct ObjectContext<'a> {
|
|
2799
|
+
engine: &'a Execution<'a>,
|
|
2800
|
+
rules: &'a Object,
|
|
2801
|
+
depth: usize,
|
|
2802
|
+
active: scope::Context<'a>,
|
|
2803
|
+
}
|
|
2804
|
+
/// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
|
|
2805
|
+
/// # Safety
|
|
2806
|
+
/// The context must be a live ObjectContext from this engine and all VALUEs must be live under the GVL.
|
|
2807
|
+
#[unsafe(no_mangle)]
|
|
2808
|
+
pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, value: Value) -> i32 {
|
|
2809
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2810
|
+
let c = unsafe { &*context.cast::<ObjectContext>() };
|
|
2811
|
+
if !c.engine.budget.checkpoint() {
|
|
2812
|
+
return Unsupported as i32;
|
|
2813
|
+
}
|
|
2814
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2815
|
+
return Unsupported as i32;
|
|
2816
|
+
}
|
|
2817
|
+
if let Some(child) = c.rules.names {
|
|
2818
|
+
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
2819
|
+
if result != Valid {
|
|
2820
|
+
return result as i32;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
match c.rules.visit_value_rules::<false>(
|
|
2824
|
+
c.engine.caches,
|
|
2825
|
+
&c.engine.budget,
|
|
2826
|
+
key,
|
|
2827
|
+
None,
|
|
2828
|
+
|_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
|
|
2829
|
+
Valid => Ok(()),
|
|
2830
|
+
status => Err(status),
|
|
2831
|
+
},
|
|
2832
|
+
) {
|
|
2833
|
+
Ok(_) => Valid as i32,
|
|
2834
|
+
Err(status) => status as i32,
|
|
2835
|
+
}
|
|
2836
|
+
}))
|
|
2837
|
+
.unwrap_or(Unsupported as i32)
|
|
2838
|
+
}
|
|
2839
|
+
#[unsafe(no_mangle)]
|
|
2840
|
+
pub extern "C" fn sn_new() -> *mut Engine {
|
|
2841
|
+
Box::into_raw(Box::new(Engine {
|
|
2842
|
+
roots: Box::into_raw(Box::new(Roots::default())),
|
|
2843
|
+
fixnum_flag: unsafe { sn_fixnum_flag() },
|
|
2844
|
+
undefined: unsafe { sn_undefined() },
|
|
2845
|
+
..Engine::default()
|
|
2846
|
+
}))
|
|
2847
|
+
}
|
|
2848
|
+
/// # Safety
|
|
2849
|
+
/// Engine/error must be writable. Program must be a live VM Program (or Array
|
|
2850
|
+
/// of Programs when multiple is set), and compiler must be live under the GVL.
|
|
2851
|
+
#[unsafe(no_mangle)]
|
|
2852
|
+
pub unsafe extern "C" fn sn_compile(
|
|
2853
|
+
engine: *mut Engine,
|
|
2854
|
+
program: Value,
|
|
2855
|
+
compiler: Value,
|
|
2856
|
+
format: i32,
|
|
2857
|
+
content: i32,
|
|
2858
|
+
multiple: i32,
|
|
2859
|
+
error: *mut Value,
|
|
2860
|
+
) -> i32 {
|
|
2861
|
+
match catch_unwind(AssertUnwindSafe(|| {
|
|
2862
|
+
let e = unsafe { &mut *engine };
|
|
2863
|
+
e.validate_format = format != 0;
|
|
2864
|
+
e.validate_content = content != 0;
|
|
2865
|
+
e.compiler = e.retain(compiler);
|
|
2866
|
+
if multiple != 0 {
|
|
2867
|
+
for program in e.values(program) {
|
|
2868
|
+
e.compile(program)?;
|
|
2869
|
+
}
|
|
2870
|
+
} else {
|
|
2871
|
+
e.root = e.compile(program)?;
|
|
2872
|
+
}
|
|
2873
|
+
e.bind_dynamic_anchors()?;
|
|
2874
|
+
e.simplify_dynamic_references();
|
|
2875
|
+
e.compile_memoization();
|
|
2876
|
+
Ok::<_, Value>(())
|
|
2877
|
+
})) {
|
|
2878
|
+
Ok(Ok(())) => VERDICT_VALID,
|
|
2879
|
+
Ok(Err(value)) => {
|
|
2880
|
+
unsafe { *error = value };
|
|
2881
|
+
STATUS_EXCEPTION
|
|
2882
|
+
}
|
|
2883
|
+
Err(_) => STATUS_PANIC,
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/// # Safety
|
|
2887
|
+
/// Engine must be fully initialized and program must be live.
|
|
2888
|
+
#[unsafe(no_mangle)]
|
|
2889
|
+
pub unsafe extern "C" fn sn_node_index(engine: *const Engine, program: Value) -> usize {
|
|
2890
|
+
unsafe { &*engine }
|
|
2891
|
+
.indices
|
|
2892
|
+
.get(&program)
|
|
2893
|
+
.copied()
|
|
2894
|
+
.unwrap_or(usize::MAX)
|
|
2895
|
+
}
|
|
2896
|
+
/// # Safety
|
|
2897
|
+
/// Engine must be initialized.
|
|
2898
|
+
#[unsafe(no_mangle)]
|
|
2899
|
+
pub unsafe extern "C" fn sn_root_index(engine: *const Engine) -> usize {
|
|
2900
|
+
unsafe { &*engine }.root
|
|
2901
|
+
}
|
|
2902
|
+
/// # Safety
|
|
2903
|
+
/// Roots must be live and compilation must have finished.
|
|
2904
|
+
#[unsafe(no_mangle)]
|
|
2905
|
+
pub unsafe extern "C" fn sn_root_count(roots: *const Roots) -> usize {
|
|
2906
|
+
unsafe { &*roots }.values.len()
|
|
2907
|
+
}
|
|
2908
|
+
/// # Safety
|
|
2909
|
+
/// Roots must be live and index in bounds, outside compilation.
|
|
2910
|
+
#[unsafe(no_mangle)]
|
|
2911
|
+
pub unsafe extern "C" fn sn_root_at(roots: *const Roots, index: usize) -> Value {
|
|
2912
|
+
unsafe { &*roots }.values[index]
|
|
2913
|
+
}
|
|
2914
|
+
/// # Safety
|
|
2915
|
+
/// Engine must be initialized, root in bounds and value live under the GVL.
|
|
2916
|
+
/// Caches must be null or live, owned by this validator and not in use.
|
|
2917
|
+
#[unsafe(no_mangle)]
|
|
2918
|
+
pub unsafe extern "C" fn sn_valid(
|
|
2919
|
+
engine: *const Engine,
|
|
2920
|
+
caches: *const pattern::Caches,
|
|
2921
|
+
root: usize,
|
|
2922
|
+
value: Value,
|
|
2923
|
+
) -> i32 {
|
|
2924
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2925
|
+
let e = unsafe { &*engine };
|
|
2926
|
+
let local = pattern::Caches::default();
|
|
2927
|
+
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
2928
|
+
Execution {
|
|
2929
|
+
engine: e,
|
|
2930
|
+
caches,
|
|
2931
|
+
memo: memo::Memo::default(),
|
|
2932
|
+
budget: budget::Budget::default(),
|
|
2933
|
+
}
|
|
2934
|
+
.eval(root, value, 0, scope::Context::default()) as i32
|
|
2935
|
+
}))
|
|
2936
|
+
.unwrap_or(STATUS_PANIC)
|
|
2937
|
+
}
|
|
2938
|
+
/// # Safety
|
|
2939
|
+
/// Same requirements as sn_valid; all limit/output pointers must be live.
|
|
2940
|
+
#[unsafe(no_mangle)]
|
|
2941
|
+
pub unsafe extern "C" fn sn_valid_with_limits(
|
|
2942
|
+
engine: *const Engine,
|
|
2943
|
+
caches: *const pattern::Caches,
|
|
2944
|
+
root: usize,
|
|
2945
|
+
value: Value,
|
|
2946
|
+
limits: *const budget::Limits,
|
|
2947
|
+
outcome: *mut budget::Outcome,
|
|
2948
|
+
) -> i32 {
|
|
2949
|
+
let budget = budget::Budget::new(unsafe { *limits });
|
|
2950
|
+
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
2951
|
+
let local = pattern::Caches::default();
|
|
2952
|
+
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
2953
|
+
let execution = Execution {
|
|
2954
|
+
engine: unsafe { &*engine },
|
|
2955
|
+
caches,
|
|
2956
|
+
memo: memo::Memo::default(),
|
|
2957
|
+
budget,
|
|
2958
|
+
};
|
|
2959
|
+
let result = if execution.budget.check_deadline() {
|
|
2960
|
+
execution.eval(root, value, 0, scope::Context::default())
|
|
2961
|
+
} else {
|
|
2962
|
+
Unsupported
|
|
2963
|
+
};
|
|
2964
|
+
let result = if execution.budget.check_deadline() {
|
|
2965
|
+
result
|
|
2966
|
+
} else {
|
|
2967
|
+
Unsupported
|
|
2968
|
+
};
|
|
2969
|
+
unsafe {
|
|
2970
|
+
((*outcome).kind, (*outcome).attempted) = execution.budget.hit.get();
|
|
2971
|
+
}
|
|
2972
|
+
result as i32
|
|
2973
|
+
}));
|
|
2974
|
+
result.unwrap_or(STATUS_PANIC)
|
|
2975
|
+
}
|
|
2976
|
+
/// # Safety
|
|
2977
|
+
/// Engine must be an owned pointer returned by sn_new, freed exactly once.
|
|
2978
|
+
#[unsafe(no_mangle)]
|
|
2979
|
+
pub unsafe extern "C" fn sn_free(engine: *mut Engine) {
|
|
2980
|
+
let engine = unsafe { Box::from_raw(engine) };
|
|
2981
|
+
drop(unsafe { Box::from_raw(engine.roots) });
|
|
2982
|
+
drop(engine);
|
|
2983
|
+
}
|
|
2984
|
+
/// # Safety
|
|
2985
|
+
/// Engine must be live and not exclusively borrowed by the compiler.
|
|
2986
|
+
#[unsafe(no_mangle)]
|
|
2987
|
+
pub unsafe extern "C" fn sn_roots(engine: *const Engine) -> *const Roots {
|
|
2988
|
+
unsafe { &*engine }.roots
|
|
2989
|
+
}
|
|
2990
|
+
/// # Safety
|
|
2991
|
+
/// Roots must be live; call synchronously from the owning Ruby GC mark callback.
|
|
2992
|
+
/// No mutable root borrow can be live across a Ruby allocation/callback.
|
|
2993
|
+
#[unsafe(no_mangle)]
|
|
2994
|
+
pub unsafe extern "C" fn sn_roots_mark(roots: *const Roots) {
|
|
2995
|
+
for &value in unsafe { &*roots }
|
|
2996
|
+
.values
|
|
2997
|
+
.iter()
|
|
2998
|
+
.chain(&unsafe { &*roots }.binding_names)
|
|
2999
|
+
{
|
|
3000
|
+
unsafe { sn_pin(value) };
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
/// # Safety
|
|
3004
|
+
/// Roots must be live with no mutable root borrow active.
|
|
3005
|
+
#[unsafe(no_mangle)]
|
|
3006
|
+
pub unsafe extern "C" fn sn_roots_size(roots: *const Roots) -> usize {
|
|
3007
|
+
std::mem::size_of::<Roots>()
|
|
3008
|
+
+ (unsafe { &*roots }.values.capacity() + unsafe { &*roots }.binding_names.capacity())
|
|
3009
|
+
* std::mem::size_of::<Value>()
|
|
3010
|
+
}
|
|
3011
|
+
#[unsafe(no_mangle)]
|
|
3012
|
+
pub extern "C" fn sn_engine_header_size() -> usize {
|
|
3013
|
+
std::mem::size_of::<Engine>()
|
|
3014
|
+
}
|
|
3015
|
+
/// # Safety
|
|
3016
|
+
/// Engine must be live and not exclusively borrowed by the compiler.
|
|
3017
|
+
#[unsafe(no_mangle)]
|
|
3018
|
+
pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
3019
|
+
let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
|
|
3020
|
+
let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
|
|
3021
|
+
let contextual = unsafe { &*std::ptr::addr_of!((*engine).memo_contextual) };
|
|
3022
|
+
let guarded = unsafe { &*std::ptr::addr_of!((*engine).memo_guarded) };
|
|
3023
|
+
let sources = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_sources) };
|
|
3024
|
+
let same = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_same_sources) };
|
|
3025
|
+
let scalar = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_scalar_sources) };
|
|
3026
|
+
let amplification = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_scalar_amplification) };
|
|
3027
|
+
let projection = unsafe { &*std::ptr::addr_of!((*engine).memo_scope_projection) };
|
|
3028
|
+
std::mem::size_of::<Engine>()
|
|
3029
|
+
+ projection.memory_usage()
|
|
3030
|
+
+ (contextual.capacity()
|
|
3031
|
+
+ guarded.capacity()
|
|
3032
|
+
+ sources.capacity()
|
|
3033
|
+
+ same.capacity()
|
|
3034
|
+
+ scalar.capacity()
|
|
3035
|
+
+ amplification.capacity())
|
|
3036
|
+
* std::mem::size_of::<bool>()
|
|
3037
|
+
+ nodes.capacity() * std::mem::size_of::<Vec<Op>>()
|
|
3038
|
+
+ operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
|
|
3039
|
+
+ operands
|
|
3040
|
+
.iter()
|
|
3041
|
+
.map(|ops| ops.capacity() * std::mem::size_of::<(i32, Value)>())
|
|
3042
|
+
.sum::<usize>()
|
|
3043
|
+
+ nodes
|
|
3044
|
+
.iter()
|
|
3045
|
+
.map(|n| n.capacity() * std::mem::size_of::<Op>())
|
|
3046
|
+
.sum::<usize>()
|
|
3047
|
+
+ nodes
|
|
3048
|
+
.iter()
|
|
3049
|
+
.flatten()
|
|
3050
|
+
.map(|op| match op {
|
|
3051
|
+
Op::Enum(values) => values.memory_usage(),
|
|
3052
|
+
Op::String(_, rules) => {
|
|
3053
|
+
std::mem::size_of::<StringRules>()
|
|
3054
|
+
+ rules
|
|
3055
|
+
.pattern
|
|
3056
|
+
.as_ref()
|
|
3057
|
+
.map_or(0, pattern::Pattern::memory_usage)
|
|
3058
|
+
}
|
|
3059
|
+
Op::Object(_, rules) => {
|
|
3060
|
+
rules
|
|
3061
|
+
.pattern_group
|
|
3062
|
+
.as_ref()
|
|
3063
|
+
.map_or(0, pattern::PatternGroup::memory_usage)
|
|
3064
|
+
+ rules.patterns.capacity()
|
|
3065
|
+
* std::mem::size_of::<(pattern::Pattern, usize)>()
|
|
3066
|
+
+ rules
|
|
3067
|
+
.patterns
|
|
3068
|
+
.iter()
|
|
3069
|
+
.map(|(pattern, _)| pattern.memory_usage())
|
|
3070
|
+
.sum::<usize>()
|
|
3071
|
+
}
|
|
3072
|
+
_ => 0,
|
|
3073
|
+
})
|
|
3074
|
+
.sum::<usize>()
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
#[cfg(test)]
|
|
3078
|
+
mod tests {
|
|
3079
|
+
use super::*;
|
|
3080
|
+
|
|
3081
|
+
#[test]
|
|
3082
|
+
fn falls_back_for_clustered_slots_without_comparing_distinct_fingerprints() {
|
|
3083
|
+
let values: Vec<_> = (1..=4096).collect();
|
|
3084
|
+
let mut hashes = 0;
|
|
3085
|
+
assert_eq!(
|
|
3086
|
+
indexed_unique(
|
|
3087
|
+
&values,
|
|
3088
|
+
|value| {
|
|
3089
|
+
hashes += 1;
|
|
3090
|
+
Some((value as u64) << 32)
|
|
3091
|
+
},
|
|
3092
|
+
|_, _| panic!("distinct full fingerprints cannot require equality"),
|
|
3093
|
+
),
|
|
3094
|
+
Valid
|
|
3095
|
+
);
|
|
3096
|
+
assert!(hashes > values.len());
|
|
3097
|
+
assert!(hashes <= values.len() * 2);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
#[test]
|
|
3101
|
+
fn sorted_collisions_preserve_exact_equality_and_early_results() {
|
|
3102
|
+
let hash = |value| (value != 99).then_some(value as u64 % 2);
|
|
3103
|
+
let equal = |a, b| verdict(a == b);
|
|
3104
|
+
assert_eq!(sorted_unique(&[0, 2, 4, 1, 3, 5], hash, equal), Valid);
|
|
3105
|
+
assert_eq!(sorted_unique(&[0, 2, 0, 99], hash, equal), Invalid);
|
|
3106
|
+
assert_eq!(sorted_unique(&[0, 2, 99, 0], hash, equal), Unsupported);
|
|
3107
|
+
assert_eq!(
|
|
3108
|
+
sorted_unique(&[0, 1, 1, 2], hash, |a, b| {
|
|
3109
|
+
if (a, b) == (0, 2) {
|
|
3110
|
+
Unsupported
|
|
3111
|
+
} else {
|
|
3112
|
+
equal(a, b)
|
|
3113
|
+
}
|
|
3114
|
+
}),
|
|
3115
|
+
Invalid
|
|
3116
|
+
);
|
|
3117
|
+
assert_eq!(
|
|
3118
|
+
sorted_unique(&[0, 2, 1, 1], hash, |a, b| {
|
|
3119
|
+
if (a, b) == (0, 2) {
|
|
3120
|
+
Unsupported
|
|
3121
|
+
} else {
|
|
3122
|
+
equal(a, b)
|
|
3123
|
+
}
|
|
3124
|
+
}),
|
|
3125
|
+
Unsupported
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
#[test]
|
|
3130
|
+
fn bounds_unique_index_allocation() {
|
|
3131
|
+
assert_eq!(
|
|
3132
|
+
allocate_unique_slots::<FullUniqueSlot>(16).unwrap().len(),
|
|
3133
|
+
16
|
|
3134
|
+
);
|
|
3135
|
+
let over_limit = MAX_UNIQUE_INDEX_BYTES / std::mem::size_of::<FullUniqueSlot>() + 1;
|
|
3136
|
+
assert!(allocate_unique_slots::<FullUniqueSlot>(over_limit).is_none());
|
|
3137
|
+
assert!(allocate_unique_slots::<FullUniqueSlot>(usize::MAX).is_none());
|
|
3138
|
+
}
|
|
3139
|
+
}
|