schemurai 2.2.2 → 2.3.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 +14 -3
- 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 +639 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +965 -0
- data/ext/schemurai_native/src/format.rs +357 -0
- data/ext/schemurai_native/src/lib.rs +2376 -0
- data/ext/schemurai_native/src/number.rs +1055 -0
- data/ext/schemurai_native/src/pattern.rs +288 -0
- data/ext/schemurai_native/src/scope.rs +210 -0
- data/ext/schemurai_native/src/tracking.rs +532 -0
- data/lib/schemurai/backend.rb +2 -1
- data/lib/schemurai/native/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +32 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/evaluator.rb +28 -32
- data/lib/schemurai.rb +8 -7
- data/sig/schemurai.rbs +1 -1
- metadata +22 -4
|
@@ -0,0 +1,2376 @@
|
|
|
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 content;
|
|
9
|
+
mod diagnostics;
|
|
10
|
+
mod format;
|
|
11
|
+
mod number;
|
|
12
|
+
mod pattern;
|
|
13
|
+
// Some shared constants are consumed only by C or Ruby.
|
|
14
|
+
#[allow(dead_code)]
|
|
15
|
+
mod protocol {
|
|
16
|
+
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
|
|
17
|
+
}
|
|
18
|
+
use protocol::*;
|
|
19
|
+
mod scope;
|
|
20
|
+
mod tracking;
|
|
21
|
+
type Value = usize;
|
|
22
|
+
unsafe extern "C" {
|
|
23
|
+
fn sn_pin(value: Value);
|
|
24
|
+
fn sn_nil() -> Value;
|
|
25
|
+
fn sn_undefined() -> Value;
|
|
26
|
+
fn sn_kind(value: Value) -> i32;
|
|
27
|
+
fn sn_shallow_kind(value: Value) -> i32;
|
|
28
|
+
fn sn_fingerprint_view(
|
|
29
|
+
value: Value,
|
|
30
|
+
data: *mut *const c_void,
|
|
31
|
+
length: *mut usize,
|
|
32
|
+
number: *mut f64,
|
|
33
|
+
) -> i32;
|
|
34
|
+
fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
|
|
35
|
+
fn sn_true(value: Value) -> i32;
|
|
36
|
+
fn sn_fixnum(value: Value) -> i32;
|
|
37
|
+
fn sn_fixnum_flag() -> Value;
|
|
38
|
+
fn sn_array_data(value: Value) -> *const Value;
|
|
39
|
+
fn sn_integer(value: Value) -> i64;
|
|
40
|
+
fn sn_integer128(value: Value, words: *mut u64) -> i32;
|
|
41
|
+
fn sn_integer_bounds128(value: Value, words: *mut u64) -> i32;
|
|
42
|
+
fn sn_number_bounds_view(
|
|
43
|
+
value: Value,
|
|
44
|
+
words: *mut u64,
|
|
45
|
+
kind: *mut i32,
|
|
46
|
+
floating: *mut f64,
|
|
47
|
+
required: i32,
|
|
48
|
+
) -> i32;
|
|
49
|
+
fn sn_integer1024(value: Value, words: *mut u64) -> i32;
|
|
50
|
+
fn sn_is_bignum(value: Value) -> i32;
|
|
51
|
+
fn sn_big256(value: Value, words: *mut u64) -> i32;
|
|
52
|
+
fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
|
|
53
|
+
fn sn_big_low_word(value: Value) -> u64;
|
|
54
|
+
fn sn_big_low64_word(value: Value, word: *mut u64) -> i32;
|
|
55
|
+
fn sn_big_equal(big: Value, other: Value) -> i32;
|
|
56
|
+
fn sn_big_order(left: Value, right: Value) -> i32;
|
|
57
|
+
fn sn_wide_integer_compare(value: Value, bound: Value) -> i32;
|
|
58
|
+
fn sn_integer_bytes(value: Value) -> usize;
|
|
59
|
+
fn sn_float(value: Value) -> f64;
|
|
60
|
+
fn sn_is_float(value: Value) -> i32;
|
|
61
|
+
fn sn_is_rational(value: Value) -> i32;
|
|
62
|
+
fn sn_len(value: Value) -> usize;
|
|
63
|
+
fn sn_entry(value: Value, index: usize) -> Value;
|
|
64
|
+
fn sn_lookup(hash: Value, key: Value) -> Value;
|
|
65
|
+
fn sn_missing(value: Value) -> i32;
|
|
66
|
+
fn sn_same(a: Value, b: Value) -> i32;
|
|
67
|
+
fn sn_same_class(a: Value, b: Value) -> i32;
|
|
68
|
+
fn sn_string_data(value: Value) -> *const u8;
|
|
69
|
+
fn sn_string_bytes(value: Value) -> usize;
|
|
70
|
+
fn sn_string_equal(a: Value, b: Value) -> i32;
|
|
71
|
+
fn sn_string_length_guarded(value: Value, length: *mut usize) -> i32;
|
|
72
|
+
fn sn_record_key_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
73
|
+
fn sn_string_raw_view(value: Value, data: *mut *const u8, length: *mut usize);
|
|
74
|
+
fn sn_string_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
75
|
+
fn sn_pattern_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
76
|
+
fn sn_pattern_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
77
|
+
fn sn_format_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
78
|
+
fn sn_json_encoding(value: Value) -> i32;
|
|
79
|
+
fn sn_json_item_view(value: Value, data: *mut *const u8, length: *mut usize) -> i32;
|
|
80
|
+
fn sn_resolve(compiler: Value, program: Value, reference: Value, result: *mut Value) -> i32;
|
|
81
|
+
fn sn_resolution_error(error: Value) -> i32;
|
|
82
|
+
fn sn_resource_program(compiler: Value, resource: Value, result: *mut Value) -> i32;
|
|
83
|
+
fn sn_anchor_program(compiler: Value, resource: Value, name: Value, result: *mut Value) -> i32;
|
|
84
|
+
fn sn_hash_each(
|
|
85
|
+
value: Value,
|
|
86
|
+
context: *mut c_void,
|
|
87
|
+
callback: unsafe extern "C" fn(*mut c_void, Value, Value) -> i32,
|
|
88
|
+
) -> i32;
|
|
89
|
+
fn sn_hash_each_key_view(
|
|
90
|
+
value: Value,
|
|
91
|
+
context: *mut c_void,
|
|
92
|
+
callback: unsafe extern "C" fn(*mut c_void, *const u8, usize, Value) -> i32,
|
|
93
|
+
) -> i32;
|
|
94
|
+
fn sn_field(object: Value, name: *const c_char, value: *mut Value) -> i32;
|
|
95
|
+
fn sn_pairs(hash: Value, value: *mut Value) -> i32;
|
|
96
|
+
fn sn_key_copy(key: Value, value: *mut Value) -> i32;
|
|
97
|
+
fn sn_opcode(symbol: Value) -> i32;
|
|
98
|
+
fn sn_object_each(value: Value, context: *mut c_void) -> i32;
|
|
99
|
+
}
|
|
100
|
+
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
101
|
+
#[repr(i32)]
|
|
102
|
+
enum Verdict {
|
|
103
|
+
Invalid = VERDICT_INVALID,
|
|
104
|
+
Valid = VERDICT_VALID,
|
|
105
|
+
Unsupported = VERDICT_UNSUPPORTED,
|
|
106
|
+
}
|
|
107
|
+
use Verdict::*;
|
|
108
|
+
const MAX_EVALUATION_DEPTH: usize = 256;
|
|
109
|
+
const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
|
|
110
|
+
#[derive(Debug)]
|
|
111
|
+
enum Op {
|
|
112
|
+
Unsupported,
|
|
113
|
+
Boolean(bool),
|
|
114
|
+
Ref(scope::Reference),
|
|
115
|
+
Type(i32),
|
|
116
|
+
Const(Value),
|
|
117
|
+
Enum(Vec<Value>),
|
|
118
|
+
All(Vec<usize>),
|
|
119
|
+
Any(Vec<(usize, usize)>),
|
|
120
|
+
One(Vec<(usize, usize)>),
|
|
121
|
+
Not(usize),
|
|
122
|
+
Conditional(usize, Option<usize>, Option<usize>),
|
|
123
|
+
Number(i32, Box<number::Rules>),
|
|
124
|
+
String(i32, Box<StringRules>),
|
|
125
|
+
Array(i32, Box<Array>),
|
|
126
|
+
Object(i32, Box<Object>),
|
|
127
|
+
}
|
|
128
|
+
#[derive(Debug)]
|
|
129
|
+
struct Array {
|
|
130
|
+
min: Option<usize>,
|
|
131
|
+
max: Option<usize>,
|
|
132
|
+
unique: bool,
|
|
133
|
+
prefix: Vec<usize>,
|
|
134
|
+
items: Vec<usize>,
|
|
135
|
+
list: bool,
|
|
136
|
+
additional: Option<usize>,
|
|
137
|
+
contains: Option<usize>,
|
|
138
|
+
min_contains: usize,
|
|
139
|
+
max_contains: Option<usize>,
|
|
140
|
+
unevaluated: Option<usize>,
|
|
141
|
+
}
|
|
142
|
+
#[derive(Debug)]
|
|
143
|
+
struct StringRules {
|
|
144
|
+
min: Option<usize>,
|
|
145
|
+
max: Option<usize>,
|
|
146
|
+
pattern: Option<pattern::Pattern>,
|
|
147
|
+
format: Option<format::Format>,
|
|
148
|
+
content: Option<content::Content>,
|
|
149
|
+
}
|
|
150
|
+
impl StringRules {
|
|
151
|
+
fn content_only(&self) -> Option<&content::Content> {
|
|
152
|
+
if self.min.is_none()
|
|
153
|
+
&& self.max.is_none()
|
|
154
|
+
&& self.pattern.is_none()
|
|
155
|
+
&& self.format.is_none()
|
|
156
|
+
{
|
|
157
|
+
self.content.as_ref()
|
|
158
|
+
} else {
|
|
159
|
+
None
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
fn format_only(&self) -> Option<&format::Format> {
|
|
163
|
+
if self.min.is_none()
|
|
164
|
+
&& self.max.is_none()
|
|
165
|
+
&& self.pattern.is_none()
|
|
166
|
+
&& self.content.is_none()
|
|
167
|
+
{
|
|
168
|
+
self.format.as_ref()
|
|
169
|
+
} else {
|
|
170
|
+
None
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
fn pattern_only(&self) -> Option<&pattern::Pattern> {
|
|
174
|
+
if self.min.is_none()
|
|
175
|
+
&& self.max.is_none()
|
|
176
|
+
&& self.format.is_none()
|
|
177
|
+
&& self.content.is_none()
|
|
178
|
+
{
|
|
179
|
+
self.pattern.as_ref()
|
|
180
|
+
} else {
|
|
181
|
+
None
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
fn length_only(&self) -> bool {
|
|
185
|
+
(self.min.is_some() || self.max.is_some())
|
|
186
|
+
&& self.pattern.is_none()
|
|
187
|
+
&& self.format.is_none()
|
|
188
|
+
&& self.content.is_none()
|
|
189
|
+
}
|
|
190
|
+
fn eval_length_guarded(&self, required: i32, value: Value) -> Verdict {
|
|
191
|
+
length_guarded(value, required, self.min, self.max)
|
|
192
|
+
}
|
|
193
|
+
fn eval_guarded(&self, required: i32, value: Value) -> Verdict {
|
|
194
|
+
let mut data = std::ptr::null();
|
|
195
|
+
let mut len = 0;
|
|
196
|
+
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
197
|
+
if encoding == -TYPE_UNSUPPORTED {
|
|
198
|
+
Unsupported
|
|
199
|
+
} else if encoding < 0 {
|
|
200
|
+
verdict(required == 0)
|
|
201
|
+
} else {
|
|
202
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
203
|
+
self.eval_with_view::<true>(value, Some((bytes, encoding)))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
fn eval(&self, value: Value) -> Verdict {
|
|
207
|
+
self.eval_with_view::<false>(value, None)
|
|
208
|
+
}
|
|
209
|
+
fn eval_with_view<const BORROWED: bool>(
|
|
210
|
+
&self,
|
|
211
|
+
value: Value,
|
|
212
|
+
view: Option<(&[u8], i32)>,
|
|
213
|
+
) -> Verdict {
|
|
214
|
+
// Specialize the standalone path so diagnostics and ordinary string
|
|
215
|
+
// checks do not dispatch through borrowed-view branches at runtime.
|
|
216
|
+
let view = if BORROWED { view } else { None };
|
|
217
|
+
if (self.min.is_some() || self.max.is_some())
|
|
218
|
+
&& bounds(unsafe { sn_len(value) }, self.min, self.max) == Invalid
|
|
219
|
+
{
|
|
220
|
+
return Invalid;
|
|
221
|
+
}
|
|
222
|
+
if let Some(pattern) = &self.pattern {
|
|
223
|
+
let result = match view {
|
|
224
|
+
Some((bytes, encoding)) => pattern.eval_bytes(bytes, encoding),
|
|
225
|
+
None => pattern.eval(value),
|
|
226
|
+
};
|
|
227
|
+
if result != Valid {
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if let Some(format) = &self.format {
|
|
232
|
+
let result = match view {
|
|
233
|
+
Some((bytes, _)) => format.eval_bytes(bytes),
|
|
234
|
+
None => format.eval(value),
|
|
235
|
+
};
|
|
236
|
+
if result != Valid {
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
self.content.as_ref().map_or(Valid, |content| match view {
|
|
241
|
+
Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
|
|
242
|
+
None => content.eval(value),
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
#[derive(Debug)]
|
|
247
|
+
enum DiagnosticDependency {
|
|
248
|
+
Required(Value, Vec<Value>, &'static str),
|
|
249
|
+
Schema(Value, usize, &'static str),
|
|
250
|
+
}
|
|
251
|
+
#[derive(Debug)]
|
|
252
|
+
struct Object {
|
|
253
|
+
record: Option<RecordObject>,
|
|
254
|
+
min: Option<usize>,
|
|
255
|
+
max: Option<usize>,
|
|
256
|
+
required: Vec<Value>,
|
|
257
|
+
required_order: Vec<Value>,
|
|
258
|
+
properties: Vec<(Value, usize)>,
|
|
259
|
+
property_targets: HashMap<tracking::JsonKey, (Value, usize), BuildHasherDefault<BucketHasher>>,
|
|
260
|
+
patterns: Vec<(pattern::Pattern, usize)>,
|
|
261
|
+
pattern_names: Vec<Value>,
|
|
262
|
+
required_properties: Vec<bool>,
|
|
263
|
+
property_keys: tracking::KeySet,
|
|
264
|
+
property_indices: Vec<usize>,
|
|
265
|
+
additional: Option<usize>,
|
|
266
|
+
names: Option<usize>,
|
|
267
|
+
dependent_required: Vec<(Value, Vec<Value>)>,
|
|
268
|
+
dependent_schemas: Vec<(Value, usize)>,
|
|
269
|
+
diagnostic_dependencies: Vec<DiagnosticDependency>,
|
|
270
|
+
unevaluated: Option<usize>,
|
|
271
|
+
}
|
|
272
|
+
#[derive(Clone, Copy, Debug)]
|
|
273
|
+
struct RecordField {
|
|
274
|
+
key: Value,
|
|
275
|
+
name: Value,
|
|
276
|
+
short_key: ShortRecordKey,
|
|
277
|
+
child: Option<usize>,
|
|
278
|
+
bit: u64,
|
|
279
|
+
check: Option<RecordCheck>,
|
|
280
|
+
}
|
|
281
|
+
#[derive(Clone, Copy, Debug)]
|
|
282
|
+
enum RecordCheck {
|
|
283
|
+
IntegerBounds(i64, i64),
|
|
284
|
+
StringLength(i32, Option<usize>, Option<usize>),
|
|
285
|
+
}
|
|
286
|
+
#[derive(Debug)]
|
|
287
|
+
struct RecordObject {
|
|
288
|
+
fields: Vec<RecordField>,
|
|
289
|
+
short_index: Option<RecordShortIndex>,
|
|
290
|
+
// Immutable schema handle, used only to choose a lookup strategy.
|
|
291
|
+
identity_hint: Value,
|
|
292
|
+
required: u64,
|
|
293
|
+
}
|
|
294
|
+
#[derive(Debug)]
|
|
295
|
+
struct RecordShortIndex {
|
|
296
|
+
slots: [u8; 16],
|
|
297
|
+
shift: u8,
|
|
298
|
+
}
|
|
299
|
+
impl RecordShortIndex {
|
|
300
|
+
fn compile(fields: &[RecordField]) -> Option<Self> {
|
|
301
|
+
if !(4..=8).contains(&fields.len()) || fields.iter().any(|field| field.short_key.tag == 0) {
|
|
302
|
+
return None;
|
|
303
|
+
}
|
|
304
|
+
for shift in 0..=60 {
|
|
305
|
+
// Use a four-bit window only if every compiled key owns a distinct
|
|
306
|
+
// slot. Unknown input keys still undergo full equality checks.
|
|
307
|
+
let mut index = Self {
|
|
308
|
+
slots: [0; 16],
|
|
309
|
+
shift,
|
|
310
|
+
};
|
|
311
|
+
let mut unique = true;
|
|
312
|
+
for (at, field) in fields.iter().enumerate() {
|
|
313
|
+
let slot = index.slot(field.short_key);
|
|
314
|
+
if index.slots[slot] != 0 {
|
|
315
|
+
unique = false;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
index.slots[slot] = at as u8 + 1;
|
|
319
|
+
}
|
|
320
|
+
if unique {
|
|
321
|
+
return Some(index);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
None
|
|
325
|
+
}
|
|
326
|
+
fn slot(&self, key: ShortRecordKey) -> usize {
|
|
327
|
+
// Encoding is excluded: equal ASCII keys may have compatible but
|
|
328
|
+
// different encodings. Exact byte/length/encoding checks follow lookup.
|
|
329
|
+
let word = key.word ^ (key.tag & 15).wrapping_mul(0x9e3779b97f4a7c15);
|
|
330
|
+
((word >> self.shift) & 15) as usize
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
impl Object {
|
|
334
|
+
fn with_record(mut self) -> Self {
|
|
335
|
+
if self.required_order.len() > 64
|
|
336
|
+
|| !self.patterns.is_empty()
|
|
337
|
+
|| self.names.is_some()
|
|
338
|
+
|| !self.diagnostic_dependencies.is_empty()
|
|
339
|
+
|| self.unevaluated.is_some()
|
|
340
|
+
{
|
|
341
|
+
return self;
|
|
342
|
+
}
|
|
343
|
+
let mut fields: HashMap<_, _, BuildHasherDefault<BucketHasher>> = self
|
|
344
|
+
.properties
|
|
345
|
+
.iter()
|
|
346
|
+
.map(|&(name, child)| {
|
|
347
|
+
(
|
|
348
|
+
tracking::JsonKey::new(name),
|
|
349
|
+
RecordField {
|
|
350
|
+
key: name,
|
|
351
|
+
name,
|
|
352
|
+
short_key: short_record_key(name),
|
|
353
|
+
child: Some(child),
|
|
354
|
+
bit: 0,
|
|
355
|
+
check: None,
|
|
356
|
+
},
|
|
357
|
+
)
|
|
358
|
+
})
|
|
359
|
+
.collect();
|
|
360
|
+
for &name in &self.required_order {
|
|
361
|
+
fields
|
|
362
|
+
.entry(tracking::JsonKey::new(name))
|
|
363
|
+
.or_insert(RecordField {
|
|
364
|
+
key: name,
|
|
365
|
+
name,
|
|
366
|
+
short_key: short_record_key(name),
|
|
367
|
+
child: None,
|
|
368
|
+
bit: 0,
|
|
369
|
+
check: None,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if fields.len() > 8 {
|
|
373
|
+
return self;
|
|
374
|
+
}
|
|
375
|
+
for (index, field) in fields.values_mut().enumerate() {
|
|
376
|
+
field.bit = 1_u64 << index;
|
|
377
|
+
}
|
|
378
|
+
// Required names share the field bits already used for duplicate-key
|
|
379
|
+
// detection. Repeated required names therefore need no extra state.
|
|
380
|
+
let required = self.required_order.iter().fold(0, |bits, &name| {
|
|
381
|
+
bits | fields[&tracking::JsonKey::new(name)].bit
|
|
382
|
+
});
|
|
383
|
+
let fields: Vec<_> = fields.into_values().collect();
|
|
384
|
+
let short_index = RecordShortIndex::compile(&fields);
|
|
385
|
+
self.record = Some(RecordObject {
|
|
386
|
+
identity_hint: self.properties.first().map_or(0, |&(key, _)| key),
|
|
387
|
+
fields,
|
|
388
|
+
short_index,
|
|
389
|
+
required,
|
|
390
|
+
});
|
|
391
|
+
self
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
#[derive(Default)]
|
|
395
|
+
pub struct Roots {
|
|
396
|
+
values: Vec<Value>,
|
|
397
|
+
}
|
|
398
|
+
#[derive(Default)]
|
|
399
|
+
pub struct Engine {
|
|
400
|
+
nodes: Vec<Vec<Op>>,
|
|
401
|
+
error_operands: Vec<Vec<(i32, Value)>>,
|
|
402
|
+
tracks: Vec<bool>,
|
|
403
|
+
metadata: Vec<scope::Metadata>,
|
|
404
|
+
resources: HashMap<Value, usize>,
|
|
405
|
+
dynamic_bindings: HashMap<(Value, usize), Option<usize>>,
|
|
406
|
+
anchor_names: Vec<tracking::JsonKey>,
|
|
407
|
+
anchor_tables: Vec<Vec<(Value, usize)>>,
|
|
408
|
+
property_ids: HashMap<tracking::JsonKey, usize, BuildHasherDefault<BucketHasher>>,
|
|
409
|
+
indices: HashMap<Value, usize>,
|
|
410
|
+
roots: *mut Roots,
|
|
411
|
+
root: usize,
|
|
412
|
+
fixnum_flag: Value,
|
|
413
|
+
compiler: Value,
|
|
414
|
+
validate_format: bool,
|
|
415
|
+
validate_content: bool,
|
|
416
|
+
undefined: Value,
|
|
417
|
+
}
|
|
418
|
+
impl Engine {
|
|
419
|
+
fn retain(&mut self, value: Value) -> Value {
|
|
420
|
+
// A separate allocation lets GC mark roots without aliasing the
|
|
421
|
+
// compiler's exclusive Engine reference. This borrow ends before any
|
|
422
|
+
// allocating Ruby call; Vec growth uses the Rust allocator only.
|
|
423
|
+
unsafe { &mut *self.roots }.values.push(value);
|
|
424
|
+
value
|
|
425
|
+
}
|
|
426
|
+
fn field(&mut self, value: Value, name: &'static [u8]) -> Result<Value, Value> {
|
|
427
|
+
let mut result = 0;
|
|
428
|
+
if unsafe { sn_field(value, name.as_ptr().cast(), &mut result) } != 0 {
|
|
429
|
+
return Err(result);
|
|
430
|
+
}
|
|
431
|
+
Ok(self.retain(result))
|
|
432
|
+
}
|
|
433
|
+
fn values(&mut self, value: Value) -> Vec<Value> {
|
|
434
|
+
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
435
|
+
return vec![];
|
|
436
|
+
}
|
|
437
|
+
(0..unsafe { sn_len(value) })
|
|
438
|
+
.map(|i| {
|
|
439
|
+
let item = unsafe { sn_entry(value, i) };
|
|
440
|
+
self.retain(item)
|
|
441
|
+
})
|
|
442
|
+
.collect()
|
|
443
|
+
}
|
|
444
|
+
fn map(&mut self, value: Value) -> Result<Vec<(Value, Value)>, Value> {
|
|
445
|
+
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
446
|
+
return Ok(vec![]);
|
|
447
|
+
}
|
|
448
|
+
let mut pairs = 0;
|
|
449
|
+
if unsafe { sn_pairs(value, &mut pairs) } != 0 {
|
|
450
|
+
return Err(pairs);
|
|
451
|
+
}
|
|
452
|
+
self.retain(pairs);
|
|
453
|
+
Ok(self
|
|
454
|
+
.values(pairs)
|
|
455
|
+
.as_chunks::<2>()
|
|
456
|
+
.0
|
|
457
|
+
.iter()
|
|
458
|
+
.map(|p| (p[0], p[1]))
|
|
459
|
+
.collect())
|
|
460
|
+
}
|
|
461
|
+
fn size_field(&mut self, value: Value, name: &'static [u8]) -> Result<Option<usize>, Value> {
|
|
462
|
+
let n = self.field(value, name)?;
|
|
463
|
+
Ok(if unsafe { sn_same(n, sn_nil()) } != 0 {
|
|
464
|
+
None
|
|
465
|
+
} else if unsafe { sn_fixnum(n) } != 0 {
|
|
466
|
+
Some(unsafe { sn_integer(n) } as usize)
|
|
467
|
+
} else if unsafe { sn_is_float(n) } != 0 {
|
|
468
|
+
// Count keywords allow integral JSON numbers such as 2.0.
|
|
469
|
+
// Saturation is exact for comparisons against representable lengths.
|
|
470
|
+
Some(unsafe { sn_float(n) } as usize)
|
|
471
|
+
} else if unsafe { sn_kind(n) } & TYPE_INTEGER != 0 {
|
|
472
|
+
Some(usize::MAX)
|
|
473
|
+
} else {
|
|
474
|
+
None // The only non-integral bound is maxContains' +Infinity.
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
fn child(&mut self, value: Value, name: &'static [u8]) -> Result<Option<usize>, Value> {
|
|
478
|
+
let child = self.field(value, name)?;
|
|
479
|
+
if unsafe { sn_same(child, sn_nil()) } != 0 {
|
|
480
|
+
Ok(None)
|
|
481
|
+
} else {
|
|
482
|
+
self.compile(child).map(Some)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
fn children(&mut self, value: Value) -> Result<Vec<usize>, Value> {
|
|
486
|
+
self.values(value)
|
|
487
|
+
.into_iter()
|
|
488
|
+
.map(|c| self.compile(c))
|
|
489
|
+
.collect()
|
|
490
|
+
}
|
|
491
|
+
fn branches(&mut self, value: Value) -> Result<Vec<(usize, usize)>, Value> {
|
|
492
|
+
let nodes = self.children(value)?;
|
|
493
|
+
let mut groups: Vec<(usize, usize)> = Vec::with_capacity(nodes.len());
|
|
494
|
+
for node in nodes {
|
|
495
|
+
if let Some(group) = groups
|
|
496
|
+
.last_mut()
|
|
497
|
+
.filter(|(prior, _)| *prior == node || self.same_simple_object(*prior, node))
|
|
498
|
+
{
|
|
499
|
+
group.1 += 1;
|
|
500
|
+
} else {
|
|
501
|
+
groups.push((node, 1));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
Ok(groups)
|
|
505
|
+
}
|
|
506
|
+
fn same_simple_object(&self, a: usize, b: usize) -> bool {
|
|
507
|
+
let ([Op::Object(required_a, a)], [Op::Object(required_b, b)]) =
|
|
508
|
+
(self.nodes[a].as_slice(), self.nodes[b].as_slice())
|
|
509
|
+
else {
|
|
510
|
+
return false;
|
|
511
|
+
};
|
|
512
|
+
let simple = |r: &Object| {
|
|
513
|
+
r.patterns.is_empty()
|
|
514
|
+
&& r.additional.is_none()
|
|
515
|
+
&& r.names.is_none()
|
|
516
|
+
&& r.dependent_required.is_empty()
|
|
517
|
+
&& r.dependent_schemas.is_empty()
|
|
518
|
+
&& r.unevaluated.is_none()
|
|
519
|
+
};
|
|
520
|
+
required_a == required_b
|
|
521
|
+
&& simple(a)
|
|
522
|
+
&& simple(b)
|
|
523
|
+
&& a.min == b.min
|
|
524
|
+
&& a.max == b.max
|
|
525
|
+
&& a.required == b.required
|
|
526
|
+
&& a.required_properties == b.required_properties
|
|
527
|
+
&& a.properties.len() == b.properties.len()
|
|
528
|
+
&& a.properties
|
|
529
|
+
.iter()
|
|
530
|
+
.zip(&b.properties)
|
|
531
|
+
.all(|(&(ka, ca), &(kb, cb))| {
|
|
532
|
+
// Exact key handles also preserve identity-Hash lookups. Only
|
|
533
|
+
// shape-independent leaves can ignore resource/path metadata.
|
|
534
|
+
ka == kb
|
|
535
|
+
&& match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
|
|
536
|
+
([Op::Type(a)], [Op::Type(b)]) => a == b,
|
|
537
|
+
([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
|
|
538
|
+
_ => false,
|
|
539
|
+
}
|
|
540
|
+
})
|
|
541
|
+
}
|
|
542
|
+
fn compile_map(&mut self, value: Value) -> Result<Vec<(Value, usize)>, Value> {
|
|
543
|
+
self.map(value)?
|
|
544
|
+
.into_iter()
|
|
545
|
+
.map(|(k, c)| Ok((k, self.compile(c)?)))
|
|
546
|
+
.collect()
|
|
547
|
+
}
|
|
548
|
+
fn compile(&mut self, program: Value) -> Result<usize, Value> {
|
|
549
|
+
if let Some(&i) = self.indices.get(&program) {
|
|
550
|
+
return Ok(i);
|
|
551
|
+
}
|
|
552
|
+
self.retain(program);
|
|
553
|
+
let index = self.nodes.len();
|
|
554
|
+
self.indices.insert(program, index);
|
|
555
|
+
self.nodes.push(vec![]);
|
|
556
|
+
self.error_operands.push(vec![]);
|
|
557
|
+
self.tracks.push(false);
|
|
558
|
+
self.metadata.push(scope::Metadata::default());
|
|
559
|
+
let code = self.field(program, b"code\0")?;
|
|
560
|
+
let flags = unsafe { sn_integer(sn_entry(code, 0)) };
|
|
561
|
+
self.tracks[index] = flags & i64::from(TRACKS_EVALUATION) != 0;
|
|
562
|
+
self.compile_scope(program, index, flags)?;
|
|
563
|
+
let mut ops = vec![];
|
|
564
|
+
let mut error_operands = vec![];
|
|
565
|
+
for i in (1..unsafe { sn_len(code) }).step_by(2) {
|
|
566
|
+
let operand = self.retain(unsafe { sn_entry(code, i + 1) });
|
|
567
|
+
let opcode = unsafe { sn_opcode(sn_entry(code, i)) };
|
|
568
|
+
error_operands.push((opcode, operand));
|
|
569
|
+
let op = match opcode {
|
|
570
|
+
OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
|
|
571
|
+
OP_TYPE_NULL => Op::Type(TYPE_NULL),
|
|
572
|
+
OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
|
|
573
|
+
OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
|
|
574
|
+
OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
|
|
575
|
+
OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
|
|
576
|
+
OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
|
|
577
|
+
OP_TYPE_STRING => Op::Type(TYPE_STRING),
|
|
578
|
+
OP_TYPES => {
|
|
579
|
+
let mask = self.field(operand, b"mask\0")?;
|
|
580
|
+
Op::Type(unsafe { sn_integer(mask) } as i32)
|
|
581
|
+
}
|
|
582
|
+
OP_CONST => Op::Const(operand),
|
|
583
|
+
OP_ENUM => Op::Enum(self.values(operand)),
|
|
584
|
+
OP_ALL_OF => Op::All(self.children(operand)?),
|
|
585
|
+
OP_ANY_OF => Op::Any(self.branches(operand)?),
|
|
586
|
+
OP_ONE_OF => Op::One(self.branches(operand)?),
|
|
587
|
+
OP_NOT => Op::Not(self.compile(operand)?),
|
|
588
|
+
OP_CONDITIONAL => {
|
|
589
|
+
let condition = self.field(operand, b"condition\0")?;
|
|
590
|
+
Op::Conditional(
|
|
591
|
+
self.compile(condition)?,
|
|
592
|
+
self.child(operand, b"then_branch\0")?,
|
|
593
|
+
self.child(operand, b"else_branch\0")?,
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
OP_NUMBER | OP_TYPED_NUMBER | OP_TYPED_INTEGER => {
|
|
597
|
+
if let Some(rules) = self.compile_number(operand)? {
|
|
598
|
+
Op::Number(
|
|
599
|
+
if opcode == OP_NUMBER {
|
|
600
|
+
0
|
|
601
|
+
} else if opcode == OP_TYPED_NUMBER {
|
|
602
|
+
TYPE_NUMBER
|
|
603
|
+
} else {
|
|
604
|
+
TYPE_INTEGER
|
|
605
|
+
},
|
|
606
|
+
Box::new(rules),
|
|
607
|
+
)
|
|
608
|
+
} else {
|
|
609
|
+
Op::Unsupported
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
OP_STRING | OP_TYPED_STRING => {
|
|
613
|
+
let pattern = self.field(operand, b"pattern\0")?;
|
|
614
|
+
let format = self.field(operand, b"format\0")?;
|
|
615
|
+
let base64 = self.field(operand, b"decode_base64\0")?;
|
|
616
|
+
let json = self.field(operand, b"parse_json\0")?;
|
|
617
|
+
{
|
|
618
|
+
let content = if self.validate_content
|
|
619
|
+
&& unsafe { sn_true(base64) | sn_true(json) } != 0
|
|
620
|
+
{
|
|
621
|
+
Some(content::Content {
|
|
622
|
+
base64: unsafe { sn_true(base64) } != 0,
|
|
623
|
+
json: unsafe { sn_true(json) } != 0,
|
|
624
|
+
})
|
|
625
|
+
} else {
|
|
626
|
+
None
|
|
627
|
+
};
|
|
628
|
+
let assertion = self.field(operand, b"format_assertion\0")?;
|
|
629
|
+
let format = if (self.validate_format || unsafe { sn_true(assertion) } != 0)
|
|
630
|
+
&& unsafe { sn_same(format, sn_nil()) } == 0
|
|
631
|
+
{
|
|
632
|
+
let name = self.field(format, b"name\0")?;
|
|
633
|
+
if unsafe { sn_same(name, sn_nil()) } != 0 {
|
|
634
|
+
None
|
|
635
|
+
} else {
|
|
636
|
+
Some(format::Format::compile(name))
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
None
|
|
640
|
+
};
|
|
641
|
+
Op::String(
|
|
642
|
+
if opcode == OP_TYPED_STRING {
|
|
643
|
+
TYPE_STRING
|
|
644
|
+
} else {
|
|
645
|
+
0
|
|
646
|
+
},
|
|
647
|
+
Box::new(StringRules {
|
|
648
|
+
format,
|
|
649
|
+
content,
|
|
650
|
+
min: self.size_field(operand, b"min_length\0")?,
|
|
651
|
+
max: self.size_field(operand, b"max_length\0")?,
|
|
652
|
+
pattern: if unsafe { sn_same(pattern, sn_nil()) } != 0 {
|
|
653
|
+
None
|
|
654
|
+
} else {
|
|
655
|
+
Some(pattern::Pattern::compile(pattern))
|
|
656
|
+
},
|
|
657
|
+
}),
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
OP_ARRAY | OP_TYPED_ARRAY => {
|
|
662
|
+
let unique = self.field(operand, b"unique\0")?;
|
|
663
|
+
let prefix = self.field(operand, b"prefix_items\0")?;
|
|
664
|
+
let prefix = self.children(prefix)?;
|
|
665
|
+
let items = self.field(operand, b"items\0")?;
|
|
666
|
+
let list = self.field(operand, b"items_list\0")?;
|
|
667
|
+
let list = unsafe { sn_true(list) } != 0;
|
|
668
|
+
let items = if list {
|
|
669
|
+
self.children(items)?
|
|
670
|
+
} else if unsafe { sn_same(items, sn_nil()) } != 0 {
|
|
671
|
+
vec![]
|
|
672
|
+
} else {
|
|
673
|
+
vec![self.compile(items)?]
|
|
674
|
+
};
|
|
675
|
+
Op::Array(
|
|
676
|
+
if opcode == OP_TYPED_ARRAY {
|
|
677
|
+
TYPE_ARRAY
|
|
678
|
+
} else {
|
|
679
|
+
0
|
|
680
|
+
},
|
|
681
|
+
Box::new(Array {
|
|
682
|
+
unique: unsafe { sn_true(unique) } != 0,
|
|
683
|
+
min: self.size_field(operand, b"min_items\0")?,
|
|
684
|
+
max: self.size_field(operand, b"max_items\0")?,
|
|
685
|
+
prefix,
|
|
686
|
+
items,
|
|
687
|
+
list,
|
|
688
|
+
additional: self.child(operand, b"additional\0")?,
|
|
689
|
+
contains: self.child(operand, b"contains\0")?,
|
|
690
|
+
min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
|
|
691
|
+
max_contains: self.size_field(operand, b"max_contains\0")?,
|
|
692
|
+
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
693
|
+
}),
|
|
694
|
+
)
|
|
695
|
+
}
|
|
696
|
+
OP_OBJECT | OP_TYPED_OBJECT => {
|
|
697
|
+
let patterns = self.field(operand, b"patterns\0")?;
|
|
698
|
+
let dependencies = self.field(operand, b"dependencies\0")?;
|
|
699
|
+
{
|
|
700
|
+
let patterns = self.compile_map(patterns)?;
|
|
701
|
+
let pattern_names = patterns.iter().map(|&(key, _)| key).collect();
|
|
702
|
+
let patterns = patterns
|
|
703
|
+
.into_iter()
|
|
704
|
+
.map(|(key, child)| (pattern::Pattern::compile(key), child))
|
|
705
|
+
.collect();
|
|
706
|
+
let required = self.field(operand, b"required\0")?;
|
|
707
|
+
let required = self.values(required);
|
|
708
|
+
let required_order = required.clone();
|
|
709
|
+
let properties = self.field(operand, b"properties\0")?;
|
|
710
|
+
let properties = self.compile_map(properties)?;
|
|
711
|
+
if required
|
|
712
|
+
.iter()
|
|
713
|
+
.chain(properties.iter().map(|(key, _)| key))
|
|
714
|
+
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
715
|
+
{
|
|
716
|
+
ops.push(Op::Unsupported);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
let property_targets = properties
|
|
720
|
+
.iter()
|
|
721
|
+
.map(|&(key, child)| (tracking::JsonKey::new(key), (key, child)))
|
|
722
|
+
.collect();
|
|
723
|
+
let property_locations: Vec<_> = properties
|
|
724
|
+
.iter()
|
|
725
|
+
.map(|&(key, _)| tracking::JsonKey::new(key))
|
|
726
|
+
.collect();
|
|
727
|
+
let property_keys = property_locations.iter().copied().collect();
|
|
728
|
+
let property_indices = property_locations
|
|
729
|
+
.iter()
|
|
730
|
+
.map(|&key| {
|
|
731
|
+
let next = self.property_ids.len();
|
|
732
|
+
*self.property_ids.entry(key).or_insert(next)
|
|
733
|
+
})
|
|
734
|
+
.collect();
|
|
735
|
+
let required_properties = properties
|
|
736
|
+
.iter()
|
|
737
|
+
.map(|&(key, _)| {
|
|
738
|
+
required
|
|
739
|
+
.iter()
|
|
740
|
+
.any(|&name| unsafe { sn_string_equal(key, name) } != 0)
|
|
741
|
+
})
|
|
742
|
+
.collect();
|
|
743
|
+
let required = required
|
|
744
|
+
.into_iter()
|
|
745
|
+
.filter(|&name| {
|
|
746
|
+
!properties
|
|
747
|
+
.iter()
|
|
748
|
+
.any(|&(key, _)| unsafe { sn_string_equal(key, name) } != 0)
|
|
749
|
+
})
|
|
750
|
+
.collect();
|
|
751
|
+
let dependent = self.field(operand, b"dependent_required\0")?;
|
|
752
|
+
let mut dependent_required = vec![];
|
|
753
|
+
let mut dependent_schemas = vec![];
|
|
754
|
+
let mut diagnostic_dependencies = vec![];
|
|
755
|
+
for pair in self.values(dependencies) {
|
|
756
|
+
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
757
|
+
let dependency = self.retain(unsafe { sn_entry(pair, 1) });
|
|
758
|
+
if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
|
|
759
|
+
let names = self.values(dependency);
|
|
760
|
+
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
761
|
+
name,
|
|
762
|
+
names.clone(),
|
|
763
|
+
"dependencies",
|
|
764
|
+
));
|
|
765
|
+
dependent_required.push((name, names));
|
|
766
|
+
} else {
|
|
767
|
+
let child = self.compile(dependency)?;
|
|
768
|
+
diagnostic_dependencies.push(DiagnosticDependency::Schema(
|
|
769
|
+
name,
|
|
770
|
+
child,
|
|
771
|
+
"dependencies",
|
|
772
|
+
));
|
|
773
|
+
dependent_schemas.push((name, child));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
for pair in self.values(dependent) {
|
|
777
|
+
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
778
|
+
let names = self.values(unsafe { sn_entry(pair, 1) });
|
|
779
|
+
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
780
|
+
name,
|
|
781
|
+
names.clone(),
|
|
782
|
+
"dependentRequired",
|
|
783
|
+
));
|
|
784
|
+
dependent_required.push((name, names));
|
|
785
|
+
}
|
|
786
|
+
let dependent = self.field(operand, b"dependent_schemas\0")?;
|
|
787
|
+
let schemas = self.compile_map(dependent)?;
|
|
788
|
+
diagnostic_dependencies.extend(schemas.iter().map(|&(name, child)| {
|
|
789
|
+
DiagnosticDependency::Schema(name, child, "dependentSchemas")
|
|
790
|
+
}));
|
|
791
|
+
dependent_schemas.extend(schemas);
|
|
792
|
+
if dependent_required.iter().any(|(name, keys)| {
|
|
793
|
+
(unsafe { sn_kind(*name) }) != TYPE_STRING
|
|
794
|
+
|| keys
|
|
795
|
+
.iter()
|
|
796
|
+
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
797
|
+
}) || dependent_schemas
|
|
798
|
+
.iter()
|
|
799
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
800
|
+
{
|
|
801
|
+
ops.push(Op::Unsupported);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
let mut object = Object {
|
|
805
|
+
record: None,
|
|
806
|
+
min: self.size_field(operand, b"min_properties\0")?,
|
|
807
|
+
max: self.size_field(operand, b"max_properties\0")?,
|
|
808
|
+
required,
|
|
809
|
+
required_order,
|
|
810
|
+
properties,
|
|
811
|
+
property_targets,
|
|
812
|
+
patterns,
|
|
813
|
+
pattern_names,
|
|
814
|
+
required_properties,
|
|
815
|
+
property_keys,
|
|
816
|
+
property_indices,
|
|
817
|
+
additional: self.child(operand, b"additional\0")?,
|
|
818
|
+
names: self.child(operand, b"property_names\0")?,
|
|
819
|
+
dependent_required,
|
|
820
|
+
dependent_schemas,
|
|
821
|
+
diagnostic_dependencies,
|
|
822
|
+
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
823
|
+
}
|
|
824
|
+
.with_record();
|
|
825
|
+
if let Some(record) = &mut object.record {
|
|
826
|
+
for field in &mut record.fields {
|
|
827
|
+
field.check = field.child.and_then(|child| {
|
|
828
|
+
match self.nodes[child].as_slice() {
|
|
829
|
+
[Op::Number(_, rules)] => {
|
|
830
|
+
rules.fast_limits.as_ref().map(|limits| {
|
|
831
|
+
let (min, max) = limits.iter().fold(
|
|
832
|
+
(i64::MIN, i64::MAX),
|
|
833
|
+
|(min, max), &(bit, limit)| match bit {
|
|
834
|
+
MAXIMUM => (min, max.min(limit)),
|
|
835
|
+
MINIMUM => (min.max(limit), max),
|
|
836
|
+
EXCLUSIVE_MAXIMUM => {
|
|
837
|
+
(min, max.min(limit.saturating_sub(1)))
|
|
838
|
+
}
|
|
839
|
+
_ => {
|
|
840
|
+
(min.max(limit.saturating_add(1)), max)
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
);
|
|
844
|
+
RecordCheck::IntegerBounds(min, max)
|
|
845
|
+
})
|
|
846
|
+
}
|
|
847
|
+
[Op::String(required, rules)] if rules.length_only() => {
|
|
848
|
+
Some(RecordCheck::StringLength(
|
|
849
|
+
*required, rules.min, rules.max,
|
|
850
|
+
))
|
|
851
|
+
}
|
|
852
|
+
_ => None,
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
let mut key = 0;
|
|
856
|
+
if unsafe { sn_key_copy(field.name, &mut key) } != 0 {
|
|
857
|
+
return Err(key);
|
|
858
|
+
}
|
|
859
|
+
// Distinct schema-only lookup handles also work
|
|
860
|
+
// when VM keys and input keys share an fstring.
|
|
861
|
+
field.name = self.retain(key);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
Op::Object(
|
|
865
|
+
if opcode == OP_TYPED_OBJECT {
|
|
866
|
+
TYPE_OBJECT
|
|
867
|
+
} else {
|
|
868
|
+
0
|
|
869
|
+
},
|
|
870
|
+
Box::new(object),
|
|
871
|
+
)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
OP_REF | OP_RECURSIVE_REF | OP_DYNAMIC_REF => {
|
|
875
|
+
let reference = self.field(operand, b"value\0")?;
|
|
876
|
+
let mut target = 0;
|
|
877
|
+
if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
|
|
878
|
+
if unsafe { sn_resolution_error(target) } != 0 {
|
|
879
|
+
Op::Unsupported
|
|
880
|
+
} else {
|
|
881
|
+
return Err(target);
|
|
882
|
+
}
|
|
883
|
+
} else {
|
|
884
|
+
self.retain(target);
|
|
885
|
+
let target = self.compile(target)?;
|
|
886
|
+
self.compile_reference(operand, opcode, target)?
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
_ => Op::Unsupported,
|
|
890
|
+
};
|
|
891
|
+
ops.push(op);
|
|
892
|
+
}
|
|
893
|
+
self.nodes[index] = ops;
|
|
894
|
+
self.error_operands[index] = error_operands;
|
|
895
|
+
Ok(index)
|
|
896
|
+
}
|
|
897
|
+
fn eval(&self, node: usize, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
898
|
+
let frame = scope::ScopeFrame {
|
|
899
|
+
resource: self.metadata[node].resource,
|
|
900
|
+
parent: active.scope,
|
|
901
|
+
primary_binding: active
|
|
902
|
+
.scope
|
|
903
|
+
.and_then(|frame| frame.primary_binding)
|
|
904
|
+
.or(self.metadata[node].primary_binding),
|
|
905
|
+
};
|
|
906
|
+
let active = self.enter_scope(node, active, &frame);
|
|
907
|
+
if self.tracks[node] {
|
|
908
|
+
return self.eval_tracked(node, value, depth, active).status;
|
|
909
|
+
}
|
|
910
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
911
|
+
return Unsupported;
|
|
912
|
+
}
|
|
913
|
+
if let [Op::String(required, rules)] = self.nodes[node].as_slice()
|
|
914
|
+
&& rules.pattern.is_some()
|
|
915
|
+
{
|
|
916
|
+
return rules.eval_guarded(*required, value);
|
|
917
|
+
}
|
|
918
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
919
|
+
TYPE_INTEGRAL_NUMBER
|
|
920
|
+
} else {
|
|
921
|
+
self.instance_kind(node, value)
|
|
922
|
+
};
|
|
923
|
+
if kind == TYPE_UNSUPPORTED {
|
|
924
|
+
return Unsupported;
|
|
925
|
+
}
|
|
926
|
+
for op in &self.nodes[node] {
|
|
927
|
+
let outcome = match op {
|
|
928
|
+
Op::Ref(target) => {
|
|
929
|
+
if RefFrame::contains(active.active, node, value) {
|
|
930
|
+
Valid
|
|
931
|
+
} else {
|
|
932
|
+
let frame = RefFrame {
|
|
933
|
+
source: node,
|
|
934
|
+
value,
|
|
935
|
+
parent: active.active,
|
|
936
|
+
};
|
|
937
|
+
self.eval(
|
|
938
|
+
self.reference_target(*target, active),
|
|
939
|
+
value,
|
|
940
|
+
depth + 1,
|
|
941
|
+
scope::Context {
|
|
942
|
+
active: Some(&frame),
|
|
943
|
+
..active
|
|
944
|
+
},
|
|
945
|
+
)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
Op::All(nodes) => {
|
|
949
|
+
let mut outcome = Valid;
|
|
950
|
+
for &n in nodes {
|
|
951
|
+
outcome = self.eval(n, value, depth + 1, active);
|
|
952
|
+
if outcome != Valid {
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
outcome
|
|
957
|
+
}
|
|
958
|
+
Op::Any(nodes) | Op::One(nodes) => {
|
|
959
|
+
let mut matches = 0;
|
|
960
|
+
let mut unknown = false;
|
|
961
|
+
for &(n, multiplicity) in nodes {
|
|
962
|
+
match self.eval(n, value, depth + 1, active) {
|
|
963
|
+
Valid => matches += multiplicity,
|
|
964
|
+
Unsupported => {
|
|
965
|
+
unknown = true;
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
Invalid => {}
|
|
969
|
+
}
|
|
970
|
+
if matches > 0 && matches!(op, Op::Any(_)) {
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
if matches > 1 && matches!(op, Op::One(_)) {
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if unknown {
|
|
978
|
+
Unsupported
|
|
979
|
+
} else {
|
|
980
|
+
verdict(if matches!(op, Op::Any(_)) {
|
|
981
|
+
matches > 0
|
|
982
|
+
} else {
|
|
983
|
+
matches == 1
|
|
984
|
+
})
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
Op::Not(n) => match self.eval(*n, value, depth + 1, active) {
|
|
988
|
+
Valid => Invalid,
|
|
989
|
+
Invalid => Valid,
|
|
990
|
+
Unsupported => Unsupported,
|
|
991
|
+
},
|
|
992
|
+
Op::Conditional(c, yes, no) => match self.eval(*c, value, depth + 1, active) {
|
|
993
|
+
Unsupported => Unsupported,
|
|
994
|
+
result => match if result == Valid { yes } else { no } {
|
|
995
|
+
Some(n) => self.eval(*n, value, depth + 1, active),
|
|
996
|
+
None => Valid,
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
Op::Array(required, rules) => {
|
|
1000
|
+
if *required != 0 && kind != TYPE_ARRAY {
|
|
1001
|
+
Invalid
|
|
1002
|
+
} else if kind != TYPE_ARRAY {
|
|
1003
|
+
Valid
|
|
1004
|
+
} else {
|
|
1005
|
+
self.array(rules, value, depth + 1, active)
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
Op::Object(required, rules) => {
|
|
1009
|
+
if *required != 0 && kind != TYPE_OBJECT {
|
|
1010
|
+
Invalid
|
|
1011
|
+
} else if kind != TYPE_OBJECT {
|
|
1012
|
+
Valid
|
|
1013
|
+
} else {
|
|
1014
|
+
self.object(rules, value, depth + 1, active)
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
_ => self.leaf(op, kind, value, depth),
|
|
1018
|
+
};
|
|
1019
|
+
if outcome != Valid {
|
|
1020
|
+
return outcome;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
Valid
|
|
1024
|
+
}
|
|
1025
|
+
fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
|
|
1026
|
+
match op {
|
|
1027
|
+
Op::Unsupported => Unsupported,
|
|
1028
|
+
Op::Boolean(v) => verdict(*v),
|
|
1029
|
+
Op::Type(mask) => verdict(kind & mask != 0),
|
|
1030
|
+
Op::Const(v) => equal(*v, value, depth),
|
|
1031
|
+
Op::Enum(values) => {
|
|
1032
|
+
let mut outcome = Invalid;
|
|
1033
|
+
for &v in values {
|
|
1034
|
+
outcome = equal(v, value, depth);
|
|
1035
|
+
if outcome != Invalid {
|
|
1036
|
+
break;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
outcome
|
|
1040
|
+
}
|
|
1041
|
+
Op::Number(required, rules) => {
|
|
1042
|
+
if *required != 0 && kind & required == 0 {
|
|
1043
|
+
Invalid
|
|
1044
|
+
} else if kind & TYPE_NUMBER == 0 {
|
|
1045
|
+
Valid
|
|
1046
|
+
} else {
|
|
1047
|
+
rules.eval(value)
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
Op::String(required, rules) => {
|
|
1051
|
+
if *required != 0 && kind != TYPE_STRING {
|
|
1052
|
+
Invalid
|
|
1053
|
+
} else if kind != TYPE_STRING {
|
|
1054
|
+
Valid
|
|
1055
|
+
} else {
|
|
1056
|
+
rules.eval(value)
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
_ => unreachable!("applicators must be evaluated with their context"),
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
1063
|
+
let len = unsafe { sn_len(value) };
|
|
1064
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
1065
|
+
return Invalid;
|
|
1066
|
+
}
|
|
1067
|
+
if r.unique {
|
|
1068
|
+
let result = unique(value, depth, self.fixnum_flag);
|
|
1069
|
+
if result != Valid {
|
|
1070
|
+
return result;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for (i, &child) in r.prefix.iter().take(len).enumerate() {
|
|
1074
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1075
|
+
if result != Valid {
|
|
1076
|
+
return result;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if r.list {
|
|
1080
|
+
for (i, &child) in r.items.iter().take(len).enumerate() {
|
|
1081
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1082
|
+
if result != Valid {
|
|
1083
|
+
return result;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if let Some(child) = r.additional {
|
|
1087
|
+
for i in r.items.len()..len {
|
|
1088
|
+
let result = self.eval(child, unsafe { sn_entry(value, i) }, depth, active);
|
|
1089
|
+
if result != Valid {
|
|
1090
|
+
return result;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} else if let Some(&child) = r.items.first() {
|
|
1095
|
+
// Borrow CRuby's storage only for this Ruby-allocation-free traversal.
|
|
1096
|
+
// No Ruby callback or Ruby allocation occurs while it is live.
|
|
1097
|
+
let values = if len == 0 {
|
|
1098
|
+
&[]
|
|
1099
|
+
} else {
|
|
1100
|
+
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
1101
|
+
};
|
|
1102
|
+
let primitive = match self.nodes[child].as_slice() {
|
|
1103
|
+
[Op::Type(mask)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
|
|
1104
|
+
Some(&[][..])
|
|
1105
|
+
}
|
|
1106
|
+
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
1107
|
+
_ => None,
|
|
1108
|
+
};
|
|
1109
|
+
let number = match self.nodes[child].as_slice() {
|
|
1110
|
+
[Op::Number(required, rules)]
|
|
1111
|
+
if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
|
|
1112
|
+
{
|
|
1113
|
+
Some((*required, rules))
|
|
1114
|
+
}
|
|
1115
|
+
_ => None,
|
|
1116
|
+
};
|
|
1117
|
+
let string = match self.nodes[child].as_slice() {
|
|
1118
|
+
[Op::String(required, rules)] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1119
|
+
Some((*required, rules))
|
|
1120
|
+
}
|
|
1121
|
+
_ => None,
|
|
1122
|
+
};
|
|
1123
|
+
let object = match self.nodes[child].as_slice() {
|
|
1124
|
+
[Op::Object(required, rules)]
|
|
1125
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1126
|
+
&& !self.tracks[child]
|
|
1127
|
+
&& rules.record.is_some() =>
|
|
1128
|
+
{
|
|
1129
|
+
Some((*required, rules))
|
|
1130
|
+
}
|
|
1131
|
+
_ => None,
|
|
1132
|
+
};
|
|
1133
|
+
// Sibling items enter the same schema resource. Reuse the immutable
|
|
1134
|
+
// scope frame while their child evaluations remain synchronous.
|
|
1135
|
+
let frame = scope::ScopeFrame {
|
|
1136
|
+
resource: self.metadata[child].resource,
|
|
1137
|
+
parent: active.scope,
|
|
1138
|
+
primary_binding: active
|
|
1139
|
+
.scope
|
|
1140
|
+
.and_then(|frame| frame.primary_binding)
|
|
1141
|
+
.or(self.metadata[child].primary_binding),
|
|
1142
|
+
};
|
|
1143
|
+
let object_active = self.enter_scope(child, active, &frame);
|
|
1144
|
+
let pattern = string.and_then(|(required, rules)| {
|
|
1145
|
+
rules.pattern_only().map(|pattern| (required, pattern))
|
|
1146
|
+
});
|
|
1147
|
+
let format = string
|
|
1148
|
+
.and_then(|(required, rules)| rules.format_only().map(|format| (required, format)));
|
|
1149
|
+
let content = string.and_then(|(required, rules)| {
|
|
1150
|
+
rules.content_only().map(|content| (required, content))
|
|
1151
|
+
});
|
|
1152
|
+
if let Some((required, pattern)) = pattern {
|
|
1153
|
+
let result =
|
|
1154
|
+
pattern.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
|
|
1155
|
+
if result != Valid {
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1158
|
+
} else if let Some((required, format)) = format {
|
|
1159
|
+
let result =
|
|
1160
|
+
format.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
|
|
1161
|
+
if result != Valid {
|
|
1162
|
+
return result;
|
|
1163
|
+
}
|
|
1164
|
+
} else if let Some((required, content)) = content {
|
|
1165
|
+
let result =
|
|
1166
|
+
content.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
|
|
1167
|
+
if result != Valid {
|
|
1168
|
+
return result;
|
|
1169
|
+
}
|
|
1170
|
+
} else if let Some((required, rules)) = object {
|
|
1171
|
+
let result = self.record_items(
|
|
1172
|
+
rules,
|
|
1173
|
+
required,
|
|
1174
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1175
|
+
depth + 1,
|
|
1176
|
+
object_active,
|
|
1177
|
+
);
|
|
1178
|
+
if result != Valid {
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
} else if let Some(limits) = primitive {
|
|
1182
|
+
let result = self.integer_items(
|
|
1183
|
+
limits,
|
|
1184
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1185
|
+
child,
|
|
1186
|
+
depth,
|
|
1187
|
+
active,
|
|
1188
|
+
);
|
|
1189
|
+
if result != Valid {
|
|
1190
|
+
return result;
|
|
1191
|
+
}
|
|
1192
|
+
} else {
|
|
1193
|
+
for &item in values.iter().skip(r.prefix.len()) {
|
|
1194
|
+
let result = if let Some((required, rules)) = number {
|
|
1195
|
+
rules.eval_guarded(required, item)
|
|
1196
|
+
} else if let Some((required, rules)) = string {
|
|
1197
|
+
if rules.pattern.is_some() {
|
|
1198
|
+
rules.eval_guarded(required, item)
|
|
1199
|
+
} else if rules.length_only() {
|
|
1200
|
+
rules.eval_length_guarded(required, item)
|
|
1201
|
+
} else {
|
|
1202
|
+
let kind = unsafe { sn_kind(item) };
|
|
1203
|
+
if kind == TYPE_UNSUPPORTED {
|
|
1204
|
+
Unsupported
|
|
1205
|
+
} else if kind != TYPE_STRING {
|
|
1206
|
+
verdict(required == 0)
|
|
1207
|
+
} else {
|
|
1208
|
+
rules.eval(item)
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
} else {
|
|
1212
|
+
self.eval(child, item, depth, active)
|
|
1213
|
+
};
|
|
1214
|
+
if result != Valid {
|
|
1215
|
+
return result;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if let Some(child) = r.contains {
|
|
1221
|
+
let mut matches = 0;
|
|
1222
|
+
for i in 0..len {
|
|
1223
|
+
match self.eval(child, unsafe { sn_entry(value, i) }, depth, active) {
|
|
1224
|
+
Valid => matches += 1,
|
|
1225
|
+
Unsupported => return Unsupported,
|
|
1226
|
+
Invalid => {}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if bounds(matches, Some(r.min_contains), r.max_contains) == Invalid {
|
|
1230
|
+
return Invalid;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
Valid
|
|
1234
|
+
}
|
|
1235
|
+
#[inline(never)]
|
|
1236
|
+
fn integer_items(
|
|
1237
|
+
&self,
|
|
1238
|
+
limits: &[(i32, i64)],
|
|
1239
|
+
values: &[Value],
|
|
1240
|
+
child: usize,
|
|
1241
|
+
depth: usize,
|
|
1242
|
+
active: scope::Context<'_>,
|
|
1243
|
+
) -> Verdict {
|
|
1244
|
+
// Interpret exact limits once; only header-validated immediate integers
|
|
1245
|
+
// use this interval. Other values retain the original guarded evaluator.
|
|
1246
|
+
let (min, max) =
|
|
1247
|
+
limits.iter().fold(
|
|
1248
|
+
(i64::MIN, i64::MAX),
|
|
1249
|
+
|(min, max), &(bit, limit)| match bit {
|
|
1250
|
+
MAXIMUM => (min, max.min(limit)),
|
|
1251
|
+
MINIMUM => (min.max(limit), max),
|
|
1252
|
+
EXCLUSIVE_MAXIMUM => (min, max.min(limit.saturating_sub(1))),
|
|
1253
|
+
_ => (min.max(limit.saturating_add(1)), max),
|
|
1254
|
+
},
|
|
1255
|
+
);
|
|
1256
|
+
let number = match self.nodes[child].as_slice() {
|
|
1257
|
+
[Op::Number(required, rules)]
|
|
1258
|
+
if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
|
|
1259
|
+
{
|
|
1260
|
+
Some((*required, rules))
|
|
1261
|
+
}
|
|
1262
|
+
_ => None,
|
|
1263
|
+
};
|
|
1264
|
+
for &value in values {
|
|
1265
|
+
let result = if value & self.fixnum_flag != 0 {
|
|
1266
|
+
let n = immediate_integer(value);
|
|
1267
|
+
verdict(n >= min && n <= max)
|
|
1268
|
+
} else if let Some((required, rules)) = number {
|
|
1269
|
+
rules.eval_guarded(required, value)
|
|
1270
|
+
} else {
|
|
1271
|
+
self.eval(child, value, depth, active)
|
|
1272
|
+
};
|
|
1273
|
+
if result != Valid {
|
|
1274
|
+
return result;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
Valid
|
|
1278
|
+
}
|
|
1279
|
+
#[inline(never)]
|
|
1280
|
+
fn record_items(
|
|
1281
|
+
&self,
|
|
1282
|
+
rules: &Object,
|
|
1283
|
+
required: i32,
|
|
1284
|
+
values: &[Value],
|
|
1285
|
+
depth: usize,
|
|
1286
|
+
active: scope::Context<'_>,
|
|
1287
|
+
) -> Verdict {
|
|
1288
|
+
for &value in values {
|
|
1289
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
1290
|
+
TYPE_INTEGRAL_NUMBER
|
|
1291
|
+
} else {
|
|
1292
|
+
unsafe { sn_shallow_kind(value) }
|
|
1293
|
+
};
|
|
1294
|
+
let result = if kind == TYPE_UNSUPPORTED {
|
|
1295
|
+
Unsupported
|
|
1296
|
+
} else if kind != TYPE_OBJECT {
|
|
1297
|
+
verdict(required == 0)
|
|
1298
|
+
} else {
|
|
1299
|
+
self.object(rules, value, depth, active)
|
|
1300
|
+
};
|
|
1301
|
+
if result != Valid {
|
|
1302
|
+
return result;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
Valid
|
|
1306
|
+
}
|
|
1307
|
+
fn object(
|
|
1308
|
+
&self,
|
|
1309
|
+
r: &Object,
|
|
1310
|
+
value: Value,
|
|
1311
|
+
depth: usize,
|
|
1312
|
+
active: scope::Context<'_>,
|
|
1313
|
+
) -> Verdict {
|
|
1314
|
+
if let Some(record) = &r.record {
|
|
1315
|
+
// C initializes only the written key/value pairs. The untouched
|
|
1316
|
+
// tail is never read, including empty and unsupported records.
|
|
1317
|
+
let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
|
|
1318
|
+
let len = unsafe { sn_small_hash(value, buffer.as_mut_ptr().cast()) };
|
|
1319
|
+
if len == SMALL_HASH_TOO_LARGE {
|
|
1320
|
+
return if unsafe { sn_kind(value) } == TYPE_OBJECT {
|
|
1321
|
+
self.object_general(r, value, depth, active)
|
|
1322
|
+
} else {
|
|
1323
|
+
Unsupported
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
if len < 0 {
|
|
1327
|
+
return Unsupported;
|
|
1328
|
+
}
|
|
1329
|
+
let ordinary = len & SMALL_HASH_ORDINARY != 0;
|
|
1330
|
+
let len = len & SMALL_HASH_COUNT_MASK;
|
|
1331
|
+
let status = bounds(len as usize, r.min, r.max);
|
|
1332
|
+
if status != Valid {
|
|
1333
|
+
return status;
|
|
1334
|
+
}
|
|
1335
|
+
// Successful calls wrote exactly len pairs, bounded by eight;
|
|
1336
|
+
// no Ruby callback or allocation can change the Hash during C's walk.
|
|
1337
|
+
let entries = unsafe {
|
|
1338
|
+
std::slice::from_raw_parts(buffer.as_ptr().cast::<Value>(), 2 * len as usize)
|
|
1339
|
+
};
|
|
1340
|
+
let mut context = RecordContext {
|
|
1341
|
+
engine: self,
|
|
1342
|
+
rules: r,
|
|
1343
|
+
record,
|
|
1344
|
+
// Choose for this record only. Both lookup strategies verify
|
|
1345
|
+
// every actual key; mixed layouts cannot change the verdict.
|
|
1346
|
+
indexed: record.short_index.is_some()
|
|
1347
|
+
&& entries.first().is_some_and(|key| {
|
|
1348
|
+
record.identity_hint != *key
|
|
1349
|
+
&& !record.fields.iter().any(|field| field.key == *key)
|
|
1350
|
+
}),
|
|
1351
|
+
depth,
|
|
1352
|
+
active,
|
|
1353
|
+
visited: 0,
|
|
1354
|
+
// A proven ordinary Hash never uses this lookup handle.
|
|
1355
|
+
// Seed its unused slot to avoid collecting a distinct key.
|
|
1356
|
+
probe: ordinary.then_some(self.undefined),
|
|
1357
|
+
status,
|
|
1358
|
+
};
|
|
1359
|
+
for entry in entries.as_chunks::<2>().0 {
|
|
1360
|
+
if record_entry(&mut context, entry[0], entry[1]) != Valid {
|
|
1361
|
+
return Unsupported;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if !ordinary && let Some(name) = context.probe {
|
|
1365
|
+
// All keys have now been checked. A successful lookup by
|
|
1366
|
+
// a distinct equal schema key proves ordinary Hash key
|
|
1367
|
+
// semantics, without calling a Ruby identity predicate.
|
|
1368
|
+
if missing(value, name, self.undefined) {
|
|
1369
|
+
return Unsupported;
|
|
1370
|
+
}
|
|
1371
|
+
} else if !ordinary && context.visited != 0 {
|
|
1372
|
+
return Unsupported;
|
|
1373
|
+
}
|
|
1374
|
+
return if context.status != Valid {
|
|
1375
|
+
context.status
|
|
1376
|
+
} else {
|
|
1377
|
+
verdict(context.visited & record.required == record.required)
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
self.object_general(r, value, depth, active)
|
|
1381
|
+
}
|
|
1382
|
+
fn object_general(
|
|
1383
|
+
&self,
|
|
1384
|
+
r: &Object,
|
|
1385
|
+
value: Value,
|
|
1386
|
+
depth: usize,
|
|
1387
|
+
active: scope::Context<'_>,
|
|
1388
|
+
) -> Verdict {
|
|
1389
|
+
let len = unsafe { sn_len(value) };
|
|
1390
|
+
if bounds(len, r.min, r.max) == Invalid {
|
|
1391
|
+
return Invalid;
|
|
1392
|
+
}
|
|
1393
|
+
for &key in &r.required {
|
|
1394
|
+
if missing(value, key, self.undefined) {
|
|
1395
|
+
return Invalid;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
let mut found = 0;
|
|
1399
|
+
for (index, &(key, child)) in r.properties.iter().enumerate() {
|
|
1400
|
+
let item = unsafe { sn_lookup(value, key) };
|
|
1401
|
+
if item == self.undefined {
|
|
1402
|
+
if r.required_properties[index] {
|
|
1403
|
+
return Invalid;
|
|
1404
|
+
}
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
found += 1;
|
|
1408
|
+
let result = self.eval(child, item, depth, active);
|
|
1409
|
+
if result != Valid {
|
|
1410
|
+
return result;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
if r.names.is_some() || !r.patterns.is_empty() || (r.additional.is_some() && found < len) {
|
|
1414
|
+
let mut context = ObjectContext {
|
|
1415
|
+
engine: self,
|
|
1416
|
+
rules: r,
|
|
1417
|
+
depth,
|
|
1418
|
+
active,
|
|
1419
|
+
};
|
|
1420
|
+
let result =
|
|
1421
|
+
unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
|
|
1422
|
+
if result != VERDICT_VALID {
|
|
1423
|
+
return if result == 0 { Invalid } else { Unsupported };
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
for (name, required) in &r.dependent_required {
|
|
1427
|
+
if !missing(value, *name, self.undefined) {
|
|
1428
|
+
for &key in required {
|
|
1429
|
+
if missing(value, key, self.undefined) {
|
|
1430
|
+
return Invalid;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
for &(name, child) in &r.dependent_schemas {
|
|
1436
|
+
if !missing(value, name, self.undefined) {
|
|
1437
|
+
let result = self.eval(child, value, depth, active);
|
|
1438
|
+
if result != Valid {
|
|
1439
|
+
return result;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
Valid
|
|
1444
|
+
}
|
|
1445
|
+
fn instance_kind(&self, node: usize, value: Value) -> i32 {
|
|
1446
|
+
if matches!(self.nodes[node].as_slice(), [Op::Object(_, rules)] if rules.record.is_some()) {
|
|
1447
|
+
unsafe { sn_shallow_kind(value) }
|
|
1448
|
+
} else {
|
|
1449
|
+
unsafe { sn_kind(value) }
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
fn record_value(
|
|
1453
|
+
&self,
|
|
1454
|
+
node: usize,
|
|
1455
|
+
value: Value,
|
|
1456
|
+
depth: usize,
|
|
1457
|
+
active: scope::Context<'_>,
|
|
1458
|
+
) -> Verdict {
|
|
1459
|
+
match self.nodes[node].as_slice() {
|
|
1460
|
+
[Op::String(required, rules)]
|
|
1461
|
+
if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
|
|
1462
|
+
{
|
|
1463
|
+
rules.eval_length_guarded(*required, value)
|
|
1464
|
+
}
|
|
1465
|
+
[Op::String(required, rules)]
|
|
1466
|
+
if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
|
|
1467
|
+
{
|
|
1468
|
+
rules.eval_guarded(*required, value)
|
|
1469
|
+
}
|
|
1470
|
+
[
|
|
1471
|
+
op @ (Op::Type(_)
|
|
1472
|
+
| Op::Boolean(_)
|
|
1473
|
+
| Op::Number(..)
|
|
1474
|
+
| Op::String(..)
|
|
1475
|
+
| Op::Const(_)
|
|
1476
|
+
| Op::Enum(_)),
|
|
1477
|
+
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1478
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
1479
|
+
TYPE_INTEGRAL_NUMBER
|
|
1480
|
+
} else {
|
|
1481
|
+
unsafe { sn_kind(value) }
|
|
1482
|
+
};
|
|
1483
|
+
if kind == TYPE_UNSUPPORTED {
|
|
1484
|
+
Unsupported
|
|
1485
|
+
} else {
|
|
1486
|
+
self.leaf(op, kind, value, depth)
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
_ => self.eval(node, value, depth, active),
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
struct RecordContext<'a, 'b> {
|
|
1494
|
+
engine: &'a Engine,
|
|
1495
|
+
rules: &'a Object,
|
|
1496
|
+
record: &'a RecordObject,
|
|
1497
|
+
indexed: bool,
|
|
1498
|
+
depth: usize,
|
|
1499
|
+
active: scope::Context<'b>,
|
|
1500
|
+
visited: u64,
|
|
1501
|
+
probe: Option<Value>,
|
|
1502
|
+
status: Verdict,
|
|
1503
|
+
}
|
|
1504
|
+
fn indexed_record_field(record: &RecordObject, key: Value) -> Option<&RecordField> {
|
|
1505
|
+
let index = record.short_index.as_ref()?;
|
|
1506
|
+
let short = short_record_key(key);
|
|
1507
|
+
if short.tag == 0 {
|
|
1508
|
+
return None;
|
|
1509
|
+
}
|
|
1510
|
+
index.slots[index.slot(short)]
|
|
1511
|
+
.checked_sub(1)
|
|
1512
|
+
.and_then(|at| record.fields.get(at as usize))
|
|
1513
|
+
.filter(|field| {
|
|
1514
|
+
field.short_key.tag & 15 == short.tag & 15
|
|
1515
|
+
&& field.short_key.word == short.word
|
|
1516
|
+
&& (field.short_key.tag == short.tag
|
|
1517
|
+
|| unsafe { sn_string_equal(field.key, key) } != 0)
|
|
1518
|
+
})
|
|
1519
|
+
}
|
|
1520
|
+
fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verdict {
|
|
1521
|
+
let field = if c.indexed {
|
|
1522
|
+
indexed_record_field(c.record, key)
|
|
1523
|
+
} else {
|
|
1524
|
+
c.record
|
|
1525
|
+
.fields
|
|
1526
|
+
.iter()
|
|
1527
|
+
.find(|field| field.key == key)
|
|
1528
|
+
.or_else(|| {
|
|
1529
|
+
if c.record.short_index.is_some() {
|
|
1530
|
+
return indexed_record_field(c.record, key);
|
|
1531
|
+
}
|
|
1532
|
+
let short = (c.record.fields.len() >= 4).then(|| short_record_key(key));
|
|
1533
|
+
c.record.fields.iter().find(|field| {
|
|
1534
|
+
if let Some(short) = short.filter(|short| short.tag != 0) {
|
|
1535
|
+
field.short_key.tag & 15 == short.tag & 15
|
|
1536
|
+
&& field.short_key.word == short.word
|
|
1537
|
+
&& (field.short_key.tag == short.tag
|
|
1538
|
+
|| unsafe { sn_string_equal(field.key, key) } != 0)
|
|
1539
|
+
} else {
|
|
1540
|
+
(unsafe { sn_string_equal(field.key, key) }) != 0
|
|
1541
|
+
}
|
|
1542
|
+
})
|
|
1543
|
+
})
|
|
1544
|
+
};
|
|
1545
|
+
let child = if let Some(field) = field {
|
|
1546
|
+
if c.visited & field.bit != 0 {
|
|
1547
|
+
return Unsupported;
|
|
1548
|
+
}
|
|
1549
|
+
c.visited |= field.bit;
|
|
1550
|
+
if c.probe.is_none() && field.name != key {
|
|
1551
|
+
c.probe = Some(field.name);
|
|
1552
|
+
}
|
|
1553
|
+
field.child.or(c.rules.additional)
|
|
1554
|
+
} else {
|
|
1555
|
+
c.rules.additional
|
|
1556
|
+
};
|
|
1557
|
+
if c.status == Valid
|
|
1558
|
+
&& let Some(child) = child
|
|
1559
|
+
{
|
|
1560
|
+
c.status = if c.depth <= MAX_EVALUATION_DEPTH {
|
|
1561
|
+
match field.and_then(|field| field.check) {
|
|
1562
|
+
Some(RecordCheck::IntegerBounds(min, max)) if value & c.engine.fixnum_flag != 0 => {
|
|
1563
|
+
let n = immediate_integer(value);
|
|
1564
|
+
verdict(n >= min && n <= max)
|
|
1565
|
+
}
|
|
1566
|
+
Some(RecordCheck::StringLength(required, min, max)) => {
|
|
1567
|
+
length_guarded(value, required, min, max)
|
|
1568
|
+
}
|
|
1569
|
+
_ => c.engine.record_value(child, value, c.depth, c.active),
|
|
1570
|
+
}
|
|
1571
|
+
} else {
|
|
1572
|
+
c.engine.record_value(child, value, c.depth, c.active)
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
// Keep recording matches after failure: duplicate equal keys indicate
|
|
1576
|
+
// identity semantics, so their speculative child results cannot be used.
|
|
1577
|
+
Valid
|
|
1578
|
+
}
|
|
1579
|
+
struct RefFrame<'a> {
|
|
1580
|
+
source: usize,
|
|
1581
|
+
value: Value,
|
|
1582
|
+
parent: Option<&'a RefFrame<'a>>,
|
|
1583
|
+
}
|
|
1584
|
+
impl RefFrame<'_> {
|
|
1585
|
+
fn contains(mut active: Option<&Self>, source: usize, value: Value) -> bool {
|
|
1586
|
+
while let Some(frame) = active {
|
|
1587
|
+
if frame.source == source && frame.value == value {
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
active = frame.parent;
|
|
1591
|
+
}
|
|
1592
|
+
false
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
fn missing(hash: Value, key: Value, undefined: Value) -> bool {
|
|
1596
|
+
unsafe { sn_lookup(hash, key) == undefined }
|
|
1597
|
+
}
|
|
1598
|
+
fn verdict(valid: bool) -> Verdict {
|
|
1599
|
+
if valid { Valid } else { Invalid }
|
|
1600
|
+
}
|
|
1601
|
+
fn bounds(len: usize, min: Option<usize>, max: Option<usize>) -> Verdict {
|
|
1602
|
+
verdict(!min.is_some_and(|m| len < m) && !max.is_some_and(|m| len > m))
|
|
1603
|
+
}
|
|
1604
|
+
fn length_guarded(value: Value, required: i32, min: Option<usize>, max: Option<usize>) -> Verdict {
|
|
1605
|
+
let mut len = 0;
|
|
1606
|
+
match unsafe { sn_string_length_guarded(value, &mut len) } {
|
|
1607
|
+
TYPE_UNSUPPORTED => Unsupported,
|
|
1608
|
+
TYPE_STRING => bounds(len, min, max),
|
|
1609
|
+
_ => verdict(required == 0),
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
// Call only after value & the header-validated fixnum_flag is nonzero.
|
|
1613
|
+
// Rust's signed shift is arithmetic; other encodings retain the C adapters.
|
|
1614
|
+
fn immediate_integer(value: Value) -> i64 {
|
|
1615
|
+
(value as isize >> 1) as i64
|
|
1616
|
+
}
|
|
1617
|
+
fn equal(a: Value, b: Value, depth: usize) -> Verdict {
|
|
1618
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
1619
|
+
return Unsupported;
|
|
1620
|
+
}
|
|
1621
|
+
let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
|
|
1622
|
+
if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
|
|
1623
|
+
return Unsupported;
|
|
1624
|
+
}
|
|
1625
|
+
if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
|
|
1626
|
+
let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
|
|
1627
|
+
let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
|
|
1628
|
+
if ia && ib {
|
|
1629
|
+
return verdict(unsafe { sn_integer(a) == sn_integer(b) });
|
|
1630
|
+
}
|
|
1631
|
+
if fa && fb {
|
|
1632
|
+
return verdict(unsafe { sn_float(a) == sn_float(b) });
|
|
1633
|
+
}
|
|
1634
|
+
if (ia && fb) || (fa && ib) {
|
|
1635
|
+
let (n, f) = unsafe {
|
|
1636
|
+
if ia {
|
|
1637
|
+
(sn_integer(a), sn_float(b))
|
|
1638
|
+
} else {
|
|
1639
|
+
(sn_integer(b), sn_float(a))
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
// Convert the float to an integer, never the integer to a rounded float.
|
|
1643
|
+
// Fixnums are strictly inside i64's bounds on supported CRuby targets.
|
|
1644
|
+
return verdict(
|
|
1645
|
+
f.is_finite()
|
|
1646
|
+
&& f.trunc() == f
|
|
1647
|
+
&& f >= i64::MIN as f64
|
|
1648
|
+
&& f < i64::MAX as f64
|
|
1649
|
+
&& f as i64 == n,
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
if !fa && !fb {
|
|
1653
|
+
return verdict(
|
|
1654
|
+
unsafe {
|
|
1655
|
+
if ia {
|
|
1656
|
+
sn_big_equal(b, a)
|
|
1657
|
+
} else {
|
|
1658
|
+
sn_big_equal(a, b)
|
|
1659
|
+
}
|
|
1660
|
+
} != 0,
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
return if fa {
|
|
1664
|
+
number::integer_float_equal(b, unsafe { sn_float(a) })
|
|
1665
|
+
} else {
|
|
1666
|
+
number::integer_float_equal(a, unsafe { sn_float(b) })
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
// JSON container equality visits every value, even for the same object:
|
|
1670
|
+
// nested NaN values are unequal and unsupported values need VM evaluation.
|
|
1671
|
+
if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
|
|
1672
|
+
return Valid;
|
|
1673
|
+
}
|
|
1674
|
+
if ka != kb {
|
|
1675
|
+
return Invalid;
|
|
1676
|
+
}
|
|
1677
|
+
if unsafe { sn_same_class(a, b) } == 0 {
|
|
1678
|
+
return Invalid;
|
|
1679
|
+
}
|
|
1680
|
+
match ka {
|
|
1681
|
+
TYPE_STRING => verdict(unsafe { sn_string_equal(a, b) } != 0),
|
|
1682
|
+
TYPE_ARRAY => {
|
|
1683
|
+
let len = unsafe { sn_len(a) };
|
|
1684
|
+
if len != unsafe { sn_len(b) } {
|
|
1685
|
+
return Invalid;
|
|
1686
|
+
}
|
|
1687
|
+
for i in 0..len {
|
|
1688
|
+
let result = equal(
|
|
1689
|
+
unsafe { sn_entry(a, i) },
|
|
1690
|
+
unsafe { sn_entry(b, i) },
|
|
1691
|
+
depth + 1,
|
|
1692
|
+
);
|
|
1693
|
+
if result != Valid {
|
|
1694
|
+
return result;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
Valid
|
|
1698
|
+
}
|
|
1699
|
+
TYPE_OBJECT => {
|
|
1700
|
+
if unsafe { sn_len(a) != sn_len(b) } {
|
|
1701
|
+
return Invalid;
|
|
1702
|
+
}
|
|
1703
|
+
let mut context = EqualityContext {
|
|
1704
|
+
other: b,
|
|
1705
|
+
depth: depth + 1,
|
|
1706
|
+
};
|
|
1707
|
+
decode(unsafe {
|
|
1708
|
+
sn_hash_each(
|
|
1709
|
+
a,
|
|
1710
|
+
(&mut context as *mut EqualityContext).cast(),
|
|
1711
|
+
equal_entry,
|
|
1712
|
+
)
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
TYPE_UNSUPPORTED => Unsupported,
|
|
1716
|
+
_ => Invalid,
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
fn decode(result: i32) -> Verdict {
|
|
1720
|
+
match result {
|
|
1721
|
+
VERDICT_INVALID => Invalid,
|
|
1722
|
+
VERDICT_VALID => Valid,
|
|
1723
|
+
_ => Unsupported,
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
struct EqualityContext {
|
|
1727
|
+
other: Value,
|
|
1728
|
+
depth: usize,
|
|
1729
|
+
}
|
|
1730
|
+
unsafe extern "C" fn equal_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
1731
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
1732
|
+
let c = unsafe { &*context.cast::<EqualityContext>() };
|
|
1733
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
1734
|
+
return Unsupported as i32;
|
|
1735
|
+
}
|
|
1736
|
+
let other = unsafe { sn_lookup(c.other, key) };
|
|
1737
|
+
if unsafe { sn_missing(other) } != 0 {
|
|
1738
|
+
Invalid as i32
|
|
1739
|
+
} else {
|
|
1740
|
+
equal(value, other, c.depth) as i32
|
|
1741
|
+
}
|
|
1742
|
+
}))
|
|
1743
|
+
.unwrap_or(Unsupported as i32)
|
|
1744
|
+
}
|
|
1745
|
+
fn mix(n: u64) -> u64 {
|
|
1746
|
+
let n = (n ^ (n >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
|
|
1747
|
+
let n = (n ^ (n >> 27)).wrapping_mul(0x94d049bb133111eb);
|
|
1748
|
+
n ^ (n >> 31)
|
|
1749
|
+
}
|
|
1750
|
+
fn string_fingerprint(value: Value) -> u64 {
|
|
1751
|
+
bytes_fingerprint(unsafe { string_bytes(value) })
|
|
1752
|
+
}
|
|
1753
|
+
#[derive(Clone, Copy, Debug)]
|
|
1754
|
+
struct ShortRecordKey {
|
|
1755
|
+
word: u64,
|
|
1756
|
+
// Low four bits encode length + 1; zero means a longer key. The remaining
|
|
1757
|
+
// bits hold the public CRuby encoding index, not a borrowed pointer.
|
|
1758
|
+
tag: u64,
|
|
1759
|
+
}
|
|
1760
|
+
fn short_record_key(value: Value) -> ShortRecordKey {
|
|
1761
|
+
let mut data = std::ptr::null();
|
|
1762
|
+
let mut len = 0;
|
|
1763
|
+
let encoding = unsafe { sn_record_key_view(value, &mut data, &mut len) };
|
|
1764
|
+
if len > 8 {
|
|
1765
|
+
return ShortRecordKey { word: 0, tag: 0 };
|
|
1766
|
+
}
|
|
1767
|
+
let bytes = unsafe { byte_view(data, len) };
|
|
1768
|
+
ShortRecordKey {
|
|
1769
|
+
word: short_word(bytes),
|
|
1770
|
+
tag: ((encoding as u64) << 4) | (len as u64 + 1),
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
// Borrow an already classified String under the GVL. The caller must not
|
|
1774
|
+
// allocate Ruby objects or run Ruby callbacks while the returned view is live.
|
|
1775
|
+
unsafe fn string_bytes<'a>(value: Value) -> &'a [u8] {
|
|
1776
|
+
let mut data = std::ptr::null();
|
|
1777
|
+
let mut len = 0;
|
|
1778
|
+
unsafe { sn_string_raw_view(value, &mut data, &mut len) };
|
|
1779
|
+
unsafe { byte_view(data, len) }
|
|
1780
|
+
}
|
|
1781
|
+
// The caller must keep the original Ruby string alive and unchanged under
|
|
1782
|
+
// the GVL for the entire lifetime of the view, without allocating in Ruby.
|
|
1783
|
+
unsafe fn byte_view<'a>(data: *const u8, len: usize) -> &'a [u8] {
|
|
1784
|
+
if len == 0 {
|
|
1785
|
+
&[]
|
|
1786
|
+
} else {
|
|
1787
|
+
unsafe { std::slice::from_raw_parts(data, len) }
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
// For at most eight bytes, the first/last scalar words cover every byte.
|
|
1791
|
+
fn short_word(bytes: &[u8]) -> u64 {
|
|
1792
|
+
let len = bytes.len();
|
|
1793
|
+
match bytes.len() {
|
|
1794
|
+
0 => 0,
|
|
1795
|
+
1 => bytes[0] as u64,
|
|
1796
|
+
2 => bytes[0] as u64 | (bytes[1] as u64) << 8,
|
|
1797
|
+
3 => bytes[0] as u64 | (bytes[1] as u64) << 8 | (bytes[2] as u64) << 16,
|
|
1798
|
+
_ => {
|
|
1799
|
+
let first = u32::from_le_bytes(bytes[..4].try_into().unwrap()) as u64;
|
|
1800
|
+
let last = u32::from_le_bytes(bytes[len - 4..].try_into().unwrap()) as u64;
|
|
1801
|
+
first | last << 32
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
fn bytes_fingerprint(bytes: &[u8]) -> u64 {
|
|
1806
|
+
// Short keys and values fit in one scalar. Overlapping first/last words
|
|
1807
|
+
// cover every byte without a byte-at-a-time multiplication chain.
|
|
1808
|
+
let len = bytes.len();
|
|
1809
|
+
if len <= 8 {
|
|
1810
|
+
let word = short_word(bytes);
|
|
1811
|
+
return mix(word ^ (len as u64).wrapping_mul(0x100000001b3) ^ TYPE_STRING as u64);
|
|
1812
|
+
}
|
|
1813
|
+
let mut result = 0xcbf29ce484222325_u64;
|
|
1814
|
+
for &byte in bytes {
|
|
1815
|
+
result = (result ^ byte as u64).wrapping_mul(0x100000001b3);
|
|
1816
|
+
}
|
|
1817
|
+
mix(result ^ TYPE_STRING as u64)
|
|
1818
|
+
}
|
|
1819
|
+
fn fingerprint(value: Value, depth: usize, fixnum_flag: Value, big_hint: bool) -> Option<u64> {
|
|
1820
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
1821
|
+
return None;
|
|
1822
|
+
}
|
|
1823
|
+
if value & fixnum_flag != 0 {
|
|
1824
|
+
// sn_fixnum_flag enables this hint only after the C headers confirm
|
|
1825
|
+
// the signed immediate encoding. Rust's signed shift is arithmetic.
|
|
1826
|
+
let integer = immediate_integer(value);
|
|
1827
|
+
return Some(mix(integer as u64 ^ TYPE_NUMBER as u64));
|
|
1828
|
+
}
|
|
1829
|
+
if big_hint && let Some(hash) = number::big_fingerprint(value) {
|
|
1830
|
+
return Some(hash);
|
|
1831
|
+
}
|
|
1832
|
+
// The C key-view iterator validates every key without Ruby lookup.
|
|
1833
|
+
// Avoid a separate preliminary walk of the same keys.
|
|
1834
|
+
let mut data = std::mem::MaybeUninit::<*const c_void>::uninit();
|
|
1835
|
+
let mut len = std::mem::MaybeUninit::<usize>::uninit();
|
|
1836
|
+
let mut n = std::mem::MaybeUninit::<f64>::uninit();
|
|
1837
|
+
// Read only the fields the C adapter initialized for the returned kind.
|
|
1838
|
+
let kind =
|
|
1839
|
+
unsafe { sn_fingerprint_view(value, data.as_mut_ptr(), len.as_mut_ptr(), n.as_mut_ptr()) };
|
|
1840
|
+
if kind == TYPE_NUMBER {
|
|
1841
|
+
let n = unsafe { n.assume_init() };
|
|
1842
|
+
// Normalize integral floats without rounding distinct integers. Exact
|
|
1843
|
+
// JSON equality still resolves every collision before declaring duplicates.
|
|
1844
|
+
if n.is_finite() && n.trunc() == n && n >= i64::MIN as f64 && n < i64::MAX as f64 {
|
|
1845
|
+
return Some(mix(n as i64 as u64 ^ TYPE_NUMBER as u64));
|
|
1846
|
+
}
|
|
1847
|
+
return Some(mix(n.to_bits() ^ TYPE_NUMBER as u64));
|
|
1848
|
+
}
|
|
1849
|
+
match kind {
|
|
1850
|
+
TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
|
|
1851
|
+
TYPE_NULL => Some(mix(TYPE_NULL as u64)),
|
|
1852
|
+
TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
|
|
1853
|
+
2
|
|
1854
|
+
} else {
|
|
1855
|
+
3
|
|
1856
|
+
})),
|
|
1857
|
+
TYPE_STRING => Some(bytes_fingerprint(unsafe {
|
|
1858
|
+
byte_view(data.assume_init().cast(), len.assume_init())
|
|
1859
|
+
})),
|
|
1860
|
+
TYPE_ARRAY => {
|
|
1861
|
+
let mut hash = mix(TYPE_ARRAY as u64);
|
|
1862
|
+
let len = unsafe { len.assume_init() };
|
|
1863
|
+
let values = if len == 0 {
|
|
1864
|
+
&[]
|
|
1865
|
+
} else {
|
|
1866
|
+
unsafe { std::slice::from_raw_parts(data.assume_init().cast::<Value>(), len) }
|
|
1867
|
+
};
|
|
1868
|
+
// All descendants are read under the GVL without allocating Ruby
|
|
1869
|
+
// objects or invoking Ruby callbacks, so the borrowed view is stable.
|
|
1870
|
+
for &item in values {
|
|
1871
|
+
hash = mix(hash ^ fingerprint(item, depth + 1, fixnum_flag, false)?);
|
|
1872
|
+
}
|
|
1873
|
+
Some(hash)
|
|
1874
|
+
}
|
|
1875
|
+
TYPE_OBJECT => {
|
|
1876
|
+
let len = unsafe { len.assume_init() };
|
|
1877
|
+
let mut c = FingerprintContext {
|
|
1878
|
+
hash: 0,
|
|
1879
|
+
depth: depth + 1,
|
|
1880
|
+
fixnum_flag,
|
|
1881
|
+
};
|
|
1882
|
+
let status = unsafe {
|
|
1883
|
+
sn_hash_each_key_view(
|
|
1884
|
+
value,
|
|
1885
|
+
(&mut c as *mut FingerprintContext).cast(),
|
|
1886
|
+
fingerprint_entry,
|
|
1887
|
+
)
|
|
1888
|
+
};
|
|
1889
|
+
(status == VERDICT_VALID).then(|| mix(c.hash ^ len as u64 ^ TYPE_OBJECT as u64))
|
|
1890
|
+
}
|
|
1891
|
+
_ => None,
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
struct FingerprintContext {
|
|
1895
|
+
hash: u64,
|
|
1896
|
+
depth: usize,
|
|
1897
|
+
fixnum_flag: Value,
|
|
1898
|
+
}
|
|
1899
|
+
unsafe extern "C" fn fingerprint_entry(
|
|
1900
|
+
context: *mut c_void,
|
|
1901
|
+
data: *const u8,
|
|
1902
|
+
len: usize,
|
|
1903
|
+
value: Value,
|
|
1904
|
+
) -> i32 {
|
|
1905
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
1906
|
+
let c = unsafe { &mut *context.cast::<FingerprintContext>() };
|
|
1907
|
+
let Some(hash) = fingerprint(value, c.depth, c.fixnum_flag, false) else {
|
|
1908
|
+
return Unsupported as i32;
|
|
1909
|
+
};
|
|
1910
|
+
c.hash ^= mix(bytes_fingerprint(unsafe { byte_view(data, len) }) ^ hash.rotate_left(17));
|
|
1911
|
+
Valid as i32
|
|
1912
|
+
}))
|
|
1913
|
+
.unwrap_or(Unsupported as i32)
|
|
1914
|
+
}
|
|
1915
|
+
// Fingerprints are already mixed uniformly. Rehashing each u64 with SipHash
|
|
1916
|
+
// adds work without changing collision resolution, which always uses JSON equality.
|
|
1917
|
+
#[derive(Default)]
|
|
1918
|
+
struct BucketHasher(u64);
|
|
1919
|
+
impl Hasher for BucketHasher {
|
|
1920
|
+
fn finish(&self) -> u64 {
|
|
1921
|
+
self.0
|
|
1922
|
+
}
|
|
1923
|
+
fn write_u64(&mut self, value: u64) {
|
|
1924
|
+
self.0 = value;
|
|
1925
|
+
}
|
|
1926
|
+
fn write(&mut self, bytes: &[u8]) {
|
|
1927
|
+
for &byte in bytes {
|
|
1928
|
+
self.0 = mix(self.0 ^ byte as u64);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
fn unique(value: Value, depth: usize, fixnum_flag: Value) -> Verdict {
|
|
1933
|
+
let len = unsafe { sn_len(value) };
|
|
1934
|
+
if len <= 16 {
|
|
1935
|
+
for i in 0..len {
|
|
1936
|
+
for previous in 0..i {
|
|
1937
|
+
match equal(
|
|
1938
|
+
unsafe { sn_entry(value, previous) },
|
|
1939
|
+
unsafe { sn_entry(value, i) },
|
|
1940
|
+
depth,
|
|
1941
|
+
) {
|
|
1942
|
+
Valid => return Invalid,
|
|
1943
|
+
Unsupported => return Unsupported,
|
|
1944
|
+
Invalid => {}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return Valid;
|
|
1949
|
+
}
|
|
1950
|
+
let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
|
|
1951
|
+
// This is a checked optimization hint, not an instance shape assumption.
|
|
1952
|
+
let big_hint = unsafe { sn_is_bignum(values[0]) } != 0;
|
|
1953
|
+
if big_hint
|
|
1954
|
+
&& depth <= MAX_EVALUATION_DEPTH
|
|
1955
|
+
&& let Some(result) = number::ordered_big_unique(values)
|
|
1956
|
+
{
|
|
1957
|
+
return result;
|
|
1958
|
+
}
|
|
1959
|
+
if big_hint && depth <= MAX_EVALUATION_DEPTH {
|
|
1960
|
+
let result = unordered_big_unique(values);
|
|
1961
|
+
if result != Unsupported {
|
|
1962
|
+
return result;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
indexed_unique(
|
|
1966
|
+
values,
|
|
1967
|
+
|item| fingerprint(item, depth, fixnum_flag, big_hint),
|
|
1968
|
+
|a, b| equal(a, b, depth),
|
|
1969
|
+
)
|
|
1970
|
+
}
|
|
1971
|
+
// Keep Big-only sampling and retry state out of the common scalar path.
|
|
1972
|
+
#[inline(never)]
|
|
1973
|
+
fn unordered_big_unique(values: &[Value]) -> Verdict {
|
|
1974
|
+
let mut all_big = true;
|
|
1975
|
+
let result = indexed_unique_short(
|
|
1976
|
+
values,
|
|
1977
|
+
|item| {
|
|
1978
|
+
let hash = number::big_low_fingerprint(item);
|
|
1979
|
+
if hash.is_none() {
|
|
1980
|
+
all_big = false;
|
|
1981
|
+
}
|
|
1982
|
+
hash
|
|
1983
|
+
},
|
|
1984
|
+
|a, b| {
|
|
1985
|
+
// Distinct integers sharing the short sample need the wider pass.
|
|
1986
|
+
if unsafe { sn_big_equal(a, b) } != 0 {
|
|
1987
|
+
Valid
|
|
1988
|
+
} else {
|
|
1989
|
+
Unsupported
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1992
|
+
);
|
|
1993
|
+
if result != Unsupported {
|
|
1994
|
+
return result;
|
|
1995
|
+
}
|
|
1996
|
+
if all_big {
|
|
1997
|
+
let result = indexed_unique_impl::<true>(values, number::big_low64_fingerprint, |a, b| {
|
|
1998
|
+
verdict(unsafe { sn_big_equal(a, b) } != 0)
|
|
1999
|
+
});
|
|
2000
|
+
if result != Unsupported {
|
|
2001
|
+
return result;
|
|
2002
|
+
}
|
|
2003
|
+
let result = indexed_unique(values, number::big_only_fingerprint, |a, b| {
|
|
2004
|
+
verdict(unsafe { sn_big_equal(a, b) } != 0)
|
|
2005
|
+
});
|
|
2006
|
+
if result != Unsupported {
|
|
2007
|
+
return result;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
// A non-Bignum invalidates both Big-only hash conventions. Restart
|
|
2011
|
+
// from the original array so equal Integer/Float pairs share a bucket.
|
|
2012
|
+
Unsupported
|
|
2013
|
+
}
|
|
2014
|
+
fn indexed_unique(
|
|
2015
|
+
values: &[Value],
|
|
2016
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2017
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2018
|
+
) -> Verdict {
|
|
2019
|
+
indexed_unique_impl::<false>(values, fingerprint, equal)
|
|
2020
|
+
}
|
|
2021
|
+
fn indexed_unique_short(
|
|
2022
|
+
values: &[Value],
|
|
2023
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2024
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2025
|
+
) -> Verdict {
|
|
2026
|
+
// Index zero means empty. Choose a larger slot beyond the 16-bit limit
|
|
2027
|
+
// so the short sample remains available without truncating an index.
|
|
2028
|
+
if values.len() > u32::MAX as usize {
|
|
2029
|
+
return Unsupported;
|
|
2030
|
+
}
|
|
2031
|
+
let Some(capacity) = values
|
|
2032
|
+
.len()
|
|
2033
|
+
.checked_mul(2)
|
|
2034
|
+
.and_then(usize::checked_next_power_of_two)
|
|
2035
|
+
else {
|
|
2036
|
+
return Unsupported;
|
|
2037
|
+
};
|
|
2038
|
+
if values.len() <= u16::MAX as usize {
|
|
2039
|
+
indexed_unique_slots::<ShortUniqueSlot, true>(values, capacity, fingerprint, equal)
|
|
2040
|
+
} else {
|
|
2041
|
+
indexed_unique_slots::<ShortWideUniqueSlot, true>(values, capacity, fingerprint, equal)
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
fn indexed_unique_impl<const BOUNDED: bool>(
|
|
2045
|
+
values: &[Value],
|
|
2046
|
+
fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2047
|
+
equal: impl FnMut(Value, Value) -> Verdict,
|
|
2048
|
+
) -> Verdict {
|
|
2049
|
+
let Some(capacity) = values
|
|
2050
|
+
.len()
|
|
2051
|
+
.checked_mul(2)
|
|
2052
|
+
.and_then(usize::checked_next_power_of_two)
|
|
2053
|
+
else {
|
|
2054
|
+
return Unsupported;
|
|
2055
|
+
};
|
|
2056
|
+
if values.len() <= u16::MAX as usize {
|
|
2057
|
+
indexed_unique_slots::<PackedUniqueSlot, BOUNDED>(values, capacity, fingerprint, equal)
|
|
2058
|
+
} else {
|
|
2059
|
+
indexed_unique_slots::<FullUniqueSlot, BOUNDED>(values, capacity, fingerprint, equal)
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
trait UniqueSlot: Copy {
|
|
2063
|
+
const EMPTY: Self;
|
|
2064
|
+
fn new(hash: u64, index: usize) -> Self;
|
|
2065
|
+
fn index(self) -> Option<usize>;
|
|
2066
|
+
fn matches(self, hash: u64) -> bool;
|
|
2067
|
+
}
|
|
2068
|
+
#[derive(Clone, Copy)]
|
|
2069
|
+
struct ShortUniqueSlot(u32);
|
|
2070
|
+
impl UniqueSlot for ShortUniqueSlot {
|
|
2071
|
+
const EMPTY: Self = Self(0);
|
|
2072
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2073
|
+
debug_assert!(index < u16::MAX as usize);
|
|
2074
|
+
Self((hash as u32 & !0xffff) | (index + 1) as u32)
|
|
2075
|
+
}
|
|
2076
|
+
fn index(self) -> Option<usize> {
|
|
2077
|
+
let index = self.0 & 0xffff;
|
|
2078
|
+
(index != 0).then(|| index as usize - 1)
|
|
2079
|
+
}
|
|
2080
|
+
fn matches(self, hash: u64) -> bool {
|
|
2081
|
+
// Truncated filters only select an exact comparison or wider retry.
|
|
2082
|
+
self.0 & !0xffff == hash as u32 & !0xffff
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
#[derive(Clone, Copy)]
|
|
2086
|
+
struct ShortWideUniqueSlot(u64);
|
|
2087
|
+
impl UniqueSlot for ShortWideUniqueSlot {
|
|
2088
|
+
const EMPTY: Self = Self(0);
|
|
2089
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2090
|
+
debug_assert!(index < u32::MAX as usize);
|
|
2091
|
+
Self((u64::from(hash as u32) << 32) | (index + 1) as u64)
|
|
2092
|
+
}
|
|
2093
|
+
fn index(self) -> Option<usize> {
|
|
2094
|
+
let index = self.0 as u32;
|
|
2095
|
+
(index != 0).then(|| index as usize - 1)
|
|
2096
|
+
}
|
|
2097
|
+
fn matches(self, hash: u64) -> bool {
|
|
2098
|
+
self.0 >> 32 == u64::from(hash as u32)
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
#[derive(Clone, Copy)]
|
|
2102
|
+
struct PackedUniqueSlot(u64);
|
|
2103
|
+
impl UniqueSlot for PackedUniqueSlot {
|
|
2104
|
+
const EMPTY: Self = Self(0);
|
|
2105
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2106
|
+
// Selected only for at most 65535 entries. Zero is the empty marker;
|
|
2107
|
+
// the remaining 48 hash bits only filter exact JSON comparisons.
|
|
2108
|
+
debug_assert!(index < u16::MAX as usize);
|
|
2109
|
+
Self((hash & !0xffff) | (index + 1) as u64)
|
|
2110
|
+
}
|
|
2111
|
+
fn index(self) -> Option<usize> {
|
|
2112
|
+
let index = self.0 & 0xffff;
|
|
2113
|
+
(index != 0).then(|| index as usize - 1)
|
|
2114
|
+
}
|
|
2115
|
+
fn matches(self, hash: u64) -> bool {
|
|
2116
|
+
self.0 & !0xffff == hash & !0xffff
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
#[derive(Clone, Copy)]
|
|
2120
|
+
struct FullUniqueSlot(u64, usize);
|
|
2121
|
+
impl UniqueSlot for FullUniqueSlot {
|
|
2122
|
+
const EMPTY: Self = Self(0, usize::MAX);
|
|
2123
|
+
fn new(hash: u64, index: usize) -> Self {
|
|
2124
|
+
Self(hash, index)
|
|
2125
|
+
}
|
|
2126
|
+
fn index(self) -> Option<usize> {
|
|
2127
|
+
(self.1 != usize::MAX).then_some(self.1)
|
|
2128
|
+
}
|
|
2129
|
+
fn matches(self, hash: u64) -> bool {
|
|
2130
|
+
self.0 == hash
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
fn indexed_unique_slots<S: UniqueSlot, const BOUNDED: bool>(
|
|
2134
|
+
values: &[Value],
|
|
2135
|
+
capacity: usize,
|
|
2136
|
+
mut fingerprint: impl FnMut(Value) -> Option<u64>,
|
|
2137
|
+
mut equal: impl FnMut(Value, Value) -> Verdict,
|
|
2138
|
+
) -> Verdict {
|
|
2139
|
+
// Keep at least half the slots empty, so every probe terminates. Slots
|
|
2140
|
+
// retain only a fingerprint and an index into the original Ruby array.
|
|
2141
|
+
// Equal fingerprints continue probing after exact inequality; neither
|
|
2142
|
+
// table-position collisions nor fingerprint collisions imply duplicates.
|
|
2143
|
+
let Some(mut slots) = allocate_unique_slots::<S>(capacity) else {
|
|
2144
|
+
return Unsupported;
|
|
2145
|
+
};
|
|
2146
|
+
let mask = capacity - 1;
|
|
2147
|
+
let mut collisions = 0;
|
|
2148
|
+
let mut probes = 0;
|
|
2149
|
+
for (i, &item) in values.iter().enumerate() {
|
|
2150
|
+
let Some(hash) = fingerprint(item) else {
|
|
2151
|
+
return Unsupported;
|
|
2152
|
+
};
|
|
2153
|
+
let mut at = hash as usize & mask;
|
|
2154
|
+
loop {
|
|
2155
|
+
let slot = slots[at];
|
|
2156
|
+
let Some(previous) = slot.index() else {
|
|
2157
|
+
slots[at] = S::new(hash, i);
|
|
2158
|
+
break;
|
|
2159
|
+
};
|
|
2160
|
+
if BOUNDED {
|
|
2161
|
+
// Bound total bucket probing as well as equal fingerprints.
|
|
2162
|
+
// A poor sampled-word distribution restarts with full hashes.
|
|
2163
|
+
probes += 1;
|
|
2164
|
+
if probes > values.len().saturating_mul(2) {
|
|
2165
|
+
return Unsupported;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
if slot.matches(hash) {
|
|
2169
|
+
match equal(values[previous], item) {
|
|
2170
|
+
Valid => return Invalid,
|
|
2171
|
+
Unsupported => return Unsupported,
|
|
2172
|
+
Invalid => {
|
|
2173
|
+
if BOUNDED {
|
|
2174
|
+
collisions += 1;
|
|
2175
|
+
if collisions >= 16 {
|
|
2176
|
+
return Unsupported;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
at = (at + 1) & mask;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
Valid
|
|
2186
|
+
}
|
|
2187
|
+
fn allocate_unique_slots<S: UniqueSlot>(capacity: usize) -> Option<Vec<S>> {
|
|
2188
|
+
let bytes = capacity.checked_mul(std::mem::size_of::<S>())?;
|
|
2189
|
+
if bytes > MAX_UNIQUE_INDEX_BYTES {
|
|
2190
|
+
return None;
|
|
2191
|
+
}
|
|
2192
|
+
let mut slots = Vec::new();
|
|
2193
|
+
slots.try_reserve_exact(capacity).ok()?;
|
|
2194
|
+
slots.resize(capacity, S::EMPTY);
|
|
2195
|
+
Some(slots)
|
|
2196
|
+
}
|
|
2197
|
+
struct ObjectContext<'a> {
|
|
2198
|
+
engine: &'a Engine,
|
|
2199
|
+
rules: &'a Object,
|
|
2200
|
+
depth: usize,
|
|
2201
|
+
active: scope::Context<'a>,
|
|
2202
|
+
}
|
|
2203
|
+
/// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
|
|
2204
|
+
/// # Safety
|
|
2205
|
+
/// The context must be a live ObjectContext from this engine and all VALUEs must be live under the GVL.
|
|
2206
|
+
#[unsafe(no_mangle)]
|
|
2207
|
+
pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, value: Value) -> i32 {
|
|
2208
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2209
|
+
let c = unsafe { &*context.cast::<ObjectContext>() };
|
|
2210
|
+
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2211
|
+
return Unsupported as i32;
|
|
2212
|
+
}
|
|
2213
|
+
if let Some(child) = c.rules.names {
|
|
2214
|
+
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
2215
|
+
if result != Valid {
|
|
2216
|
+
return result as i32;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
let mut matched = c.rules.property_keys.contains(&tracking::JsonKey::new(key));
|
|
2220
|
+
for (pattern, child) in &c.rules.patterns {
|
|
2221
|
+
match pattern.eval(key) {
|
|
2222
|
+
Unsupported => return Unsupported as i32,
|
|
2223
|
+
Invalid => {}
|
|
2224
|
+
Valid => {
|
|
2225
|
+
matched = true;
|
|
2226
|
+
let result = c.engine.eval(*child, value, c.depth, c.active);
|
|
2227
|
+
if result != Valid {
|
|
2228
|
+
return result as i32;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if !matched && let Some(child) = c.rules.additional {
|
|
2234
|
+
return c.engine.eval(child, value, c.depth, c.active) as i32;
|
|
2235
|
+
}
|
|
2236
|
+
Valid as i32
|
|
2237
|
+
}))
|
|
2238
|
+
.unwrap_or(Unsupported as i32)
|
|
2239
|
+
}
|
|
2240
|
+
#[unsafe(no_mangle)]
|
|
2241
|
+
pub extern "C" fn sn_new() -> *mut Engine {
|
|
2242
|
+
Box::into_raw(Box::new(Engine {
|
|
2243
|
+
roots: Box::into_raw(Box::new(Roots::default())),
|
|
2244
|
+
fixnum_flag: unsafe { sn_fixnum_flag() },
|
|
2245
|
+
undefined: unsafe { sn_undefined() },
|
|
2246
|
+
..Engine::default()
|
|
2247
|
+
}))
|
|
2248
|
+
}
|
|
2249
|
+
/// # Safety
|
|
2250
|
+
/// Engine and error must be valid writable pointers; program must be a live VM Program under the GVL.
|
|
2251
|
+
#[unsafe(no_mangle)]
|
|
2252
|
+
pub unsafe extern "C" fn sn_compile(
|
|
2253
|
+
engine: *mut Engine,
|
|
2254
|
+
program: Value,
|
|
2255
|
+
compiler: Value,
|
|
2256
|
+
format: i32,
|
|
2257
|
+
content: i32,
|
|
2258
|
+
error: *mut Value,
|
|
2259
|
+
) -> i32 {
|
|
2260
|
+
match catch_unwind(AssertUnwindSafe(|| {
|
|
2261
|
+
let e = unsafe { &mut *engine };
|
|
2262
|
+
e.validate_format = format != 0;
|
|
2263
|
+
e.validate_content = content != 0;
|
|
2264
|
+
e.compiler = e.retain(compiler);
|
|
2265
|
+
e.root = e.compile(program)?;
|
|
2266
|
+
e.bind_dynamic_anchors()?;
|
|
2267
|
+
Ok::<_, Value>(())
|
|
2268
|
+
})) {
|
|
2269
|
+
Ok(Ok(())) => VERDICT_VALID,
|
|
2270
|
+
Ok(Err(value)) => {
|
|
2271
|
+
unsafe { *error = value };
|
|
2272
|
+
STATUS_EXCEPTION
|
|
2273
|
+
}
|
|
2274
|
+
Err(_) => STATUS_PANIC,
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
/// # Safety
|
|
2278
|
+
/// Engine must be initialized and value must be a live Ruby VALUE under the GVL.
|
|
2279
|
+
#[unsafe(no_mangle)]
|
|
2280
|
+
pub unsafe extern "C" fn sn_valid(engine: *const Engine, value: Value) -> i32 {
|
|
2281
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2282
|
+
let e = unsafe { &*engine };
|
|
2283
|
+
e.eval(e.root, value, 0, scope::Context::default()) as i32
|
|
2284
|
+
}))
|
|
2285
|
+
.unwrap_or(STATUS_PANIC)
|
|
2286
|
+
}
|
|
2287
|
+
/// # Safety
|
|
2288
|
+
/// Engine must be an owned pointer returned by sn_new, freed exactly once.
|
|
2289
|
+
#[unsafe(no_mangle)]
|
|
2290
|
+
pub unsafe extern "C" fn sn_free(engine: *mut Engine) {
|
|
2291
|
+
let engine = unsafe { Box::from_raw(engine) };
|
|
2292
|
+
drop(unsafe { Box::from_raw(engine.roots) });
|
|
2293
|
+
drop(engine);
|
|
2294
|
+
}
|
|
2295
|
+
/// # Safety
|
|
2296
|
+
/// Engine must be live and not exclusively borrowed by the compiler.
|
|
2297
|
+
#[unsafe(no_mangle)]
|
|
2298
|
+
pub unsafe extern "C" fn sn_roots(engine: *const Engine) -> *const Roots {
|
|
2299
|
+
unsafe { &*engine }.roots
|
|
2300
|
+
}
|
|
2301
|
+
/// # Safety
|
|
2302
|
+
/// Roots must be live; call synchronously from the owning Ruby GC mark callback.
|
|
2303
|
+
/// No mutable root borrow can be live across a Ruby allocation/callback.
|
|
2304
|
+
#[unsafe(no_mangle)]
|
|
2305
|
+
pub unsafe extern "C" fn sn_roots_mark(roots: *const Roots) {
|
|
2306
|
+
for &value in &unsafe { &*roots }.values {
|
|
2307
|
+
unsafe { sn_pin(value) };
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
/// # Safety
|
|
2311
|
+
/// Roots must be live with no mutable root borrow active.
|
|
2312
|
+
#[unsafe(no_mangle)]
|
|
2313
|
+
pub unsafe extern "C" fn sn_roots_size(roots: *const Roots) -> usize {
|
|
2314
|
+
std::mem::size_of::<Roots>()
|
|
2315
|
+
+ unsafe { &*roots }.values.capacity() * std::mem::size_of::<Value>()
|
|
2316
|
+
}
|
|
2317
|
+
#[unsafe(no_mangle)]
|
|
2318
|
+
pub extern "C" fn sn_engine_header_size() -> usize {
|
|
2319
|
+
std::mem::size_of::<Engine>()
|
|
2320
|
+
}
|
|
2321
|
+
/// # Safety
|
|
2322
|
+
/// Engine must be live and not exclusively borrowed by the compiler.
|
|
2323
|
+
#[unsafe(no_mangle)]
|
|
2324
|
+
pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
2325
|
+
let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
|
|
2326
|
+
let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
|
|
2327
|
+
std::mem::size_of::<Engine>()
|
|
2328
|
+
+ nodes.capacity() * std::mem::size_of::<Vec<Op>>()
|
|
2329
|
+
+ operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
|
|
2330
|
+
+ operands
|
|
2331
|
+
.iter()
|
|
2332
|
+
.map(|ops| ops.capacity() * std::mem::size_of::<(i32, Value)>())
|
|
2333
|
+
.sum::<usize>()
|
|
2334
|
+
+ nodes
|
|
2335
|
+
.iter()
|
|
2336
|
+
.map(|n| n.capacity() * std::mem::size_of::<Op>())
|
|
2337
|
+
.sum::<usize>()
|
|
2338
|
+
+ nodes
|
|
2339
|
+
.iter()
|
|
2340
|
+
.flatten()
|
|
2341
|
+
.map(|op| match op {
|
|
2342
|
+
Op::String(_, rules) => {
|
|
2343
|
+
std::mem::size_of::<StringRules>()
|
|
2344
|
+
+ rules
|
|
2345
|
+
.pattern
|
|
2346
|
+
.as_ref()
|
|
2347
|
+
.map_or(0, pattern::Pattern::memory_usage)
|
|
2348
|
+
}
|
|
2349
|
+
Op::Object(_, rules) => {
|
|
2350
|
+
rules.patterns.capacity() * std::mem::size_of::<(pattern::Pattern, usize)>()
|
|
2351
|
+
+ rules
|
|
2352
|
+
.patterns
|
|
2353
|
+
.iter()
|
|
2354
|
+
.map(|(pattern, _)| pattern.memory_usage())
|
|
2355
|
+
.sum::<usize>()
|
|
2356
|
+
}
|
|
2357
|
+
_ => 0,
|
|
2358
|
+
})
|
|
2359
|
+
.sum::<usize>()
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
#[cfg(test)]
|
|
2363
|
+
mod tests {
|
|
2364
|
+
use super::*;
|
|
2365
|
+
|
|
2366
|
+
#[test]
|
|
2367
|
+
fn bounds_unique_index_allocation() {
|
|
2368
|
+
assert_eq!(
|
|
2369
|
+
allocate_unique_slots::<FullUniqueSlot>(16).unwrap().len(),
|
|
2370
|
+
16
|
|
2371
|
+
);
|
|
2372
|
+
let over_limit = MAX_UNIQUE_INDEX_BYTES / std::mem::size_of::<FullUniqueSlot>() + 1;
|
|
2373
|
+
assert!(allocate_unique_slots::<FullUniqueSlot>(over_limit).is_none());
|
|
2374
|
+
assert!(allocate_unique_slots::<FullUniqueSlot>(usize::MAX).is_none());
|
|
2375
|
+
}
|
|
2376
|
+
}
|