schemurai 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/ext/schemurai_native/Cargo.lock +55 -1
- data/ext/schemurai_native/Cargo.toml +4 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +74 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +126 -14
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/integer_scan.rs +132 -0
- data/ext/schemurai_native/src/lib.rs +917 -238
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +147 -109
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +25 -2
|
@@ -6,6 +6,7 @@ use std::ffi::{c_char, c_void};
|
|
|
6
6
|
use std::hash::{BuildHasherDefault, Hasher};
|
|
7
7
|
use std::panic::{AssertUnwindSafe, catch_unwind};
|
|
8
8
|
mod budget;
|
|
9
|
+
mod container;
|
|
9
10
|
mod content;
|
|
10
11
|
mod diagnostics;
|
|
11
12
|
mod enumeration;
|
|
@@ -13,10 +14,14 @@ mod evaluation;
|
|
|
13
14
|
mod format;
|
|
14
15
|
mod graph;
|
|
15
16
|
mod instance_graph;
|
|
17
|
+
mod integer_scan;
|
|
16
18
|
mod memo;
|
|
17
19
|
mod memo_scope;
|
|
18
20
|
mod number;
|
|
19
21
|
mod pattern;
|
|
22
|
+
mod pointer;
|
|
23
|
+
mod runtime;
|
|
24
|
+
mod string;
|
|
20
25
|
// Some shared constants are consumed only by C or Ruby.
|
|
21
26
|
#[allow(dead_code)]
|
|
22
27
|
mod protocol {
|
|
@@ -41,6 +46,7 @@ unsafe extern "C" {
|
|
|
41
46
|
) -> i32;
|
|
42
47
|
fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
|
|
43
48
|
fn sn_true(value: Value) -> i32;
|
|
49
|
+
fn sn_truthy(value: Value) -> i32;
|
|
44
50
|
fn sn_fixnum(value: Value) -> i32;
|
|
45
51
|
fn sn_fixnum_flag() -> Value;
|
|
46
52
|
fn sn_array_data(value: Value) -> *const Value;
|
|
@@ -55,6 +61,8 @@ unsafe extern "C" {
|
|
|
55
61
|
required: i32,
|
|
56
62
|
) -> i32;
|
|
57
63
|
fn sn_integer1024(value: Value, words: *mut u64) -> i32;
|
|
64
|
+
fn sn_integer_word_count(value: Value) -> usize;
|
|
65
|
+
fn sn_integer_words(value: Value, words: *mut u64, count: usize) -> i32;
|
|
58
66
|
fn sn_is_bignum(value: Value) -> i32;
|
|
59
67
|
fn sn_big256(value: Value, words: *mut u64) -> i32;
|
|
60
68
|
fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
|
|
@@ -115,13 +123,24 @@ enum Verdict {
|
|
|
115
123
|
}
|
|
116
124
|
use Verdict::*;
|
|
117
125
|
const MAX_EVALUATION_DEPTH: usize = 256;
|
|
126
|
+
// Control-flow instructions can forward an instance without examining its kind.
|
|
127
|
+
// No real classified Ruby value has this kind.
|
|
128
|
+
const TYPE_UNCHECKED: i32 = 0;
|
|
129
|
+
type InstanceKind = std::cell::Cell<i32>;
|
|
130
|
+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
131
|
+
enum KindDemand {
|
|
132
|
+
None,
|
|
133
|
+
Predicate,
|
|
134
|
+
Data,
|
|
135
|
+
}
|
|
118
136
|
const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
|
|
119
137
|
#[derive(Debug)]
|
|
120
138
|
enum Op {
|
|
121
139
|
Unsupported,
|
|
140
|
+
UnresolvedRef(Value),
|
|
122
141
|
Boolean(bool),
|
|
123
142
|
Ref(scope::Reference),
|
|
124
|
-
Type(i32),
|
|
143
|
+
Type(i32, bool),
|
|
125
144
|
Const(Value),
|
|
126
145
|
Enum(Box<enumeration::Enumeration>),
|
|
127
146
|
All(Vec<usize>),
|
|
@@ -136,6 +155,7 @@ enum Op {
|
|
|
136
155
|
}
|
|
137
156
|
#[derive(Debug)]
|
|
138
157
|
struct Array {
|
|
158
|
+
header: container::Header,
|
|
139
159
|
min: Option<usize>,
|
|
140
160
|
max: Option<usize>,
|
|
141
161
|
unique: bool,
|
|
@@ -146,10 +166,14 @@ struct Array {
|
|
|
146
166
|
contains: Option<usize>,
|
|
147
167
|
min_contains: usize,
|
|
148
168
|
max_contains: Option<usize>,
|
|
169
|
+
count_contains: bool,
|
|
149
170
|
unevaluated: Option<usize>,
|
|
150
171
|
}
|
|
151
172
|
#[derive(Debug)]
|
|
152
173
|
struct StringRules {
|
|
174
|
+
source: Value,
|
|
175
|
+
runtime_flags: i32,
|
|
176
|
+
runtime_format: bool,
|
|
153
177
|
min: Option<usize>,
|
|
154
178
|
max: Option<usize>,
|
|
155
179
|
pattern: Option<pattern::Pattern>,
|
|
@@ -196,15 +220,31 @@ impl StringRules {
|
|
|
196
220
|
&& self.format.is_none()
|
|
197
221
|
&& self.content.is_none()
|
|
198
222
|
}
|
|
199
|
-
fn eval_length_guarded(
|
|
200
|
-
|
|
223
|
+
fn eval_length_guarded(
|
|
224
|
+
&self,
|
|
225
|
+
caches: &runtime::Caches,
|
|
226
|
+
required: i32,
|
|
227
|
+
value: Value,
|
|
228
|
+
) -> Verdict {
|
|
229
|
+
if caches.dynamic_calls() {
|
|
230
|
+
return self.runtime_valid(caches, required, value);
|
|
231
|
+
}
|
|
232
|
+
let result = length_guarded(value, required, self.min, self.max);
|
|
233
|
+
if result == Unsupported {
|
|
234
|
+
self.runtime_valid(caches, required, value)
|
|
235
|
+
} else {
|
|
236
|
+
result
|
|
237
|
+
}
|
|
201
238
|
}
|
|
202
|
-
fn eval_guarded(&self, caches: &
|
|
239
|
+
fn eval_guarded(&self, caches: &runtime::Caches, required: i32, value: Value) -> Verdict {
|
|
240
|
+
if caches.dynamic_calls() {
|
|
241
|
+
return self.runtime_valid(caches, required, value);
|
|
242
|
+
}
|
|
203
243
|
let mut data = std::ptr::null();
|
|
204
244
|
let mut len = 0;
|
|
205
245
|
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
206
246
|
if encoding == -TYPE_UNSUPPORTED {
|
|
207
|
-
|
|
247
|
+
self.runtime_valid(caches, required, value)
|
|
208
248
|
} else if encoding < 0 {
|
|
209
249
|
verdict(required == 0)
|
|
210
250
|
} else {
|
|
@@ -212,12 +252,12 @@ impl StringRules {
|
|
|
212
252
|
self.eval_with_view::<true>(caches, value, Some((bytes, encoding)))
|
|
213
253
|
}
|
|
214
254
|
}
|
|
215
|
-
fn eval(&self, caches: &
|
|
255
|
+
fn eval(&self, caches: &runtime::Caches, value: Value) -> Verdict {
|
|
216
256
|
self.eval_with_view::<false>(caches, value, None)
|
|
217
257
|
}
|
|
218
258
|
fn eval_with_view<const BORROWED: bool>(
|
|
219
259
|
&self,
|
|
220
|
-
caches: &
|
|
260
|
+
caches: &runtime::Caches,
|
|
221
261
|
value: Value,
|
|
222
262
|
view: Option<(&[u8], i32)>,
|
|
223
263
|
) -> Verdict {
|
|
@@ -231,8 +271,8 @@ impl StringRules {
|
|
|
231
271
|
}
|
|
232
272
|
if let Some(pattern) = &self.pattern {
|
|
233
273
|
let result = match view {
|
|
234
|
-
Some((bytes, encoding)) => pattern.eval_bytes(caches, bytes, encoding),
|
|
235
|
-
None => pattern.
|
|
274
|
+
Some((bytes, encoding)) => pattern.eval_bytes(caches, value, bytes, encoding),
|
|
275
|
+
None => pattern.assertion(caches, value),
|
|
236
276
|
};
|
|
237
277
|
if result != Valid {
|
|
238
278
|
return result;
|
|
@@ -248,16 +288,41 @@ impl StringRules {
|
|
|
248
288
|
}
|
|
249
289
|
}
|
|
250
290
|
self.content.as_ref().map_or(Valid, |content| match view {
|
|
251
|
-
Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
|
|
252
|
-
None => content.eval(value),
|
|
291
|
+
Some((bytes, encoding)) => content.eval_borrowed(caches, value, bytes, encoding),
|
|
292
|
+
None => content.eval(caches, value),
|
|
253
293
|
})
|
|
254
294
|
}
|
|
255
295
|
}
|
|
256
296
|
#[derive(Debug)]
|
|
257
297
|
enum DiagnosticDependency {
|
|
258
|
-
Required(Value, Vec<
|
|
298
|
+
Required(Value, Vec<RequiredKey>, &'static str),
|
|
259
299
|
Schema(Value, usize, &'static str),
|
|
260
300
|
}
|
|
301
|
+
// Keep ordinary String lookups native; other operands may dispatch Ruby hash
|
|
302
|
+
// methods. Retain the original value for runtime lookup and ordered rendering.
|
|
303
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
304
|
+
struct RequiredKey {
|
|
305
|
+
name: Value,
|
|
306
|
+
ordinary_string: bool,
|
|
307
|
+
}
|
|
308
|
+
impl RequiredKey {
|
|
309
|
+
fn missing(
|
|
310
|
+
self,
|
|
311
|
+
caches: &runtime::Caches,
|
|
312
|
+
value: Value,
|
|
313
|
+
undefined: Value,
|
|
314
|
+
) -> Result<bool, Verdict> {
|
|
315
|
+
if self.ordinary_string {
|
|
316
|
+
Ok(missing(value, self.name, undefined))
|
|
317
|
+
} else {
|
|
318
|
+
match caches.required_missing(self.name, value) {
|
|
319
|
+
Valid => Ok(true),
|
|
320
|
+
Invalid => Ok(false),
|
|
321
|
+
Unsupported => Err(Unsupported),
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
261
326
|
#[derive(Clone, Copy, Debug)]
|
|
262
327
|
struct PropertyTarget {
|
|
263
328
|
name: Value,
|
|
@@ -266,11 +331,13 @@ struct PropertyTarget {
|
|
|
266
331
|
}
|
|
267
332
|
#[derive(Debug)]
|
|
268
333
|
struct Object {
|
|
334
|
+
header: container::Header,
|
|
335
|
+
inspect_required: bool,
|
|
269
336
|
record: Option<RecordObject>,
|
|
270
337
|
min: Option<usize>,
|
|
271
338
|
max: Option<usize>,
|
|
272
|
-
required: Vec<
|
|
273
|
-
required_order: Vec<
|
|
339
|
+
required: Vec<RequiredKey>,
|
|
340
|
+
required_order: Vec<RequiredKey>,
|
|
274
341
|
properties: Vec<(Value, usize)>,
|
|
275
342
|
property_targets: HashMap<tracking::JsonKey, PropertyTarget, BuildHasherDefault<BucketHasher>>,
|
|
276
343
|
patterns: Vec<(pattern::Pattern, usize)>,
|
|
@@ -282,7 +349,7 @@ struct Object {
|
|
|
282
349
|
property_indices: Vec<usize>,
|
|
283
350
|
additional: Option<usize>,
|
|
284
351
|
names: Option<usize>,
|
|
285
|
-
dependent_required: Vec<(Value, Vec<
|
|
352
|
+
dependent_required: Vec<(Value, Vec<RequiredKey>)>,
|
|
286
353
|
dependent_schemas: Vec<(Value, usize)>,
|
|
287
354
|
diagnostic_dependencies: Vec<DiagnosticDependency>,
|
|
288
355
|
unevaluated: Option<usize>,
|
|
@@ -351,13 +418,18 @@ impl RecordShortIndex {
|
|
|
351
418
|
impl Object {
|
|
352
419
|
// Keep these calls at their original evaluation positions: property
|
|
353
420
|
// failures and unsupported children must retain their ordering.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
421
|
+
fn check_required(
|
|
422
|
+
&self,
|
|
423
|
+
caches: &runtime::Caches,
|
|
424
|
+
value: Value,
|
|
425
|
+
undefined: Value,
|
|
426
|
+
budget: &budget::Budget,
|
|
427
|
+
) -> Verdict {
|
|
428
|
+
Self::check_keys(&self.required, caches, value, undefined, budget)
|
|
357
429
|
}
|
|
358
|
-
#[inline]
|
|
359
430
|
fn check_keys(
|
|
360
|
-
keys: &[
|
|
431
|
+
keys: &[RequiredKey],
|
|
432
|
+
caches: &runtime::Caches,
|
|
361
433
|
value: Value,
|
|
362
434
|
undefined: Value,
|
|
363
435
|
budget: &budget::Budget,
|
|
@@ -366,15 +438,17 @@ impl Object {
|
|
|
366
438
|
if !budget.checkpoint() {
|
|
367
439
|
return Unsupported;
|
|
368
440
|
}
|
|
369
|
-
|
|
370
|
-
|
|
441
|
+
match key.missing(caches, value, undefined) {
|
|
442
|
+
Ok(false) => {}
|
|
443
|
+
Ok(true) => return Invalid,
|
|
444
|
+
Err(status) => return status,
|
|
371
445
|
}
|
|
372
446
|
}
|
|
373
447
|
Valid
|
|
374
448
|
}
|
|
375
|
-
#[inline]
|
|
376
449
|
fn check_dependent_required(
|
|
377
450
|
&self,
|
|
451
|
+
caches: &runtime::Caches,
|
|
378
452
|
value: Value,
|
|
379
453
|
undefined: Value,
|
|
380
454
|
budget: &budget::Budget,
|
|
@@ -384,7 +458,7 @@ impl Object {
|
|
|
384
458
|
return Unsupported;
|
|
385
459
|
}
|
|
386
460
|
if !missing(value, *name, undefined) {
|
|
387
|
-
let status = Self::check_keys(required, value, undefined, budget);
|
|
461
|
+
let status = Self::check_keys(required, caches, value, undefined, budget);
|
|
388
462
|
if status != Valid {
|
|
389
463
|
return status;
|
|
390
464
|
}
|
|
@@ -481,10 +555,11 @@ impl Object {
|
|
|
481
555
|
// All execution policies use the same property/pattern/additional routing.
|
|
482
556
|
// Schema-order validity scans have already visited declared properties;
|
|
483
557
|
// input-order diagnostic scans visit them here as well.
|
|
484
|
-
fn visit_value_rules
|
|
558
|
+
fn visit_value_rules(
|
|
485
559
|
&self,
|
|
486
|
-
caches: &
|
|
560
|
+
caches: &runtime::Caches,
|
|
487
561
|
budget: &budget::Budget,
|
|
562
|
+
properties: bool,
|
|
488
563
|
key: Value,
|
|
489
564
|
location: Option<tracking::JsonKey>,
|
|
490
565
|
mut visit: impl FnMut(&'static str, Option<Value>, usize) -> Result<(), Verdict>,
|
|
@@ -493,7 +568,7 @@ impl Object {
|
|
|
493
568
|
// Reuse that result, and skip hashing for pattern-only object rules.
|
|
494
569
|
let mut matched = if self.properties.is_empty() {
|
|
495
570
|
false
|
|
496
|
-
} else if
|
|
571
|
+
} else if properties {
|
|
497
572
|
if let Some(target) = self
|
|
498
573
|
.property_targets
|
|
499
574
|
.get(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
|
|
@@ -548,6 +623,7 @@ impl Object {
|
|
|
548
623
|
|
|
549
624
|
fn with_record(mut self) -> Self {
|
|
550
625
|
if self.required_order.len() > 64
|
|
626
|
+
|| self.required_order.iter().any(|key| !key.ordinary_string)
|
|
551
627
|
|| !self.patterns.is_empty()
|
|
552
628
|
|| self.names.is_some()
|
|
553
629
|
|| !self.diagnostic_dependencies.is_empty()
|
|
@@ -572,7 +648,8 @@ impl Object {
|
|
|
572
648
|
)
|
|
573
649
|
})
|
|
574
650
|
.collect();
|
|
575
|
-
for
|
|
651
|
+
for key in &self.required_order {
|
|
652
|
+
let name = key.name;
|
|
576
653
|
fields
|
|
577
654
|
.entry(tracking::JsonKey::new(name))
|
|
578
655
|
.or_insert(RecordField {
|
|
@@ -592,8 +669,8 @@ impl Object {
|
|
|
592
669
|
}
|
|
593
670
|
// Required names share the field bits already used for duplicate-key
|
|
594
671
|
// detection. Repeated required names therefore need no extra state.
|
|
595
|
-
let required = self.required_order.iter().fold(0, |bits,
|
|
596
|
-
bits | fields[&tracking::JsonKey::new(name)].bit
|
|
672
|
+
let required = self.required_order.iter().fold(0, |bits, key| {
|
|
673
|
+
bits | fields[&tracking::JsonKey::new(key.name)].bit
|
|
597
674
|
});
|
|
598
675
|
let fields: Vec<_> = fields.into_values().collect();
|
|
599
676
|
let short_index = RecordShortIndex::compile(&fields);
|
|
@@ -614,9 +691,15 @@ pub struct Roots {
|
|
|
614
691
|
#[derive(Default)]
|
|
615
692
|
pub struct Engine {
|
|
616
693
|
nodes: Vec<Vec<Op>>,
|
|
694
|
+
programs: Vec<Value>,
|
|
617
695
|
pattern_cache_count: usize,
|
|
618
696
|
error_operands: Vec<Vec<(i32, Value)>>,
|
|
619
697
|
tracks: Vec<bool>,
|
|
698
|
+
kind_demand: Vec<KindDemand>,
|
|
699
|
+
callback_roots: Vec<bool>,
|
|
700
|
+
validity_supported: Vec<bool>,
|
|
701
|
+
report_supported: Vec<bool>,
|
|
702
|
+
has_references: bool,
|
|
620
703
|
memoizable: Vec<bool>,
|
|
621
704
|
memo_contextual: Vec<bool>,
|
|
622
705
|
memo_guarded: Vec<bool>,
|
|
@@ -637,6 +720,7 @@ pub struct Engine {
|
|
|
637
720
|
root: usize,
|
|
638
721
|
fixnum_flag: Value,
|
|
639
722
|
compiler: Value,
|
|
723
|
+
runtime_messages: bool,
|
|
640
724
|
validate_format: bool,
|
|
641
725
|
validate_content: bool,
|
|
642
726
|
undefined: Value,
|
|
@@ -656,6 +740,24 @@ impl Engine {
|
|
|
656
740
|
}
|
|
657
741
|
Ok(self.retain(result))
|
|
658
742
|
}
|
|
743
|
+
fn required_keys(&mut self, value: Value) -> Vec<RequiredKey> {
|
|
744
|
+
// Non-array operands retain their Ruby iteration/error behavior in a
|
|
745
|
+
// runtime frame; they cannot authorize borrowed Array storage.
|
|
746
|
+
if unsafe { sn_kind(value) } != TYPE_ARRAY {
|
|
747
|
+
return vec![];
|
|
748
|
+
}
|
|
749
|
+
self.values(value)
|
|
750
|
+
.into_iter()
|
|
751
|
+
.map(|name| {
|
|
752
|
+
let ordinary_string = unsafe { sn_kind(name) } == TYPE_STRING;
|
|
753
|
+
self.runtime_messages |= !ordinary_string;
|
|
754
|
+
RequiredKey {
|
|
755
|
+
name,
|
|
756
|
+
ordinary_string,
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
.collect()
|
|
760
|
+
}
|
|
659
761
|
fn values(&mut self, value: Value) -> Vec<Value> {
|
|
660
762
|
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
661
763
|
return vec![];
|
|
@@ -759,7 +861,7 @@ impl Engine {
|
|
|
759
861
|
// shape-independent leaves can ignore resource/path metadata.
|
|
760
862
|
ka == kb
|
|
761
863
|
&& match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
|
|
762
|
-
([Op::Type(a)], [Op::Type(b)]) => a == b,
|
|
864
|
+
([Op::Type(a, ua)], [Op::Type(b, ub)]) => a == b && ua == ub,
|
|
763
865
|
([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
|
|
764
866
|
_ => false,
|
|
765
867
|
}
|
|
@@ -779,8 +881,10 @@ impl Engine {
|
|
|
779
881
|
let index = self.nodes.len();
|
|
780
882
|
self.indices.insert(program, index);
|
|
781
883
|
self.nodes.push(vec![]);
|
|
884
|
+
self.programs.push(program);
|
|
782
885
|
self.error_operands.push(vec![]);
|
|
783
886
|
self.tracks.push(false);
|
|
887
|
+
self.kind_demand.push(KindDemand::None);
|
|
784
888
|
self.metadata.push(scope::Metadata::default());
|
|
785
889
|
let code = self.field(program, b"code\0")?;
|
|
786
890
|
let flags = unsafe { sn_integer(sn_entry(code, 0)) };
|
|
@@ -794,19 +898,20 @@ impl Engine {
|
|
|
794
898
|
error_operands.push((opcode, operand));
|
|
795
899
|
let op = match opcode {
|
|
796
900
|
OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
|
|
797
|
-
OP_TYPE_NULL => Op::Type(TYPE_NULL),
|
|
798
|
-
OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
|
|
799
|
-
OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
|
|
800
|
-
OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
|
|
801
|
-
OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
|
|
802
|
-
OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
|
|
803
|
-
OP_TYPE_STRING => Op::Type(TYPE_STRING),
|
|
901
|
+
OP_TYPE_NULL => Op::Type(TYPE_NULL, false),
|
|
902
|
+
OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN, false),
|
|
903
|
+
OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT, false),
|
|
904
|
+
OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY, false),
|
|
905
|
+
OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER, false),
|
|
906
|
+
OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER, false),
|
|
907
|
+
OP_TYPE_STRING => Op::Type(TYPE_STRING, false),
|
|
804
908
|
OP_TYPES => {
|
|
805
909
|
let mask = self.field(operand, b"mask\0")?;
|
|
806
|
-
Op::Type(unsafe { sn_integer(mask) } as i32)
|
|
910
|
+
Op::Type(unsafe { sn_integer(mask) } as i32, true)
|
|
807
911
|
}
|
|
808
912
|
OP_CONST => Op::Const(operand),
|
|
809
913
|
OP_ENUM => Op::Enum(Box::new(enumeration::Enumeration::compile(
|
|
914
|
+
operand,
|
|
810
915
|
self.values(operand),
|
|
811
916
|
self.fixnum_flag,
|
|
812
917
|
))),
|
|
@@ -855,9 +960,10 @@ impl Engine {
|
|
|
855
960
|
None
|
|
856
961
|
};
|
|
857
962
|
let assertion = self.field(operand, b"format_assertion\0")?;
|
|
858
|
-
let
|
|
859
|
-
|
|
860
|
-
|
|
963
|
+
let runtime_format = (self.validate_format
|
|
964
|
+
|| unsafe { sn_true(assertion) } != 0)
|
|
965
|
+
&& unsafe { sn_same(format, sn_nil()) } == 0;
|
|
966
|
+
let format = if runtime_format {
|
|
861
967
|
let name = self.field(format, b"name\0")?;
|
|
862
968
|
if unsafe { sn_same(name, sn_nil()) } != 0 {
|
|
863
969
|
None
|
|
@@ -867,6 +973,10 @@ impl Engine {
|
|
|
867
973
|
} else {
|
|
868
974
|
None
|
|
869
975
|
};
|
|
976
|
+
let max_length = self.field(operand, b"max_length\0")?;
|
|
977
|
+
let min_length = self.field(operand, b"min_length\0")?;
|
|
978
|
+
let length_flags = (unsafe { sn_truthy(max_length) } << 2)
|
|
979
|
+
| (unsafe { sn_truthy(min_length) } << 3);
|
|
870
980
|
Op::String(
|
|
871
981
|
if opcode == OP_TYPED_STRING {
|
|
872
982
|
TYPE_STRING
|
|
@@ -874,11 +984,16 @@ impl Engine {
|
|
|
874
984
|
0
|
|
875
985
|
},
|
|
876
986
|
Box::new(StringRules {
|
|
987
|
+
source: operand,
|
|
988
|
+
runtime_flags: i32::from(self.validate_format)
|
|
989
|
+
| (i32::from(self.validate_content) << 1)
|
|
990
|
+
| length_flags,
|
|
991
|
+
runtime_format,
|
|
877
992
|
format,
|
|
878
993
|
content,
|
|
879
994
|
min: self.size_field(operand, b"min_length\0")?,
|
|
880
995
|
max: self.size_field(operand, b"max_length\0")?,
|
|
881
|
-
pattern: if unsafe {
|
|
996
|
+
pattern: if unsafe { sn_truthy(pattern) } == 0 {
|
|
882
997
|
None
|
|
883
998
|
} else {
|
|
884
999
|
Some(pattern::Pattern::compile(
|
|
@@ -911,7 +1026,18 @@ impl Engine {
|
|
|
911
1026
|
0
|
|
912
1027
|
},
|
|
913
1028
|
Box::new(Array {
|
|
914
|
-
|
|
1029
|
+
header: self.container_header(
|
|
1030
|
+
operand,
|
|
1031
|
+
b"max_items\0",
|
|
1032
|
+
b"min_items\0",
|
|
1033
|
+
false,
|
|
1034
|
+
(if unsafe { sn_truthy(unique) } != 0 {
|
|
1035
|
+
4
|
|
1036
|
+
} else {
|
|
1037
|
+
0
|
|
1038
|
+
}) | (if list { 8 } else { 0 }),
|
|
1039
|
+
)?,
|
|
1040
|
+
unique: unsafe { sn_truthy(unique) } != 0,
|
|
915
1041
|
min: self.size_field(operand, b"min_items\0")?,
|
|
916
1042
|
max: self.size_field(operand, b"max_items\0")?,
|
|
917
1043
|
prefix,
|
|
@@ -921,6 +1047,9 @@ impl Engine {
|
|
|
921
1047
|
contains: self.child(operand, b"contains\0")?,
|
|
922
1048
|
min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
|
|
923
1049
|
max_contains: self.size_field(operand, b"max_contains\0")?,
|
|
1050
|
+
count_contains: unsafe {
|
|
1051
|
+
sn_true(self.field(operand, b"count_contains\0")?)
|
|
1052
|
+
} != 0,
|
|
924
1053
|
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
925
1054
|
}),
|
|
926
1055
|
)
|
|
@@ -943,14 +1072,21 @@ impl Engine {
|
|
|
943
1072
|
let pattern_group =
|
|
944
1073
|
pattern::PatternGroup::compile(&pattern_names, &patterns);
|
|
945
1074
|
let required = self.field(operand, b"required\0")?;
|
|
946
|
-
let
|
|
1075
|
+
let inspect_required = unsafe { sn_truthy(required) } != 0
|
|
1076
|
+
&& !(unsafe { sn_kind(required) } == TYPE_ARRAY
|
|
1077
|
+
&& unsafe { sn_len(required) } == 0);
|
|
1078
|
+
let required = self.required_keys(required);
|
|
947
1079
|
let required_order = required.clone();
|
|
1080
|
+
let required_strings: Vec<_> = required
|
|
1081
|
+
.iter()
|
|
1082
|
+
.filter(|key| key.ordinary_string)
|
|
1083
|
+
.map(|key| key.name)
|
|
1084
|
+
.collect();
|
|
948
1085
|
let properties = self.field(operand, b"properties\0")?;
|
|
949
1086
|
let properties = self.compile_map(properties)?;
|
|
950
|
-
if
|
|
1087
|
+
if properties
|
|
951
1088
|
.iter()
|
|
952
|
-
.
|
|
953
|
-
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1089
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
954
1090
|
{
|
|
955
1091
|
ops.push(Op::Unsupported);
|
|
956
1092
|
continue;
|
|
@@ -982,7 +1118,7 @@ impl Engine {
|
|
|
982
1118
|
// content-keyed sets and the already compiled property index.
|
|
983
1119
|
let indexed_required = properties.len().saturating_mul(required.len()) > 64;
|
|
984
1120
|
let required_keys: Option<tracking::KeySet> = indexed_required.then(|| {
|
|
985
|
-
|
|
1121
|
+
required_strings
|
|
986
1122
|
.iter()
|
|
987
1123
|
.map(|&name| tracking::JsonKey::new(name))
|
|
988
1124
|
.collect()
|
|
@@ -992,7 +1128,7 @@ impl Engine {
|
|
|
992
1128
|
.map(|key| {
|
|
993
1129
|
required_keys.as_ref().map_or_else(
|
|
994
1130
|
|| {
|
|
995
|
-
|
|
1131
|
+
required_strings.iter().any(
|
|
996
1132
|
|&name| unsafe { sn_string_equal(key.0, name) } != 0,
|
|
997
1133
|
)
|
|
998
1134
|
},
|
|
@@ -1007,7 +1143,11 @@ impl Engine {
|
|
|
1007
1143
|
.collect();
|
|
1008
1144
|
let required = required
|
|
1009
1145
|
.into_iter()
|
|
1010
|
-
.filter(
|
|
1146
|
+
.filter(|key| {
|
|
1147
|
+
if !key.ordinary_string {
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
let name = key.name;
|
|
1011
1151
|
if indexed_required {
|
|
1012
1152
|
!property_targets.contains_key(&tracking::JsonKey::new(name))
|
|
1013
1153
|
} else {
|
|
@@ -1025,7 +1165,7 @@ impl Engine {
|
|
|
1025
1165
|
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1026
1166
|
let dependency = self.retain(unsafe { sn_entry(pair, 1) });
|
|
1027
1167
|
if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
|
|
1028
|
-
let names = self.
|
|
1168
|
+
let names = self.required_keys(dependency);
|
|
1029
1169
|
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1030
1170
|
name,
|
|
1031
1171
|
names.clone(),
|
|
@@ -1044,7 +1184,7 @@ impl Engine {
|
|
|
1044
1184
|
}
|
|
1045
1185
|
for pair in self.values(dependent) {
|
|
1046
1186
|
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1047
|
-
let names = self.
|
|
1187
|
+
let names = self.required_keys(unsafe { sn_entry(pair, 1) });
|
|
1048
1188
|
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1049
1189
|
name,
|
|
1050
1190
|
names.clone(),
|
|
@@ -1058,19 +1198,25 @@ impl Engine {
|
|
|
1058
1198
|
DiagnosticDependency::Schema(name, child, "dependentSchemas")
|
|
1059
1199
|
}));
|
|
1060
1200
|
dependent_schemas.extend(schemas);
|
|
1061
|
-
if dependent_required
|
|
1062
|
-
(unsafe { sn_kind(*name) }) != TYPE_STRING
|
|
1063
|
-
|| keys
|
|
1064
|
-
.iter()
|
|
1065
|
-
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1066
|
-
}) || dependent_schemas
|
|
1201
|
+
if dependent_required
|
|
1067
1202
|
.iter()
|
|
1068
|
-
.any(
|
|
1203
|
+
.any(|(name, _)| unsafe { sn_kind(*name) } != TYPE_STRING)
|
|
1204
|
+
|| dependent_schemas
|
|
1205
|
+
.iter()
|
|
1206
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1069
1207
|
{
|
|
1070
1208
|
ops.push(Op::Unsupported);
|
|
1071
1209
|
continue;
|
|
1072
1210
|
}
|
|
1073
1211
|
let mut object = Object {
|
|
1212
|
+
header: self.container_header(
|
|
1213
|
+
operand,
|
|
1214
|
+
b"max_properties\0",
|
|
1215
|
+
b"min_properties\0",
|
|
1216
|
+
true,
|
|
1217
|
+
0,
|
|
1218
|
+
)?,
|
|
1219
|
+
inspect_required,
|
|
1074
1220
|
record: None,
|
|
1075
1221
|
min: self.size_field(operand, b"min_properties\0")?,
|
|
1076
1222
|
max: self.size_field(operand, b"max_properties\0")?,
|
|
@@ -1147,7 +1293,8 @@ impl Engine {
|
|
|
1147
1293
|
let mut target = 0;
|
|
1148
1294
|
if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
|
|
1149
1295
|
if unsafe { sn_resolution_error(target) } != 0 {
|
|
1150
|
-
|
|
1296
|
+
let message = self.field(target, b"message\0")?;
|
|
1297
|
+
Op::UnresolvedRef(message)
|
|
1151
1298
|
} else {
|
|
1152
1299
|
return Err(target);
|
|
1153
1300
|
}
|
|
@@ -1165,12 +1312,53 @@ impl Engine {
|
|
|
1165
1312
|
self.error_operands[index] = error_operands;
|
|
1166
1313
|
Ok(index)
|
|
1167
1314
|
}
|
|
1315
|
+
fn compile_kind_demands(&mut self) {
|
|
1316
|
+
self.kind_demand = self
|
|
1317
|
+
.nodes
|
|
1318
|
+
.iter()
|
|
1319
|
+
.map(|ops| self.kind_demand_for(ops))
|
|
1320
|
+
.collect();
|
|
1321
|
+
}
|
|
1322
|
+
fn kind_demand_for(&self, ops: &[Op]) -> KindDemand {
|
|
1323
|
+
ops.iter()
|
|
1324
|
+
.map(|op| match op {
|
|
1325
|
+
Op::Type(..) | Op::Number(..) | Op::String(..) | Op::Const(_) | Op::Enum(_) => {
|
|
1326
|
+
KindDemand::Predicate
|
|
1327
|
+
}
|
|
1328
|
+
Op::Array(_, rules)
|
|
1329
|
+
if rules.header_only()
|
|
1330
|
+
|| self.array_callbacks(rules)
|
|
1331
|
+
|| self.array_report_callbacks(rules) =>
|
|
1332
|
+
{
|
|
1333
|
+
KindDemand::Predicate
|
|
1334
|
+
}
|
|
1335
|
+
Op::Object(_, rules)
|
|
1336
|
+
if rules.header_only()
|
|
1337
|
+
|| self.object_callbacks(rules)
|
|
1338
|
+
|| self.object_report_callbacks(rules) =>
|
|
1339
|
+
{
|
|
1340
|
+
KindDemand::Predicate
|
|
1341
|
+
}
|
|
1342
|
+
Op::Array(..) | Op::Object(..) => KindDemand::Data,
|
|
1343
|
+
Op::Unsupported
|
|
1344
|
+
| Op::Boolean(_)
|
|
1345
|
+
| Op::UnresolvedRef(_)
|
|
1346
|
+
| Op::Ref(_)
|
|
1347
|
+
| Op::All(_)
|
|
1348
|
+
| Op::Any(_)
|
|
1349
|
+
| Op::One(_)
|
|
1350
|
+
| Op::Not(_)
|
|
1351
|
+
| Op::Conditional(..) => KindDemand::None,
|
|
1352
|
+
})
|
|
1353
|
+
.max()
|
|
1354
|
+
.unwrap_or(KindDemand::None)
|
|
1355
|
+
}
|
|
1168
1356
|
}
|
|
1169
1357
|
|
|
1170
|
-
// A view of immutable compiled data and validator-owned
|
|
1358
|
+
// A view of immutable compiled data and validator-owned runtime caches.
|
|
1171
1359
|
struct Execution<'a> {
|
|
1172
1360
|
engine: &'a Engine,
|
|
1173
|
-
caches: &'a
|
|
1361
|
+
caches: &'a runtime::Caches,
|
|
1174
1362
|
memo: memo::Memo,
|
|
1175
1363
|
budget: budget::Budget,
|
|
1176
1364
|
}
|
|
@@ -1192,9 +1380,14 @@ impl Execution<'_> {
|
|
|
1192
1380
|
value: Value,
|
|
1193
1381
|
depth: usize,
|
|
1194
1382
|
active: scope::Context<'_>,
|
|
1195
|
-
known_kind: Option
|
|
1383
|
+
known_kind: Option<&InstanceKind>,
|
|
1196
1384
|
) -> Verdict {
|
|
1197
|
-
|
|
1385
|
+
// Suspend before entering the next node. C runs a fresh native stack
|
|
1386
|
+
// only after these frames and their borrowed views have unwound.
|
|
1387
|
+
if depth >= MAX_EVALUATION_DEPTH && self.caches.continuations_allowed() {
|
|
1388
|
+
return self.caches.native_child(node, value);
|
|
1389
|
+
}
|
|
1390
|
+
if self.caches.callback_checkpoint(value) != Valid || !self.budget.checkpoint() {
|
|
1198
1391
|
return Unsupported;
|
|
1199
1392
|
}
|
|
1200
1393
|
let Some(key) = self
|
|
@@ -1222,7 +1415,7 @@ impl Execution<'_> {
|
|
|
1222
1415
|
value: Value,
|
|
1223
1416
|
depth: usize,
|
|
1224
1417
|
active: scope::Context<'_>,
|
|
1225
|
-
known_kind: Option
|
|
1418
|
+
known_kind: Option<&InstanceKind>,
|
|
1226
1419
|
) -> Verdict {
|
|
1227
1420
|
let frame = scope::ScopeFrame {
|
|
1228
1421
|
resource: self.metadata[node].resource,
|
|
@@ -1233,7 +1426,13 @@ impl Execution<'_> {
|
|
|
1233
1426
|
.or(self.metadata[node].primary_binding),
|
|
1234
1427
|
};
|
|
1235
1428
|
let active = self.enter_scope(node, active, &frame);
|
|
1429
|
+
let _scope = self
|
|
1430
|
+
.caches
|
|
1431
|
+
.scope_guard(frame.resource, self.metadata[node].enters_scope);
|
|
1236
1432
|
if self.tracks[node] {
|
|
1433
|
+
if let Some(result) = self.caches.callback_tracked(node, value) {
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1237
1436
|
return match known_kind {
|
|
1238
1437
|
Some(kind) => {
|
|
1239
1438
|
self.eval_tracked_kind(node, value, depth, active, kind)
|
|
@@ -1247,26 +1446,42 @@ impl Execution<'_> {
|
|
|
1247
1446
|
}
|
|
1248
1447
|
if let [Op::String(required, rules)] = self.nodes[node].as_slice()
|
|
1249
1448
|
&& rules.pattern.is_some()
|
|
1449
|
+
&& !self.caches.scalar_callback()
|
|
1250
1450
|
{
|
|
1251
1451
|
return rules.eval_guarded(self.caches, *required, value);
|
|
1252
1452
|
}
|
|
1253
|
-
let
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1453
|
+
let local_kind = InstanceKind::new(TYPE_UNCHECKED);
|
|
1454
|
+
let kind_state = known_kind.unwrap_or(&local_kind);
|
|
1455
|
+
let kind = if known_kind.is_some() {
|
|
1456
|
+
// A proof shared with sibling branches must guard every Hash key.
|
|
1457
|
+
self.diagnostic_kind(node, value, kind_state)
|
|
1458
|
+
} else {
|
|
1459
|
+
let kind = self.instance_kind(node, value);
|
|
1460
|
+
kind_state.set(kind);
|
|
1461
|
+
kind
|
|
1462
|
+
};
|
|
1463
|
+
if self.unsupported_kind(node, kind) {
|
|
1261
1464
|
return Unsupported;
|
|
1262
1465
|
}
|
|
1263
1466
|
for op in &self.nodes[node] {
|
|
1467
|
+
// Runtime observations checkpoint each reached opcode once, after
|
|
1468
|
+
// earlier assertions and descendant calls have completed.
|
|
1469
|
+
if self.caches.callback_checkpoint(value) != Valid {
|
|
1470
|
+
return Unsupported;
|
|
1471
|
+
}
|
|
1264
1472
|
if !self.budget.checkpoint() {
|
|
1265
1473
|
return Unsupported;
|
|
1266
1474
|
}
|
|
1267
1475
|
let outcome = match op {
|
|
1268
1476
|
Op::Ref(target) => {
|
|
1269
|
-
|
|
1477
|
+
let target = match self.resolve_reference(*target, value, active) {
|
|
1478
|
+
Ok(target) => target,
|
|
1479
|
+
Err(status) => return status,
|
|
1480
|
+
};
|
|
1481
|
+
let program = self.programs[node];
|
|
1482
|
+
if RefFrame::contains(active.active, node, value)
|
|
1483
|
+
|| self.caches.reference_active(program, value)
|
|
1484
|
+
{
|
|
1270
1485
|
Valid
|
|
1271
1486
|
} else {
|
|
1272
1487
|
let frame = RefFrame {
|
|
@@ -1274,15 +1489,17 @@ impl Execution<'_> {
|
|
|
1274
1489
|
value,
|
|
1275
1490
|
parent: active.active,
|
|
1276
1491
|
};
|
|
1492
|
+
let reference = self.caches.reference_frame(program, value);
|
|
1493
|
+
let _guard = self.caches.reference_guard(&reference);
|
|
1277
1494
|
self.eval_with_kind(
|
|
1278
|
-
|
|
1495
|
+
target,
|
|
1279
1496
|
value,
|
|
1280
1497
|
depth + 1,
|
|
1281
1498
|
scope::Context {
|
|
1282
1499
|
active: Some(&frame),
|
|
1283
1500
|
..active
|
|
1284
1501
|
},
|
|
1285
|
-
Some(
|
|
1502
|
+
Some(kind_state),
|
|
1286
1503
|
)
|
|
1287
1504
|
}
|
|
1288
1505
|
}
|
|
@@ -1292,7 +1509,8 @@ impl Execution<'_> {
|
|
|
1292
1509
|
if !self.budget.checkpoint() {
|
|
1293
1510
|
return Unsupported;
|
|
1294
1511
|
}
|
|
1295
|
-
outcome =
|
|
1512
|
+
outcome =
|
|
1513
|
+
self.eval_with_kind(n, value, depth + 1, active, Some(kind_state));
|
|
1296
1514
|
if outcome != Valid {
|
|
1297
1515
|
break;
|
|
1298
1516
|
}
|
|
@@ -1308,30 +1526,61 @@ impl Execution<'_> {
|
|
|
1308
1526
|
match evaluation::scan_branches(
|
|
1309
1527
|
nodes,
|
|
1310
1528
|
evaluation::BranchPolicy::Validity(branch_kind),
|
|
1311
|
-
|
|
1529
|
+
self.caches.dynamic_calls(),
|
|
1530
|
+
|child| {
|
|
1531
|
+
if matches!(op, Op::One(_))
|
|
1532
|
+
&& self.caches.callback_checkpoint(value) != Valid
|
|
1533
|
+
{
|
|
1534
|
+
return Unsupported;
|
|
1535
|
+
}
|
|
1536
|
+
self.eval_with_kind(child, value, depth + 1, active, Some(kind_state))
|
|
1537
|
+
},
|
|
1538
|
+
|_, _| Ok(()),
|
|
1312
1539
|
) {
|
|
1313
1540
|
Ok(result) => verdict(branch_kind.accepts(result.matches)),
|
|
1314
1541
|
Err(status) => status,
|
|
1315
1542
|
}
|
|
1316
1543
|
}
|
|
1317
|
-
Op::Not(n) =>
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1544
|
+
Op::Not(n) => {
|
|
1545
|
+
match self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state)) {
|
|
1546
|
+
Valid => Invalid,
|
|
1547
|
+
Invalid => Valid,
|
|
1548
|
+
Unsupported => Unsupported,
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1322
1551
|
Op::Conditional(c, yes, no) => {
|
|
1323
|
-
match self.eval_with_kind(*c, value, depth + 1, active, Some(
|
|
1552
|
+
match self.eval_with_kind(*c, value, depth + 1, active, Some(kind_state)) {
|
|
1324
1553
|
Unsupported => Unsupported,
|
|
1325
1554
|
result => match if result == Valid { yes } else { no } {
|
|
1326
1555
|
Some(n) => {
|
|
1327
|
-
self.eval_with_kind(*n, value, depth + 1, active, Some(
|
|
1556
|
+
self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state))
|
|
1328
1557
|
}
|
|
1329
1558
|
None => Valid,
|
|
1330
1559
|
},
|
|
1331
1560
|
}
|
|
1332
1561
|
}
|
|
1333
1562
|
Op::Array(required, rules) => {
|
|
1334
|
-
if
|
|
1563
|
+
if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
|
|
1564
|
+
rules.header.valid(self.caches, *required, value)
|
|
1565
|
+
} else if self.caches.callbacks_allowed()
|
|
1566
|
+
&& rules.header.runtime_required(self.caches, kind)
|
|
1567
|
+
&& self.scalar_contains(rules)
|
|
1568
|
+
{
|
|
1569
|
+
rules.header.contains(
|
|
1570
|
+
self.caches,
|
|
1571
|
+
*required,
|
|
1572
|
+
value,
|
|
1573
|
+
rules.contains.expect("scalar contains child"),
|
|
1574
|
+
self.scalar_callback_kind(
|
|
1575
|
+
rules.contains.expect("scalar contains child"),
|
|
1576
|
+
),
|
|
1577
|
+
)
|
|
1578
|
+
} else if self.caches.callbacks_allowed()
|
|
1579
|
+
&& rules.header.runtime_required(self.caches, kind)
|
|
1580
|
+
&& self.array_callbacks(rules)
|
|
1581
|
+
{
|
|
1582
|
+
rules.header.children(self.caches, *required, value, node)
|
|
1583
|
+
} else if *required != 0 && kind != TYPE_ARRAY {
|
|
1335
1584
|
Invalid
|
|
1336
1585
|
} else if kind != TYPE_ARRAY {
|
|
1337
1586
|
Valid
|
|
@@ -1340,12 +1589,29 @@ impl Execution<'_> {
|
|
|
1340
1589
|
}
|
|
1341
1590
|
}
|
|
1342
1591
|
Op::Object(required, rules) => {
|
|
1343
|
-
if
|
|
1592
|
+
if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
|
|
1593
|
+
rules.header.valid(self.caches, *required, value)
|
|
1594
|
+
} else if self.caches.callbacks_allowed()
|
|
1595
|
+
&& rules.header.runtime_required(self.caches, kind)
|
|
1596
|
+
&& self.object_callbacks(rules)
|
|
1597
|
+
{
|
|
1598
|
+
rules.header.children(self.caches, *required, value, node)
|
|
1599
|
+
} else if *required != 0 && kind != TYPE_OBJECT {
|
|
1344
1600
|
Invalid
|
|
1345
1601
|
} else if kind != TYPE_OBJECT {
|
|
1346
1602
|
Valid
|
|
1603
|
+
} else if rules.header_only() {
|
|
1604
|
+
rules.valid_header(self.caches, value, self.undefined, &self.budget)
|
|
1347
1605
|
} else {
|
|
1348
|
-
self.object(rules, value, depth + 1, active)
|
|
1606
|
+
let result = self.object(rules, value, depth + 1, active);
|
|
1607
|
+
if self.can_call_runtime(result)
|
|
1608
|
+
&& self.caches.callbacks_allowed()
|
|
1609
|
+
&& self.object_callbacks(rules)
|
|
1610
|
+
{
|
|
1611
|
+
rules.header.children(self.caches, *required, value, node)
|
|
1612
|
+
} else {
|
|
1613
|
+
result
|
|
1614
|
+
}
|
|
1349
1615
|
}
|
|
1350
1616
|
}
|
|
1351
1617
|
_ => self.leaf(op, kind, value, depth),
|
|
@@ -1359,20 +1625,49 @@ impl Execution<'_> {
|
|
|
1359
1625
|
fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
|
|
1360
1626
|
match op {
|
|
1361
1627
|
Op::Unsupported => Unsupported,
|
|
1628
|
+
Op::UnresolvedRef(_) => Invalid,
|
|
1362
1629
|
Op::Boolean(v) => verdict(*v),
|
|
1363
|
-
Op::Type(mask) =>
|
|
1364
|
-
Op::Const(v) =>
|
|
1365
|
-
|
|
1630
|
+
Op::Type(mask, union) => self.type_verdict(*mask, *union, kind, value),
|
|
1631
|
+
Op::Const(v) => {
|
|
1632
|
+
let result = if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1633
|
+
Unsupported
|
|
1634
|
+
} else {
|
|
1635
|
+
equal_budgeted(*v, value, depth, &self.budget)
|
|
1636
|
+
};
|
|
1637
|
+
if self.can_call_runtime(result) {
|
|
1638
|
+
self.caches.equality(*v, value, RUNTIME_EQUAL)
|
|
1639
|
+
} else {
|
|
1640
|
+
result
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
Op::Enum(values) => {
|
|
1644
|
+
let result = if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1645
|
+
Unsupported
|
|
1646
|
+
} else {
|
|
1647
|
+
values.eval(value, kind, depth, self.fixnum_flag, &self.budget)
|
|
1648
|
+
};
|
|
1649
|
+
if self.can_call_runtime(result) {
|
|
1650
|
+
values.runtime(self.caches, value)
|
|
1651
|
+
} else {
|
|
1652
|
+
result
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1366
1655
|
Op::Number(required, rules) => {
|
|
1656
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1657
|
+
return rules.runtime_valid(self.caches, *required, value);
|
|
1658
|
+
}
|
|
1367
1659
|
if *required != 0 && kind & required == 0 {
|
|
1368
1660
|
Invalid
|
|
1369
1661
|
} else if kind & TYPE_NUMBER == 0 {
|
|
1370
1662
|
Valid
|
|
1371
1663
|
} else {
|
|
1372
|
-
rules.eval(value)
|
|
1664
|
+
rules.eval(self.caches, value, &self.budget)
|
|
1373
1665
|
}
|
|
1374
1666
|
}
|
|
1375
1667
|
Op::String(required, rules) => {
|
|
1668
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1669
|
+
return rules.runtime_valid(self.caches, *required, value);
|
|
1670
|
+
}
|
|
1376
1671
|
if *required != 0 && kind != TYPE_STRING {
|
|
1377
1672
|
Invalid
|
|
1378
1673
|
} else if kind != TYPE_STRING {
|
|
@@ -1384,6 +1679,22 @@ impl Execution<'_> {
|
|
|
1384
1679
|
_ => unreachable!("applicators must be evaluated with their context"),
|
|
1385
1680
|
}
|
|
1386
1681
|
}
|
|
1682
|
+
fn can_call_runtime(&self, result: Verdict) -> bool {
|
|
1683
|
+
result == Unsupported && self.budget.failure.get() == 0 && self.budget.hit.get().0 == 0
|
|
1684
|
+
}
|
|
1685
|
+
fn unique_values(&self, value: Value, depth: usize) -> Verdict {
|
|
1686
|
+
let result = if self.caches.dynamic_calls() {
|
|
1687
|
+
Unsupported
|
|
1688
|
+
} else {
|
|
1689
|
+
unique(value, depth, self.fixnum_flag, &self.budget)
|
|
1690
|
+
};
|
|
1691
|
+
if self.can_call_runtime(result) {
|
|
1692
|
+
self.caches
|
|
1693
|
+
.equality(unsafe { sn_nil() }, value, RUNTIME_UNIQUE)
|
|
1694
|
+
} else {
|
|
1695
|
+
result
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1387
1698
|
fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
1388
1699
|
if !self.budget.checkpoint() {
|
|
1389
1700
|
return Unsupported;
|
|
@@ -1393,7 +1704,7 @@ impl Execution<'_> {
|
|
|
1393
1704
|
return Invalid;
|
|
1394
1705
|
}
|
|
1395
1706
|
if r.unique {
|
|
1396
|
-
let result =
|
|
1707
|
+
let result = self.unique_values(value, depth);
|
|
1397
1708
|
if result != Valid {
|
|
1398
1709
|
return result;
|
|
1399
1710
|
}
|
|
@@ -1437,15 +1748,18 @@ impl Execution<'_> {
|
|
|
1437
1748
|
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
1438
1749
|
};
|
|
1439
1750
|
let primitive = match self.nodes[child].as_slice() {
|
|
1440
|
-
[Op::Type(mask)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
|
|
1751
|
+
[Op::Type(mask, _)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
|
|
1441
1752
|
Some(&[][..])
|
|
1442
1753
|
}
|
|
1443
1754
|
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
1444
1755
|
_ => None,
|
|
1445
1756
|
};
|
|
1757
|
+
let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
|
|
1446
1758
|
let number = match self.nodes[child].as_slice() {
|
|
1447
1759
|
[Op::Number(required, rules)]
|
|
1448
|
-
if depth <= MAX_EVALUATION_DEPTH
|
|
1760
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1761
|
+
&& !self.tracks[child]
|
|
1762
|
+
&& !self.caches.dynamic_calls() =>
|
|
1449
1763
|
{
|
|
1450
1764
|
Some((*required, rules))
|
|
1451
1765
|
}
|
|
@@ -1478,47 +1792,60 @@ impl Execution<'_> {
|
|
|
1478
1792
|
.or(self.metadata[child].primary_binding),
|
|
1479
1793
|
};
|
|
1480
1794
|
let object_active = self.enter_scope(child, active, &frame);
|
|
1795
|
+
let _scope = self.caches.scope_guard(
|
|
1796
|
+
frame.resource,
|
|
1797
|
+
object.is_some() && self.metadata[child].enters_scope,
|
|
1798
|
+
);
|
|
1481
1799
|
let pattern = string.and_then(|(required, rules)| {
|
|
1482
1800
|
rules
|
|
1483
1801
|
.pattern_only()
|
|
1484
1802
|
.map(|pattern| (required, pattern))
|
|
1485
|
-
.filter(|_| !self.budget.timed())
|
|
1803
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1486
1804
|
});
|
|
1487
1805
|
let format = string.and_then(|(required, rules)| {
|
|
1488
1806
|
rules
|
|
1489
1807
|
.format_only()
|
|
1490
1808
|
.map(|format| (required, format))
|
|
1491
|
-
.filter(|_| !self.budget.timed())
|
|
1809
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1492
1810
|
});
|
|
1493
1811
|
let content = string.and_then(|(required, rules)| {
|
|
1494
1812
|
rules
|
|
1495
1813
|
.content_only()
|
|
1496
1814
|
.map(|content| (required, content))
|
|
1497
|
-
.filter(|_| !self.budget.timed())
|
|
1815
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1498
1816
|
});
|
|
1499
1817
|
if let Some((required, pattern)) = pattern {
|
|
1500
|
-
let result = pattern.
|
|
1818
|
+
let result = pattern.visit_invalid_items(
|
|
1501
1819
|
self.caches,
|
|
1502
1820
|
required,
|
|
1503
1821
|
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1822
|
+
|_, item| self.eval(child, item, depth, active),
|
|
1504
1823
|
);
|
|
1505
1824
|
if result != Valid {
|
|
1506
1825
|
return result;
|
|
1507
1826
|
}
|
|
1508
1827
|
} else if let Some((required, format)) = format {
|
|
1509
|
-
let result =
|
|
1510
|
-
|
|
1828
|
+
let result = format.visit_invalid_items(
|
|
1829
|
+
required,
|
|
1830
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1831
|
+
|_, item| self.eval(child, item, depth, active),
|
|
1832
|
+
);
|
|
1511
1833
|
if result != Valid {
|
|
1512
1834
|
return result;
|
|
1513
1835
|
}
|
|
1514
1836
|
} else if let Some((required, content)) = content {
|
|
1515
|
-
let result =
|
|
1516
|
-
|
|
1837
|
+
let result = content.visit_invalid_items(
|
|
1838
|
+
self.caches,
|
|
1839
|
+
required,
|
|
1840
|
+
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1841
|
+
|_, item| self.eval(child, item, depth, active),
|
|
1842
|
+
);
|
|
1517
1843
|
if result != Valid {
|
|
1518
1844
|
return result;
|
|
1519
1845
|
}
|
|
1520
1846
|
} else if let Some((required, rules)) = object {
|
|
1521
1847
|
let result = self.record_items(
|
|
1848
|
+
child,
|
|
1522
1849
|
rules,
|
|
1523
1850
|
required,
|
|
1524
1851
|
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
@@ -1545,16 +1872,21 @@ impl Execution<'_> {
|
|
|
1545
1872
|
return Unsupported;
|
|
1546
1873
|
}
|
|
1547
1874
|
let result = if let Some((required, rules)) = number {
|
|
1548
|
-
rules.eval_guarded(required, item)
|
|
1875
|
+
let status = rules.eval_guarded(self.caches, required, item, &self.budget);
|
|
1876
|
+
if status == Unsupported {
|
|
1877
|
+
self.eval(child, item, depth, active)
|
|
1878
|
+
} else {
|
|
1879
|
+
status
|
|
1880
|
+
}
|
|
1549
1881
|
} else if let Some((required, rules)) = string {
|
|
1550
1882
|
if rules.pattern.is_some() {
|
|
1551
1883
|
rules.eval_guarded(self.caches, required, item)
|
|
1552
1884
|
} else if rules.length_only() {
|
|
1553
|
-
rules.eval_length_guarded(required, item)
|
|
1885
|
+
rules.eval_length_guarded(self.caches, required, item)
|
|
1554
1886
|
} else {
|
|
1555
1887
|
let kind = unsafe { sn_kind(item) };
|
|
1556
|
-
if kind == TYPE_UNSUPPORTED {
|
|
1557
|
-
|
|
1888
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1889
|
+
rules.runtime_valid(self.caches, required, item)
|
|
1558
1890
|
} else if kind != TYPE_STRING {
|
|
1559
1891
|
verdict(required == 0)
|
|
1560
1892
|
} else {
|
|
@@ -1571,18 +1903,30 @@ impl Execution<'_> {
|
|
|
1571
1903
|
}
|
|
1572
1904
|
}
|
|
1573
1905
|
if let Some(child) = r.contains {
|
|
1574
|
-
if
|
|
1906
|
+
if r.count_contains
|
|
1907
|
+
&& let Some(status) = self.fixnum_contains(r, child, value, len, depth)
|
|
1908
|
+
{
|
|
1575
1909
|
return status;
|
|
1576
1910
|
}
|
|
1577
1911
|
let matches = match evaluation::scan_contains(
|
|
1578
1912
|
len,
|
|
1913
|
+
if r.count_contains {
|
|
1914
|
+
evaluation::ContainsPolicy::AllMatches
|
|
1915
|
+
} else {
|
|
1916
|
+
evaluation::ContainsPolicy::FirstMatch
|
|
1917
|
+
},
|
|
1579
1918
|
|index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
|
|
1580
1919
|
|_| {},
|
|
1581
1920
|
) {
|
|
1582
1921
|
Ok(matches) => matches,
|
|
1583
1922
|
Err(status) => return status,
|
|
1584
1923
|
};
|
|
1585
|
-
|
|
1924
|
+
let (min, max) = if r.count_contains {
|
|
1925
|
+
(r.min_contains, r.max_contains)
|
|
1926
|
+
} else {
|
|
1927
|
+
(1, None)
|
|
1928
|
+
};
|
|
1929
|
+
if bounds(matches, Some(min), max) == Invalid {
|
|
1586
1930
|
return Invalid;
|
|
1587
1931
|
}
|
|
1588
1932
|
}
|
|
@@ -1603,7 +1947,7 @@ impl Execution<'_> {
|
|
|
1603
1947
|
return None;
|
|
1604
1948
|
};
|
|
1605
1949
|
let total = match op {
|
|
1606
|
-
Op::Type(
|
|
1950
|
+
Op::Type(..) | Op::Boolean(_) => true,
|
|
1607
1951
|
Op::Const(expected) => expected & self.fixnum_flag != 0,
|
|
1608
1952
|
Op::Number(_, rules) => rules.fast_limits.is_some(),
|
|
1609
1953
|
_ => false,
|
|
@@ -1629,7 +1973,6 @@ impl Execution<'_> {
|
|
|
1629
1973
|
|index| self.leaf(op, TYPE_INTEGRAL_NUMBER, values[index], depth) == Valid,
|
|
1630
1974
|
))
|
|
1631
1975
|
}
|
|
1632
|
-
#[inline(never)]
|
|
1633
1976
|
fn integer_items(
|
|
1634
1977
|
&self,
|
|
1635
1978
|
limits: &[(i32, i64)],
|
|
@@ -1652,13 +1995,20 @@ impl Execution<'_> {
|
|
|
1652
1995
|
);
|
|
1653
1996
|
let number = match self.nodes[child].as_slice() {
|
|
1654
1997
|
[Op::Number(required, rules)]
|
|
1655
|
-
if depth <= MAX_EVALUATION_DEPTH
|
|
1998
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1999
|
+
&& !self.tracks[child]
|
|
2000
|
+
&& !self.caches.dynamic_calls() =>
|
|
1656
2001
|
{
|
|
1657
2002
|
Some((*required, rules))
|
|
1658
2003
|
}
|
|
1659
2004
|
_ => None,
|
|
1660
2005
|
};
|
|
1661
|
-
|
|
2006
|
+
let prefix = if self.budget.timed() {
|
|
2007
|
+
0
|
|
2008
|
+
} else {
|
|
2009
|
+
integer_scan::valid_prefix(values, min, max, self.fixnum_flag)
|
|
2010
|
+
};
|
|
2011
|
+
for &value in &values[prefix..] {
|
|
1662
2012
|
if !self.budget.checkpoint() {
|
|
1663
2013
|
return Unsupported;
|
|
1664
2014
|
}
|
|
@@ -1666,7 +2016,12 @@ impl Execution<'_> {
|
|
|
1666
2016
|
let n = immediate_integer(value);
|
|
1667
2017
|
verdict(n >= min && n <= max)
|
|
1668
2018
|
} else if let Some((required, rules)) = number {
|
|
1669
|
-
rules.eval_guarded(required, value)
|
|
2019
|
+
let status = rules.eval_guarded(self.caches, required, value, &self.budget);
|
|
2020
|
+
if status == Unsupported {
|
|
2021
|
+
self.eval(child, value, depth, active)
|
|
2022
|
+
} else {
|
|
2023
|
+
status
|
|
2024
|
+
}
|
|
1670
2025
|
} else {
|
|
1671
2026
|
self.eval(child, value, depth, active)
|
|
1672
2027
|
};
|
|
@@ -1676,15 +2031,16 @@ impl Execution<'_> {
|
|
|
1676
2031
|
}
|
|
1677
2032
|
Valid
|
|
1678
2033
|
}
|
|
1679
|
-
#[inline(never)]
|
|
1680
2034
|
fn record_items(
|
|
1681
2035
|
&self,
|
|
2036
|
+
node: usize,
|
|
1682
2037
|
rules: &Object,
|
|
1683
2038
|
required: i32,
|
|
1684
2039
|
values: &[Value],
|
|
1685
2040
|
depth: usize,
|
|
1686
2041
|
active: scope::Context<'_>,
|
|
1687
2042
|
) -> Verdict {
|
|
2043
|
+
let header_only = rules.header_only();
|
|
1688
2044
|
for &value in values {
|
|
1689
2045
|
if !self.budget.checkpoint() {
|
|
1690
2046
|
return Unsupported;
|
|
@@ -1694,13 +2050,29 @@ impl Execution<'_> {
|
|
|
1694
2050
|
} else {
|
|
1695
2051
|
unsafe { sn_shallow_kind(value) }
|
|
1696
2052
|
};
|
|
1697
|
-
let
|
|
2053
|
+
let runtime = header_only && rules.header.runtime_required(self.caches, kind);
|
|
2054
|
+
let result = if runtime {
|
|
2055
|
+
rules.header.valid(self.caches, required, value)
|
|
2056
|
+
} else if kind == TYPE_UNSUPPORTED {
|
|
1698
2057
|
Unsupported
|
|
1699
2058
|
} else if kind != TYPE_OBJECT {
|
|
1700
2059
|
verdict(required == 0)
|
|
1701
2060
|
} else {
|
|
1702
2061
|
self.object(rules, value, depth, active)
|
|
1703
2062
|
};
|
|
2063
|
+
let result = if self.can_call_runtime(result)
|
|
2064
|
+
&& self.caches.callbacks_allowed()
|
|
2065
|
+
&& self.object_callbacks(rules)
|
|
2066
|
+
{
|
|
2067
|
+
rules.header.children(self.caches, required, value, node)
|
|
2068
|
+
} else if header_only && !runtime && self.can_call_runtime(result) {
|
|
2069
|
+
// A declined record guard has not invoked instance methods.
|
|
2070
|
+
// Preserve the fast record walk while handing special values
|
|
2071
|
+
// to the same runtime frame as general header evaluation.
|
|
2072
|
+
rules.header.valid(self.caches, required, value)
|
|
2073
|
+
} else {
|
|
2074
|
+
result
|
|
2075
|
+
};
|
|
1704
2076
|
if result != Valid {
|
|
1705
2077
|
return result;
|
|
1706
2078
|
}
|
|
@@ -1717,7 +2089,9 @@ impl Execution<'_> {
|
|
|
1717
2089
|
if !self.budget.checkpoint() {
|
|
1718
2090
|
return Unsupported;
|
|
1719
2091
|
}
|
|
1720
|
-
if
|
|
2092
|
+
if !self.caches.runtime_order()
|
|
2093
|
+
&& let Some(record) = &r.record
|
|
2094
|
+
{
|
|
1721
2095
|
// C initializes only the written key/value pairs. The untouched
|
|
1722
2096
|
// tail is never read, including empty and unsupported records.
|
|
1723
2097
|
let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
|
|
@@ -1799,26 +2173,36 @@ impl Execution<'_> {
|
|
|
1799
2173
|
if bounds(len, r.min, r.max) == Invalid {
|
|
1800
2174
|
return Invalid;
|
|
1801
2175
|
}
|
|
1802
|
-
let status = r.check_required(value, self.undefined, &self.budget);
|
|
2176
|
+
let status = r.check_required(self.caches, value, self.undefined, &self.budget);
|
|
1803
2177
|
if status != Valid {
|
|
1804
2178
|
return status;
|
|
1805
2179
|
}
|
|
1806
|
-
let
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2180
|
+
let ordered = self.caches.runtime_order();
|
|
2181
|
+
let found = if ordered {
|
|
2182
|
+
0
|
|
2183
|
+
} else {
|
|
2184
|
+
match r.visit_properties(
|
|
2185
|
+
value,
|
|
2186
|
+
len,
|
|
2187
|
+
self.undefined,
|
|
2188
|
+
&self.budget,
|
|
2189
|
+
|_, child, item| self.eval(child, item, depth, active),
|
|
2190
|
+
) {
|
|
2191
|
+
Ok(found) => found,
|
|
2192
|
+
Err(status) => return status,
|
|
2193
|
+
}
|
|
1815
2194
|
};
|
|
1816
|
-
if
|
|
2195
|
+
if ordered
|
|
2196
|
+
|| r.names.is_some()
|
|
2197
|
+
|| !r.patterns.is_empty()
|
|
2198
|
+
|| (r.additional.is_some() && found < len)
|
|
2199
|
+
{
|
|
1817
2200
|
let mut context = ObjectContext {
|
|
1818
2201
|
engine: self,
|
|
1819
2202
|
rules: r,
|
|
1820
2203
|
depth,
|
|
1821
2204
|
active,
|
|
2205
|
+
names_only: false,
|
|
1822
2206
|
};
|
|
1823
2207
|
let result =
|
|
1824
2208
|
unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
|
|
@@ -1826,7 +2210,13 @@ impl Execution<'_> {
|
|
|
1826
2210
|
return if result == 0 { Invalid } else { Unsupported };
|
|
1827
2211
|
}
|
|
1828
2212
|
}
|
|
1829
|
-
|
|
2213
|
+
if ordered && r.names.is_some() {
|
|
2214
|
+
let status = self.object_names(r, value, depth, active);
|
|
2215
|
+
if status != Valid {
|
|
2216
|
+
return status;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
let status = r.check_dependent_required(self.caches, value, self.undefined, &self.budget);
|
|
1830
2220
|
if status != Valid {
|
|
1831
2221
|
return status;
|
|
1832
2222
|
}
|
|
@@ -1843,13 +2233,76 @@ impl Execution<'_> {
|
|
|
1843
2233
|
}
|
|
1844
2234
|
Valid
|
|
1845
2235
|
}
|
|
2236
|
+
fn object_names(
|
|
2237
|
+
&self,
|
|
2238
|
+
rules: &Object,
|
|
2239
|
+
value: Value,
|
|
2240
|
+
depth: usize,
|
|
2241
|
+
active: scope::Context<'_>,
|
|
2242
|
+
) -> Verdict {
|
|
2243
|
+
let mut context = ObjectContext {
|
|
2244
|
+
engine: self,
|
|
2245
|
+
rules,
|
|
2246
|
+
depth,
|
|
2247
|
+
active,
|
|
2248
|
+
names_only: true,
|
|
2249
|
+
};
|
|
2250
|
+
decode(unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) })
|
|
2251
|
+
}
|
|
2252
|
+
fn unsupported_kind(&self, node: usize, kind: i32) -> bool {
|
|
2253
|
+
kind == TYPE_UNSUPPORTED
|
|
2254
|
+
&& (self.kind_demand[node] == KindDemand::Data
|
|
2255
|
+
|| (!self.caches.callbacks_allowed()
|
|
2256
|
+
&& !self.caches.report_continuations_allowed()
|
|
2257
|
+
&& self.callback_container_node(node)))
|
|
2258
|
+
}
|
|
2259
|
+
fn type_verdict(&self, mask: i32, union: bool, kind: i32, value: Value) -> Verdict {
|
|
2260
|
+
if kind == TYPE_UNSUPPORTED
|
|
2261
|
+
|| self.caches.scalar_callback()
|
|
2262
|
+
|| (self.caches.callbacks_allowed() && self.caches.dynamic_calls())
|
|
2263
|
+
{
|
|
2264
|
+
self.caches.type_predicate(value, mask, union)
|
|
2265
|
+
} else {
|
|
2266
|
+
verdict(kind & mask != 0)
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
1846
2269
|
fn instance_kind(&self, node: usize, value: Value) -> i32 {
|
|
1847
|
-
if
|
|
2270
|
+
if self.caches.scalar_callback() && self.kind_demand[node] == KindDemand::Predicate {
|
|
2271
|
+
return TYPE_UNSUPPORTED;
|
|
2272
|
+
}
|
|
2273
|
+
if value & self.fixnum_flag != 0 {
|
|
2274
|
+
return TYPE_INTEGRAL_NUMBER;
|
|
2275
|
+
}
|
|
2276
|
+
if self.kind_demand[node] == KindDemand::None {
|
|
2277
|
+
return TYPE_UNCHECKED;
|
|
2278
|
+
}
|
|
2279
|
+
if matches!(self.nodes[node].as_slice(), [Op::Object(_, rules)]
|
|
2280
|
+
if rules.record.is_some() && (!rules.header_only() || (!rules.inspect_required && rules.dependent_required.is_empty())))
|
|
2281
|
+
{
|
|
1848
2282
|
unsafe { sn_shallow_kind(value) }
|
|
1849
2283
|
} else {
|
|
1850
2284
|
unsafe { sn_kind(value) }
|
|
1851
2285
|
}
|
|
1852
2286
|
}
|
|
2287
|
+
// Diagnostic/trial paths require a full Hash-key guard. A known parent kind
|
|
2288
|
+
// remains reusable, while control-flow-only children need no classification.
|
|
2289
|
+
fn diagnostic_kind(&self, node: usize, value: Value, known: &InstanceKind) -> i32 {
|
|
2290
|
+
if self.caches.scalar_callback() && self.kind_demand[node] == KindDemand::Predicate {
|
|
2291
|
+
return TYPE_UNSUPPORTED;
|
|
2292
|
+
}
|
|
2293
|
+
if known.get() != TYPE_UNCHECKED {
|
|
2294
|
+
return known.get();
|
|
2295
|
+
}
|
|
2296
|
+
let kind = if value & self.fixnum_flag != 0 {
|
|
2297
|
+
TYPE_INTEGRAL_NUMBER
|
|
2298
|
+
} else if self.kind_demand[node] == KindDemand::None {
|
|
2299
|
+
TYPE_UNCHECKED
|
|
2300
|
+
} else {
|
|
2301
|
+
unsafe { sn_kind(value) }
|
|
2302
|
+
};
|
|
2303
|
+
known.set(kind);
|
|
2304
|
+
kind
|
|
2305
|
+
}
|
|
1853
2306
|
fn record_value(
|
|
1854
2307
|
&self,
|
|
1855
2308
|
node: usize,
|
|
@@ -1864,7 +2317,7 @@ impl Execution<'_> {
|
|
|
1864
2317
|
[Op::String(required, rules)]
|
|
1865
2318
|
if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
|
|
1866
2319
|
{
|
|
1867
|
-
rules.eval_length_guarded(*required, value)
|
|
2320
|
+
rules.eval_length_guarded(self.caches, *required, value)
|
|
1868
2321
|
}
|
|
1869
2322
|
[Op::String(required, rules)]
|
|
1870
2323
|
if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
|
|
@@ -1872,19 +2325,15 @@ impl Execution<'_> {
|
|
|
1872
2325
|
rules.eval_guarded(self.caches, *required, value)
|
|
1873
2326
|
}
|
|
1874
2327
|
[
|
|
1875
|
-
op @ (Op::Type(
|
|
2328
|
+
op @ (Op::Type(..)
|
|
1876
2329
|
| Op::Boolean(_)
|
|
1877
2330
|
| Op::Number(..)
|
|
1878
2331
|
| Op::String(..)
|
|
1879
2332
|
| Op::Const(_)
|
|
1880
2333
|
| Op::Enum(_)),
|
|
1881
2334
|
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1882
|
-
let kind =
|
|
1883
|
-
|
|
1884
|
-
} else {
|
|
1885
|
-
unsafe { sn_kind(value) }
|
|
1886
|
-
};
|
|
1887
|
-
if kind == TYPE_UNSUPPORTED {
|
|
2335
|
+
let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
|
|
2336
|
+
if self.unsupported_kind(node, kind) {
|
|
1888
2337
|
Unsupported
|
|
1889
2338
|
} else {
|
|
1890
2339
|
self.leaf(op, kind, value, depth)
|
|
@@ -1963,12 +2412,21 @@ fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verd
|
|
|
1963
2412
|
{
|
|
1964
2413
|
c.status = if c.depth <= MAX_EVALUATION_DEPTH {
|
|
1965
2414
|
match field.and_then(|field| field.check) {
|
|
1966
|
-
Some(RecordCheck::IntegerBounds(min, max))
|
|
2415
|
+
Some(RecordCheck::IntegerBounds(min, max))
|
|
2416
|
+
if value & c.engine.fixnum_flag != 0 && !c.engine.caches.dynamic_calls() =>
|
|
2417
|
+
{
|
|
1967
2418
|
let n = immediate_integer(value);
|
|
1968
2419
|
verdict(n >= min && n <= max)
|
|
1969
2420
|
}
|
|
1970
|
-
Some(RecordCheck::StringLength(required, min, max))
|
|
1971
|
-
|
|
2421
|
+
Some(RecordCheck::StringLength(required, min, max))
|
|
2422
|
+
if !c.engine.caches.dynamic_calls() =>
|
|
2423
|
+
{
|
|
2424
|
+
let status = length_guarded(value, required, min, max);
|
|
2425
|
+
if status == Unsupported {
|
|
2426
|
+
c.engine.record_value(child, value, c.depth, c.active)
|
|
2427
|
+
} else {
|
|
2428
|
+
status
|
|
2429
|
+
}
|
|
1972
2430
|
}
|
|
1973
2431
|
_ => c.engine.record_value(child, value, c.depth, c.active),
|
|
1974
2432
|
}
|
|
@@ -2032,20 +2490,79 @@ fn equal_cached(
|
|
|
2032
2490
|
return Unsupported;
|
|
2033
2491
|
}
|
|
2034
2492
|
if depth > MAX_EVALUATION_DEPTH {
|
|
2035
|
-
return
|
|
2493
|
+
return equal_iterative(a, b, depth, memo, budget);
|
|
2494
|
+
}
|
|
2495
|
+
let ka = match equality_kind(a, b) {
|
|
2496
|
+
Ok(kind) => kind,
|
|
2497
|
+
Err(result) => return result,
|
|
2498
|
+
};
|
|
2499
|
+
let key = (a, b, depth);
|
|
2500
|
+
if memo.eligible(depth)
|
|
2501
|
+
&& let Some(result) = memo.get(key)
|
|
2502
|
+
{
|
|
2503
|
+
return result;
|
|
2504
|
+
}
|
|
2505
|
+
let result = match ka {
|
|
2506
|
+
TYPE_ARRAY => {
|
|
2507
|
+
let len = unsafe { sn_len(a) };
|
|
2508
|
+
if len != unsafe { sn_len(b) } {
|
|
2509
|
+
return Invalid;
|
|
2510
|
+
}
|
|
2511
|
+
for i in 0..len {
|
|
2512
|
+
let result = equal_cached(
|
|
2513
|
+
unsafe { sn_entry(a, i) },
|
|
2514
|
+
unsafe { sn_entry(b, i) },
|
|
2515
|
+
depth + 1,
|
|
2516
|
+
memo,
|
|
2517
|
+
budget,
|
|
2518
|
+
);
|
|
2519
|
+
if result != Valid {
|
|
2520
|
+
return result;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
Valid
|
|
2524
|
+
}
|
|
2525
|
+
TYPE_OBJECT => {
|
|
2526
|
+
if unsafe { sn_len(a) != sn_len(b) } {
|
|
2527
|
+
return Invalid;
|
|
2528
|
+
}
|
|
2529
|
+
let mut context = EqualityContext {
|
|
2530
|
+
other: b,
|
|
2531
|
+
depth: depth + 1,
|
|
2532
|
+
memo,
|
|
2533
|
+
budget,
|
|
2534
|
+
};
|
|
2535
|
+
decode(unsafe {
|
|
2536
|
+
sn_hash_each(
|
|
2537
|
+
a,
|
|
2538
|
+
(&mut context as *mut EqualityContext<'_>).cast(),
|
|
2539
|
+
equal_entry,
|
|
2540
|
+
)
|
|
2541
|
+
})
|
|
2542
|
+
}
|
|
2543
|
+
_ => unreachable!("equality_kind returns only container kinds"),
|
|
2544
|
+
};
|
|
2545
|
+
if memo.eligible(depth) {
|
|
2546
|
+
memo.record(key, result);
|
|
2036
2547
|
}
|
|
2548
|
+
result
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// Resolve scalar equality once for both recursive and explicit-stack traversal.
|
|
2552
|
+
// Only ordinary equal-class containers require child traversal.
|
|
2553
|
+
fn equality_kind(a: Value, b: Value) -> Result<i32, Verdict> {
|
|
2037
2554
|
let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
|
|
2038
2555
|
if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
|
|
2039
|
-
return Unsupported;
|
|
2556
|
+
return Err(Unsupported);
|
|
2040
2557
|
}
|
|
2041
2558
|
if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
|
|
2042
2559
|
let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
|
|
2043
2560
|
let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
|
|
2044
2561
|
if ia && ib {
|
|
2045
|
-
return verdict(unsafe { sn_integer(a) == sn_integer(b) });
|
|
2562
|
+
return Err(verdict(unsafe { sn_integer(a) == sn_integer(b) }));
|
|
2046
2563
|
}
|
|
2047
2564
|
if fa && fb {
|
|
2048
|
-
return verdict(unsafe { sn_float(a) == sn_float(b) });
|
|
2565
|
+
return Err(verdict(unsafe { sn_float(a) == sn_float(b) }));
|
|
2049
2566
|
}
|
|
2050
2567
|
if (ia && fb) || (fa && ib) {
|
|
2051
2568
|
let (n, f) = unsafe {
|
|
@@ -2057,16 +2574,16 @@ fn equal_cached(
|
|
|
2057
2574
|
};
|
|
2058
2575
|
// Convert the float to an integer, never the integer to a rounded float.
|
|
2059
2576
|
// Fixnums are strictly inside i64's bounds on supported CRuby targets.
|
|
2060
|
-
return verdict(
|
|
2577
|
+
return Err(verdict(
|
|
2061
2578
|
f.is_finite()
|
|
2062
2579
|
&& f.trunc() == f
|
|
2063
2580
|
&& f >= i64::MIN as f64
|
|
2064
2581
|
&& f < i64::MAX as f64
|
|
2065
2582
|
&& f as i64 == n,
|
|
2066
|
-
);
|
|
2583
|
+
));
|
|
2067
2584
|
}
|
|
2068
2585
|
if !fa && !fb {
|
|
2069
|
-
return verdict(
|
|
2586
|
+
return Err(verdict(
|
|
2070
2587
|
unsafe {
|
|
2071
2588
|
if ia {
|
|
2072
2589
|
sn_big_equal(b, a)
|
|
@@ -2074,80 +2591,150 @@ fn equal_cached(
|
|
|
2074
2591
|
sn_big_equal(a, b)
|
|
2075
2592
|
}
|
|
2076
2593
|
} != 0,
|
|
2077
|
-
);
|
|
2594
|
+
));
|
|
2078
2595
|
}
|
|
2079
|
-
return if fa {
|
|
2596
|
+
return Err(if fa {
|
|
2080
2597
|
number::integer_float_equal(b, unsafe { sn_float(a) })
|
|
2081
2598
|
} else {
|
|
2082
2599
|
number::integer_float_equal(a, unsafe { sn_float(b) })
|
|
2083
|
-
};
|
|
2600
|
+
});
|
|
2084
2601
|
}
|
|
2085
2602
|
// JSON container equality visits every value, even for the same object:
|
|
2086
2603
|
// nested NaN values are unequal and unsupported values need VM evaluation.
|
|
2087
2604
|
if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
|
|
2088
|
-
return Valid;
|
|
2605
|
+
return Err(Valid);
|
|
2089
2606
|
}
|
|
2090
2607
|
if ka != kb {
|
|
2091
|
-
return Invalid;
|
|
2608
|
+
return Err(Invalid);
|
|
2092
2609
|
}
|
|
2093
2610
|
if unsafe { sn_same_class(a, b) } == 0 {
|
|
2094
|
-
return Invalid;
|
|
2611
|
+
return Err(Invalid);
|
|
2095
2612
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
&& let Some(result) = memo.get(key)
|
|
2101
|
-
{
|
|
2102
|
-
return result;
|
|
2613
|
+
match ka {
|
|
2614
|
+
TYPE_ARRAY | TYPE_OBJECT => Ok(ka),
|
|
2615
|
+
TYPE_STRING => Err(verdict(unsafe { sn_string_equal(a, b) } != 0)),
|
|
2616
|
+
_ => Err(Invalid),
|
|
2103
2617
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
enum EqualityWork {
|
|
2621
|
+
Compare(Value, Value, usize),
|
|
2622
|
+
Array(Value, Value, usize, usize, usize),
|
|
2623
|
+
Object(Value, Vec<(Value, Value)>, usize, usize),
|
|
2624
|
+
Finish(Value, Value, usize),
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// Keep shallow comparisons on their allocation-free recursive path. Beyond
|
|
2628
|
+
// that bounded prefix, explicit continuations preserve depth-first order
|
|
2629
|
+
// without growing the native call stack. Scalar rules remain shared above.
|
|
2630
|
+
fn equal_iterative(
|
|
2631
|
+
a: Value,
|
|
2632
|
+
b: Value,
|
|
2633
|
+
depth: usize,
|
|
2634
|
+
memo: &mut structural::Cache<(Value, Value, usize), Verdict>,
|
|
2635
|
+
budget: &budget::Budget,
|
|
2636
|
+
) -> Verdict {
|
|
2637
|
+
let mut work = vec![EqualityWork::Compare(a, b, depth)];
|
|
2638
|
+
let mut active = std::collections::HashSet::new();
|
|
2639
|
+
while let Some(task) = work.pop() {
|
|
2640
|
+
if !budget.checkpoint() {
|
|
2641
|
+
return Unsupported;
|
|
2642
|
+
}
|
|
2643
|
+
match task {
|
|
2644
|
+
EqualityWork::Compare(a, b, depth) => {
|
|
2645
|
+
let kind = match equality_kind(a, b) {
|
|
2646
|
+
Ok(kind) => kind,
|
|
2647
|
+
Err(Valid) => continue,
|
|
2648
|
+
Err(result) => return result,
|
|
2649
|
+
};
|
|
2650
|
+
let key = (a, b, depth);
|
|
2651
|
+
if memo.eligible(depth)
|
|
2652
|
+
&& let Some(result) = memo.get(key)
|
|
2653
|
+
{
|
|
2654
|
+
if result != Valid {
|
|
2655
|
+
return result;
|
|
2656
|
+
}
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
let len = unsafe { sn_len(a) };
|
|
2660
|
+
if len != unsafe { sn_len(b) } {
|
|
2661
|
+
return Invalid;
|
|
2662
|
+
}
|
|
2663
|
+
// Report Ruby's recursive-comparison error after unwinding;
|
|
2664
|
+
// an active pair is not proof of JSON equality.
|
|
2665
|
+
if !active.insert((a, b)) {
|
|
2666
|
+
budget.failure.set(STATUS_STACK_ERROR);
|
|
2667
|
+
return Unsupported;
|
|
2668
|
+
}
|
|
2669
|
+
work.push(EqualityWork::Finish(a, b, depth));
|
|
2670
|
+
if len == 0 {
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
if kind == TYPE_ARRAY {
|
|
2674
|
+
work.push(EqualityWork::Array(a, b, 0, len, depth + 1));
|
|
2675
|
+
} else {
|
|
2676
|
+
let mut entries = EqualityEntries {
|
|
2677
|
+
values: Vec::with_capacity(len),
|
|
2678
|
+
budget,
|
|
2679
|
+
};
|
|
2680
|
+
let status = decode(unsafe {
|
|
2681
|
+
sn_hash_each(
|
|
2682
|
+
a,
|
|
2683
|
+
(&mut entries as *mut EqualityEntries<'_>).cast(),
|
|
2684
|
+
equality_entry,
|
|
2685
|
+
)
|
|
2686
|
+
});
|
|
2687
|
+
if status != Valid {
|
|
2688
|
+
return status;
|
|
2689
|
+
}
|
|
2690
|
+
work.push(EqualityWork::Object(b, entries.values, 0, depth + 1));
|
|
2691
|
+
}
|
|
2110
2692
|
}
|
|
2111
|
-
|
|
2112
|
-
let
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
depth + 1,
|
|
2116
|
-
memo,
|
|
2117
|
-
budget,
|
|
2118
|
-
);
|
|
2119
|
-
if result != Valid {
|
|
2120
|
-
return result;
|
|
2693
|
+
EqualityWork::Array(a, b, index, len, depth) => {
|
|
2694
|
+
let pair = unsafe { (sn_entry(a, index), sn_entry(b, index)) };
|
|
2695
|
+
if index + 1 < len {
|
|
2696
|
+
work.push(EqualityWork::Array(a, b, index + 1, len, depth));
|
|
2121
2697
|
}
|
|
2698
|
+
work.push(EqualityWork::Compare(pair.0, pair.1, depth));
|
|
2122
2699
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2700
|
+
EqualityWork::Object(b, entries, index, depth) => {
|
|
2701
|
+
let (key, value) = entries[index];
|
|
2702
|
+
let other = unsafe { sn_lookup(b, key) };
|
|
2703
|
+
if unsafe { sn_missing(other) } != 0 {
|
|
2704
|
+
return Invalid;
|
|
2705
|
+
}
|
|
2706
|
+
if index + 1 < entries.len() {
|
|
2707
|
+
work.push(EqualityWork::Object(b, entries, index + 1, depth));
|
|
2708
|
+
}
|
|
2709
|
+
work.push(EqualityWork::Compare(value, other, depth));
|
|
2710
|
+
}
|
|
2711
|
+
EqualityWork::Finish(a, b, depth) => {
|
|
2712
|
+
active.remove(&(a, b));
|
|
2713
|
+
if memo.eligible(depth) {
|
|
2714
|
+
memo.record((a, b, depth), Valid);
|
|
2715
|
+
}
|
|
2128
2716
|
}
|
|
2129
|
-
let mut context = EqualityContext {
|
|
2130
|
-
other: b,
|
|
2131
|
-
depth: depth + 1,
|
|
2132
|
-
memo,
|
|
2133
|
-
budget,
|
|
2134
|
-
};
|
|
2135
|
-
decode(unsafe {
|
|
2136
|
-
sn_hash_each(
|
|
2137
|
-
a,
|
|
2138
|
-
(&mut context as *mut EqualityContext<'_>).cast(),
|
|
2139
|
-
equal_entry,
|
|
2140
|
-
)
|
|
2141
|
-
})
|
|
2142
2717
|
}
|
|
2143
|
-
TYPE_UNSUPPORTED => Unsupported,
|
|
2144
|
-
_ => Invalid,
|
|
2145
|
-
};
|
|
2146
|
-
if container && memo.eligible(depth) {
|
|
2147
|
-
memo.record(key, result);
|
|
2148
2718
|
}
|
|
2149
|
-
|
|
2719
|
+
Valid
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
struct EqualityEntries<'a> {
|
|
2723
|
+
values: Vec<(Value, Value)>,
|
|
2724
|
+
budget: &'a budget::Budget,
|
|
2150
2725
|
}
|
|
2726
|
+
unsafe extern "C" fn equality_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
2727
|
+
catch_unwind(AssertUnwindSafe(|| {
|
|
2728
|
+
let entries = unsafe { &mut *context.cast::<EqualityEntries<'_>>() };
|
|
2729
|
+
if !entries.budget.checkpoint() {
|
|
2730
|
+
return Unsupported as i32;
|
|
2731
|
+
}
|
|
2732
|
+
entries.values.push((key, value));
|
|
2733
|
+
Valid as i32
|
|
2734
|
+
}))
|
|
2735
|
+
.unwrap_or(Unsupported as i32)
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2151
2738
|
fn decode(result: i32) -> Verdict {
|
|
2152
2739
|
match result {
|
|
2153
2740
|
VERDICT_INVALID => Invalid,
|
|
@@ -2315,13 +2902,12 @@ fn fingerprint_cached(
|
|
|
2315
2902
|
// before the depth-based cache handles deeper shared graphs.
|
|
2316
2903
|
let cacheable = container && memo.eligible_container(depth, unsafe { len.assume_init() });
|
|
2317
2904
|
let key = (value, depth);
|
|
2318
|
-
if cacheable
|
|
2319
|
-
&& let Some(result) = memo.get(key)
|
|
2320
|
-
{
|
|
2905
|
+
if cacheable && let Some(result) = memo.get(key) {
|
|
2321
2906
|
return result;
|
|
2322
2907
|
}
|
|
2323
2908
|
let result = match kind {
|
|
2324
2909
|
TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
|
|
2910
|
+
TYPE_IDENTITY_SCALAR => Some(mix(value as u64 ^ TYPE_IDENTITY_SCALAR as u64)),
|
|
2325
2911
|
TYPE_NULL => Some(mix(TYPE_NULL as u64)),
|
|
2326
2912
|
TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
|
|
2327
2913
|
2
|
|
@@ -2423,21 +3009,7 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
|
|
|
2423
3009
|
return Unsupported;
|
|
2424
3010
|
}
|
|
2425
3011
|
if len <= 16 {
|
|
2426
|
-
|
|
2427
|
-
for previous in 0..i {
|
|
2428
|
-
match equal_budgeted(
|
|
2429
|
-
unsafe { sn_entry(value, previous) },
|
|
2430
|
-
unsafe { sn_entry(value, i) },
|
|
2431
|
-
depth,
|
|
2432
|
-
budget,
|
|
2433
|
-
) {
|
|
2434
|
-
Valid => return Invalid,
|
|
2435
|
-
Unsupported => return Unsupported,
|
|
2436
|
-
Invalid => {}
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
return Valid;
|
|
3012
|
+
return unique_linear(value, depth, budget, false);
|
|
2441
3013
|
}
|
|
2442
3014
|
let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
|
|
2443
3015
|
// This is a checked optimization hint, not an instance shape assumption.
|
|
@@ -2456,11 +3028,54 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
|
|
|
2456
3028
|
}
|
|
2457
3029
|
}
|
|
2458
3030
|
let mut fingerprints = structural::Cache::batch(depth);
|
|
2459
|
-
indexed_unique(
|
|
3031
|
+
let result = indexed_unique(
|
|
2460
3032
|
values,
|
|
2461
|
-
|item|
|
|
3033
|
+
|item| {
|
|
3034
|
+
fingerprint_cached(
|
|
3035
|
+
item,
|
|
3036
|
+
depth,
|
|
3037
|
+
fixnum_flag,
|
|
3038
|
+
big_hint,
|
|
3039
|
+
&mut fingerprints,
|
|
3040
|
+
budget,
|
|
3041
|
+
)
|
|
3042
|
+
},
|
|
2462
3043
|
|a, b| equal_budgeted(a, b, depth, budget),
|
|
2463
|
-
)
|
|
3044
|
+
);
|
|
3045
|
+
if result == Unsupported && budget.failure.get() == 0 && budget.checkpoint() {
|
|
3046
|
+
unique_linear(value, depth, budget, true)
|
|
3047
|
+
} else {
|
|
3048
|
+
result
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
// Index availability is an optimization decision. Exact ordered comparisons
|
|
3052
|
+
// can still decide uniqueness when a depth or memory bound declines hashing.
|
|
3053
|
+
fn unique_linear(value: Value, depth: usize, budget: &budget::Budget, check_hash: bool) -> Verdict {
|
|
3054
|
+
for i in 0..unsafe { sn_len(value) } {
|
|
3055
|
+
if check_hash {
|
|
3056
|
+
match instance_graph::ordinary_graph(unsafe { sn_entry(value, i) }, budget) {
|
|
3057
|
+
instance_graph::Safety::Ordinary => {}
|
|
3058
|
+
instance_graph::Safety::Cyclic => {
|
|
3059
|
+
budget.failure.set(STATUS_STACK_ERROR);
|
|
3060
|
+
return Unsupported;
|
|
3061
|
+
}
|
|
3062
|
+
instance_graph::Safety::Unsupported => return Unsupported,
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
for previous in 0..i {
|
|
3066
|
+
match equal_budgeted(
|
|
3067
|
+
unsafe { sn_entry(value, previous) },
|
|
3068
|
+
unsafe { sn_entry(value, i) },
|
|
3069
|
+
depth,
|
|
3070
|
+
budget,
|
|
3071
|
+
) {
|
|
3072
|
+
Valid => return Invalid,
|
|
3073
|
+
Unsupported => return Unsupported,
|
|
3074
|
+
Invalid => {}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
Valid
|
|
2464
3079
|
}
|
|
2465
3080
|
// Keep Big-only sampling and retry state out of the common scalar path.
|
|
2466
3081
|
#[inline(never)]
|
|
@@ -2800,6 +3415,7 @@ struct ObjectContext<'a> {
|
|
|
2800
3415
|
rules: &'a Object,
|
|
2801
3416
|
depth: usize,
|
|
2802
3417
|
active: scope::Context<'a>,
|
|
3418
|
+
names_only: bool,
|
|
2803
3419
|
}
|
|
2804
3420
|
/// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
|
|
2805
3421
|
/// # Safety
|
|
@@ -2814,15 +3430,21 @@ pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, val
|
|
|
2814
3430
|
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2815
3431
|
return Unsupported as i32;
|
|
2816
3432
|
}
|
|
2817
|
-
if
|
|
3433
|
+
if (c.names_only || !c.engine.caches.runtime_order())
|
|
3434
|
+
&& let Some(child) = c.rules.names
|
|
3435
|
+
{
|
|
2818
3436
|
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
2819
3437
|
if result != Valid {
|
|
2820
3438
|
return result as i32;
|
|
2821
3439
|
}
|
|
2822
3440
|
}
|
|
2823
|
-
|
|
3441
|
+
if c.names_only {
|
|
3442
|
+
return Valid as i32;
|
|
3443
|
+
}
|
|
3444
|
+
match c.rules.visit_value_rules(
|
|
2824
3445
|
c.engine.caches,
|
|
2825
3446
|
&c.engine.budget,
|
|
3447
|
+
c.engine.caches.runtime_order(),
|
|
2826
3448
|
key,
|
|
2827
3449
|
None,
|
|
2828
3450
|
|_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
|
|
@@ -2873,9 +3495,17 @@ pub unsafe extern "C" fn sn_compile(
|
|
|
2873
3495
|
e.bind_dynamic_anchors()?;
|
|
2874
3496
|
e.simplify_dynamic_references();
|
|
2875
3497
|
e.compile_memoization();
|
|
2876
|
-
|
|
3498
|
+
e.compile_native_validity_support();
|
|
3499
|
+
e.compile_kind_demands();
|
|
3500
|
+
Ok::<_, Value>(
|
|
3501
|
+
!e.nodes
|
|
3502
|
+
.iter()
|
|
3503
|
+
.flatten()
|
|
3504
|
+
.any(|op| matches!(op, Op::Unsupported)),
|
|
3505
|
+
)
|
|
2877
3506
|
})) {
|
|
2878
|
-
Ok(Ok(
|
|
3507
|
+
Ok(Ok(true)) => VERDICT_VALID,
|
|
3508
|
+
Ok(Ok(false)) => VERDICT_UNSUPPORTED,
|
|
2879
3509
|
Ok(Err(value)) => {
|
|
2880
3510
|
unsafe { *error = value };
|
|
2881
3511
|
STATUS_EXCEPTION
|
|
@@ -2894,12 +3524,44 @@ pub unsafe extern "C" fn sn_node_index(engine: *const Engine, program: Value) ->
|
|
|
2894
3524
|
.unwrap_or(usize::MAX)
|
|
2895
3525
|
}
|
|
2896
3526
|
/// # Safety
|
|
3527
|
+
/// Engine must be initialized, node compiled, and flags writable.
|
|
3528
|
+
#[unsafe(no_mangle)]
|
|
3529
|
+
pub unsafe extern "C" fn sn_callback_kind(
|
|
3530
|
+
engine: *const Engine,
|
|
3531
|
+
node: usize,
|
|
3532
|
+
flags: *mut i32,
|
|
3533
|
+
) -> i32 {
|
|
3534
|
+
let (kind, value) = unsafe { &*engine }.scalar_callback_kind(node);
|
|
3535
|
+
unsafe { *flags = value };
|
|
3536
|
+
kind
|
|
3537
|
+
}
|
|
3538
|
+
/// # Safety
|
|
3539
|
+
/// Engine must be initialized and root compiled.
|
|
3540
|
+
#[unsafe(no_mangle)]
|
|
3541
|
+
pub unsafe extern "C" fn sn_reference_context(engine: *const Engine) -> i32 {
|
|
3542
|
+
// Any scalar Ruby primitive can reenter a schema block, including from
|
|
3543
|
+
// tracked/diagnostic traversals that cannot admit container callbacks.
|
|
3544
|
+
i32::from(unsafe { &*engine }.has_references)
|
|
3545
|
+
}
|
|
3546
|
+
/// # Safety
|
|
2897
3547
|
/// Engine must be initialized.
|
|
2898
3548
|
#[unsafe(no_mangle)]
|
|
2899
3549
|
pub unsafe extern "C" fn sn_root_index(engine: *const Engine) -> usize {
|
|
2900
3550
|
unsafe { &*engine }.root
|
|
2901
3551
|
}
|
|
2902
3552
|
/// # Safety
|
|
3553
|
+
/// Engine must be live and compilation must have finished.
|
|
3554
|
+
#[unsafe(no_mangle)]
|
|
3555
|
+
pub unsafe extern "C" fn sn_has_unresolved_references(engine: *const Engine) -> i32 {
|
|
3556
|
+
i32::from(
|
|
3557
|
+
unsafe { &*engine }
|
|
3558
|
+
.nodes
|
|
3559
|
+
.iter()
|
|
3560
|
+
.flatten()
|
|
3561
|
+
.any(|op| matches!(op, Op::UnresolvedRef(_))),
|
|
3562
|
+
)
|
|
3563
|
+
}
|
|
3564
|
+
/// # Safety
|
|
2903
3565
|
/// Roots must be live and compilation must have finished.
|
|
2904
3566
|
#[unsafe(no_mangle)]
|
|
2905
3567
|
pub unsafe extern "C" fn sn_root_count(roots: *const Roots) -> usize {
|
|
@@ -2917,21 +3579,25 @@ pub unsafe extern "C" fn sn_root_at(roots: *const Roots, index: usize) -> Value
|
|
|
2917
3579
|
#[unsafe(no_mangle)]
|
|
2918
3580
|
pub unsafe extern "C" fn sn_valid(
|
|
2919
3581
|
engine: *const Engine,
|
|
2920
|
-
caches: *const
|
|
3582
|
+
caches: *const runtime::Caches,
|
|
2921
3583
|
root: usize,
|
|
2922
3584
|
value: Value,
|
|
3585
|
+
depth: usize,
|
|
2923
3586
|
) -> i32 {
|
|
2924
3587
|
catch_unwind(AssertUnwindSafe(|| {
|
|
2925
3588
|
let e = unsafe { &*engine };
|
|
2926
|
-
let local =
|
|
3589
|
+
let local = runtime::Caches::default();
|
|
2927
3590
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
2928
|
-
|
|
3591
|
+
caches.admit(e, root);
|
|
3592
|
+
let execution = Execution {
|
|
2929
3593
|
engine: e,
|
|
2930
3594
|
caches,
|
|
2931
3595
|
memo: memo::Memo::default(),
|
|
2932
3596
|
budget: budget::Budget::default(),
|
|
2933
|
-
}
|
|
2934
|
-
.eval(root, value,
|
|
3597
|
+
};
|
|
3598
|
+
let result = execution.eval(root, value, depth, scope::Context::default());
|
|
3599
|
+
caches.finish_callback_report(result);
|
|
3600
|
+
execution.budget.status(result)
|
|
2935
3601
|
}))
|
|
2936
3602
|
.unwrap_or(STATUS_PANIC)
|
|
2937
3603
|
}
|
|
@@ -2940,24 +3606,26 @@ pub unsafe extern "C" fn sn_valid(
|
|
|
2940
3606
|
#[unsafe(no_mangle)]
|
|
2941
3607
|
pub unsafe extern "C" fn sn_valid_with_limits(
|
|
2942
3608
|
engine: *const Engine,
|
|
2943
|
-
caches: *const
|
|
3609
|
+
caches: *const runtime::Caches,
|
|
2944
3610
|
root: usize,
|
|
2945
3611
|
value: Value,
|
|
3612
|
+
depth: usize,
|
|
2946
3613
|
limits: *const budget::Limits,
|
|
2947
3614
|
outcome: *mut budget::Outcome,
|
|
2948
3615
|
) -> i32 {
|
|
2949
|
-
let budget = budget::Budget::new(unsafe { *limits });
|
|
2950
3616
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
2951
|
-
let local =
|
|
3617
|
+
let local = runtime::Caches::default();
|
|
2952
3618
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
3619
|
+
let e = unsafe { &*engine };
|
|
3620
|
+
caches.admit(e, root);
|
|
2953
3621
|
let execution = Execution {
|
|
2954
3622
|
engine: unsafe { &*engine },
|
|
2955
3623
|
caches,
|
|
2956
3624
|
memo: memo::Memo::default(),
|
|
2957
|
-
budget,
|
|
3625
|
+
budget: caches.budget(unsafe { *limits }),
|
|
2958
3626
|
};
|
|
2959
3627
|
let result = if execution.budget.check_deadline() {
|
|
2960
|
-
execution.eval(root, value,
|
|
3628
|
+
execution.eval(root, value, depth, scope::Context::default())
|
|
2961
3629
|
} else {
|
|
2962
3630
|
Unsupported
|
|
2963
3631
|
};
|
|
@@ -2969,7 +3637,8 @@ pub unsafe extern "C" fn sn_valid_with_limits(
|
|
|
2969
3637
|
unsafe {
|
|
2970
3638
|
((*outcome).kind, (*outcome).attempted) = execution.budget.hit.get();
|
|
2971
3639
|
}
|
|
2972
|
-
result
|
|
3640
|
+
caches.finish_callback_report(result);
|
|
3641
|
+
execution.budget.status(result)
|
|
2973
3642
|
}));
|
|
2974
3643
|
result.unwrap_or(STATUS_PANIC)
|
|
2975
3644
|
}
|
|
@@ -3018,7 +3687,12 @@ pub extern "C" fn sn_engine_header_size() -> usize {
|
|
|
3018
3687
|
pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
3019
3688
|
let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
|
|
3020
3689
|
let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
|
|
3690
|
+
let programs = unsafe { &*std::ptr::addr_of!((*engine).programs) };
|
|
3021
3691
|
let contextual = unsafe { &*std::ptr::addr_of!((*engine).memo_contextual) };
|
|
3692
|
+
let kinds = unsafe { &*std::ptr::addr_of!((*engine).kind_demand) };
|
|
3693
|
+
let callback_roots = unsafe { &*std::ptr::addr_of!((*engine).callback_roots) };
|
|
3694
|
+
let validity_supported = unsafe { &*std::ptr::addr_of!((*engine).validity_supported) };
|
|
3695
|
+
let report_supported = unsafe { &*std::ptr::addr_of!((*engine).report_supported) };
|
|
3022
3696
|
let guarded = unsafe { &*std::ptr::addr_of!((*engine).memo_guarded) };
|
|
3023
3697
|
let sources = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_sources) };
|
|
3024
3698
|
let same = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_same_sources) };
|
|
@@ -3027,6 +3701,9 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3027
3701
|
let projection = unsafe { &*std::ptr::addr_of!((*engine).memo_scope_projection) };
|
|
3028
3702
|
std::mem::size_of::<Engine>()
|
|
3029
3703
|
+ projection.memory_usage()
|
|
3704
|
+
+ kinds.capacity() * std::mem::size_of::<KindDemand>()
|
|
3705
|
+
+ (callback_roots.capacity() + validity_supported.capacity() + report_supported.capacity())
|
|
3706
|
+
* std::mem::size_of::<bool>()
|
|
3030
3707
|
+ (contextual.capacity()
|
|
3031
3708
|
+ guarded.capacity()
|
|
3032
3709
|
+ sources.capacity()
|
|
@@ -3034,6 +3711,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3034
3711
|
+ scalar.capacity()
|
|
3035
3712
|
+ amplification.capacity())
|
|
3036
3713
|
* std::mem::size_of::<bool>()
|
|
3714
|
+
+ programs.capacity() * std::mem::size_of::<Value>()
|
|
3037
3715
|
+ nodes.capacity() * std::mem::size_of::<Vec<Op>>()
|
|
3038
3716
|
+ operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
|
|
3039
3717
|
+ operands
|
|
@@ -3048,6 +3726,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3048
3726
|
.iter()
|
|
3049
3727
|
.flatten()
|
|
3050
3728
|
.map(|op| match op {
|
|
3729
|
+
Op::Number(_, rules) => rules.memory_usage(),
|
|
3051
3730
|
Op::Enum(values) => values.memory_usage(),
|
|
3052
3731
|
Op::String(_, rules) => {
|
|
3053
3732
|
std::mem::size_of::<StringRules>()
|