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.
@@ -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
+ }