schemurai 3.0.1 → 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 +48 -1
- data/ext/schemurai_native/Cargo.toml +3 -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 +56 -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 +8 -6
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/lib.rs +910 -237
- 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 +113 -101
- 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 +24 -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;
|
|
@@ -18,6 +19,9 @@ mod memo;
|
|
|
18
19
|
mod memo_scope;
|
|
19
20
|
mod number;
|
|
20
21
|
mod pattern;
|
|
22
|
+
mod pointer;
|
|
23
|
+
mod runtime;
|
|
24
|
+
mod string;
|
|
21
25
|
// Some shared constants are consumed only by C or Ruby.
|
|
22
26
|
#[allow(dead_code)]
|
|
23
27
|
mod protocol {
|
|
@@ -42,6 +46,7 @@ unsafe extern "C" {
|
|
|
42
46
|
) -> i32;
|
|
43
47
|
fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
|
|
44
48
|
fn sn_true(value: Value) -> i32;
|
|
49
|
+
fn sn_truthy(value: Value) -> i32;
|
|
45
50
|
fn sn_fixnum(value: Value) -> i32;
|
|
46
51
|
fn sn_fixnum_flag() -> Value;
|
|
47
52
|
fn sn_array_data(value: Value) -> *const Value;
|
|
@@ -56,6 +61,8 @@ unsafe extern "C" {
|
|
|
56
61
|
required: i32,
|
|
57
62
|
) -> i32;
|
|
58
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;
|
|
59
66
|
fn sn_is_bignum(value: Value) -> i32;
|
|
60
67
|
fn sn_big256(value: Value, words: *mut u64) -> i32;
|
|
61
68
|
fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
|
|
@@ -116,13 +123,24 @@ enum Verdict {
|
|
|
116
123
|
}
|
|
117
124
|
use Verdict::*;
|
|
118
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
|
+
}
|
|
119
136
|
const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
|
|
120
137
|
#[derive(Debug)]
|
|
121
138
|
enum Op {
|
|
122
139
|
Unsupported,
|
|
140
|
+
UnresolvedRef(Value),
|
|
123
141
|
Boolean(bool),
|
|
124
142
|
Ref(scope::Reference),
|
|
125
|
-
Type(i32),
|
|
143
|
+
Type(i32, bool),
|
|
126
144
|
Const(Value),
|
|
127
145
|
Enum(Box<enumeration::Enumeration>),
|
|
128
146
|
All(Vec<usize>),
|
|
@@ -137,6 +155,7 @@ enum Op {
|
|
|
137
155
|
}
|
|
138
156
|
#[derive(Debug)]
|
|
139
157
|
struct Array {
|
|
158
|
+
header: container::Header,
|
|
140
159
|
min: Option<usize>,
|
|
141
160
|
max: Option<usize>,
|
|
142
161
|
unique: bool,
|
|
@@ -147,10 +166,14 @@ struct Array {
|
|
|
147
166
|
contains: Option<usize>,
|
|
148
167
|
min_contains: usize,
|
|
149
168
|
max_contains: Option<usize>,
|
|
169
|
+
count_contains: bool,
|
|
150
170
|
unevaluated: Option<usize>,
|
|
151
171
|
}
|
|
152
172
|
#[derive(Debug)]
|
|
153
173
|
struct StringRules {
|
|
174
|
+
source: Value,
|
|
175
|
+
runtime_flags: i32,
|
|
176
|
+
runtime_format: bool,
|
|
154
177
|
min: Option<usize>,
|
|
155
178
|
max: Option<usize>,
|
|
156
179
|
pattern: Option<pattern::Pattern>,
|
|
@@ -197,15 +220,31 @@ impl StringRules {
|
|
|
197
220
|
&& self.format.is_none()
|
|
198
221
|
&& self.content.is_none()
|
|
199
222
|
}
|
|
200
|
-
fn eval_length_guarded(
|
|
201
|
-
|
|
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
|
+
}
|
|
202
238
|
}
|
|
203
|
-
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
|
+
}
|
|
204
243
|
let mut data = std::ptr::null();
|
|
205
244
|
let mut len = 0;
|
|
206
245
|
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
207
246
|
if encoding == -TYPE_UNSUPPORTED {
|
|
208
|
-
|
|
247
|
+
self.runtime_valid(caches, required, value)
|
|
209
248
|
} else if encoding < 0 {
|
|
210
249
|
verdict(required == 0)
|
|
211
250
|
} else {
|
|
@@ -213,12 +252,12 @@ impl StringRules {
|
|
|
213
252
|
self.eval_with_view::<true>(caches, value, Some((bytes, encoding)))
|
|
214
253
|
}
|
|
215
254
|
}
|
|
216
|
-
fn eval(&self, caches: &
|
|
255
|
+
fn eval(&self, caches: &runtime::Caches, value: Value) -> Verdict {
|
|
217
256
|
self.eval_with_view::<false>(caches, value, None)
|
|
218
257
|
}
|
|
219
258
|
fn eval_with_view<const BORROWED: bool>(
|
|
220
259
|
&self,
|
|
221
|
-
caches: &
|
|
260
|
+
caches: &runtime::Caches,
|
|
222
261
|
value: Value,
|
|
223
262
|
view: Option<(&[u8], i32)>,
|
|
224
263
|
) -> Verdict {
|
|
@@ -232,8 +271,8 @@ impl StringRules {
|
|
|
232
271
|
}
|
|
233
272
|
if let Some(pattern) = &self.pattern {
|
|
234
273
|
let result = match view {
|
|
235
|
-
Some((bytes, encoding)) => pattern.eval_bytes(caches, bytes, encoding),
|
|
236
|
-
None => pattern.
|
|
274
|
+
Some((bytes, encoding)) => pattern.eval_bytes(caches, value, bytes, encoding),
|
|
275
|
+
None => pattern.assertion(caches, value),
|
|
237
276
|
};
|
|
238
277
|
if result != Valid {
|
|
239
278
|
return result;
|
|
@@ -249,16 +288,41 @@ impl StringRules {
|
|
|
249
288
|
}
|
|
250
289
|
}
|
|
251
290
|
self.content.as_ref().map_or(Valid, |content| match view {
|
|
252
|
-
Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
|
|
253
|
-
None => content.eval(value),
|
|
291
|
+
Some((bytes, encoding)) => content.eval_borrowed(caches, value, bytes, encoding),
|
|
292
|
+
None => content.eval(caches, value),
|
|
254
293
|
})
|
|
255
294
|
}
|
|
256
295
|
}
|
|
257
296
|
#[derive(Debug)]
|
|
258
297
|
enum DiagnosticDependency {
|
|
259
|
-
Required(Value, Vec<
|
|
298
|
+
Required(Value, Vec<RequiredKey>, &'static str),
|
|
260
299
|
Schema(Value, usize, &'static str),
|
|
261
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
|
+
}
|
|
262
326
|
#[derive(Clone, Copy, Debug)]
|
|
263
327
|
struct PropertyTarget {
|
|
264
328
|
name: Value,
|
|
@@ -267,11 +331,13 @@ struct PropertyTarget {
|
|
|
267
331
|
}
|
|
268
332
|
#[derive(Debug)]
|
|
269
333
|
struct Object {
|
|
334
|
+
header: container::Header,
|
|
335
|
+
inspect_required: bool,
|
|
270
336
|
record: Option<RecordObject>,
|
|
271
337
|
min: Option<usize>,
|
|
272
338
|
max: Option<usize>,
|
|
273
|
-
required: Vec<
|
|
274
|
-
required_order: Vec<
|
|
339
|
+
required: Vec<RequiredKey>,
|
|
340
|
+
required_order: Vec<RequiredKey>,
|
|
275
341
|
properties: Vec<(Value, usize)>,
|
|
276
342
|
property_targets: HashMap<tracking::JsonKey, PropertyTarget, BuildHasherDefault<BucketHasher>>,
|
|
277
343
|
patterns: Vec<(pattern::Pattern, usize)>,
|
|
@@ -283,7 +349,7 @@ struct Object {
|
|
|
283
349
|
property_indices: Vec<usize>,
|
|
284
350
|
additional: Option<usize>,
|
|
285
351
|
names: Option<usize>,
|
|
286
|
-
dependent_required: Vec<(Value, Vec<
|
|
352
|
+
dependent_required: Vec<(Value, Vec<RequiredKey>)>,
|
|
287
353
|
dependent_schemas: Vec<(Value, usize)>,
|
|
288
354
|
diagnostic_dependencies: Vec<DiagnosticDependency>,
|
|
289
355
|
unevaluated: Option<usize>,
|
|
@@ -352,13 +418,18 @@ impl RecordShortIndex {
|
|
|
352
418
|
impl Object {
|
|
353
419
|
// Keep these calls at their original evaluation positions: property
|
|
354
420
|
// failures and unsupported children must retain their ordering.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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)
|
|
358
429
|
}
|
|
359
|
-
#[inline]
|
|
360
430
|
fn check_keys(
|
|
361
|
-
keys: &[
|
|
431
|
+
keys: &[RequiredKey],
|
|
432
|
+
caches: &runtime::Caches,
|
|
362
433
|
value: Value,
|
|
363
434
|
undefined: Value,
|
|
364
435
|
budget: &budget::Budget,
|
|
@@ -367,15 +438,17 @@ impl Object {
|
|
|
367
438
|
if !budget.checkpoint() {
|
|
368
439
|
return Unsupported;
|
|
369
440
|
}
|
|
370
|
-
|
|
371
|
-
|
|
441
|
+
match key.missing(caches, value, undefined) {
|
|
442
|
+
Ok(false) => {}
|
|
443
|
+
Ok(true) => return Invalid,
|
|
444
|
+
Err(status) => return status,
|
|
372
445
|
}
|
|
373
446
|
}
|
|
374
447
|
Valid
|
|
375
448
|
}
|
|
376
|
-
#[inline]
|
|
377
449
|
fn check_dependent_required(
|
|
378
450
|
&self,
|
|
451
|
+
caches: &runtime::Caches,
|
|
379
452
|
value: Value,
|
|
380
453
|
undefined: Value,
|
|
381
454
|
budget: &budget::Budget,
|
|
@@ -385,7 +458,7 @@ impl Object {
|
|
|
385
458
|
return Unsupported;
|
|
386
459
|
}
|
|
387
460
|
if !missing(value, *name, undefined) {
|
|
388
|
-
let status = Self::check_keys(required, value, undefined, budget);
|
|
461
|
+
let status = Self::check_keys(required, caches, value, undefined, budget);
|
|
389
462
|
if status != Valid {
|
|
390
463
|
return status;
|
|
391
464
|
}
|
|
@@ -482,10 +555,11 @@ impl Object {
|
|
|
482
555
|
// All execution policies use the same property/pattern/additional routing.
|
|
483
556
|
// Schema-order validity scans have already visited declared properties;
|
|
484
557
|
// input-order diagnostic scans visit them here as well.
|
|
485
|
-
fn visit_value_rules
|
|
558
|
+
fn visit_value_rules(
|
|
486
559
|
&self,
|
|
487
|
-
caches: &
|
|
560
|
+
caches: &runtime::Caches,
|
|
488
561
|
budget: &budget::Budget,
|
|
562
|
+
properties: bool,
|
|
489
563
|
key: Value,
|
|
490
564
|
location: Option<tracking::JsonKey>,
|
|
491
565
|
mut visit: impl FnMut(&'static str, Option<Value>, usize) -> Result<(), Verdict>,
|
|
@@ -494,7 +568,7 @@ impl Object {
|
|
|
494
568
|
// Reuse that result, and skip hashing for pattern-only object rules.
|
|
495
569
|
let mut matched = if self.properties.is_empty() {
|
|
496
570
|
false
|
|
497
|
-
} else if
|
|
571
|
+
} else if properties {
|
|
498
572
|
if let Some(target) = self
|
|
499
573
|
.property_targets
|
|
500
574
|
.get(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
|
|
@@ -549,6 +623,7 @@ impl Object {
|
|
|
549
623
|
|
|
550
624
|
fn with_record(mut self) -> Self {
|
|
551
625
|
if self.required_order.len() > 64
|
|
626
|
+
|| self.required_order.iter().any(|key| !key.ordinary_string)
|
|
552
627
|
|| !self.patterns.is_empty()
|
|
553
628
|
|| self.names.is_some()
|
|
554
629
|
|| !self.diagnostic_dependencies.is_empty()
|
|
@@ -573,7 +648,8 @@ impl Object {
|
|
|
573
648
|
)
|
|
574
649
|
})
|
|
575
650
|
.collect();
|
|
576
|
-
for
|
|
651
|
+
for key in &self.required_order {
|
|
652
|
+
let name = key.name;
|
|
577
653
|
fields
|
|
578
654
|
.entry(tracking::JsonKey::new(name))
|
|
579
655
|
.or_insert(RecordField {
|
|
@@ -593,8 +669,8 @@ impl Object {
|
|
|
593
669
|
}
|
|
594
670
|
// Required names share the field bits already used for duplicate-key
|
|
595
671
|
// detection. Repeated required names therefore need no extra state.
|
|
596
|
-
let required = self.required_order.iter().fold(0, |bits,
|
|
597
|
-
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
|
|
598
674
|
});
|
|
599
675
|
let fields: Vec<_> = fields.into_values().collect();
|
|
600
676
|
let short_index = RecordShortIndex::compile(&fields);
|
|
@@ -615,9 +691,15 @@ pub struct Roots {
|
|
|
615
691
|
#[derive(Default)]
|
|
616
692
|
pub struct Engine {
|
|
617
693
|
nodes: Vec<Vec<Op>>,
|
|
694
|
+
programs: Vec<Value>,
|
|
618
695
|
pattern_cache_count: usize,
|
|
619
696
|
error_operands: Vec<Vec<(i32, Value)>>,
|
|
620
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,
|
|
621
703
|
memoizable: Vec<bool>,
|
|
622
704
|
memo_contextual: Vec<bool>,
|
|
623
705
|
memo_guarded: Vec<bool>,
|
|
@@ -638,6 +720,7 @@ pub struct Engine {
|
|
|
638
720
|
root: usize,
|
|
639
721
|
fixnum_flag: Value,
|
|
640
722
|
compiler: Value,
|
|
723
|
+
runtime_messages: bool,
|
|
641
724
|
validate_format: bool,
|
|
642
725
|
validate_content: bool,
|
|
643
726
|
undefined: Value,
|
|
@@ -657,6 +740,24 @@ impl Engine {
|
|
|
657
740
|
}
|
|
658
741
|
Ok(self.retain(result))
|
|
659
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
|
+
}
|
|
660
761
|
fn values(&mut self, value: Value) -> Vec<Value> {
|
|
661
762
|
if unsafe { sn_same(value, sn_nil()) } != 0 {
|
|
662
763
|
return vec![];
|
|
@@ -760,7 +861,7 @@ impl Engine {
|
|
|
760
861
|
// shape-independent leaves can ignore resource/path metadata.
|
|
761
862
|
ka == kb
|
|
762
863
|
&& match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
|
|
763
|
-
([Op::Type(a)], [Op::Type(b)]) => a == b,
|
|
864
|
+
([Op::Type(a, ua)], [Op::Type(b, ub)]) => a == b && ua == ub,
|
|
764
865
|
([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
|
|
765
866
|
_ => false,
|
|
766
867
|
}
|
|
@@ -780,8 +881,10 @@ impl Engine {
|
|
|
780
881
|
let index = self.nodes.len();
|
|
781
882
|
self.indices.insert(program, index);
|
|
782
883
|
self.nodes.push(vec![]);
|
|
884
|
+
self.programs.push(program);
|
|
783
885
|
self.error_operands.push(vec![]);
|
|
784
886
|
self.tracks.push(false);
|
|
887
|
+
self.kind_demand.push(KindDemand::None);
|
|
785
888
|
self.metadata.push(scope::Metadata::default());
|
|
786
889
|
let code = self.field(program, b"code\0")?;
|
|
787
890
|
let flags = unsafe { sn_integer(sn_entry(code, 0)) };
|
|
@@ -795,19 +898,20 @@ impl Engine {
|
|
|
795
898
|
error_operands.push((opcode, operand));
|
|
796
899
|
let op = match opcode {
|
|
797
900
|
OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
|
|
798
|
-
OP_TYPE_NULL => Op::Type(TYPE_NULL),
|
|
799
|
-
OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
|
|
800
|
-
OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
|
|
801
|
-
OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
|
|
802
|
-
OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
|
|
803
|
-
OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
|
|
804
|
-
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),
|
|
805
908
|
OP_TYPES => {
|
|
806
909
|
let mask = self.field(operand, b"mask\0")?;
|
|
807
|
-
Op::Type(unsafe { sn_integer(mask) } as i32)
|
|
910
|
+
Op::Type(unsafe { sn_integer(mask) } as i32, true)
|
|
808
911
|
}
|
|
809
912
|
OP_CONST => Op::Const(operand),
|
|
810
913
|
OP_ENUM => Op::Enum(Box::new(enumeration::Enumeration::compile(
|
|
914
|
+
operand,
|
|
811
915
|
self.values(operand),
|
|
812
916
|
self.fixnum_flag,
|
|
813
917
|
))),
|
|
@@ -856,9 +960,10 @@ impl Engine {
|
|
|
856
960
|
None
|
|
857
961
|
};
|
|
858
962
|
let assertion = self.field(operand, b"format_assertion\0")?;
|
|
859
|
-
let
|
|
860
|
-
|
|
861
|
-
|
|
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 {
|
|
862
967
|
let name = self.field(format, b"name\0")?;
|
|
863
968
|
if unsafe { sn_same(name, sn_nil()) } != 0 {
|
|
864
969
|
None
|
|
@@ -868,6 +973,10 @@ impl Engine {
|
|
|
868
973
|
} else {
|
|
869
974
|
None
|
|
870
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);
|
|
871
980
|
Op::String(
|
|
872
981
|
if opcode == OP_TYPED_STRING {
|
|
873
982
|
TYPE_STRING
|
|
@@ -875,11 +984,16 @@ impl Engine {
|
|
|
875
984
|
0
|
|
876
985
|
},
|
|
877
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,
|
|
878
992
|
format,
|
|
879
993
|
content,
|
|
880
994
|
min: self.size_field(operand, b"min_length\0")?,
|
|
881
995
|
max: self.size_field(operand, b"max_length\0")?,
|
|
882
|
-
pattern: if unsafe {
|
|
996
|
+
pattern: if unsafe { sn_truthy(pattern) } == 0 {
|
|
883
997
|
None
|
|
884
998
|
} else {
|
|
885
999
|
Some(pattern::Pattern::compile(
|
|
@@ -912,7 +1026,18 @@ impl Engine {
|
|
|
912
1026
|
0
|
|
913
1027
|
},
|
|
914
1028
|
Box::new(Array {
|
|
915
|
-
|
|
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,
|
|
916
1041
|
min: self.size_field(operand, b"min_items\0")?,
|
|
917
1042
|
max: self.size_field(operand, b"max_items\0")?,
|
|
918
1043
|
prefix,
|
|
@@ -922,6 +1047,9 @@ impl Engine {
|
|
|
922
1047
|
contains: self.child(operand, b"contains\0")?,
|
|
923
1048
|
min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
|
|
924
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,
|
|
925
1053
|
unevaluated: self.child(operand, b"unevaluated\0")?,
|
|
926
1054
|
}),
|
|
927
1055
|
)
|
|
@@ -944,14 +1072,21 @@ impl Engine {
|
|
|
944
1072
|
let pattern_group =
|
|
945
1073
|
pattern::PatternGroup::compile(&pattern_names, &patterns);
|
|
946
1074
|
let required = self.field(operand, b"required\0")?;
|
|
947
|
-
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);
|
|
948
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();
|
|
949
1085
|
let properties = self.field(operand, b"properties\0")?;
|
|
950
1086
|
let properties = self.compile_map(properties)?;
|
|
951
|
-
if
|
|
1087
|
+
if properties
|
|
952
1088
|
.iter()
|
|
953
|
-
.
|
|
954
|
-
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1089
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
955
1090
|
{
|
|
956
1091
|
ops.push(Op::Unsupported);
|
|
957
1092
|
continue;
|
|
@@ -983,7 +1118,7 @@ impl Engine {
|
|
|
983
1118
|
// content-keyed sets and the already compiled property index.
|
|
984
1119
|
let indexed_required = properties.len().saturating_mul(required.len()) > 64;
|
|
985
1120
|
let required_keys: Option<tracking::KeySet> = indexed_required.then(|| {
|
|
986
|
-
|
|
1121
|
+
required_strings
|
|
987
1122
|
.iter()
|
|
988
1123
|
.map(|&name| tracking::JsonKey::new(name))
|
|
989
1124
|
.collect()
|
|
@@ -993,7 +1128,7 @@ impl Engine {
|
|
|
993
1128
|
.map(|key| {
|
|
994
1129
|
required_keys.as_ref().map_or_else(
|
|
995
1130
|
|| {
|
|
996
|
-
|
|
1131
|
+
required_strings.iter().any(
|
|
997
1132
|
|&name| unsafe { sn_string_equal(key.0, name) } != 0,
|
|
998
1133
|
)
|
|
999
1134
|
},
|
|
@@ -1008,7 +1143,11 @@ impl Engine {
|
|
|
1008
1143
|
.collect();
|
|
1009
1144
|
let required = required
|
|
1010
1145
|
.into_iter()
|
|
1011
|
-
.filter(
|
|
1146
|
+
.filter(|key| {
|
|
1147
|
+
if !key.ordinary_string {
|
|
1148
|
+
return true;
|
|
1149
|
+
}
|
|
1150
|
+
let name = key.name;
|
|
1012
1151
|
if indexed_required {
|
|
1013
1152
|
!property_targets.contains_key(&tracking::JsonKey::new(name))
|
|
1014
1153
|
} else {
|
|
@@ -1026,7 +1165,7 @@ impl Engine {
|
|
|
1026
1165
|
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1027
1166
|
let dependency = self.retain(unsafe { sn_entry(pair, 1) });
|
|
1028
1167
|
if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
|
|
1029
|
-
let names = self.
|
|
1168
|
+
let names = self.required_keys(dependency);
|
|
1030
1169
|
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1031
1170
|
name,
|
|
1032
1171
|
names.clone(),
|
|
@@ -1045,7 +1184,7 @@ impl Engine {
|
|
|
1045
1184
|
}
|
|
1046
1185
|
for pair in self.values(dependent) {
|
|
1047
1186
|
let name = self.retain(unsafe { sn_entry(pair, 0) });
|
|
1048
|
-
let names = self.
|
|
1187
|
+
let names = self.required_keys(unsafe { sn_entry(pair, 1) });
|
|
1049
1188
|
diagnostic_dependencies.push(DiagnosticDependency::Required(
|
|
1050
1189
|
name,
|
|
1051
1190
|
names.clone(),
|
|
@@ -1059,19 +1198,25 @@ impl Engine {
|
|
|
1059
1198
|
DiagnosticDependency::Schema(name, child, "dependentSchemas")
|
|
1060
1199
|
}));
|
|
1061
1200
|
dependent_schemas.extend(schemas);
|
|
1062
|
-
if dependent_required
|
|
1063
|
-
(unsafe { sn_kind(*name) }) != TYPE_STRING
|
|
1064
|
-
|| keys
|
|
1065
|
-
.iter()
|
|
1066
|
-
.any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1067
|
-
}) || dependent_schemas
|
|
1201
|
+
if dependent_required
|
|
1068
1202
|
.iter()
|
|
1069
|
-
.any(
|
|
1203
|
+
.any(|(name, _)| unsafe { sn_kind(*name) } != TYPE_STRING)
|
|
1204
|
+
|| dependent_schemas
|
|
1205
|
+
.iter()
|
|
1206
|
+
.any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
|
|
1070
1207
|
{
|
|
1071
1208
|
ops.push(Op::Unsupported);
|
|
1072
1209
|
continue;
|
|
1073
1210
|
}
|
|
1074
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,
|
|
1075
1220
|
record: None,
|
|
1076
1221
|
min: self.size_field(operand, b"min_properties\0")?,
|
|
1077
1222
|
max: self.size_field(operand, b"max_properties\0")?,
|
|
@@ -1148,7 +1293,8 @@ impl Engine {
|
|
|
1148
1293
|
let mut target = 0;
|
|
1149
1294
|
if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
|
|
1150
1295
|
if unsafe { sn_resolution_error(target) } != 0 {
|
|
1151
|
-
|
|
1296
|
+
let message = self.field(target, b"message\0")?;
|
|
1297
|
+
Op::UnresolvedRef(message)
|
|
1152
1298
|
} else {
|
|
1153
1299
|
return Err(target);
|
|
1154
1300
|
}
|
|
@@ -1166,12 +1312,53 @@ impl Engine {
|
|
|
1166
1312
|
self.error_operands[index] = error_operands;
|
|
1167
1313
|
Ok(index)
|
|
1168
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
|
+
}
|
|
1169
1356
|
}
|
|
1170
1357
|
|
|
1171
|
-
// A view of immutable compiled data and validator-owned
|
|
1358
|
+
// A view of immutable compiled data and validator-owned runtime caches.
|
|
1172
1359
|
struct Execution<'a> {
|
|
1173
1360
|
engine: &'a Engine,
|
|
1174
|
-
caches: &'a
|
|
1361
|
+
caches: &'a runtime::Caches,
|
|
1175
1362
|
memo: memo::Memo,
|
|
1176
1363
|
budget: budget::Budget,
|
|
1177
1364
|
}
|
|
@@ -1193,9 +1380,14 @@ impl Execution<'_> {
|
|
|
1193
1380
|
value: Value,
|
|
1194
1381
|
depth: usize,
|
|
1195
1382
|
active: scope::Context<'_>,
|
|
1196
|
-
known_kind: Option
|
|
1383
|
+
known_kind: Option<&InstanceKind>,
|
|
1197
1384
|
) -> Verdict {
|
|
1198
|
-
|
|
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() {
|
|
1199
1391
|
return Unsupported;
|
|
1200
1392
|
}
|
|
1201
1393
|
let Some(key) = self
|
|
@@ -1223,7 +1415,7 @@ impl Execution<'_> {
|
|
|
1223
1415
|
value: Value,
|
|
1224
1416
|
depth: usize,
|
|
1225
1417
|
active: scope::Context<'_>,
|
|
1226
|
-
known_kind: Option
|
|
1418
|
+
known_kind: Option<&InstanceKind>,
|
|
1227
1419
|
) -> Verdict {
|
|
1228
1420
|
let frame = scope::ScopeFrame {
|
|
1229
1421
|
resource: self.metadata[node].resource,
|
|
@@ -1234,7 +1426,13 @@ impl Execution<'_> {
|
|
|
1234
1426
|
.or(self.metadata[node].primary_binding),
|
|
1235
1427
|
};
|
|
1236
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);
|
|
1237
1432
|
if self.tracks[node] {
|
|
1433
|
+
if let Some(result) = self.caches.callback_tracked(node, value) {
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1238
1436
|
return match known_kind {
|
|
1239
1437
|
Some(kind) => {
|
|
1240
1438
|
self.eval_tracked_kind(node, value, depth, active, kind)
|
|
@@ -1248,26 +1446,42 @@ impl Execution<'_> {
|
|
|
1248
1446
|
}
|
|
1249
1447
|
if let [Op::String(required, rules)] = self.nodes[node].as_slice()
|
|
1250
1448
|
&& rules.pattern.is_some()
|
|
1449
|
+
&& !self.caches.scalar_callback()
|
|
1251
1450
|
{
|
|
1252
1451
|
return rules.eval_guarded(self.caches, *required, value);
|
|
1253
1452
|
}
|
|
1254
|
-
let
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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) {
|
|
1262
1464
|
return Unsupported;
|
|
1263
1465
|
}
|
|
1264
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
|
+
}
|
|
1265
1472
|
if !self.budget.checkpoint() {
|
|
1266
1473
|
return Unsupported;
|
|
1267
1474
|
}
|
|
1268
1475
|
let outcome = match op {
|
|
1269
1476
|
Op::Ref(target) => {
|
|
1270
|
-
|
|
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
|
+
{
|
|
1271
1485
|
Valid
|
|
1272
1486
|
} else {
|
|
1273
1487
|
let frame = RefFrame {
|
|
@@ -1275,15 +1489,17 @@ impl Execution<'_> {
|
|
|
1275
1489
|
value,
|
|
1276
1490
|
parent: active.active,
|
|
1277
1491
|
};
|
|
1492
|
+
let reference = self.caches.reference_frame(program, value);
|
|
1493
|
+
let _guard = self.caches.reference_guard(&reference);
|
|
1278
1494
|
self.eval_with_kind(
|
|
1279
|
-
|
|
1495
|
+
target,
|
|
1280
1496
|
value,
|
|
1281
1497
|
depth + 1,
|
|
1282
1498
|
scope::Context {
|
|
1283
1499
|
active: Some(&frame),
|
|
1284
1500
|
..active
|
|
1285
1501
|
},
|
|
1286
|
-
Some(
|
|
1502
|
+
Some(kind_state),
|
|
1287
1503
|
)
|
|
1288
1504
|
}
|
|
1289
1505
|
}
|
|
@@ -1293,7 +1509,8 @@ impl Execution<'_> {
|
|
|
1293
1509
|
if !self.budget.checkpoint() {
|
|
1294
1510
|
return Unsupported;
|
|
1295
1511
|
}
|
|
1296
|
-
outcome =
|
|
1512
|
+
outcome =
|
|
1513
|
+
self.eval_with_kind(n, value, depth + 1, active, Some(kind_state));
|
|
1297
1514
|
if outcome != Valid {
|
|
1298
1515
|
break;
|
|
1299
1516
|
}
|
|
@@ -1309,30 +1526,61 @@ impl Execution<'_> {
|
|
|
1309
1526
|
match evaluation::scan_branches(
|
|
1310
1527
|
nodes,
|
|
1311
1528
|
evaluation::BranchPolicy::Validity(branch_kind),
|
|
1312
|
-
|
|
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(()),
|
|
1313
1539
|
) {
|
|
1314
1540
|
Ok(result) => verdict(branch_kind.accepts(result.matches)),
|
|
1315
1541
|
Err(status) => status,
|
|
1316
1542
|
}
|
|
1317
1543
|
}
|
|
1318
|
-
Op::Not(n) =>
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
+
}
|
|
1323
1551
|
Op::Conditional(c, yes, no) => {
|
|
1324
|
-
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)) {
|
|
1325
1553
|
Unsupported => Unsupported,
|
|
1326
1554
|
result => match if result == Valid { yes } else { no } {
|
|
1327
1555
|
Some(n) => {
|
|
1328
|
-
self.eval_with_kind(*n, value, depth + 1, active, Some(
|
|
1556
|
+
self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state))
|
|
1329
1557
|
}
|
|
1330
1558
|
None => Valid,
|
|
1331
1559
|
},
|
|
1332
1560
|
}
|
|
1333
1561
|
}
|
|
1334
1562
|
Op::Array(required, rules) => {
|
|
1335
|
-
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 {
|
|
1336
1584
|
Invalid
|
|
1337
1585
|
} else if kind != TYPE_ARRAY {
|
|
1338
1586
|
Valid
|
|
@@ -1341,12 +1589,29 @@ impl Execution<'_> {
|
|
|
1341
1589
|
}
|
|
1342
1590
|
}
|
|
1343
1591
|
Op::Object(required, rules) => {
|
|
1344
|
-
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 {
|
|
1345
1600
|
Invalid
|
|
1346
1601
|
} else if kind != TYPE_OBJECT {
|
|
1347
1602
|
Valid
|
|
1603
|
+
} else if rules.header_only() {
|
|
1604
|
+
rules.valid_header(self.caches, value, self.undefined, &self.budget)
|
|
1348
1605
|
} else {
|
|
1349
|
-
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
|
+
}
|
|
1350
1615
|
}
|
|
1351
1616
|
}
|
|
1352
1617
|
_ => self.leaf(op, kind, value, depth),
|
|
@@ -1360,20 +1625,49 @@ impl Execution<'_> {
|
|
|
1360
1625
|
fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
|
|
1361
1626
|
match op {
|
|
1362
1627
|
Op::Unsupported => Unsupported,
|
|
1628
|
+
Op::UnresolvedRef(_) => Invalid,
|
|
1363
1629
|
Op::Boolean(v) => verdict(*v),
|
|
1364
|
-
Op::Type(mask) =>
|
|
1365
|
-
Op::Const(v) =>
|
|
1366
|
-
|
|
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
|
+
}
|
|
1367
1655
|
Op::Number(required, rules) => {
|
|
1656
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1657
|
+
return rules.runtime_valid(self.caches, *required, value);
|
|
1658
|
+
}
|
|
1368
1659
|
if *required != 0 && kind & required == 0 {
|
|
1369
1660
|
Invalid
|
|
1370
1661
|
} else if kind & TYPE_NUMBER == 0 {
|
|
1371
1662
|
Valid
|
|
1372
1663
|
} else {
|
|
1373
|
-
rules.eval(value)
|
|
1664
|
+
rules.eval(self.caches, value, &self.budget)
|
|
1374
1665
|
}
|
|
1375
1666
|
}
|
|
1376
1667
|
Op::String(required, rules) => {
|
|
1668
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1669
|
+
return rules.runtime_valid(self.caches, *required, value);
|
|
1670
|
+
}
|
|
1377
1671
|
if *required != 0 && kind != TYPE_STRING {
|
|
1378
1672
|
Invalid
|
|
1379
1673
|
} else if kind != TYPE_STRING {
|
|
@@ -1385,6 +1679,22 @@ impl Execution<'_> {
|
|
|
1385
1679
|
_ => unreachable!("applicators must be evaluated with their context"),
|
|
1386
1680
|
}
|
|
1387
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
|
+
}
|
|
1388
1698
|
fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
|
|
1389
1699
|
if !self.budget.checkpoint() {
|
|
1390
1700
|
return Unsupported;
|
|
@@ -1394,7 +1704,7 @@ impl Execution<'_> {
|
|
|
1394
1704
|
return Invalid;
|
|
1395
1705
|
}
|
|
1396
1706
|
if r.unique {
|
|
1397
|
-
let result =
|
|
1707
|
+
let result = self.unique_values(value, depth);
|
|
1398
1708
|
if result != Valid {
|
|
1399
1709
|
return result;
|
|
1400
1710
|
}
|
|
@@ -1438,15 +1748,18 @@ impl Execution<'_> {
|
|
|
1438
1748
|
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
1439
1749
|
};
|
|
1440
1750
|
let primitive = match self.nodes[child].as_slice() {
|
|
1441
|
-
[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 => {
|
|
1442
1752
|
Some(&[][..])
|
|
1443
1753
|
}
|
|
1444
1754
|
[Op::Number(_, rules)] => rules.fast_limits.as_deref(),
|
|
1445
1755
|
_ => None,
|
|
1446
1756
|
};
|
|
1757
|
+
let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
|
|
1447
1758
|
let number = match self.nodes[child].as_slice() {
|
|
1448
1759
|
[Op::Number(required, rules)]
|
|
1449
|
-
if depth <= MAX_EVALUATION_DEPTH
|
|
1760
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1761
|
+
&& !self.tracks[child]
|
|
1762
|
+
&& !self.caches.dynamic_calls() =>
|
|
1450
1763
|
{
|
|
1451
1764
|
Some((*required, rules))
|
|
1452
1765
|
}
|
|
@@ -1479,47 +1792,60 @@ impl Execution<'_> {
|
|
|
1479
1792
|
.or(self.metadata[child].primary_binding),
|
|
1480
1793
|
};
|
|
1481
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
|
+
);
|
|
1482
1799
|
let pattern = string.and_then(|(required, rules)| {
|
|
1483
1800
|
rules
|
|
1484
1801
|
.pattern_only()
|
|
1485
1802
|
.map(|pattern| (required, pattern))
|
|
1486
|
-
.filter(|_| !self.budget.timed())
|
|
1803
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1487
1804
|
});
|
|
1488
1805
|
let format = string.and_then(|(required, rules)| {
|
|
1489
1806
|
rules
|
|
1490
1807
|
.format_only()
|
|
1491
1808
|
.map(|format| (required, format))
|
|
1492
|
-
.filter(|_| !self.budget.timed())
|
|
1809
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1493
1810
|
});
|
|
1494
1811
|
let content = string.and_then(|(required, rules)| {
|
|
1495
1812
|
rules
|
|
1496
1813
|
.content_only()
|
|
1497
1814
|
.map(|content| (required, content))
|
|
1498
|
-
.filter(|_| !self.budget.timed())
|
|
1815
|
+
.filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
|
|
1499
1816
|
});
|
|
1500
1817
|
if let Some((required, pattern)) = pattern {
|
|
1501
|
-
let result = pattern.
|
|
1818
|
+
let result = pattern.visit_invalid_items(
|
|
1502
1819
|
self.caches,
|
|
1503
1820
|
required,
|
|
1504
1821
|
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
1822
|
+
|_, item| self.eval(child, item, depth, active),
|
|
1505
1823
|
);
|
|
1506
1824
|
if result != Valid {
|
|
1507
1825
|
return result;
|
|
1508
1826
|
}
|
|
1509
1827
|
} else if let Some((required, format)) = format {
|
|
1510
|
-
let result =
|
|
1511
|
-
|
|
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
|
+
);
|
|
1512
1833
|
if result != Valid {
|
|
1513
1834
|
return result;
|
|
1514
1835
|
}
|
|
1515
1836
|
} else if let Some((required, content)) = content {
|
|
1516
|
-
let result =
|
|
1517
|
-
|
|
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
|
+
);
|
|
1518
1843
|
if result != Valid {
|
|
1519
1844
|
return result;
|
|
1520
1845
|
}
|
|
1521
1846
|
} else if let Some((required, rules)) = object {
|
|
1522
1847
|
let result = self.record_items(
|
|
1848
|
+
child,
|
|
1523
1849
|
rules,
|
|
1524
1850
|
required,
|
|
1525
1851
|
values.get(r.prefix.len()..).unwrap_or(&[]),
|
|
@@ -1546,16 +1872,21 @@ impl Execution<'_> {
|
|
|
1546
1872
|
return Unsupported;
|
|
1547
1873
|
}
|
|
1548
1874
|
let result = if let Some((required, rules)) = number {
|
|
1549
|
-
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
|
+
}
|
|
1550
1881
|
} else if let Some((required, rules)) = string {
|
|
1551
1882
|
if rules.pattern.is_some() {
|
|
1552
1883
|
rules.eval_guarded(self.caches, required, item)
|
|
1553
1884
|
} else if rules.length_only() {
|
|
1554
|
-
rules.eval_length_guarded(required, item)
|
|
1885
|
+
rules.eval_length_guarded(self.caches, required, item)
|
|
1555
1886
|
} else {
|
|
1556
1887
|
let kind = unsafe { sn_kind(item) };
|
|
1557
|
-
if kind == TYPE_UNSUPPORTED {
|
|
1558
|
-
|
|
1888
|
+
if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
|
|
1889
|
+
rules.runtime_valid(self.caches, required, item)
|
|
1559
1890
|
} else if kind != TYPE_STRING {
|
|
1560
1891
|
verdict(required == 0)
|
|
1561
1892
|
} else {
|
|
@@ -1572,18 +1903,30 @@ impl Execution<'_> {
|
|
|
1572
1903
|
}
|
|
1573
1904
|
}
|
|
1574
1905
|
if let Some(child) = r.contains {
|
|
1575
|
-
if
|
|
1906
|
+
if r.count_contains
|
|
1907
|
+
&& let Some(status) = self.fixnum_contains(r, child, value, len, depth)
|
|
1908
|
+
{
|
|
1576
1909
|
return status;
|
|
1577
1910
|
}
|
|
1578
1911
|
let matches = match evaluation::scan_contains(
|
|
1579
1912
|
len,
|
|
1913
|
+
if r.count_contains {
|
|
1914
|
+
evaluation::ContainsPolicy::AllMatches
|
|
1915
|
+
} else {
|
|
1916
|
+
evaluation::ContainsPolicy::FirstMatch
|
|
1917
|
+
},
|
|
1580
1918
|
|index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
|
|
1581
1919
|
|_| {},
|
|
1582
1920
|
) {
|
|
1583
1921
|
Ok(matches) => matches,
|
|
1584
1922
|
Err(status) => return status,
|
|
1585
1923
|
};
|
|
1586
|
-
|
|
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 {
|
|
1587
1930
|
return Invalid;
|
|
1588
1931
|
}
|
|
1589
1932
|
}
|
|
@@ -1604,7 +1947,7 @@ impl Execution<'_> {
|
|
|
1604
1947
|
return None;
|
|
1605
1948
|
};
|
|
1606
1949
|
let total = match op {
|
|
1607
|
-
Op::Type(
|
|
1950
|
+
Op::Type(..) | Op::Boolean(_) => true,
|
|
1608
1951
|
Op::Const(expected) => expected & self.fixnum_flag != 0,
|
|
1609
1952
|
Op::Number(_, rules) => rules.fast_limits.is_some(),
|
|
1610
1953
|
_ => false,
|
|
@@ -1630,7 +1973,6 @@ impl Execution<'_> {
|
|
|
1630
1973
|
|index| self.leaf(op, TYPE_INTEGRAL_NUMBER, values[index], depth) == Valid,
|
|
1631
1974
|
))
|
|
1632
1975
|
}
|
|
1633
|
-
#[inline(never)]
|
|
1634
1976
|
fn integer_items(
|
|
1635
1977
|
&self,
|
|
1636
1978
|
limits: &[(i32, i64)],
|
|
@@ -1653,7 +1995,9 @@ impl Execution<'_> {
|
|
|
1653
1995
|
);
|
|
1654
1996
|
let number = match self.nodes[child].as_slice() {
|
|
1655
1997
|
[Op::Number(required, rules)]
|
|
1656
|
-
if depth <= MAX_EVALUATION_DEPTH
|
|
1998
|
+
if depth <= MAX_EVALUATION_DEPTH
|
|
1999
|
+
&& !self.tracks[child]
|
|
2000
|
+
&& !self.caches.dynamic_calls() =>
|
|
1657
2001
|
{
|
|
1658
2002
|
Some((*required, rules))
|
|
1659
2003
|
}
|
|
@@ -1672,7 +2016,12 @@ impl Execution<'_> {
|
|
|
1672
2016
|
let n = immediate_integer(value);
|
|
1673
2017
|
verdict(n >= min && n <= max)
|
|
1674
2018
|
} else if let Some((required, rules)) = number {
|
|
1675
|
-
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
|
+
}
|
|
1676
2025
|
} else {
|
|
1677
2026
|
self.eval(child, value, depth, active)
|
|
1678
2027
|
};
|
|
@@ -1682,15 +2031,16 @@ impl Execution<'_> {
|
|
|
1682
2031
|
}
|
|
1683
2032
|
Valid
|
|
1684
2033
|
}
|
|
1685
|
-
#[inline(never)]
|
|
1686
2034
|
fn record_items(
|
|
1687
2035
|
&self,
|
|
2036
|
+
node: usize,
|
|
1688
2037
|
rules: &Object,
|
|
1689
2038
|
required: i32,
|
|
1690
2039
|
values: &[Value],
|
|
1691
2040
|
depth: usize,
|
|
1692
2041
|
active: scope::Context<'_>,
|
|
1693
2042
|
) -> Verdict {
|
|
2043
|
+
let header_only = rules.header_only();
|
|
1694
2044
|
for &value in values {
|
|
1695
2045
|
if !self.budget.checkpoint() {
|
|
1696
2046
|
return Unsupported;
|
|
@@ -1700,13 +2050,29 @@ impl Execution<'_> {
|
|
|
1700
2050
|
} else {
|
|
1701
2051
|
unsafe { sn_shallow_kind(value) }
|
|
1702
2052
|
};
|
|
1703
|
-
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 {
|
|
1704
2057
|
Unsupported
|
|
1705
2058
|
} else if kind != TYPE_OBJECT {
|
|
1706
2059
|
verdict(required == 0)
|
|
1707
2060
|
} else {
|
|
1708
2061
|
self.object(rules, value, depth, active)
|
|
1709
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
|
+
};
|
|
1710
2076
|
if result != Valid {
|
|
1711
2077
|
return result;
|
|
1712
2078
|
}
|
|
@@ -1723,7 +2089,9 @@ impl Execution<'_> {
|
|
|
1723
2089
|
if !self.budget.checkpoint() {
|
|
1724
2090
|
return Unsupported;
|
|
1725
2091
|
}
|
|
1726
|
-
if
|
|
2092
|
+
if !self.caches.runtime_order()
|
|
2093
|
+
&& let Some(record) = &r.record
|
|
2094
|
+
{
|
|
1727
2095
|
// C initializes only the written key/value pairs. The untouched
|
|
1728
2096
|
// tail is never read, including empty and unsupported records.
|
|
1729
2097
|
let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
|
|
@@ -1805,26 +2173,36 @@ impl Execution<'_> {
|
|
|
1805
2173
|
if bounds(len, r.min, r.max) == Invalid {
|
|
1806
2174
|
return Invalid;
|
|
1807
2175
|
}
|
|
1808
|
-
let status = r.check_required(value, self.undefined, &self.budget);
|
|
2176
|
+
let status = r.check_required(self.caches, value, self.undefined, &self.budget);
|
|
1809
2177
|
if status != Valid {
|
|
1810
2178
|
return status;
|
|
1811
2179
|
}
|
|
1812
|
-
let
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
+
}
|
|
1821
2194
|
};
|
|
1822
|
-
if
|
|
2195
|
+
if ordered
|
|
2196
|
+
|| r.names.is_some()
|
|
2197
|
+
|| !r.patterns.is_empty()
|
|
2198
|
+
|| (r.additional.is_some() && found < len)
|
|
2199
|
+
{
|
|
1823
2200
|
let mut context = ObjectContext {
|
|
1824
2201
|
engine: self,
|
|
1825
2202
|
rules: r,
|
|
1826
2203
|
depth,
|
|
1827
2204
|
active,
|
|
2205
|
+
names_only: false,
|
|
1828
2206
|
};
|
|
1829
2207
|
let result =
|
|
1830
2208
|
unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
|
|
@@ -1832,7 +2210,13 @@ impl Execution<'_> {
|
|
|
1832
2210
|
return if result == 0 { Invalid } else { Unsupported };
|
|
1833
2211
|
}
|
|
1834
2212
|
}
|
|
1835
|
-
|
|
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);
|
|
1836
2220
|
if status != Valid {
|
|
1837
2221
|
return status;
|
|
1838
2222
|
}
|
|
@@ -1849,13 +2233,76 @@ impl Execution<'_> {
|
|
|
1849
2233
|
}
|
|
1850
2234
|
Valid
|
|
1851
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
|
+
}
|
|
1852
2269
|
fn instance_kind(&self, node: usize, value: Value) -> i32 {
|
|
1853
|
-
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
|
+
{
|
|
1854
2282
|
unsafe { sn_shallow_kind(value) }
|
|
1855
2283
|
} else {
|
|
1856
2284
|
unsafe { sn_kind(value) }
|
|
1857
2285
|
}
|
|
1858
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
|
+
}
|
|
1859
2306
|
fn record_value(
|
|
1860
2307
|
&self,
|
|
1861
2308
|
node: usize,
|
|
@@ -1870,7 +2317,7 @@ impl Execution<'_> {
|
|
|
1870
2317
|
[Op::String(required, rules)]
|
|
1871
2318
|
if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
|
|
1872
2319
|
{
|
|
1873
|
-
rules.eval_length_guarded(*required, value)
|
|
2320
|
+
rules.eval_length_guarded(self.caches, *required, value)
|
|
1874
2321
|
}
|
|
1875
2322
|
[Op::String(required, rules)]
|
|
1876
2323
|
if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
|
|
@@ -1878,19 +2325,15 @@ impl Execution<'_> {
|
|
|
1878
2325
|
rules.eval_guarded(self.caches, *required, value)
|
|
1879
2326
|
}
|
|
1880
2327
|
[
|
|
1881
|
-
op @ (Op::Type(
|
|
2328
|
+
op @ (Op::Type(..)
|
|
1882
2329
|
| Op::Boolean(_)
|
|
1883
2330
|
| Op::Number(..)
|
|
1884
2331
|
| Op::String(..)
|
|
1885
2332
|
| Op::Const(_)
|
|
1886
2333
|
| Op::Enum(_)),
|
|
1887
2334
|
] if depth <= MAX_EVALUATION_DEPTH => {
|
|
1888
|
-
let kind =
|
|
1889
|
-
|
|
1890
|
-
} else {
|
|
1891
|
-
unsafe { sn_kind(value) }
|
|
1892
|
-
};
|
|
1893
|
-
if kind == TYPE_UNSUPPORTED {
|
|
2335
|
+
let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
|
|
2336
|
+
if self.unsupported_kind(node, kind) {
|
|
1894
2337
|
Unsupported
|
|
1895
2338
|
} else {
|
|
1896
2339
|
self.leaf(op, kind, value, depth)
|
|
@@ -1969,12 +2412,21 @@ fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verd
|
|
|
1969
2412
|
{
|
|
1970
2413
|
c.status = if c.depth <= MAX_EVALUATION_DEPTH {
|
|
1971
2414
|
match field.and_then(|field| field.check) {
|
|
1972
|
-
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
|
+
{
|
|
1973
2418
|
let n = immediate_integer(value);
|
|
1974
2419
|
verdict(n >= min && n <= max)
|
|
1975
2420
|
}
|
|
1976
|
-
Some(RecordCheck::StringLength(required, min, max))
|
|
1977
|
-
|
|
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
|
+
}
|
|
1978
2430
|
}
|
|
1979
2431
|
_ => c.engine.record_value(child, value, c.depth, c.active),
|
|
1980
2432
|
}
|
|
@@ -2038,20 +2490,79 @@ fn equal_cached(
|
|
|
2038
2490
|
return Unsupported;
|
|
2039
2491
|
}
|
|
2040
2492
|
if depth > MAX_EVALUATION_DEPTH {
|
|
2041
|
-
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);
|
|
2042
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> {
|
|
2043
2554
|
let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
|
|
2044
2555
|
if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
|
|
2045
|
-
return Unsupported;
|
|
2556
|
+
return Err(Unsupported);
|
|
2046
2557
|
}
|
|
2047
2558
|
if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
|
|
2048
2559
|
let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
|
|
2049
2560
|
let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
|
|
2050
2561
|
if ia && ib {
|
|
2051
|
-
return verdict(unsafe { sn_integer(a) == sn_integer(b) });
|
|
2562
|
+
return Err(verdict(unsafe { sn_integer(a) == sn_integer(b) }));
|
|
2052
2563
|
}
|
|
2053
2564
|
if fa && fb {
|
|
2054
|
-
return verdict(unsafe { sn_float(a) == sn_float(b) });
|
|
2565
|
+
return Err(verdict(unsafe { sn_float(a) == sn_float(b) }));
|
|
2055
2566
|
}
|
|
2056
2567
|
if (ia && fb) || (fa && ib) {
|
|
2057
2568
|
let (n, f) = unsafe {
|
|
@@ -2063,16 +2574,16 @@ fn equal_cached(
|
|
|
2063
2574
|
};
|
|
2064
2575
|
// Convert the float to an integer, never the integer to a rounded float.
|
|
2065
2576
|
// Fixnums are strictly inside i64's bounds on supported CRuby targets.
|
|
2066
|
-
return verdict(
|
|
2577
|
+
return Err(verdict(
|
|
2067
2578
|
f.is_finite()
|
|
2068
2579
|
&& f.trunc() == f
|
|
2069
2580
|
&& f >= i64::MIN as f64
|
|
2070
2581
|
&& f < i64::MAX as f64
|
|
2071
2582
|
&& f as i64 == n,
|
|
2072
|
-
);
|
|
2583
|
+
));
|
|
2073
2584
|
}
|
|
2074
2585
|
if !fa && !fb {
|
|
2075
|
-
return verdict(
|
|
2586
|
+
return Err(verdict(
|
|
2076
2587
|
unsafe {
|
|
2077
2588
|
if ia {
|
|
2078
2589
|
sn_big_equal(b, a)
|
|
@@ -2080,80 +2591,150 @@ fn equal_cached(
|
|
|
2080
2591
|
sn_big_equal(a, b)
|
|
2081
2592
|
}
|
|
2082
2593
|
} != 0,
|
|
2083
|
-
);
|
|
2594
|
+
));
|
|
2084
2595
|
}
|
|
2085
|
-
return if fa {
|
|
2596
|
+
return Err(if fa {
|
|
2086
2597
|
number::integer_float_equal(b, unsafe { sn_float(a) })
|
|
2087
2598
|
} else {
|
|
2088
2599
|
number::integer_float_equal(a, unsafe { sn_float(b) })
|
|
2089
|
-
};
|
|
2600
|
+
});
|
|
2090
2601
|
}
|
|
2091
2602
|
// JSON container equality visits every value, even for the same object:
|
|
2092
2603
|
// nested NaN values are unequal and unsupported values need VM evaluation.
|
|
2093
2604
|
if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
|
|
2094
|
-
return Valid;
|
|
2605
|
+
return Err(Valid);
|
|
2095
2606
|
}
|
|
2096
2607
|
if ka != kb {
|
|
2097
|
-
return Invalid;
|
|
2608
|
+
return Err(Invalid);
|
|
2098
2609
|
}
|
|
2099
2610
|
if unsafe { sn_same_class(a, b) } == 0 {
|
|
2100
|
-
return Invalid;
|
|
2611
|
+
return Err(Invalid);
|
|
2101
2612
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
&& let Some(result) = memo.get(key)
|
|
2107
|
-
{
|
|
2108
|
-
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),
|
|
2109
2617
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
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
|
+
}
|
|
2116
2692
|
}
|
|
2117
|
-
|
|
2118
|
-
let
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
depth + 1,
|
|
2122
|
-
memo,
|
|
2123
|
-
budget,
|
|
2124
|
-
);
|
|
2125
|
-
if result != Valid {
|
|
2126
|
-
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));
|
|
2127
2697
|
}
|
|
2698
|
+
work.push(EqualityWork::Compare(pair.0, pair.1, depth));
|
|
2128
2699
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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
|
+
}
|
|
2134
2716
|
}
|
|
2135
|
-
let mut context = EqualityContext {
|
|
2136
|
-
other: b,
|
|
2137
|
-
depth: depth + 1,
|
|
2138
|
-
memo,
|
|
2139
|
-
budget,
|
|
2140
|
-
};
|
|
2141
|
-
decode(unsafe {
|
|
2142
|
-
sn_hash_each(
|
|
2143
|
-
a,
|
|
2144
|
-
(&mut context as *mut EqualityContext<'_>).cast(),
|
|
2145
|
-
equal_entry,
|
|
2146
|
-
)
|
|
2147
|
-
})
|
|
2148
2717
|
}
|
|
2149
|
-
TYPE_UNSUPPORTED => Unsupported,
|
|
2150
|
-
_ => Invalid,
|
|
2151
|
-
};
|
|
2152
|
-
if container && memo.eligible(depth) {
|
|
2153
|
-
memo.record(key, result);
|
|
2154
2718
|
}
|
|
2155
|
-
|
|
2719
|
+
Valid
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
struct EqualityEntries<'a> {
|
|
2723
|
+
values: Vec<(Value, Value)>,
|
|
2724
|
+
budget: &'a budget::Budget,
|
|
2156
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
|
+
|
|
2157
2738
|
fn decode(result: i32) -> Verdict {
|
|
2158
2739
|
match result {
|
|
2159
2740
|
VERDICT_INVALID => Invalid,
|
|
@@ -2321,13 +2902,12 @@ fn fingerprint_cached(
|
|
|
2321
2902
|
// before the depth-based cache handles deeper shared graphs.
|
|
2322
2903
|
let cacheable = container && memo.eligible_container(depth, unsafe { len.assume_init() });
|
|
2323
2904
|
let key = (value, depth);
|
|
2324
|
-
if cacheable
|
|
2325
|
-
&& let Some(result) = memo.get(key)
|
|
2326
|
-
{
|
|
2905
|
+
if cacheable && let Some(result) = memo.get(key) {
|
|
2327
2906
|
return result;
|
|
2328
2907
|
}
|
|
2329
2908
|
let result = match kind {
|
|
2330
2909
|
TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
|
|
2910
|
+
TYPE_IDENTITY_SCALAR => Some(mix(value as u64 ^ TYPE_IDENTITY_SCALAR as u64)),
|
|
2331
2911
|
TYPE_NULL => Some(mix(TYPE_NULL as u64)),
|
|
2332
2912
|
TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
|
|
2333
2913
|
2
|
|
@@ -2429,21 +3009,7 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
|
|
|
2429
3009
|
return Unsupported;
|
|
2430
3010
|
}
|
|
2431
3011
|
if len <= 16 {
|
|
2432
|
-
|
|
2433
|
-
for previous in 0..i {
|
|
2434
|
-
match equal_budgeted(
|
|
2435
|
-
unsafe { sn_entry(value, previous) },
|
|
2436
|
-
unsafe { sn_entry(value, i) },
|
|
2437
|
-
depth,
|
|
2438
|
-
budget,
|
|
2439
|
-
) {
|
|
2440
|
-
Valid => return Invalid,
|
|
2441
|
-
Unsupported => return Unsupported,
|
|
2442
|
-
Invalid => {}
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
return Valid;
|
|
3012
|
+
return unique_linear(value, depth, budget, false);
|
|
2447
3013
|
}
|
|
2448
3014
|
let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
|
|
2449
3015
|
// This is a checked optimization hint, not an instance shape assumption.
|
|
@@ -2462,11 +3028,54 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
|
|
|
2462
3028
|
}
|
|
2463
3029
|
}
|
|
2464
3030
|
let mut fingerprints = structural::Cache::batch(depth);
|
|
2465
|
-
indexed_unique(
|
|
3031
|
+
let result = indexed_unique(
|
|
2466
3032
|
values,
|
|
2467
|
-
|item|
|
|
3033
|
+
|item| {
|
|
3034
|
+
fingerprint_cached(
|
|
3035
|
+
item,
|
|
3036
|
+
depth,
|
|
3037
|
+
fixnum_flag,
|
|
3038
|
+
big_hint,
|
|
3039
|
+
&mut fingerprints,
|
|
3040
|
+
budget,
|
|
3041
|
+
)
|
|
3042
|
+
},
|
|
2468
3043
|
|a, b| equal_budgeted(a, b, depth, budget),
|
|
2469
|
-
)
|
|
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
|
|
2470
3079
|
}
|
|
2471
3080
|
// Keep Big-only sampling and retry state out of the common scalar path.
|
|
2472
3081
|
#[inline(never)]
|
|
@@ -2806,6 +3415,7 @@ struct ObjectContext<'a> {
|
|
|
2806
3415
|
rules: &'a Object,
|
|
2807
3416
|
depth: usize,
|
|
2808
3417
|
active: scope::Context<'a>,
|
|
3418
|
+
names_only: bool,
|
|
2809
3419
|
}
|
|
2810
3420
|
/// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
|
|
2811
3421
|
/// # Safety
|
|
@@ -2820,15 +3430,21 @@ pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, val
|
|
|
2820
3430
|
if unsafe { sn_kind(key) } != TYPE_STRING {
|
|
2821
3431
|
return Unsupported as i32;
|
|
2822
3432
|
}
|
|
2823
|
-
if
|
|
3433
|
+
if (c.names_only || !c.engine.caches.runtime_order())
|
|
3434
|
+
&& let Some(child) = c.rules.names
|
|
3435
|
+
{
|
|
2824
3436
|
let result = c.engine.eval(child, key, c.depth, c.active);
|
|
2825
3437
|
if result != Valid {
|
|
2826
3438
|
return result as i32;
|
|
2827
3439
|
}
|
|
2828
3440
|
}
|
|
2829
|
-
|
|
3441
|
+
if c.names_only {
|
|
3442
|
+
return Valid as i32;
|
|
3443
|
+
}
|
|
3444
|
+
match c.rules.visit_value_rules(
|
|
2830
3445
|
c.engine.caches,
|
|
2831
3446
|
&c.engine.budget,
|
|
3447
|
+
c.engine.caches.runtime_order(),
|
|
2832
3448
|
key,
|
|
2833
3449
|
None,
|
|
2834
3450
|
|_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
|
|
@@ -2879,9 +3495,17 @@ pub unsafe extern "C" fn sn_compile(
|
|
|
2879
3495
|
e.bind_dynamic_anchors()?;
|
|
2880
3496
|
e.simplify_dynamic_references();
|
|
2881
3497
|
e.compile_memoization();
|
|
2882
|
-
|
|
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
|
+
)
|
|
2883
3506
|
})) {
|
|
2884
|
-
Ok(Ok(
|
|
3507
|
+
Ok(Ok(true)) => VERDICT_VALID,
|
|
3508
|
+
Ok(Ok(false)) => VERDICT_UNSUPPORTED,
|
|
2885
3509
|
Ok(Err(value)) => {
|
|
2886
3510
|
unsafe { *error = value };
|
|
2887
3511
|
STATUS_EXCEPTION
|
|
@@ -2900,12 +3524,44 @@ pub unsafe extern "C" fn sn_node_index(engine: *const Engine, program: Value) ->
|
|
|
2900
3524
|
.unwrap_or(usize::MAX)
|
|
2901
3525
|
}
|
|
2902
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
|
|
2903
3547
|
/// Engine must be initialized.
|
|
2904
3548
|
#[unsafe(no_mangle)]
|
|
2905
3549
|
pub unsafe extern "C" fn sn_root_index(engine: *const Engine) -> usize {
|
|
2906
3550
|
unsafe { &*engine }.root
|
|
2907
3551
|
}
|
|
2908
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
|
|
2909
3565
|
/// Roots must be live and compilation must have finished.
|
|
2910
3566
|
#[unsafe(no_mangle)]
|
|
2911
3567
|
pub unsafe extern "C" fn sn_root_count(roots: *const Roots) -> usize {
|
|
@@ -2923,21 +3579,25 @@ pub unsafe extern "C" fn sn_root_at(roots: *const Roots, index: usize) -> Value
|
|
|
2923
3579
|
#[unsafe(no_mangle)]
|
|
2924
3580
|
pub unsafe extern "C" fn sn_valid(
|
|
2925
3581
|
engine: *const Engine,
|
|
2926
|
-
caches: *const
|
|
3582
|
+
caches: *const runtime::Caches,
|
|
2927
3583
|
root: usize,
|
|
2928
3584
|
value: Value,
|
|
3585
|
+
depth: usize,
|
|
2929
3586
|
) -> i32 {
|
|
2930
3587
|
catch_unwind(AssertUnwindSafe(|| {
|
|
2931
3588
|
let e = unsafe { &*engine };
|
|
2932
|
-
let local =
|
|
3589
|
+
let local = runtime::Caches::default();
|
|
2933
3590
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
2934
|
-
|
|
3591
|
+
caches.admit(e, root);
|
|
3592
|
+
let execution = Execution {
|
|
2935
3593
|
engine: e,
|
|
2936
3594
|
caches,
|
|
2937
3595
|
memo: memo::Memo::default(),
|
|
2938
3596
|
budget: budget::Budget::default(),
|
|
2939
|
-
}
|
|
2940
|
-
.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)
|
|
2941
3601
|
}))
|
|
2942
3602
|
.unwrap_or(STATUS_PANIC)
|
|
2943
3603
|
}
|
|
@@ -2946,24 +3606,26 @@ pub unsafe extern "C" fn sn_valid(
|
|
|
2946
3606
|
#[unsafe(no_mangle)]
|
|
2947
3607
|
pub unsafe extern "C" fn sn_valid_with_limits(
|
|
2948
3608
|
engine: *const Engine,
|
|
2949
|
-
caches: *const
|
|
3609
|
+
caches: *const runtime::Caches,
|
|
2950
3610
|
root: usize,
|
|
2951
3611
|
value: Value,
|
|
3612
|
+
depth: usize,
|
|
2952
3613
|
limits: *const budget::Limits,
|
|
2953
3614
|
outcome: *mut budget::Outcome,
|
|
2954
3615
|
) -> i32 {
|
|
2955
|
-
let budget = budget::Budget::new(unsafe { *limits });
|
|
2956
3616
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
2957
|
-
let local =
|
|
3617
|
+
let local = runtime::Caches::default();
|
|
2958
3618
|
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
3619
|
+
let e = unsafe { &*engine };
|
|
3620
|
+
caches.admit(e, root);
|
|
2959
3621
|
let execution = Execution {
|
|
2960
3622
|
engine: unsafe { &*engine },
|
|
2961
3623
|
caches,
|
|
2962
3624
|
memo: memo::Memo::default(),
|
|
2963
|
-
budget,
|
|
3625
|
+
budget: caches.budget(unsafe { *limits }),
|
|
2964
3626
|
};
|
|
2965
3627
|
let result = if execution.budget.check_deadline() {
|
|
2966
|
-
execution.eval(root, value,
|
|
3628
|
+
execution.eval(root, value, depth, scope::Context::default())
|
|
2967
3629
|
} else {
|
|
2968
3630
|
Unsupported
|
|
2969
3631
|
};
|
|
@@ -2975,7 +3637,8 @@ pub unsafe extern "C" fn sn_valid_with_limits(
|
|
|
2975
3637
|
unsafe {
|
|
2976
3638
|
((*outcome).kind, (*outcome).attempted) = execution.budget.hit.get();
|
|
2977
3639
|
}
|
|
2978
|
-
result
|
|
3640
|
+
caches.finish_callback_report(result);
|
|
3641
|
+
execution.budget.status(result)
|
|
2979
3642
|
}));
|
|
2980
3643
|
result.unwrap_or(STATUS_PANIC)
|
|
2981
3644
|
}
|
|
@@ -3024,7 +3687,12 @@ pub extern "C" fn sn_engine_header_size() -> usize {
|
|
|
3024
3687
|
pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
3025
3688
|
let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
|
|
3026
3689
|
let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
|
|
3690
|
+
let programs = unsafe { &*std::ptr::addr_of!((*engine).programs) };
|
|
3027
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) };
|
|
3028
3696
|
let guarded = unsafe { &*std::ptr::addr_of!((*engine).memo_guarded) };
|
|
3029
3697
|
let sources = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_sources) };
|
|
3030
3698
|
let same = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_same_sources) };
|
|
@@ -3033,6 +3701,9 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3033
3701
|
let projection = unsafe { &*std::ptr::addr_of!((*engine).memo_scope_projection) };
|
|
3034
3702
|
std::mem::size_of::<Engine>()
|
|
3035
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>()
|
|
3036
3707
|
+ (contextual.capacity()
|
|
3037
3708
|
+ guarded.capacity()
|
|
3038
3709
|
+ sources.capacity()
|
|
@@ -3040,6 +3711,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3040
3711
|
+ scalar.capacity()
|
|
3041
3712
|
+ amplification.capacity())
|
|
3042
3713
|
* std::mem::size_of::<bool>()
|
|
3714
|
+
+ programs.capacity() * std::mem::size_of::<Value>()
|
|
3043
3715
|
+ nodes.capacity() * std::mem::size_of::<Vec<Op>>()
|
|
3044
3716
|
+ operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
|
|
3045
3717
|
+ operands
|
|
@@ -3054,6 +3726,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
|
|
|
3054
3726
|
.iter()
|
|
3055
3727
|
.flatten()
|
|
3056
3728
|
.map(|op| match op {
|
|
3729
|
+
Op::Number(_, rules) => rules.memory_usage(),
|
|
3057
3730
|
Op::Enum(values) => values.memory_usage(),
|
|
3058
3731
|
Op::String(_, rules) => {
|
|
3059
3732
|
std::mem::size_of::<StringRules>()
|