1use std::fmt;
2use std::sync::Arc;
3
4use crate::error::{CompileError, ErrorCode, LimitKind};
5use crate::index::{
6 BoundByteTrie, BoundedMask, TokenSliceCatalog, VocabFingerprint, VocabTrie, VocabularyHandle,
7};
8use crate::ir::SchemaIR;
9#[cfg(feature = "bench-internals")]
10use crate::ir::{ContainsPolicy, Node};
11use crate::mask::Bitmask;
12use crate::primitives::{NodeId, StateId, TokenId, TrieNodeId};
13
14use super::lexer::{DecodeStep, JsonStringDecoder};
15use super::limits::{StructuredLimits, StructuredRuntimeError};
16use super::plan::{PlanCompileFailure, StructuredPlan};
17#[cfg(any(test, feature = "bench-internals"))]
18use super::reference::ReferenceMatcher;
19use super::state::{
20 Checkpoint, ProofContext, SliceCertificate, SlicePreparation, SliceProof, StructuredState,
21};
22
23pub struct StructuredProgram {
25 #[cfg(feature = "bench-internals")]
26 ir: Arc<SchemaIR>,
27 backend: ProgramBackend,
28 limits: StructuredLimits,
29}
30
31enum ProgramBackend {
32 Incremental(Arc<StructuredPlan>),
33 #[cfg(feature = "bench-internals")]
34 ReferenceBench,
35}
36
37pub struct StructuredMatcher {
39 program: Arc<StructuredProgram>,
40 backend: MatcherBackend,
41}
42
43enum MatcherBackend {
44 Incremental(IncrementalMatcher),
45 #[cfg(feature = "bench-internals")]
46 Reference(ReferenceMatcher),
47}
48
49struct IncrementalMatcher {
50 state: Box<StructuredState<'static>>,
51 events: Vec<TrieEvent>,
52 lexical_path: Vec<u8>,
54 slice_proofs: SliceProofCache,
55 certificate_proofs: Vec<SliceProof>,
57 #[cfg(feature = "bench-internals")]
58 mask_work_metrics: MaskWorkMetrics,
59}
60
61#[derive(Clone, Copy, PartialEq, Eq)]
62struct SliceProofKey {
63 vocabulary: VocabFingerprint,
64 node: NodeId,
65 state: StateId,
66 body_prefix: bool,
67 reject_invalid: bool,
68}
69
70#[derive(Clone, Copy)]
71struct SliceProofEntry {
72 key: SliceProofKey,
73 subsumes: bool,
74}
75
76struct CandidateMaskEntry {
77 key: SliceProofKey,
78 mask: Arc<[u64]>,
79}
80
81#[derive(Default)]
82struct SliceProofCache {
83 entries: Vec<SliceProofEntry>,
84 candidate: Option<CandidateMaskEntry>,
85 next: usize,
86 #[cfg(feature = "bench-internals")]
87 hits: u64,
88 #[cfg(feature = "bench-internals")]
89 misses: u64,
90}
91
92impl SliceProofCache {
93 fn get(&mut self, key: SliceProofKey) -> Option<bool> {
94 let result = self
95 .entries
96 .iter()
97 .find(|entry| entry.key == key)
98 .map(|entry| entry.subsumes);
99 #[cfg(feature = "bench-internals")]
100 match result {
101 Some(_) => self.hits = self.hits.saturating_add(1),
102 None => self.misses = self.misses.saturating_add(1),
103 }
104 result
105 }
106
107 #[cfg(feature = "bench-internals")]
108 fn reset_measurements(&mut self) {
109 self.hits = 0;
110 self.misses = 0;
111 }
112
113 #[cfg(feature = "bench-internals")]
114 fn measurements(&self) -> SliceProofMetrics {
115 SliceProofMetrics {
116 hits: self.hits,
117 misses: self.misses,
118 entries: self.entries.len() + usize::from(self.candidate.is_some()),
119 }
120 }
121
122 fn insert(&mut self, key: SliceProofKey, subsumes: bool, byte_budget: usize) {
123 let slots = byte_budget / std::mem::size_of::<SliceProofEntry>();
124 if slots == 0 {
125 return;
126 }
127 if self.entries.capacity() == 0
128 && (self.entries.try_reserve_exact(slots).is_err()
129 || self
130 .entries
131 .capacity()
132 .checked_mul(std::mem::size_of::<SliceProofEntry>())
133 .is_none_or(|bytes| bytes > byte_budget))
134 {
135 self.entries = Vec::new();
136 return;
137 }
138 if self.entries.len() < self.entries.capacity() {
139 self.entries.push(SliceProofEntry { key, subsumes });
140 return;
141 }
142 self.entries[self.next] = SliceProofEntry { key, subsumes };
143 self.next = (self.next + 1) % self.entries.len();
144 }
145
146 fn candidate(&mut self, key: SliceProofKey) -> Option<Arc<[u64]>> {
147 let result = self
148 .candidate
149 .as_ref()
150 .filter(|entry| entry.key == key)
151 .map(|entry| entry.mask.clone());
152 #[cfg(feature = "bench-internals")]
153 match result {
154 Some(_) => self.hits = self.hits.saturating_add(1),
155 None => self.misses = self.misses.saturating_add(1),
156 }
157 result
158 }
159
160 fn insert_candidate(&mut self, key: SliceProofKey, mask: Arc<[u64]>, byte_budget: usize) {
161 let Some(bytes) = mask.len().checked_mul(std::mem::size_of::<u64>()) else {
162 return;
163 };
164 let Some(proof_bytes) = self
165 .entries
166 .capacity()
167 .checked_mul(std::mem::size_of::<SliceProofEntry>())
168 else {
169 return;
170 };
171 if proof_bytes
172 .checked_add(bytes)
173 .is_none_or(|total| total > byte_budget)
174 {
175 return;
176 }
177 self.candidate = Some(CandidateMaskEntry { key, mask });
178 }
179}
180
181#[cfg(feature = "bench-internals")]
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
184pub struct MaskWorkMetrics {
185 pub local_dfa_transitions: u64,
187 pub trie_nodes: u64,
189 pub trie_edges: u64,
191 pub pushed_bytes: u64,
193 pub checkpoints: u64,
195 pub rollbacks: u64,
197 pub dynamic_resolutions: u64,
199 pub dynamic_scope_steps: u64,
201}
202
203#[cfg(feature = "bench-internals")]
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
206pub struct SliceProofMetrics {
207 pub hits: u64,
209 pub misses: u64,
211 pub entries: usize,
213}
214
215#[cfg(feature = "bench-internals")]
216#[derive(Clone, Copy)]
217enum MaskMetric {
218 LocalDfaTransition,
219 TrieNode,
220 TrieEdge,
221 PushedByte,
222 Checkpoint,
223 Rollback,
224}
225
226#[cfg(feature = "bench-internals")]
227impl IncrementalMatcher {
228 fn reset_mask_metrics(&mut self) {
229 self.mask_work_metrics = MaskWorkMetrics::default();
230 self.state.reset_dynamic_work_metrics();
231 self.slice_proofs.reset_measurements();
232 }
233
234 fn increment_mask_metric(&mut self, metric: MaskMetric) {
235 let counter = match metric {
236 MaskMetric::LocalDfaTransition => &mut self.mask_work_metrics.local_dfa_transitions,
237 MaskMetric::TrieNode => &mut self.mask_work_metrics.trie_nodes,
238 MaskMetric::TrieEdge => &mut self.mask_work_metrics.trie_edges,
239 MaskMetric::PushedByte => &mut self.mask_work_metrics.pushed_bytes,
240 MaskMetric::Checkpoint => &mut self.mask_work_metrics.checkpoints,
241 MaskMetric::Rollback => &mut self.mask_work_metrics.rollbacks,
242 };
243 *counter = counter.saturating_add(1);
244 }
245
246 fn finish_mask_metrics(&mut self) {
247 let (resolutions, steps) = self.state.dynamic_work_metrics();
248 self.mask_work_metrics.dynamic_resolutions = resolutions;
249 self.mask_work_metrics.dynamic_scope_steps = steps;
250 }
251}
252
253enum TrieEvent {
254 Visit {
255 node: TrieNodeId,
256 incoming_byte: Option<u8>,
257 },
258 Restore(Checkpoint),
259 LocalVisit {
260 node: TrieNodeId,
261 incoming_byte: Option<u8>,
262 decoder: JsonStringDecoder,
263 path_len: usize,
264 },
265 RestoreLocalPath(usize),
266}
267
268#[derive(Clone, PartialEq, Eq, Debug)]
270pub enum StructuredMatcherError {
271 Compile(CompileError),
273 ResourceLimit {
275 kind: LimitKind,
277 observed: usize,
279 limit: usize,
281 },
282}
283
284impl fmt::Display for StructuredMatcherError {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match self {
287 Self::Compile(error) => error.fmt(f),
288 Self::ResourceLimit {
289 kind,
290 observed,
291 limit,
292 } => write!(f, "structured resource limit {kind:?}: {observed}/{limit}"),
293 }
294 }
295}
296
297impl std::error::Error for StructuredMatcherError {}
298
299impl From<CompileError> for StructuredMatcherError {
300 fn from(error: CompileError) -> Self {
301 Self::Compile(error)
302 }
303}
304
305impl From<StructuredRuntimeError> for StructuredMatcherError {
306 fn from(error: StructuredRuntimeError) -> Self {
307 Self::ResourceLimit {
308 kind: error.kind,
309 observed: error.observed,
310 limit: error.limit,
311 }
312 }
313}
314
315impl StructuredProgram {
316 pub fn compile(ir: Arc<SchemaIR>) -> Result<Arc<Self>, CompileError> {
318 Self::compile_with_limits(ir, StructuredLimits::default())
319 }
320
321 pub(crate) fn compile_with_limits(
322 ir: Arc<SchemaIR>,
323 limits: StructuredLimits,
324 ) -> Result<Arc<Self>, CompileError> {
325 let backend = StructuredPlan::compile(ir.clone(), limits)
326 .map(|plan| ProgramBackend::Incremental(Arc::new(plan)))
327 .map_err(|PlanCompileFailure::Fatal(error)| error)?;
328 Ok(Arc::new(Self {
329 #[cfg(feature = "bench-internals")]
330 ir,
331 backend,
332 limits,
333 }))
334 }
335
336 #[doc(hidden)]
338 #[cfg(feature = "bench-internals")]
339 pub fn reference_for_bench(ir: Arc<SchemaIR>) -> Arc<Self> {
340 Arc::new(Self {
341 ir,
342 backend: ProgramBackend::ReferenceBench,
343 limits: StructuredLimits::default(),
344 })
345 }
346
347 #[doc(hidden)]
349 #[cfg(feature = "bench-internals")]
350 pub fn compile_timed_for_bench(ir: Arc<SchemaIR>) -> Result<(Arc<Self>, u64), CompileError> {
351 let limits = StructuredLimits::default();
352 let started = std::time::Instant::now();
353 let plan = match StructuredPlan::compile(ir.clone(), limits) {
354 Ok(plan) => plan,
355 Err(PlanCompileFailure::Fatal(error)) => return Err(error),
356 };
357 let elapsed = u64::try_from(started.elapsed().as_nanos()).unwrap_or(u64::MAX);
358 let program = Arc::new(Self {
359 #[cfg(feature = "bench-internals")]
360 ir,
361 backend: ProgramBackend::Incremental(Arc::new(plan)),
362 limits,
363 });
364 Ok((program, elapsed))
365 }
366
367 #[doc(hidden)]
369 #[cfg(feature = "bench-internals")]
370 pub fn compile_profiled_for_bench(
371 ir: Arc<SchemaIR>,
372 ) -> Result<(Arc<Self>, u64, u64), CompileError> {
373 let limits = StructuredLimits::default();
374 let (plan, total_ns, scc_ns) = match StructuredPlan::compile_profiled(ir.clone(), limits) {
375 Ok(profile) => profile,
376 Err(PlanCompileFailure::Fatal(error)) => return Err(error),
377 };
378 let program = Arc::new(Self {
379 ir,
380 backend: ProgramBackend::Incremental(Arc::new(plan)),
381 limits,
382 });
383 Ok((program, total_ns, scc_ns))
384 }
385
386 #[doc(hidden)]
388 #[cfg(feature = "bench-internals")]
389 pub fn reachable_node_kinds_for_bench(
390 ir: &SchemaIR,
391 ) -> Result<Vec<&'static str>, CompileError> {
392 let mut seen = Vec::new();
393 seen.try_reserve_exact(ir.node_count()).map_err(|_| {
394 CompileError::new(
395 ErrorCode::InternalLimitExceeded,
396 crate::error::Stage::L3,
397 "routing census allocation",
398 )
399 })?;
400 seen.resize(ir.node_count(), false);
401 let mut stack = Vec::new();
402 stack.try_reserve_exact(ir.node_count()).map_err(|_| {
403 CompileError::new(
404 ErrorCode::InternalLimitExceeded,
405 crate::error::Stage::L3,
406 "routing census allocation",
407 )
408 })?;
409 stack.push(ir.root());
410 let mut kinds = Vec::new();
411 kinds.try_reserve_exact(ir.node_count()).map_err(|_| {
412 CompileError::new(
413 ErrorCode::InternalLimitExceeded,
414 crate::error::Stage::L3,
415 "routing census allocation",
416 )
417 })?;
418 while let Some(id) = stack.pop() {
419 let index = usize::try_from(id.get()).map_err(|_| {
420 CompileError::new(ErrorCode::Malformed, crate::error::Stage::L3, "node id")
421 })?;
422 let Some(slot) = seen.get_mut(index) else {
423 return Err(CompileError::new(
424 ErrorCode::Malformed,
425 crate::error::Stage::L3,
426 "node id",
427 ));
428 };
429 if *slot {
430 continue;
431 }
432 *slot = true;
433 let node = ir.node(id).ok_or_else(|| {
434 CompileError::new(ErrorCode::Malformed, crate::error::Stage::L3, "node id")
435 })?;
436 kinds.push(node_kind_name(node));
437 stack.extend(crate::compile::node_children(ir, node)?);
438 match node {
439 Node::Object { dependent, .. } | Node::OpenObject { dependent, .. } => {
440 stack.extend(
441 ir.props_at(*dependent)
442 .ok_or_else(|| {
443 CompileError::new(
444 ErrorCode::Malformed,
445 crate::error::Stage::L3,
446 "dependent schema slice",
447 )
448 })?
449 .iter()
450 .map(|(_, child)| *child),
451 );
452 }
453 Node::Array {
454 contains:
455 Some(crate::ir::ContainsConstraint {
456 policy: ContainsPolicy::Schema(child),
457 ..
458 }),
459 ..
460 }
461 | Node::Tuple {
462 contains:
463 Some(crate::ir::ContainsConstraint {
464 policy: ContainsPolicy::Schema(child),
465 ..
466 }),
467 ..
468 } => stack.push(*child),
469 Node::Ref { def } => stack.push(ir.def_target(*def).ok_or_else(|| {
470 CompileError::new(
471 ErrorCode::Malformed,
472 crate::error::Stage::L3,
473 "reference definition slot",
474 )
475 })?),
476 _ => {}
477 }
478 }
479 kinds.sort_unstable();
480 kinds.dedup();
481 Ok(kinds)
482 }
483
484 pub fn new_matcher(self: &Arc<Self>) -> Result<StructuredMatcher, StructuredMatcherError> {
486 let backend = match &self.backend {
487 ProgramBackend::Incremental(plan) => MatcherBackend::Incremental(IncrementalMatcher {
488 state: Box::new(StructuredState::try_new(plan.clone())?),
489 events: Vec::new(),
490 lexical_path: Vec::new(),
491 slice_proofs: SliceProofCache::default(),
492 certificate_proofs: Vec::new(),
493 #[cfg(feature = "bench-internals")]
494 mask_work_metrics: MaskWorkMetrics::default(),
495 }),
496 #[cfg(feature = "bench-internals")]
497 ProgramBackend::ReferenceBench => MatcherBackend::Reference(
498 ReferenceMatcher::new_with_limits(self.ir.clone(), self.limits),
499 ),
500 };
501 Ok(StructuredMatcher {
502 program: self.clone(),
503 backend,
504 })
505 }
506
507 #[cfg(test)]
508 pub(crate) fn backend_kind(&self) -> &'static str {
509 match &self.backend {
510 ProgramBackend::Incremental(_) => "incremental",
511 #[cfg(feature = "bench-internals")]
512 ProgramBackend::ReferenceBench => "reference",
513 }
514 }
515
516 #[must_use]
518 pub fn uses_incremental_backend(&self) -> bool {
519 matches!(self.backend, ProgramBackend::Incremental(_))
520 }
521
522 #[cfg(any(test, feature = "bench-internals"))]
524 #[must_use]
525 pub fn fallback_reason(&self) -> Option<&'static str> {
526 None
527 }
528
529 #[must_use]
531 pub fn retained_plan_bytes(&self) -> Option<usize> {
532 match &self.backend {
533 ProgramBackend::Incremental(plan) => Some(plan.retained_bytes),
534 #[cfg(feature = "bench-internals")]
535 ProgramBackend::ReferenceBench => None,
536 }
537 }
538}
539
540pub fn try_accepts(ir: Arc<SchemaIR>, bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
542 let program = StructuredProgram::compile(ir)?;
543 let mut matcher = program.new_matcher()?;
544 if !bytes.is_empty() && !matcher.advance(bytes)? {
545 return Ok(false);
546 }
547 Ok(matcher.is_accepting() && matcher.eos_legal())
548}
549
550pub fn try_can_continue(ir: Arc<SchemaIR>, bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
552 let program = StructuredProgram::compile(ir)?;
553 let mut matcher = program.new_matcher()?;
554 if !bytes.is_empty() && !matcher.advance(bytes)? {
555 return Ok(false);
556 }
557 Ok(!matcher.is_dead())
558}
559
560#[cfg(feature = "bench-internals")]
561fn node_kind_name(node: &Node) -> &'static str {
562 match node {
563 Node::Null => "Null",
564 Node::Boolean => "Boolean",
565 Node::Never => "Never",
566 Node::StringConst { .. } => "StringConst",
567 Node::StringPattern { .. } => "StringPattern",
568 Node::Integer { .. } => "Integer",
569 Node::Number { .. } | Node::LexicalNumber { .. } => "Number",
570 Node::Enum { .. } => "Enum",
571 Node::Array { .. } => "Array",
572 Node::Tuple { .. } => "Tuple",
573 Node::Object { .. } => "Object",
574 Node::OpenObject { .. } => "OpenObject",
575 Node::Union { .. } => "Union",
576 Node::Intersection { .. } => "Intersection",
577 Node::ExactlyOne { .. } => "ExactlyOne",
578 Node::Not { .. } => "Not",
579 Node::Ref { .. } => "Ref",
580 Node::DynamicRef { .. } => "DynamicRef",
581 Node::Unevaluated { .. } => "Unevaluated",
582 Node::Unsupported { .. } => "Unsupported",
583 }
584}
585
586impl StructuredMatcher {
587 #[cfg(feature = "bench-internals")]
589 #[must_use]
590 pub fn mask_work_metrics_for_bench(&self) -> MaskWorkMetrics {
591 match &self.backend {
592 MatcherBackend::Incremental(incremental) => incremental.mask_work_metrics,
593 MatcherBackend::Reference(_) => MaskWorkMetrics::default(),
594 }
595 }
596
597 #[cfg(feature = "bench-internals")]
599 #[must_use]
600 pub fn slice_proof_metrics_for_bench(&self) -> SliceProofMetrics {
601 match &self.backend {
602 MatcherBackend::Incremental(incremental) => incremental.slice_proofs.measurements(),
603 MatcherBackend::Reference(_) => SliceProofMetrics::default(),
604 }
605 }
606
607 #[cfg(feature = "bench-internals")]
609 #[must_use]
610 pub fn slice_proof_cache_bytes_for_bench(&self) -> usize {
611 match &self.backend {
612 MatcherBackend::Incremental(incremental) => incremental
613 .slice_proofs
614 .entries
615 .capacity()
616 .saturating_mul(std::mem::size_of::<SliceProofEntry>()),
617 MatcherBackend::Reference(_) => 0,
618 }
619 }
620
621 pub fn new(program: Arc<StructuredProgram>) -> Result<Self, StructuredMatcherError> {
623 program.new_matcher()
624 }
625
626 #[must_use]
628 pub fn backend_name(&self) -> &'static str {
629 match &self.backend {
630 MatcherBackend::Incremental(_) => "incremental",
631 #[cfg(feature = "bench-internals")]
632 MatcherBackend::Reference(_) => "reference",
633 }
634 }
635
636 #[must_use]
638 pub fn is_accepting(&self) -> bool {
639 match &self.backend {
640 MatcherBackend::Incremental(incremental) => incremental.state.is_accepting(),
641 #[cfg(feature = "bench-internals")]
642 MatcherBackend::Reference(reference) => reference.is_accepting(),
643 }
644 }
645
646 #[must_use]
648 pub fn is_dead(&self) -> bool {
649 match &self.backend {
650 MatcherBackend::Incremental(incremental) => incremental.state.is_dead(),
651 #[cfg(feature = "bench-internals")]
652 MatcherBackend::Reference(reference) => reference.is_dead(),
653 }
654 }
655
656 #[must_use]
658 pub fn eos_legal(&self) -> bool {
659 match &self.backend {
660 MatcherBackend::Incremental(incremental) => incremental.state.is_accepting(),
661 #[cfg(feature = "bench-internals")]
662 MatcherBackend::Reference(reference) => reference.eos_legal(),
663 }
664 }
665
666 #[must_use]
668 pub fn retained_session_bytes(&self) -> usize {
669 match &self.backend {
670 MatcherBackend::Incremental(incremental) => incremental.state.retained_bytes(),
671 #[cfg(feature = "bench-internals")]
672 MatcherBackend::Reference(reference) => reference.retained_bytes(),
673 }
674 }
675
676 #[must_use]
678 pub fn peak_session_bytes(&self) -> usize {
679 match &self.backend {
680 MatcherBackend::Incremental(incremental) => incremental.state.peak_session_bytes(),
681 #[cfg(feature = "bench-internals")]
682 MatcherBackend::Reference(reference) => reference.retained_bytes(),
683 }
684 }
685
686 #[cfg(feature = "bench-internals")]
688 #[must_use]
689 pub fn active_validator_count_for_bench(&self) -> usize {
690 match &self.backend {
691 MatcherBackend::Incremental(incremental) => {
692 incremental.state.active_validator_count_for_bench()
693 }
694 MatcherBackend::Reference(_) => 0,
695 }
696 }
697
698 #[cfg(feature = "bench-internals")]
700 #[must_use]
701 pub fn frame_profile_for_bench(&self) -> (usize, usize) {
702 match &self.backend {
703 MatcherBackend::Incremental(incremental) => incremental.state.frame_profile_for_bench(),
704 MatcherBackend::Reference(_) => (0, 0),
705 }
706 }
707
708 #[cfg(feature = "bench-internals")]
710 #[must_use]
711 pub fn retention_breakdown_for_bench(&self) -> (usize, usize) {
712 match &self.backend {
713 MatcherBackend::Incremental(incremental) => {
714 incremental.state.retention_breakdown_for_bench()
715 }
716 MatcherBackend::Reference(reference) => (reference.retained_bytes(), 0),
717 }
718 }
719
720 pub fn advance(&mut self, token_bytes: &[u8]) -> Result<bool, StructuredMatcherError> {
722 if token_bytes.is_empty() {
723 return Ok(false);
724 }
725 match &mut self.backend {
726 MatcherBackend::Incremental(incremental) => {
727 let state = &mut incremental.state;
728 let mark = state.checkpoint();
729 for &byte in token_bytes {
730 match state.try_push_byte(byte) {
731 Ok(true) => {}
732 Ok(false) => {
733 state.rollback(mark);
734 return Ok(false);
735 }
736 Err(error) => {
737 state.rollback(mark);
738 return Err(error.into());
739 }
740 }
741 }
742 if let Err(error) = state.commit_checkpoint() {
743 state.rollback(mark);
744 return Err(error.into());
745 }
746 Ok(true)
747 }
748 #[cfg(feature = "bench-internals")]
749 MatcherBackend::Reference(reference) => reference
750 .try_advance(token_bytes)
751 .map_err(StructuredMatcherError::from),
752 }
753 }
754
755 pub fn allowed_mask_from_records<'a>(
757 &mut self,
758 mask_vocab_size: usize,
759 records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
760 ) -> Result<Bitmask, StructuredMatcherError> {
761 match &mut self.backend {
762 MatcherBackend::Incremental(incremental) => {
763 let state = &mut incremental.state;
764 let mut mask = Bitmask::zeros(mask_vocab_size);
765 let mut work = 0u64;
766 for (bytes, ids) in records {
767 charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
768 if candidate_is_allowed(state, bytes)? {
769 for &id in ids {
770 mask.set(TokenId(id))
771 .map_err(StructuredMatcherError::from)?;
772 }
773 }
774 }
775 Ok(mask)
776 }
777 #[cfg(feature = "bench-internals")]
778 MatcherBackend::Reference(reference) => reference
779 .allowed_mask_from_records(mask_vocab_size, records)
780 .map_err(StructuredMatcherError::from),
781 }
782 }
783
784 pub fn write_record_mask_words_into<'a>(
787 &mut self,
788 mask_vocab_size: usize,
789 records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
790 out: &mut [u32],
791 ) -> Result<(), StructuredMatcherError> {
792 validate_mask_output_words(mask_vocab_size, out)?;
793 out.fill(0);
794 let result = match &mut self.backend {
795 MatcherBackend::Incremental(incremental) => (|| {
796 let mut work = 0u64;
797 for (bytes, ids) in records {
798 charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
799 if candidate_is_allowed(&mut incremental.state, bytes)? {
800 for &id in ids {
801 set_token_bit_word(out, mask_vocab_size, id)?;
802 }
803 }
804 }
805 Ok(())
806 })(),
807 #[cfg(feature = "bench-internals")]
808 MatcherBackend::Reference(reference) => reference
809 .allowed_mask_from_records(mask_vocab_size, records)
810 .map(|mask| out.copy_from_slice(mask.as_words()))
811 .map_err(StructuredMatcherError::from),
812 };
813 if result.is_err() {
814 out.fill(0);
815 }
816 result
817 }
818
819 pub fn write_record_mask_le_bytes_into<'a>(
821 &mut self,
822 mask_vocab_size: usize,
823 records: impl Iterator<Item = (&'a [u8], &'a [u32])>,
824 out: &mut [u8],
825 ) -> Result<(), StructuredMatcherError> {
826 validate_mask_output(mask_vocab_size, out)?;
827 out.fill(0);
828 let result = match &mut self.backend {
829 MatcherBackend::Incremental(incremental) => (|| {
830 let mut work = 0u64;
831 for (bytes, ids) in records {
832 charge_mask_work(&mut work, bytes.len(), ids.len(), self.program.limits)?;
833 if candidate_is_allowed(&mut incremental.state, bytes)? {
834 for &id in ids {
835 set_token_bit_le(out, mask_vocab_size, id)?;
836 }
837 }
838 }
839 Ok(())
840 })(),
841 #[cfg(feature = "bench-internals")]
842 MatcherBackend::Reference(reference) => reference
843 .write_mask_le_bytes_into(mask_vocab_size, records, out)
844 .map_err(StructuredMatcherError::from),
845 };
846 if result.is_err() {
847 out.fill(0);
848 }
849 result
850 }
851
852 pub fn write_mask_le_bytes_into(
854 &mut self,
855 vocabulary: &VocabularyHandle,
856 trie: Option<&BoundByteTrie>,
857 out: &mut [u8],
858 ) -> Result<(), StructuredMatcherError> {
859 let width = vocabulary.mask_vocab_size();
860 validate_mask_output(width, out)?;
861 out.fill(0);
862 let result = match &mut self.backend {
863 MatcherBackend::Incremental(incremental) => {
864 let trie = trie.ok_or_else(missing_trie_error)?;
865 walk_trie_mask(incremental, vocabulary, trie, out, self.program.limits)
866 }
867 #[cfg(feature = "bench-internals")]
868 MatcherBackend::Reference(reference) => reference
869 .write_mask_le_bytes_into(width, vocabulary.iter_records(), out)
870 .map_err(StructuredMatcherError::from),
871 };
872 if result.is_err() {
873 out.fill(0);
874 }
875 result
876 }
877
878 #[cfg(feature = "bench-internals")]
880 pub fn write_full_trie_mask_for_bench(
881 &mut self,
882 vocabulary: &VocabularyHandle,
883 trie: &BoundByteTrie,
884 out: &mut [u8],
885 ) -> Result<(), StructuredMatcherError> {
886 validate_mask_output(vocabulary.mask_vocab_size(), out)?;
887 out.fill(0);
888 let result = match &mut self.backend {
889 MatcherBackend::Incremental(incremental) => {
890 if trie.fingerprint() != vocabulary.fingerprint() {
891 return Err(missing_trie_error());
892 }
893 walk_trie_mask_inner(
894 incremental,
895 vocabulary,
896 trie.trie(),
897 out,
898 self.program.limits,
899 &mut 0,
900 )
901 }
902 MatcherBackend::Reference(reference) => reference
903 .write_mask_le_bytes_into(
904 vocabulary.mask_vocab_size(),
905 vocabulary.iter_records(),
906 out,
907 )
908 .map_err(StructuredMatcherError::from),
909 };
910 if result.is_err() {
911 out.fill(0);
912 }
913 result
914 }
915}
916
917fn validate_mask_output(width: usize, out: &[u8]) -> Result<(), StructuredMatcherError> {
918 let bytes = width
919 .div_ceil(32)
920 .checked_mul(std::mem::size_of::<u32>())
921 .ok_or_else(mask_output_error)?;
922 if out.len() != bytes {
923 return Err(mask_output_error());
924 }
925 Ok(())
926}
927
928fn mask_output_error() -> StructuredMatcherError {
929 CompileError::new(
930 ErrorCode::ArtifactOutOfBounds,
931 crate::error::Stage::L4Bind,
932 "structured mask output length does not match vocabulary width",
933 )
934 .into()
935}
936
937fn missing_trie_error() -> StructuredMatcherError {
938 CompileError::new(
939 ErrorCode::ArtifactMismatch,
940 crate::error::Stage::L4Bind,
941 "incremental structured matcher requires its bound byte trie",
942 )
943 .into()
944}
945
946fn set_token_bit_le(
947 out: &mut [u8],
948 width: usize,
949 token: u32,
950) -> Result<(), StructuredMatcherError> {
951 let token = usize::try_from(token).map_err(|_| mask_output_error())?;
952 if token >= width {
953 return Err(mask_output_error());
954 }
955 let byte = token.checked_div(8).ok_or_else(mask_output_error)?;
956 let bit = u32::try_from(token % 8).map_err(|_| mask_output_error())?;
957 out[byte] |= 1u8 << bit;
958 Ok(())
959}
960
961fn validate_mask_output_words(width: usize, out: &[u32]) -> Result<(), StructuredMatcherError> {
962 if out.len() != width.div_ceil(32) {
963 return Err(mask_output_error());
964 }
965 Ok(())
966}
967
968fn set_token_bit_word(
969 out: &mut [u32],
970 width: usize,
971 token: u32,
972) -> Result<(), StructuredMatcherError> {
973 let token = usize::try_from(token).map_err(|_| mask_output_error())?;
974 if token >= width {
975 return Err(mask_output_error());
976 }
977 out[token / 32] |= 1u32 << (token % 32);
978 Ok(())
979}
980
981fn candidate_is_allowed(
982 state: &mut StructuredState<'static>,
983 bytes: &[u8],
984) -> Result<bool, StructuredMatcherError> {
985 if bytes.is_empty() {
986 return Ok(false);
987 }
988 state.validate_retained_budget()?;
989 let mark = state.checkpoint();
990 for &byte in bytes {
991 match state.try_push_byte_for_mask(byte) {
992 Ok(true) => {}
993 Ok(false) => {
994 state.rollback(mark);
995 return Ok(false);
996 }
997 Err(error) => {
998 state.rollback(mark);
999 return Err(error.into());
1000 }
1001 }
1002 }
1003 state.rollback(mark);
1004 Ok(true)
1005}
1006
1007fn walk_trie_mask(
1008 incremental: &mut IncrementalMatcher,
1009 vocabulary: &VocabularyHandle,
1010 bound: &BoundByteTrie,
1011 out: &mut [u8],
1012 limits: StructuredLimits,
1013) -> Result<(), StructuredMatcherError> {
1014 #[cfg(feature = "bench-internals")]
1015 incremental.reset_mask_metrics();
1016 if bound.fingerprint() != vocabulary.fingerprint() {
1017 return Err(missing_trie_error());
1018 }
1019 let (selected, mut proof_work) = select_adaptive_trie(incremental, vocabulary, limits)?;
1020 if let Some(slice) = selected {
1021 match slice.bounded {
1022 Some(mask) => or_bounded_mask(out, mask, slice.candidate.as_deref()),
1023 None => or_token_mask(
1024 out,
1025 slice.catalog.safe().token_mask(),
1026 slice.candidate.as_deref(),
1027 ),
1028 }
1029 if slice.body_prefix {
1030 or_token_mask(
1031 out,
1032 slice.catalog.body_extra().token_mask(),
1033 slice.candidate.as_deref(),
1034 );
1035 }
1036 let mut result = if slice.reject_invalid {
1037 walk_body_boundary_trie_mask_inner(
1038 incremental,
1039 vocabulary,
1040 slice.catalog.body_boundary_trie(),
1041 slice.catalog.body_boundary_max_bytes(),
1042 out,
1043 limits,
1044 &mut proof_work,
1045 )
1046 } else {
1047 walk_trie_mask_inner(
1048 incremental,
1049 vocabulary,
1050 if slice.body_prefix {
1051 slice.catalog.body_uncertain_trie()
1052 } else {
1053 slice.catalog.uncertain_trie()
1054 },
1055 out,
1056 limits,
1057 &mut proof_work,
1058 )
1059 };
1060 if result.is_ok() && slice.bounded.is_some_and(|mask| mask.needs_overflow_walk()) {
1061 result = walk_trie_mask_inner(
1062 incremental,
1063 vocabulary,
1064 slice.catalog.scalar_overflow_trie(),
1065 out,
1066 limits,
1067 &mut proof_work,
1068 );
1069 }
1070 #[cfg(feature = "bench-internals")]
1071 incremental.finish_mask_metrics();
1072 return result;
1073 }
1074 let result = walk_trie_mask_inner(
1075 incremental,
1076 vocabulary,
1077 bound.trie(),
1078 out,
1079 limits,
1080 &mut proof_work,
1081 );
1082 #[cfg(feature = "bench-internals")]
1083 incremental.finish_mask_metrics();
1084 result
1085}
1086
1087fn walk_trie_mask_inner(
1088 incremental: &mut IncrementalMatcher,
1089 vocabulary: &VocabularyHandle,
1090 trie: &VocabTrie,
1091 out: &mut [u8],
1092 limits: StructuredLimits,
1093 work: &mut u64,
1094) -> Result<(), StructuredMatcherError> {
1095 incremental.state.validate_retained_budget()?;
1096 let width = vocabulary.mask_vocab_size();
1097 let root = incremental.state.checkpoint();
1098 #[cfg(feature = "bench-internals")]
1099 incremental.increment_mask_metric(MaskMetric::Checkpoint);
1100 incremental.events.clear();
1101 reserve_events(&mut incremental.events, 1, limits)?;
1102 incremental.events.push(TrieEvent::Visit {
1103 node: TrieNodeId(0),
1104 incoming_byte: None,
1105 });
1106 let result = (|| {
1107 while let Some(event) = incremental.events.pop() {
1108 match event {
1109 TrieEvent::Restore(mark) => {
1110 incremental.state.rollback(mark);
1111 #[cfg(feature = "bench-internals")]
1112 incremental.increment_mask_metric(MaskMetric::Rollback);
1113 }
1114 TrieEvent::Visit {
1115 node,
1116 incoming_byte,
1117 } => {
1118 #[cfg(feature = "bench-internals")]
1119 incremental.increment_mask_metric(MaskMetric::TrieNode);
1120 let mark = incremental.state.checkpoint();
1121 #[cfg(feature = "bench-internals")]
1122 incremental.increment_mask_metric(MaskMetric::Checkpoint);
1123 if let Some(byte) = incoming_byte {
1124 #[cfg(feature = "bench-internals")]
1125 {
1126 incremental.increment_mask_metric(MaskMetric::TrieEdge);
1127 incremental.increment_mask_metric(MaskMetric::PushedByte);
1128 }
1129 match incremental.state.try_push_byte_for_mask(byte) {
1130 Ok(true) => {}
1131 Ok(false) => {
1132 incremental.state.rollback(mark);
1133 #[cfg(feature = "bench-internals")]
1134 incremental.increment_mask_metric(MaskMetric::Rollback);
1135 continue;
1136 }
1137 Err(error) => return Err(error.into()),
1138 }
1139 }
1140 for &token in trie.leaf_tokens_of(node)? {
1141 charge_mask_work(work, 0, 1, limits)?;
1142 set_token_bit_le(out, width, token)?;
1143 }
1144 let (keys, children) = trie.children(node)?;
1145 let event_count = children.len().checked_add(1).ok_or({
1146 StructuredMatcherError::ResourceLimit {
1147 kind: LimitKind::MaskScratchBytes,
1148 observed: usize::MAX,
1149 limit: limits.max_mask_scratch_bytes,
1150 }
1151 })?;
1152 reserve_events(&mut incremental.events, event_count, limits)?;
1153 incremental.events.push(TrieEvent::Restore(mark));
1154 for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1155 let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1156 charge_mask_work(work, 1, 0, limits)?;
1157 incremental.events.push(TrieEvent::Visit {
1158 node: child,
1159 incoming_byte: Some(byte),
1160 });
1161 }
1162 }
1163 TrieEvent::LocalVisit { .. } | TrieEvent::RestoreLocalPath(_) => {
1164 unreachable!("local lexical event in exact trie walk")
1165 }
1166 }
1167 }
1168 Ok(())
1169 })();
1170 incremental.state.rollback(root);
1171 #[cfg(feature = "bench-internals")]
1172 incremental.increment_mask_metric(MaskMetric::Rollback);
1173 result
1174}
1175
1176fn walk_body_boundary_trie_mask_inner(
1179 incremental: &mut IncrementalMatcher,
1180 vocabulary: &VocabularyHandle,
1181 trie: &VocabTrie,
1182 max_path_bytes: usize,
1183 out: &mut [u8],
1184 limits: StructuredLimits,
1185 work: &mut u64,
1186) -> Result<(), StructuredMatcherError> {
1187 incremental.state.validate_retained_budget()?;
1188 reserve_lexical_path(incremental, max_path_bytes, limits)?;
1189 let width = vocabulary.mask_vocab_size();
1190 let root = incremental.state.checkpoint();
1191 incremental.events.clear();
1192 incremental.lexical_path.clear();
1193 reserve_events_with_extra(
1194 &mut incremental.events,
1195 1,
1196 incremental.lexical_path.capacity(),
1197 limits,
1198 )?;
1199 incremental.events.push(TrieEvent::LocalVisit {
1200 node: TrieNodeId(0),
1201 incoming_byte: None,
1202 decoder: JsonStringDecoder::new(),
1203 path_len: 0,
1204 });
1205 let result = (|| {
1206 while let Some(event) = incremental.events.pop() {
1207 match event {
1208 TrieEvent::Restore(mark) => {
1209 incremental.state.rollback(mark);
1210 #[cfg(feature = "bench-internals")]
1211 incremental.increment_mask_metric(MaskMetric::Rollback);
1212 }
1213 TrieEvent::RestoreLocalPath(len) => incremental.lexical_path.truncate(len),
1214 TrieEvent::LocalVisit {
1215 node,
1216 incoming_byte,
1217 mut decoder,
1218 path_len,
1219 } => {
1220 incremental.lexical_path.truncate(path_len);
1221 let boundary = if let Some(byte) = incoming_byte {
1222 if incremental.lexical_path.len() == max_path_bytes {
1223 return Err(StructuredMatcherError::ResourceLimit {
1224 kind: LimitKind::MaskScratchBytes,
1225 observed: max_path_bytes.saturating_add(1),
1226 limit: limits.max_mask_scratch_bytes,
1227 });
1228 }
1229 incremental.lexical_path.push(byte);
1230 if byte == b'"' && decoder.at_boundary() {
1231 true
1232 } else {
1233 #[cfg(feature = "bench-internals")]
1234 incremental.increment_mask_metric(MaskMetric::LocalDfaTransition);
1235 if decoder.push(byte) == DecodeStep::Invalid {
1236 incremental.lexical_path.truncate(path_len);
1237 continue;
1238 }
1239 false
1240 }
1241 } else {
1242 false
1243 };
1244
1245 if boundary {
1246 #[cfg(feature = "bench-internals")]
1247 incremental.increment_mask_metric(MaskMetric::TrieNode);
1248 let mark = incremental.state.checkpoint();
1249 #[cfg(feature = "bench-internals")]
1250 incremental.increment_mask_metric(MaskMetric::Checkpoint);
1251 let mut accepted = true;
1252 for index in 0..incremental.lexical_path.len() {
1253 let byte = incremental.lexical_path[index];
1254 #[cfg(feature = "bench-internals")]
1255 {
1256 incremental.increment_mask_metric(MaskMetric::TrieEdge);
1257 incremental.increment_mask_metric(MaskMetric::PushedByte);
1258 }
1259 if !incremental.state.try_push_byte_for_mask(byte)? {
1260 accepted = false;
1261 break;
1262 }
1263 }
1264 if !accepted {
1265 incremental.state.rollback(mark);
1266 #[cfg(feature = "bench-internals")]
1267 incremental.increment_mask_metric(MaskMetric::Rollback);
1268 incremental.lexical_path.truncate(path_len);
1269 continue;
1270 }
1271 for &token in trie.leaf_tokens_of(node)? {
1272 charge_mask_work(work, 0, 1, limits)?;
1273 set_token_bit_le(out, width, token)?;
1274 }
1275 let (keys, children) = trie.children(node)?;
1276 let event_count = children.len().checked_add(1).ok_or(
1277 StructuredMatcherError::ResourceLimit {
1278 kind: LimitKind::MaskScratchBytes,
1279 observed: usize::MAX,
1280 limit: limits.max_mask_scratch_bytes,
1281 },
1282 )?;
1283 reserve_events_with_extra(
1284 &mut incremental.events,
1285 event_count,
1286 incremental.lexical_path.capacity(),
1287 limits,
1288 )?;
1289 incremental.events.push(TrieEvent::Restore(mark));
1290 for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1291 let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1292 charge_mask_work(work, 1, 0, limits)?;
1293 #[cfg(feature = "bench-internals")]
1294 incremental.increment_mask_metric(MaskMetric::LocalDfaTransition);
1295 if incremental.state.next_byte_is_lexically_impossible(byte) {
1296 continue;
1297 }
1298 incremental.events.push(TrieEvent::Visit {
1299 node: child,
1300 incoming_byte: Some(byte),
1301 });
1302 }
1303 incremental.lexical_path.truncate(path_len);
1304 continue;
1305 }
1306
1307 debug_assert!(trie.leaf_tokens_of(node)?.is_empty());
1308 let (keys, children) = trie.children(node)?;
1309 let event_count = children.len().checked_add(1).ok_or(
1310 StructuredMatcherError::ResourceLimit {
1311 kind: LimitKind::MaskScratchBytes,
1312 observed: usize::MAX,
1313 limit: limits.max_mask_scratch_bytes,
1314 },
1315 )?;
1316 reserve_events_with_extra(
1317 &mut incremental.events,
1318 event_count,
1319 incremental.lexical_path.capacity(),
1320 limits,
1321 )?;
1322 incremental
1323 .events
1324 .push(TrieEvent::RestoreLocalPath(path_len));
1325 let child_path_len = incremental.lexical_path.len();
1326 for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1327 let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1328 charge_mask_work(work, 1, 0, limits)?;
1329 incremental.events.push(TrieEvent::LocalVisit {
1330 node: child,
1331 incoming_byte: Some(byte),
1332 decoder,
1333 path_len: child_path_len,
1334 });
1335 }
1336 }
1337 TrieEvent::Visit {
1338 node,
1339 incoming_byte,
1340 } => {
1341 #[cfg(feature = "bench-internals")]
1342 incremental.increment_mask_metric(MaskMetric::TrieNode);
1343 let mark = incremental.state.checkpoint();
1344 #[cfg(feature = "bench-internals")]
1345 incremental.increment_mask_metric(MaskMetric::Checkpoint);
1346 if let Some(byte) = incoming_byte {
1347 #[cfg(feature = "bench-internals")]
1348 {
1349 incremental.increment_mask_metric(MaskMetric::TrieEdge);
1350 incremental.increment_mask_metric(MaskMetric::PushedByte);
1351 }
1352 match incremental.state.try_push_byte_for_mask(byte) {
1353 Ok(true) => {}
1354 Ok(false) => {
1355 incremental.state.rollback(mark);
1356 #[cfg(feature = "bench-internals")]
1357 incremental.increment_mask_metric(MaskMetric::Rollback);
1358 continue;
1359 }
1360 Err(error) => return Err(error.into()),
1361 }
1362 }
1363 for &token in trie.leaf_tokens_of(node)? {
1364 charge_mask_work(work, 0, 1, limits)?;
1365 set_token_bit_le(out, width, token)?;
1366 }
1367 let (keys, children) = trie.children(node)?;
1368 let event_count = children.len().checked_add(1).ok_or(
1369 StructuredMatcherError::ResourceLimit {
1370 kind: LimitKind::MaskScratchBytes,
1371 observed: usize::MAX,
1372 limit: limits.max_mask_scratch_bytes,
1373 },
1374 )?;
1375 reserve_events_with_extra(
1376 &mut incremental.events,
1377 event_count,
1378 incremental.lexical_path.capacity(),
1379 limits,
1380 )?;
1381 incremental.events.push(TrieEvent::Restore(mark));
1382 for (&key, &child) in keys.iter().zip(children.iter()).rev() {
1383 let byte = u8::try_from(key).map_err(|_| missing_trie_error())?;
1384 charge_mask_work(work, 1, 0, limits)?;
1385 incremental.events.push(TrieEvent::Visit {
1386 node: child,
1387 incoming_byte: Some(byte),
1388 });
1389 }
1390 }
1391 }
1392 }
1393 Ok(())
1394 })();
1395 incremental.state.rollback(root);
1396 result
1397}
1398
1399fn select_wrapped_slice<'a>(
1402 incremental: &mut IncrementalMatcher,
1403 vocabulary: &'a VocabularyHandle,
1404 limits: StructuredLimits,
1405 body_prefix: bool,
1406) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1407 let Some(catalog) = vocabulary.token_slices().map(AsRef::as_ref) else {
1408 return Ok((None, 0));
1409 };
1410 if catalog.safe().scalar_count().is_some() {
1411 return Ok((None, 0));
1412 }
1413 let mut proofs = std::mem::take(&mut incremental.certificate_proofs);
1414 proofs.clear();
1415 let max_bytes = catalog.safe().max_byte_len().max(if body_prefix {
1416 catalog
1417 .body_extra()
1418 .max_byte_len()
1419 .max(catalog.body_boundary_max_bytes())
1420 .max(catalog.body_invalid_max_bytes())
1421 } else {
1422 0
1423 });
1424 let Some(certificate_fuel) = incremental.state.certificate_fuel() else {
1425 incremental.certificate_proofs = proofs;
1426 return Ok((None, 0));
1427 };
1428 let mut fuel = ProofContext::for_preparation(
1429 max_bytes,
1430 incremental.state.retained_bytes(),
1431 limits.max_session_bytes,
1432 certificate_fuel,
1433 );
1434 fuel.body_prefix = body_prefix;
1435 let certificate = incremental.state.certify_slice(&mut proofs, &mut fuel);
1436 if certificate == SliceCertificate::Unknown {
1437 incremental.certificate_proofs = proofs;
1438 return Ok((None, 0));
1439 }
1440 let bounded = match fuel.remaining_scalars {
1441 Some(remaining) => {
1442 let Some(mask) = catalog.bounded_mask(max_bytes, Some(remaining)) else {
1443 incremental.certificate_proofs = proofs;
1444 return Ok((None, 0));
1445 };
1446 Some(mask)
1447 }
1448 None => None,
1449 };
1450 let mut work = 0u64;
1451 let mut proved = true;
1452 let mut reject_invalid = body_prefix;
1453 let mut candidate = None;
1454 for proof in proofs.iter().copied() {
1455 let (node, state, requires_live) = match proof {
1456 SliceProof::All(node, state) => (node, state, false),
1457 SliceProof::CandidateLive(node, state) => (node, state, true),
1458 };
1459 let key = SliceProofKey {
1460 vocabulary: vocabulary.fingerprint(),
1461 node,
1462 state,
1463 body_prefix,
1464 reject_invalid: false,
1465 };
1466 let Some(engine) = incremental.state.engine_for_node(node) else {
1467 proved = false;
1468 break;
1469 };
1470 if requires_live {
1471 if candidate.is_some() {
1472 proved = false;
1473 break;
1474 }
1475 let mask = if let Some(cached) = incremental.slice_proofs.candidate(key) {
1476 cached
1477 } else {
1478 let Some((mask, spent)) = candidate_live_mask(
1479 engine,
1480 state,
1481 catalog.safe().filtered_trie(),
1482 body_prefix.then_some(catalog.body_extra().filtered_trie()),
1483 vocabulary.mask_vocab_size(),
1484 limits,
1485 ) else {
1486 proved = false;
1487 break;
1488 };
1489 work = work.saturating_add(spent);
1490 incremental.slice_proofs.insert_candidate(
1491 key,
1492 mask.clone(),
1493 limits.max_slice_cache_bytes,
1494 );
1495 mask
1496 };
1497 candidate = Some(mask);
1498 continue;
1499 }
1500 let ok = if let Some(cached) = incremental.slice_proofs.get(key) {
1501 cached
1502 } else {
1503 let (mut ok, mut spent) =
1504 safe_slice_subsumes(engine, state, catalog.safe().filtered_trie(), limits);
1505 if ok && body_prefix {
1506 let (extra_ok, extra_spent) =
1507 slice_subsumes(engine, state, catalog.body_extra().filtered_trie(), limits);
1508 ok = extra_ok;
1509 spent = spent.saturating_add(extra_spent);
1510 }
1511 work = work.saturating_add(spent);
1512 incremental
1513 .slice_proofs
1514 .insert(key, ok, limits.max_slice_cache_bytes);
1515 ok
1516 };
1517 if !ok {
1518 proved = false;
1519 break;
1520 }
1521 if reject_invalid {
1522 let rejection_key = SliceProofKey {
1523 reject_invalid: true,
1524 ..key
1525 };
1526 reject_invalid = if let Some(cached) = incremental.slice_proofs.get(rejection_key) {
1527 cached
1528 } else {
1529 let (rejected, spent) = slice_rejects_at_lexical_error(
1530 engine,
1531 state,
1532 catalog.body_invalid_trie(),
1533 limits,
1534 );
1535 work = work.saturating_add(spent);
1536 incremental.slice_proofs.insert(
1537 rejection_key,
1538 rejected,
1539 limits.max_slice_cache_bytes,
1540 );
1541 rejected
1542 };
1543 }
1544 }
1545 incremental.certificate_proofs = proofs;
1546 if proved {
1547 incremental
1548 .state
1549 .prepare_certified_slice(&mut ProofContext::with_fuel(max_bytes, certificate_fuel))?;
1550 }
1551 let selected = SelectedSlice {
1552 catalog,
1553 bounded,
1554 body_prefix,
1555 reject_invalid,
1556 candidate,
1557 };
1558 Ok((proved.then_some(selected), work))
1559}
1560
1561struct SelectedSlice<'a> {
1563 catalog: &'a TokenSliceCatalog,
1564 bounded: Option<BoundedMask<'a>>,
1566 body_prefix: bool,
1567 reject_invalid: bool,
1568 candidate: Option<Arc<[u64]>>,
1569}
1570
1571fn select_adaptive_trie<'a>(
1572 incremental: &mut IncrementalMatcher,
1573 vocabulary: &'a VocabularyHandle,
1574 limits: StructuredLimits,
1575) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1576 let (body, body_work) = select_wrapped_slice(incremental, vocabulary, limits, true)?;
1577 if body.is_some() {
1578 return Ok((body, body_work));
1579 }
1580 let (slice, work) = select_plain_adaptive_trie(incremental, vocabulary, limits)?;
1581 Ok((slice, work.saturating_add(body_work)))
1582}
1583
1584fn select_plain_adaptive_trie<'a>(
1585 incremental: &mut IncrementalMatcher,
1586 vocabulary: &'a VocabularyHandle,
1587 limits: StructuredLimits,
1588) -> Result<(Option<SelectedSlice<'a>>, u64), StructuredMatcherError> {
1589 if incremental.state.local_slice_residual().is_none() {
1590 return select_wrapped_slice(incremental, vocabulary, limits, false);
1591 }
1592 let (remaining_document_bytes, remaining_scalars, needs_proof) = {
1593 let Some(residual) = incremental.state.local_slice_residual() else {
1594 return Ok((None, 0));
1595 };
1596 let needs_proof = match (residual.engine, residual.pattern_state) {
1597 (None, None) => false,
1599 (Some(engine), Some(state))
1600 if engine.consume_token(state, b"a").is_some()
1601 && engine.consume_token(state, b"z").is_some() =>
1602 {
1603 true
1604 }
1605 _ => return Ok((None, 0)),
1606 };
1607 (
1608 residual.remaining_document_bytes,
1609 residual.remaining_scalars,
1610 needs_proof,
1611 )
1612 };
1613 let Some(catalog) = vocabulary.token_slices().map(AsRef::as_ref) else {
1614 return Ok((None, 0));
1615 };
1616 if catalog.safe().scalar_count().is_some() {
1617 return Ok((None, 0));
1618 }
1619 if catalog.safe().max_byte_len() > remaining_document_bytes {
1622 return Ok((None, 0));
1623 }
1624 let unbounded =
1625 catalog.safe().max_byte_len() <= remaining_document_bytes && remaining_scalars.is_none();
1626 let (bounded, admitted_bytes) = if unbounded {
1627 (None, catalog.safe().max_byte_len())
1628 } else {
1629 let Some(mask) = catalog.bounded_mask(remaining_document_bytes, remaining_scalars) else {
1630 return Ok((None, 0));
1631 };
1632 (
1633 Some(mask),
1634 remaining_document_bytes
1635 .min(catalog.safe().max_byte_len())
1636 .min(remaining_scalars.map_or(usize::MAX, |scalars| scalars.saturating_mul(4))),
1637 )
1638 };
1639 if incremental.state.prepare_local_slice(admitted_bytes)? == SlicePreparation::Ineligible {
1640 return select_wrapped_slice(incremental, vocabulary, limits, false);
1641 }
1642 let Some(residual) = incremental.state.local_slice_residual() else {
1643 return Ok((None, 0));
1644 };
1645 let selected = SelectedSlice {
1646 catalog,
1647 bounded,
1648 body_prefix: false,
1649 reject_invalid: false,
1650 candidate: None,
1651 };
1652 if !needs_proof {
1653 return Ok((Some(selected), 0));
1654 }
1655 let Some(engine) = residual.engine else {
1656 return Ok((None, 0));
1657 };
1658 let Some(state) = residual.pattern_state else {
1659 return Ok((None, 0));
1660 };
1661 let key = SliceProofKey {
1662 vocabulary: vocabulary.fingerprint(),
1663 node: residual.node,
1664 state,
1665 body_prefix: false,
1666 reject_invalid: false,
1667 };
1668 if let Some(subsumes) = incremental.slice_proofs.get(key) {
1669 return Ok((subsumes.then_some(selected), 0));
1670 }
1671 let (proved, work) = safe_slice_subsumes(engine, state, catalog.safe().filtered_trie(), limits);
1672 incremental
1673 .slice_proofs
1674 .insert(key, proved, limits.max_slice_cache_bytes);
1675 Ok((proved.then_some(selected), work))
1676}
1677
1678fn safe_slice_subsumes(
1679 engine: &crate::automaton::RefEngine,
1680 start: StateId,
1681 trie: &VocabTrie,
1682 limits: StructuredLimits,
1683) -> (bool, u64) {
1684 let (proved, work) = neutral_language_subsumes(engine, start, limits);
1685 if proved {
1686 return (true, work);
1687 }
1688 let mut remaining = limits;
1689 remaining.max_mask_work = remaining.max_mask_work.saturating_sub(work);
1690 let (proved, extra) = slice_subsumes(engine, start, trie, remaining);
1691 (proved, work.saturating_add(extra))
1692}
1693
1694fn neutral_language_subsumes(
1696 engine: &crate::automaton::RefEngine,
1697 start: StateId,
1698 limits: StructuredLimits,
1699) -> (bool, u64) {
1700 const CAPACITY: usize = 64;
1701 if std::mem::size_of::<[(StateId, u8); CAPACITY]>() > limits.max_mask_scratch_bytes {
1702 return (false, 0);
1703 }
1704 let mut pairs = [(start, 0u8); CAPACITY];
1705 let mut length = 1;
1706 let mut cursor = 0;
1707 let mut work = 0;
1708 while cursor < length {
1709 let (state, mode) = pairs[cursor];
1710 cursor += 1;
1711 for byte in 0..=u8::MAX {
1712 let Some(next_mode) = neutral_utf8_step(mode, byte) else {
1713 continue;
1714 };
1715 if work == limits.max_mask_work {
1716 return (false, work);
1717 }
1718 work += 1;
1719 let Some(next) = engine.consume_token(state, &[byte]) else {
1720 return (false, work);
1721 };
1722 let pair = (next, next_mode);
1723 if !pairs[..length].contains(&pair) {
1724 if length == CAPACITY {
1725 return (false, work);
1726 }
1727 pairs[length] = pair;
1728 length += 1;
1729 }
1730 }
1731 }
1732 (true, work)
1733}
1734
1735fn neutral_utf8_step(mode: u8, byte: u8) -> Option<u8> {
1736 match (mode, byte) {
1737 (0, b'"' | b'\\') => None,
1738 (0, 0x20..=0x7f) => Some(0),
1739 (0, 0xc2..=0xdf) => Some(1),
1740 (0, 0xe0) => Some(4),
1741 (0, 0xed) => Some(5),
1742 (0, 0xe1..=0xec | 0xee..=0xef) => Some(2),
1743 (0, 0xf0) => Some(6),
1744 (0, 0xf4) => Some(7),
1745 (0, 0xf1..=0xf3) => Some(3),
1746 (1..=3, 0x80..=0xbf) => Some(mode - 1),
1747 (4, 0xa0..=0xbf) | (5, 0x80..=0x9f) => Some(1),
1748 (6, 0x90..=0xbf) | (7, 0x80..=0x8f) => Some(2),
1749 _ => None,
1750 }
1751}
1752
1753fn slice_subsumes(
1756 engine: &crate::automaton::RefEngine,
1757 start: StateId,
1758 trie: &VocabTrie,
1759 limits: StructuredLimits,
1760) -> (bool, u64) {
1761 let frontier = trie.max_dfs_frontier();
1762 let Some(bytes) = frontier.checked_mul(std::mem::size_of::<(TrieNodeId, StateId)>()) else {
1763 return (false, 0);
1764 };
1765 if bytes > limits.max_mask_scratch_bytes {
1766 return (false, 0);
1767 }
1768 let mut stack = Vec::new();
1769 if stack.try_reserve_exact(frontier.max(1)).is_err() {
1770 return (false, 0);
1771 }
1772 stack.push((TrieNodeId(0), start));
1773 let mut work = 0u64;
1774 while let Some((node, state)) = stack.pop() {
1775 let Ok((keys, children)) = trie.children(node) else {
1776 return (false, work);
1777 };
1778 for (&key, &child) in keys.iter().zip(children).rev() {
1779 let Some(next_work) = work.checked_add(1) else {
1780 return (false, limits.max_mask_work);
1781 };
1782 work = next_work;
1783 if work > limits.max_mask_work {
1784 return (false, limits.max_mask_work);
1785 }
1786 let Ok(byte) = u8::try_from(key) else {
1787 return (false, work);
1788 };
1789 let Some(next) = engine.consume_token(state, &[byte]) else {
1790 return (false, work);
1791 };
1792 stack.push((child, next));
1793 }
1794 }
1795 (true, work)
1796}
1797
1798fn candidate_live_mask(
1799 engine: &crate::automaton::RefEngine,
1800 start: StateId,
1801 safe: &VocabTrie,
1802 extra: Option<&VocabTrie>,
1803 width: usize,
1804 limits: StructuredLimits,
1805) -> Option<(Arc<[u64]>, u64)> {
1806 let words = width.div_ceil(u64::BITS as usize);
1807 let bytes = words.checked_mul(std::mem::size_of::<u64>())?;
1808 if bytes > limits.max_slice_cache_bytes / 2 || bytes > limits.max_mask_scratch_bytes {
1809 return None;
1810 }
1811 let mut mask = Vec::new();
1812 mask.try_reserve_exact(words).ok()?;
1813 mask.resize(words, 0);
1814 let mut work = 0;
1815 candidate_live_mask_into(engine, start, safe, &mut mask, &mut work, limits)?;
1816 if let Some(extra) = extra {
1817 candidate_live_mask_into(engine, start, extra, &mut mask, &mut work, limits)?;
1818 }
1819 Some((Arc::from(mask), work))
1820}
1821
1822fn candidate_live_mask_into(
1823 engine: &crate::automaton::RefEngine,
1824 start: StateId,
1825 trie: &VocabTrie,
1826 mask: &mut [u64],
1827 work: &mut u64,
1828 limits: StructuredLimits,
1829) -> Option<()> {
1830 let mut stack = Vec::new();
1831 stack
1832 .try_reserve_exact(trie.max_dfs_frontier().max(1))
1833 .ok()?;
1834 stack.push((TrieNodeId(0), start));
1835 while let Some((node, state)) = stack.pop() {
1836 for &token in trie.leaf_tokens_of(node).ok()? {
1837 let index = usize::try_from(token).ok()?;
1838 *mask.get_mut(index / u64::BITS as usize)? |= 1u64 << (index % u64::BITS as usize);
1839 }
1840 let (keys, children) = trie.children(node).ok()?;
1841 for (&key, &child) in keys.iter().zip(children).rev() {
1842 *work = work.checked_add(1)?;
1843 if *work > limits.max_mask_work {
1844 return None;
1845 }
1846 let byte = u8::try_from(key).ok()?;
1847 let Some(next) = engine.consume_token(state, &[byte]) else {
1848 continue;
1849 };
1850 if !engine.is_dead(next) {
1851 stack.push((child, next));
1852 }
1853 }
1854 }
1855 Some(())
1856}
1857
1858fn slice_rejects_at_lexical_error(
1859 engine: &crate::automaton::RefEngine,
1860 start: StateId,
1861 trie: &VocabTrie,
1862 limits: StructuredLimits,
1863) -> (bool, u64) {
1864 use super::lexer::{DecodeStep, JsonStringDecoder};
1865 let frontier = trie.max_dfs_frontier().max(1);
1866 let Some(bytes) =
1867 frontier.checked_mul(std::mem::size_of::<(TrieNodeId, StateId, JsonStringDecoder)>())
1868 else {
1869 return (false, 0);
1870 };
1871 if bytes > limits.max_mask_scratch_bytes || engine.is_accepting(start) {
1872 return (false, 0);
1873 }
1874 let mut stack = Vec::new();
1875 if stack.try_reserve_exact(frontier).is_err() {
1876 return (false, 0);
1877 }
1878 stack.push((TrieNodeId(0), start, JsonStringDecoder::new()));
1879 let mut work = 0u64;
1880 while let Some((node, state, decoder)) = stack.pop() {
1881 if !trie
1882 .leaf_tokens_of(node)
1883 .is_ok_and(|tokens| tokens.is_empty())
1884 {
1885 return (false, work);
1886 }
1887 let Ok((keys, children)) = trie.children(node) else {
1888 return (false, work);
1889 };
1890 for (&key, &child) in keys.iter().zip(children).rev() {
1891 if work >= limits.max_mask_work {
1892 return (false, work);
1893 }
1894 work += 1;
1895 let Ok(byte) = u8::try_from(key) else {
1896 return (false, work);
1897 };
1898 let mut next_decoder = decoder;
1899 if byte == b'"' && decoder.at_boundary() {
1900 return (false, work);
1901 }
1902 let next = engine.consume_token(state, &[byte]);
1903 if next_decoder.push(byte) == DecodeStep::Invalid {
1904 if next.is_some() || engine.is_accepting(state) {
1906 return (false, work);
1907 }
1908 continue;
1909 }
1910 let Some(next) = next.filter(|&state| !engine.is_accepting(state)) else {
1911 return (false, work);
1912 };
1913 stack.push((child, next, next_decoder));
1914 }
1915 }
1916 (true, work)
1917}
1918
1919fn or_token_mask(output: &mut [u8], mask: &[u64], filter: Option<&[u64]>) {
1920 for (index, (chunk, word)) in output.chunks_mut(8).zip(mask).enumerate() {
1921 let word =
1922 *word & filter.map_or(u64::MAX, |filter| filter.get(index).copied().unwrap_or(0));
1923 let bytes = word.to_le_bytes();
1924 for (dst, src) in chunk.iter_mut().zip(bytes) {
1925 *dst |= src;
1926 }
1927 }
1928}
1929
1930fn or_bounded_mask(output: &mut [u8], mask: BoundedMask<'_>, filter: Option<&[u64]>) {
1932 for (index, chunk) in output.chunks_mut(8).take(mask.words()).enumerate() {
1933 let word = mask.word(index)
1934 & filter.map_or(u64::MAX, |filter| filter.get(index).copied().unwrap_or(0));
1935 let bytes = word.to_le_bytes();
1936 for (dst, src) in chunk.iter_mut().zip(bytes) {
1937 *dst |= src;
1938 }
1939 }
1940}
1941
1942#[cfg(test)]
1943fn walk_full_trie_mask_for_test(
1944 incremental: &mut IncrementalMatcher,
1945 vocabulary: &VocabularyHandle,
1946 bound: &BoundByteTrie,
1947 out: &mut [u8],
1948 limits: StructuredLimits,
1949) -> Result<(), StructuredMatcherError> {
1950 if bound.fingerprint() != vocabulary.fingerprint() {
1951 return Err(missing_trie_error());
1952 }
1953 walk_trie_mask_inner(incremental, vocabulary, bound.trie(), out, limits, &mut 0)
1954}
1955
1956fn reserve_events(
1957 events: &mut Vec<TrieEvent>,
1958 additional: usize,
1959 limits: StructuredLimits,
1960) -> Result<(), StructuredMatcherError> {
1961 reserve_events_with_extra(events, additional, 0, limits)
1962}
1963
1964fn reserve_events_with_extra(
1965 events: &mut Vec<TrieEvent>,
1966 additional: usize,
1967 extra_bytes: usize,
1968 limits: StructuredLimits,
1969) -> Result<(), StructuredMatcherError> {
1970 let needed = events.len().checked_add(additional).ok_or({
1971 StructuredMatcherError::ResourceLimit {
1972 kind: LimitKind::MaskScratchBytes,
1973 observed: usize::MAX,
1974 limit: limits.max_mask_scratch_bytes,
1975 }
1976 })?;
1977 let bytes = needed
1978 .checked_mul(std::mem::size_of::<TrieEvent>())
1979 .and_then(|bytes| bytes.checked_add(extra_bytes))
1980 .ok_or(StructuredMatcherError::ResourceLimit {
1981 kind: LimitKind::MaskScratchBytes,
1982 observed: usize::MAX,
1983 limit: limits.max_mask_scratch_bytes,
1984 })?;
1985 if bytes > limits.max_mask_scratch_bytes {
1986 return Err(StructuredMatcherError::ResourceLimit {
1987 kind: LimitKind::MaskScratchBytes,
1988 observed: bytes,
1989 limit: limits.max_mask_scratch_bytes,
1990 });
1991 }
1992 if needed > events.capacity() {
1993 events
1994 .try_reserve_exact(needed - events.len())
1995 .map_err(|_| StructuredMatcherError::ResourceLimit {
1996 kind: LimitKind::MaskScratchBytes,
1997 observed: bytes,
1998 limit: limits.max_mask_scratch_bytes,
1999 })?;
2000 }
2001 let actual = events
2002 .capacity()
2003 .checked_mul(std::mem::size_of::<TrieEvent>())
2004 .and_then(|bytes| bytes.checked_add(extra_bytes))
2005 .ok_or(StructuredMatcherError::ResourceLimit {
2006 kind: LimitKind::MaskScratchBytes,
2007 observed: usize::MAX,
2008 limit: limits.max_mask_scratch_bytes,
2009 })?;
2010 if actual > limits.max_mask_scratch_bytes {
2011 return Err(StructuredMatcherError::ResourceLimit {
2012 kind: LimitKind::MaskScratchBytes,
2013 observed: actual,
2014 limit: limits.max_mask_scratch_bytes,
2015 });
2016 }
2017 Ok(())
2018}
2019
2020fn reserve_lexical_path(
2021 incremental: &mut IncrementalMatcher,
2022 needed: usize,
2023 limits: StructuredLimits,
2024) -> Result<(), StructuredMatcherError> {
2025 let projected = incremental
2026 .events
2027 .capacity()
2028 .checked_mul(std::mem::size_of::<TrieEvent>())
2029 .and_then(|bytes| bytes.checked_add(needed))
2030 .ok_or(StructuredMatcherError::ResourceLimit {
2031 kind: LimitKind::MaskScratchBytes,
2032 observed: usize::MAX,
2033 limit: limits.max_mask_scratch_bytes,
2034 })?;
2035 if projected > limits.max_mask_scratch_bytes {
2036 return Err(StructuredMatcherError::ResourceLimit {
2037 kind: LimitKind::MaskScratchBytes,
2038 observed: projected,
2039 limit: limits.max_mask_scratch_bytes,
2040 });
2041 }
2042 if needed > incremental.lexical_path.capacity() {
2043 incremental
2044 .lexical_path
2045 .try_reserve_exact(needed - incremental.lexical_path.len())
2046 .map_err(|_| StructuredMatcherError::ResourceLimit {
2047 kind: LimitKind::MaskScratchBytes,
2048 observed: projected,
2049 limit: limits.max_mask_scratch_bytes,
2050 })?;
2051 }
2052 let actual = incremental
2053 .events
2054 .capacity()
2055 .checked_mul(std::mem::size_of::<TrieEvent>())
2056 .and_then(|bytes| bytes.checked_add(incremental.lexical_path.capacity()))
2057 .ok_or(StructuredMatcherError::ResourceLimit {
2058 kind: LimitKind::MaskScratchBytes,
2059 observed: usize::MAX,
2060 limit: limits.max_mask_scratch_bytes,
2061 })?;
2062 if actual > limits.max_mask_scratch_bytes {
2063 return Err(StructuredMatcherError::ResourceLimit {
2064 kind: LimitKind::MaskScratchBytes,
2065 observed: actual,
2066 limit: limits.max_mask_scratch_bytes,
2067 });
2068 }
2069 Ok(())
2070}
2071
2072fn charge_mask_work(
2073 work: &mut u64,
2074 bytes: usize,
2075 ids: usize,
2076 limits: StructuredLimits,
2077) -> Result<(), StructuredMatcherError> {
2078 let delta = u64::try_from(bytes)
2079 .ok()
2080 .and_then(|value| value.checked_add(u64::try_from(ids).ok()?))
2081 .ok_or(StructuredMatcherError::ResourceLimit {
2082 kind: LimitKind::MaskWork,
2083 observed: usize::MAX,
2084 limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2085 })?;
2086 let next = work
2087 .checked_add(delta)
2088 .ok_or(StructuredMatcherError::ResourceLimit {
2089 kind: LimitKind::MaskWork,
2090 observed: usize::MAX,
2091 limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2092 })?;
2093 if next > limits.max_mask_work {
2094 return Err(StructuredMatcherError::ResourceLimit {
2095 kind: LimitKind::MaskWork,
2096 observed: usize::try_from(next).unwrap_or(usize::MAX),
2097 limit: usize::try_from(limits.max_mask_work).unwrap_or(usize::MAX),
2098 });
2099 }
2100 *work = next;
2101 Ok(())
2102}
2103
2104#[cfg(test)]
2105mod tests {
2106 use super::*;
2107 use crate::frontend::schema_to_ir;
2108 use crate::index::TrieCache;
2109 use crate::ir::{Charset, CompileOptions, ScalarLit, MAX_UNROLLED_ENUM};
2110 use crate::vocab::build_vocabulary;
2111 use rustc_hash::FxHashMap;
2112
2113 fn ir(schema: &str) -> Arc<SchemaIR> {
2114 Arc::new(schema_to_ir(schema, CompileOptions::default()).expect("schema"))
2115 }
2116
2117 fn program(schema: &str) -> Arc<StructuredProgram> {
2118 StructuredProgram::compile(ir(schema)).expect("program")
2119 }
2120
2121 fn large_string_enum_ir() -> Arc<SchemaIR> {
2122 let mut builder = crate::ir::Builder::new(CompileOptions::default());
2123 let values = (0..=MAX_UNROLLED_ENUM)
2124 .map(|index| ScalarLit::Str(format!("member-{index:04}")))
2125 .collect();
2126 let root = builder.enum_values(values).expect("enum");
2127 Arc::new(builder.finish(root).expect("ir"))
2128 }
2129
2130 fn large_string_enum_schema() -> String {
2131 let members = (0..=MAX_UNROLLED_ENUM)
2132 .map(|index| format!(r#""member-{index:04}""#))
2133 .collect::<Vec<_>>()
2134 .join(",");
2135 format!(r#"{{"enum":["a","ab","abc","é","😀",{members}]}}"#)
2136 }
2137
2138 fn mask(
2139 matcher: &mut StructuredMatcher,
2140 width: usize,
2141 records: &[(Vec<u8>, Vec<u32>)],
2142 ) -> Bitmask {
2143 matcher
2144 .allowed_mask_from_records(
2145 width,
2146 records
2147 .iter()
2148 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
2149 )
2150 .expect("mask")
2151 }
2152
2153 #[cfg(feature = "bench-internals")]
2154 #[derive(Clone, Copy, Debug)]
2155 enum NearLimitTuning {
2156 Document(usize),
2157 Key(usize),
2158 String(usize),
2159 Session(usize),
2160 Undo(usize),
2161 Canonical(usize),
2162 }
2163
2164 #[cfg(feature = "bench-internals")]
2165 fn tune_near_limit(matcher: &mut StructuredMatcher, tuning: NearLimitTuning) {
2166 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2167 panic!("near-limit matrix requires the incremental backend")
2168 };
2169 match tuning {
2170 NearLimitTuning::Document(limit) => {
2171 inner.state.set_document_limit_for_test(limit);
2172 }
2173 NearLimitTuning::Key(limit) => inner.state.set_key_limit_for_test(limit),
2174 NearLimitTuning::String(limit) => inner.state.set_string_limit_for_test(limit),
2175 NearLimitTuning::Session(limit) => inner.state.set_session_limit_for_test(limit),
2176 NearLimitTuning::Undo(limit) => inner.state.set_undo_limit_for_test(limit),
2177 NearLimitTuning::Canonical(limit) => {
2178 inner.state.set_unique_canonical_limit_for_test(limit);
2179 }
2180 }
2181 }
2182
2183 #[cfg(feature = "bench-internals")]
2184 fn assert_incremental_accounting(matcher: &StructuredMatcher, label: &str) {
2185 let MatcherBackend::Incremental(inner) = &matcher.backend else {
2186 panic!("{label}: expected incremental matcher")
2187 };
2188 assert_eq!(
2189 matcher.retained_session_bytes(),
2190 inner.state.recomputed_retained_bytes(),
2191 "{label}: retained-byte ledger diverged from actual capacities: {}",
2192 inner.state.accounting_breakdown_for_test()
2193 );
2194 }
2195
2196 #[cfg(feature = "bench-internals")]
2197 fn assert_three_route_mask_and_advance(
2198 program: &Arc<StructuredProgram>,
2199 vocabulary: &VocabularyHandle,
2200 trie: &BoundByteTrie,
2201 prefix: &[u8],
2202 label: &str,
2203 ) -> (Vec<u8>, u64) {
2204 let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
2205 let mut adaptive = program.new_matcher().expect("adaptive matcher");
2206 let mut full = program.new_matcher().expect("full matcher");
2207 let mut record = program.new_matcher().expect("record matcher");
2208 for matcher in [&mut adaptive, &mut full, &mut record] {
2209 if !prefix.is_empty() {
2210 assert!(matcher.advance(prefix).unwrap(), "{label}: invalid prefix");
2211 }
2212 }
2213 let before = (
2214 adaptive.is_accepting(),
2215 adaptive.is_dead(),
2216 adaptive.eos_legal(),
2217 );
2218 let mut adaptive_out = vec![0xa5; output_len];
2219 let mut full_out = vec![0xa5; output_len];
2220 let mut record_out = vec![0xa5; output_len];
2221 adaptive
2222 .write_mask_le_bytes_into(vocabulary, Some(trie), &mut adaptive_out)
2223 .unwrap_or_else(|error| panic!("{label}: adaptive mask: {error}"));
2224 let nodes = adaptive.mask_work_metrics_for_bench().trie_nodes;
2225 full.write_full_trie_mask_for_bench(vocabulary, trie, &mut full_out)
2226 .unwrap_or_else(|error| panic!("{label}: full mask: {error}"));
2227 record
2228 .write_record_mask_le_bytes_into(
2229 vocabulary.mask_vocab_size(),
2230 vocabulary.iter_records(),
2231 &mut record_out,
2232 )
2233 .unwrap_or_else(|error| panic!("{label}: record mask: {error}"));
2234 assert_mask_bytes_equal(&adaptive_out, &full_out, vocabulary, label);
2235 assert_mask_bytes_equal(&adaptive_out, &record_out, vocabulary, label);
2236 assert_eq!(
2237 (
2238 adaptive.is_accepting(),
2239 adaptive.is_dead(),
2240 adaptive.eos_legal(),
2241 ),
2242 before,
2243 "{label}: mask changed committed logical state"
2244 );
2245 assert_incremental_accounting(&adaptive, label);
2246 assert_incremental_accounting(&full, label);
2247 assert_incremental_accounting(&record, label);
2248 let retained = adaptive.retained_session_bytes();
2249 let mut repeated = vec![0xa5; output_len];
2250 adaptive
2251 .write_mask_le_bytes_into(vocabulary, Some(trie), &mut repeated)
2252 .unwrap_or_else(|error| panic!("{label}: repeated mask: {error}"));
2253 assert_eq!(repeated, adaptive_out, "{label}: repeated mask drift");
2254 assert_eq!(
2255 adaptive.retained_session_bytes(),
2256 retained,
2257 "{label}: repeated mask retained additional capacity"
2258 );
2259 assert_incremental_accounting(&adaptive, label);
2260
2261 for (token, ids) in vocabulary.iter_records() {
2262 let masked = ids
2263 .first()
2264 .is_some_and(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0);
2265 assert!(
2266 ids.iter().all(|id| {
2267 (adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0) == masked
2268 }),
2269 "{label}: alias bits disagree for {token:?}"
2270 );
2271 let mut equivalent = program.new_matcher().expect("equivalent matcher");
2272 if !prefix.is_empty() {
2273 assert!(
2274 equivalent.advance(prefix).unwrap(),
2275 "{label}: replay prefix"
2276 );
2277 }
2278 let before_transition = (
2279 equivalent.is_accepting(),
2280 equivalent.is_dead(),
2281 equivalent.eos_legal(),
2282 );
2283 let accepted = equivalent
2284 .advance(token)
2285 .unwrap_or_else(|error| panic!("{label}: token transition error: {error}"));
2286 assert_eq!(
2287 masked, accepted,
2288 "{label}: mask/advance disagreement for {token:?}"
2289 );
2290 if !accepted {
2291 assert_eq!(
2292 (
2293 equivalent.is_accepting(),
2294 equivalent.is_dead(),
2295 equivalent.eos_legal(),
2296 ),
2297 before_transition,
2298 "{label}: rejected transition mutated committed state for {token:?}"
2299 );
2300 }
2301 assert_incremental_accounting(&equivalent, label);
2302 }
2303 (adaptive_out, nodes)
2304 }
2305
2306 #[cfg(feature = "bench-internals")]
2307 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
2308 enum CompletionSearch {
2309 Found,
2310 ProvenTrap,
2311 Inconclusive,
2312 }
2313
2314 #[cfg(feature = "bench-internals")]
2317 fn closer_first_completion_search(
2318 program: &Arc<StructuredProgram>,
2319 prefix: &[u8],
2320 depth: usize,
2321 remaining_nodes: &mut usize,
2322 ) -> CompletionSearch {
2323 let mut matcher = program.new_matcher().expect("liveness matcher");
2324 if !matcher.advance(prefix).expect("liveness advance") {
2325 return CompletionSearch::ProvenTrap;
2326 }
2327 if matcher.is_accepting() {
2328 return CompletionSearch::Found;
2329 }
2330 if depth == 0 || *remaining_nodes == 0 {
2331 return CompletionSearch::Inconclusive;
2332 }
2333 const CLOSERS_FIRST: &[u8] =
2334 b"}\"],:0123456789truefalsnul abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-/\\.";
2335 let mut alphabet = Vec::with_capacity(98);
2336 for byte in CLOSERS_FIRST
2337 .iter()
2338 .copied()
2339 .chain(0x20u8..=0x7e)
2340 .chain([b'\t', b'\n'])
2341 {
2342 if !alphabet.contains(&byte) {
2343 alphabet.push(byte);
2344 }
2345 }
2346 let mut saw_inconclusive = false;
2347 for byte in alphabet {
2348 if *remaining_nodes == 0 {
2349 return CompletionSearch::Inconclusive;
2350 }
2351 *remaining_nodes -= 1;
2352 let mut candidate = Vec::with_capacity(prefix.len() + 1);
2353 candidate.extend_from_slice(prefix);
2354 candidate.push(byte);
2355 let mut probe = program.new_matcher().expect("liveness probe");
2356 if !probe.advance(&candidate).expect("liveness candidate") {
2357 continue;
2358 }
2359 match closer_first_completion_search(program, &candidate, depth - 1, remaining_nodes) {
2360 CompletionSearch::Found => return CompletionSearch::Found,
2361 CompletionSearch::Inconclusive => saw_inconclusive = true,
2362 CompletionSearch::ProvenTrap => {}
2363 }
2364 }
2365 if saw_inconclusive {
2366 CompletionSearch::Inconclusive
2367 } else {
2368 CompletionSearch::ProvenTrap
2369 }
2370 }
2371
2372 #[cfg(feature = "bench-internals")]
2373 fn assert_near_limit_three_routes(
2374 program: &Arc<StructuredProgram>,
2375 vocabulary: &VocabularyHandle,
2376 trie: &BoundByteTrie,
2377 prefix: &[u8],
2378 tuning: NearLimitTuning,
2379 label: &str,
2380 ) {
2381 let mask_bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
2382 let mut adaptive = program.new_matcher().expect("adaptive matcher");
2383 let mut full = program.new_matcher().expect("full matcher");
2384 let mut records = program.new_matcher().expect("record matcher");
2385 for matcher in [&mut adaptive, &mut full, &mut records] {
2386 assert!(matcher.advance(prefix).unwrap(), "{label}: prefix");
2387 tune_near_limit(matcher, tuning);
2388 }
2389 let before = (
2390 adaptive.is_accepting(),
2391 adaptive.is_dead(),
2392 adaptive.eos_legal(),
2393 );
2394 let mut adaptive_out = vec![0xa5; mask_bytes];
2395 let mut full_out = vec![0; mask_bytes];
2396 let mut record_out = vec![0xa5; mask_bytes];
2397 let adaptive_result =
2398 adaptive.write_mask_le_bytes_into(vocabulary, Some(trie), &mut adaptive_out);
2399 let full_result = {
2400 let MatcherBackend::Incremental(inner) = &mut full.backend else {
2401 panic!("{label}: incremental full matcher")
2402 };
2403 select_adaptive_trie(inner, vocabulary, program.limits)
2404 .map(|_| ())
2405 .and_then(|()| {
2406 walk_full_trie_mask_for_test(
2407 inner,
2408 vocabulary,
2409 trie,
2410 &mut full_out,
2411 program.limits,
2412 )
2413 })
2414 };
2415 let record_preparation = {
2416 let MatcherBackend::Incremental(inner) = &mut records.backend else {
2417 panic!("{label}: incremental record matcher")
2418 };
2419 select_adaptive_trie(inner, vocabulary, program.limits).map(|_| ())
2420 };
2421 if record_preparation.is_err() {
2422 record_out.fill(0);
2423 }
2424 let record_result = record_preparation.and_then(|()| {
2425 records.write_record_mask_le_bytes_into(
2426 vocabulary.mask_vocab_size(),
2427 vocabulary.iter_records(),
2428 &mut record_out,
2429 )
2430 });
2431 assert_eq!(
2432 adaptive_result.is_ok(),
2433 full_result.is_ok(),
2434 "{label}: adaptive/full result"
2435 );
2436 assert_eq!(
2437 adaptive_result.is_ok(),
2438 record_result.is_ok(),
2439 "{label}: adaptive/record result"
2440 );
2441 assert_incremental_accounting(&adaptive, &format!("{label}/adaptive"));
2442 assert_incremental_accounting(&full, &format!("{label}/full"));
2443 assert_incremental_accounting(&records, &format!("{label}/record"));
2444 assert_eq!(
2445 (
2446 adaptive.is_accepting(),
2447 adaptive.is_dead(),
2448 adaptive.eos_legal()
2449 ),
2450 before,
2451 "{label}: mask changed committed logical state"
2452 );
2453
2454 if adaptive_result.is_err() {
2455 assert_eq!(adaptive_out, vec![0; mask_bytes], "{label}: adaptive error");
2456 assert_eq!(record_out, vec![0; mask_bytes], "{label}: record error");
2457 let mut retry = vec![0xa5; mask_bytes];
2458 assert!(
2459 adaptive
2460 .write_mask_le_bytes_into(vocabulary, Some(trie), &mut retry)
2461 .is_err(),
2462 "{label}: retry must reproduce the resource error"
2463 );
2464 assert_eq!(retry, vec![0; mask_bytes], "{label}: retry error");
2465 assert_incremental_accounting(&adaptive, label);
2466 return;
2467 }
2468
2469 assert_mask_bytes_equal(&adaptive_out, &full_out, vocabulary, label);
2470 assert_mask_bytes_equal(&adaptive_out, &record_out, vocabulary, label);
2471 let retained = adaptive.retained_session_bytes();
2472 let mut repeated = vec![0xa5; mask_bytes];
2473 adaptive
2474 .write_mask_le_bytes_into(vocabulary, Some(trie), &mut repeated)
2475 .unwrap_or_else(|error| {
2476 panic!("{label}: repeated mask failed after retaining {retained} bytes: {error}")
2477 });
2478 assert_eq!(adaptive_out, repeated, "{label}: repeated mask drift");
2479 assert_eq!(
2480 adaptive.retained_session_bytes(),
2481 retained,
2482 "{label}: repeated mask retained more capacity"
2483 );
2484 assert_incremental_accounting(&adaptive, label);
2485
2486 for (token, ids) in vocabulary.iter_records() {
2487 if !ids
2488 .iter()
2489 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
2490 {
2491 continue;
2492 }
2493 let mut equivalent = program.new_matcher().expect("equivalent matcher");
2494 assert!(
2495 equivalent.advance(prefix).unwrap(),
2496 "{label}: equivalent prefix"
2497 );
2498 tune_near_limit(&mut equivalent, tuning);
2499 let mut equivalent_out = vec![0xa5; mask_bytes];
2500 equivalent
2501 .write_mask_le_bytes_into(vocabulary, Some(trie), &mut equivalent_out)
2502 .unwrap_or_else(|error| panic!("{label}: equivalent mask failed: {error}"));
2503 assert_eq!(equivalent_out, adaptive_out, "{label}: equivalent mask");
2504 assert!(
2505 equivalent.advance(token).unwrap_or_else(|error| {
2506 panic!("{label}: emitted token raised during advance: {error}")
2507 }),
2508 "{label}: emitted token was rejected during advance"
2509 );
2510 assert_incremental_accounting(&equivalent, label);
2511 }
2512 }
2513
2514 #[cfg(feature = "bench-internals")]
2515 fn adaptive_mask_succeeds_at(
2516 program: &Arc<StructuredProgram>,
2517 vocabulary: &VocabularyHandle,
2518 trie: &BoundByteTrie,
2519 prefix: &[u8],
2520 tuning: NearLimitTuning,
2521 ) -> bool {
2522 let mut matcher = program.new_matcher().expect("limit probe matcher");
2523 assert!(matcher.advance(prefix).unwrap());
2524 tune_near_limit(&mut matcher, tuning);
2525 let mut out = vec![0xa5; vocabulary.mask_vocab_size().div_ceil(32) * 4];
2526 let result = matcher.write_mask_le_bytes_into(vocabulary, Some(trie), &mut out);
2527 if result.is_err() {
2528 assert_eq!(out, vec![0; out.len()]);
2529 }
2530 assert_incremental_accounting(&matcher, "limit probe");
2531 result.is_ok()
2532 }
2533
2534 #[cfg(feature = "bench-internals")]
2535 fn minimum_successful_limit(
2536 program: &Arc<StructuredProgram>,
2537 vocabulary: &VocabularyHandle,
2538 trie: &BoundByteTrie,
2539 prefix: &[u8],
2540 high: usize,
2541 tuning: fn(usize) -> NearLimitTuning,
2542 ) -> usize {
2543 assert!(adaptive_mask_succeeds_at(
2544 program,
2545 vocabulary,
2546 trie,
2547 prefix,
2548 tuning(high)
2549 ));
2550 let mut lo = 0usize;
2551 let mut hi = high;
2552 while lo < hi {
2553 let mid = lo + (hi - lo) / 2;
2554 if adaptive_mask_succeeds_at(program, vocabulary, trie, prefix, tuning(mid)) {
2555 hi = mid;
2556 } else {
2557 lo = mid + 1;
2558 }
2559 }
2560 lo
2561 }
2562
2563 #[cfg(feature = "bench-internals")]
2564 fn around(limit: usize) -> [usize; 3] {
2565 [limit.saturating_sub(1), limit, limit.saturating_add(1)]
2566 }
2567
2568 #[test]
2569 fn neutral_product_proof_is_bounded_and_preserves_constrained_fallback() {
2570 for (schema, expected) in [
2571 (
2572 r#"{"type":"object","properties":{"s":{"type":"string"}}}"#,
2573 true,
2574 ),
2575 (
2576 r#"{"type":"object","properties":{"s":{"type":"string","pattern":"^[a-z]*$"}}}"#,
2577 false,
2578 ),
2579 ] {
2580 let program = program(schema);
2581 let mut matcher = program.new_matcher().unwrap();
2582 assert!(matcher.advance(br#"{"s":"a"#).unwrap());
2583 #[cfg(not(feature = "bench-internals"))]
2584 let MatcherBackend::Incremental(inner) = &matcher.backend;
2585 #[cfg(feature = "bench-internals")]
2586 let inner = match &matcher.backend {
2587 MatcherBackend::Incremental(inner) => inner,
2588 MatcherBackend::Reference(_) => panic!("incremental program"),
2589 };
2590 let residual = inner.state.local_slice_residual().unwrap();
2591 let engine = residual.engine.unwrap();
2592 let state = residual.pattern_state.unwrap();
2593 let (proved, work) = neutral_language_subsumes(engine, state, program.limits);
2594 assert_eq!(proved, expected);
2595 assert!(work > 0 && work < 64 * 256);
2596 let mut limits = program.limits;
2597 limits.max_mask_work = 1;
2598 assert_eq!(neutral_language_subsumes(engine, state, limits), (false, 1));
2599 }
2600 }
2601
2602 #[cfg(feature = "bench-internals")]
2603 #[test]
2604 fn long_payment_key_mask_prepares_every_emitted_token_for_advance() {
2605 let schema = serde_json::json!({
2606 "type": "object", "unevaluatedProperties": false,
2607 "required": ["rail", "amountMinor", "currency", "approvals", "allocation"],
2608 "properties": {
2609 "rail": {"enum": ["ach", "wire", "internal"]},
2610 "amountMinor": {"type": "integer", "multipleOf": 100, "minimum": 100, "maximum": 900},
2611 "currency": {"enum": ["USD", "EUR", "RUB"], "not": {"const": "RUB"}},
2612 "iban": {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"},
2613 "approvals": {"type": "array", "items": {"enum": ["treasury", "ops", "risk"]},
2614 "contains": {"const": "treasury"}, "uniqueItems": true, "minItems": 1, "maxItems": 3},
2615 "allocation": {"$ref": "#/$defs/split"}
2616 },
2617 "if": {"properties": {"rail": {"const": "wire"}}, "required": ["rail"]},
2618 "then": {"required": ["iban"]}, "else": {"not": {"required": ["iban"]}},
2619 "$defs": {"split": {"$dynamicAnchor": "split", "type": "object", "additionalProperties": false,
2620 "required": ["pct"], "properties": {"pct": {"type": "integer", "minimum": 1, "maximum": 99},
2621 "sub": {"$dynamicRef": "#split"}}}}
2622 });
2623 let tokens = FxHashMap::from_iter([
2624 (b" .".repeat(300), vec![0, 1]),
2625 (b"a".to_vec(), vec![2]),
2626 (b"\"".to_vec(), vec![3]),
2627 (vec![0xf0, 0x9f], vec![4]),
2628 ]);
2629 let vocabulary =
2630 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
2631 let trie = TrieCache::new().bind(&vocabulary).unwrap();
2632 let program = program(&schema.to_string());
2633 for (bytes, ids) in vocabulary.iter_records() {
2634 let mut matcher = program.new_matcher().unwrap();
2635 assert!(matcher.advance(br#"{""#).unwrap());
2636 let mut fast = [0; 4];
2637 matcher
2638 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
2639 .unwrap();
2640 let retained_after_cold = matcher.retained_session_bytes();
2641 let mut repeated = [0; 4];
2642 matcher
2643 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut repeated)
2644 .unwrap();
2645 assert_eq!(fast, repeated, "repeated mask changed token bits");
2646 assert_eq!(
2647 matcher.retained_session_bytes(),
2648 retained_after_cold,
2649 "repeated mask retained another key capacity"
2650 );
2651 let MatcherBackend::Incremental(inner) = &matcher.backend else {
2652 panic!("incremental payment program")
2653 };
2654 assert_eq!(
2655 inner.state.retained_bytes(),
2656 inner.state.recomputed_retained_bytes()
2657 );
2658 assert!(
2659 matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64
2660 );
2661 let mut full = [0; 4];
2662 let mut scan = [0; 4];
2663 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2664 panic!("incremental payment program")
2665 };
2666 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
2667 .unwrap();
2668 matcher
2669 .write_record_mask_le_bytes_into(
2670 vocabulary.mask_vocab_size(),
2671 vocabulary.iter_records(),
2672 &mut scan,
2673 )
2674 .unwrap();
2675 assert_eq!(fast, full);
2676 assert_eq!(fast, scan);
2677 for (candidate, candidate_ids) in vocabulary.iter_records() {
2678 if !candidate_ids
2679 .iter()
2680 .any(|id| fast[*id as usize / 8] & (1 << (*id % 8)) != 0)
2681 {
2682 continue;
2683 }
2684 let mut equivalent = program.new_matcher().unwrap();
2685 assert!(equivalent.advance(br#"{""#).unwrap());
2686 let mut equivalent_mask = [0; 4];
2687 equivalent
2688 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut equivalent_mask)
2689 .unwrap();
2690 assert_eq!(equivalent_mask, fast);
2691 assert!(
2692 equivalent.advance(candidate).unwrap(),
2693 "emitted token {:?} must advance on an equivalent matcher",
2694 candidate_ids
2695 );
2696 }
2697 for &id in ids {
2698 if fast[id as usize / 8] & (1 << (id % 8)) != 0 {
2699 assert!(matcher.advance(bytes).unwrap(), "token {id}");
2700 break;
2701 }
2702 }
2703 }
2704 }
2705
2706 #[cfg(feature = "bench-internals")]
2707 #[test]
2708 fn key_capacity_lease_transfers_releases_and_rearms_for_a_second_key() {
2709 let long = b" .".repeat(300);
2710 let tokens = FxHashMap::from_iter([
2711 (long.clone(), vec![0]),
2712 (b"a".to_vec(), vec![1]),
2713 (b"\"".to_vec(), vec![2]),
2714 (b":".to_vec(), vec![3]),
2715 ]);
2716 let vocabulary =
2717 VocabularyHandle::new(Arc::new(build_vocabulary(4, tokens).unwrap())).unwrap();
2718 let trie = TrieCache::new().bind(&vocabulary).unwrap();
2719 let program = program(r#"{"type":"object","additionalProperties":true}"#);
2720 let mut matcher = program.new_matcher().unwrap();
2721 assert!(matcher.advance(br#"{""#).unwrap());
2722
2723 let mut adaptive = [0; 4];
2724 matcher
2725 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2726 .unwrap();
2727 let (phase, capacity, recomputed) = match &matcher.backend {
2728 MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2729 MatcherBackend::Reference(_) => panic!("incremental program"),
2730 };
2731 assert_eq!(phase, 1, "cold key mask arms a prepared lease");
2732 assert!(capacity >= long.len());
2733 assert_eq!(matcher.retained_session_bytes(), recomputed);
2734
2735 let mut full = [0; 4];
2736 let mut records = [0; 4];
2737 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2738 panic!("incremental program")
2739 };
2740 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
2741 matcher
2742 .write_record_mask_le_bytes_into(
2743 vocabulary.mask_vocab_size(),
2744 vocabulary.iter_records(),
2745 &mut records,
2746 )
2747 .unwrap();
2748 assert_eq!(adaptive, full);
2749 assert_eq!(adaptive, records);
2750
2751 assert!(
2752 matcher.advance(b"a").unwrap(),
2753 "short token uses long lease"
2754 );
2755 let (phase, _, recomputed) = match &matcher.backend {
2756 MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2757 MatcherBackend::Reference(_) => panic!("incremental program"),
2758 };
2759 assert_eq!(phase, 2, "successful commit transfers the lease");
2760 assert_eq!(matcher.retained_session_bytes(), recomputed);
2761 adaptive.fill(0);
2762 matcher
2763 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2764 .unwrap();
2765 let (phase, _, recomputed) = match &matcher.backend {
2766 MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2767 MatcherBackend::Reference(_) => panic!("incremental program"),
2768 };
2769 assert_eq!(
2770 phase, 1,
2771 "a later mask must rearm when the committed prefix increases required capacity"
2772 );
2773 assert_eq!(matcher.retained_session_bytes(), recomputed);
2774 assert!(matcher.advance(b"\"").unwrap());
2775 assert!(matcher.advance(b":").unwrap());
2776 let (phase, capacity, recomputed) = match &matcher.backend {
2777 MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2778 MatcherBackend::Reference(_) => panic!("incremental program"),
2779 };
2780 assert_eq!(phase, 0, "resolved key releases its transferred lease");
2781 assert_eq!(capacity, 0, "maximum token capacity is not retained");
2782 assert_eq!(matcher.retained_session_bytes(), recomputed);
2783
2784 assert!(matcher.advance(br#"0,""#).unwrap());
2785 adaptive.fill(0);
2786 matcher
2787 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
2788 .unwrap();
2789 let (phase, capacity, recomputed) = match &matcher.backend {
2790 MatcherBackend::Incremental(inner) => inner.state.key_lease_debug(),
2791 MatcherBackend::Reference(_) => panic!("incremental program"),
2792 };
2793 assert_eq!(phase, 1, "second key receives a fresh lease generation");
2794 assert!(capacity >= long.len());
2795 assert_eq!(matcher.retained_session_bytes(), recomputed);
2796 }
2797
2798 #[cfg(feature = "bench-internals")]
2799 #[test]
2800 fn wrapper_key_capacity_leases_are_distinct_per_structured_cursor() {
2801 let schema = r#"{"anyOf":[
2802 {"type":"object","properties":{"a":{"type":"integer"}}},
2803 {"type":"object","properties":{"b":{"type":"string"}}}
2804 ]}"#;
2805 let long = b"x".repeat(600);
2806 let vocabulary = VocabularyHandle::new(Arc::new(
2807 build_vocabulary(
2808 3,
2809 FxHashMap::from_iter([
2810 (long, vec![0]),
2811 (b"a".to_vec(), vec![1]),
2812 (b"\"".to_vec(), vec![2]),
2813 ]),
2814 )
2815 .unwrap(),
2816 ))
2817 .unwrap();
2818 let trie = TrieCache::new().bind(&vocabulary).unwrap();
2819 let program = program(schema);
2820 let mut matcher = program.new_matcher().unwrap();
2821 assert!(matcher.advance(br#"{""#).unwrap());
2822 let mut out = [0; 4];
2823 matcher
2824 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut out)
2825 .unwrap();
2826 let mut full = [0; 4];
2827 let mut records = [0; 4];
2828 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2829 panic!("incremental program")
2830 };
2831 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
2832 matcher
2833 .write_record_mask_le_bytes_into(
2834 vocabulary.mask_vocab_size(),
2835 vocabulary.iter_records(),
2836 &mut records,
2837 )
2838 .unwrap();
2839 assert_eq!(out, full);
2840 assert_eq!(out, records);
2841 for (candidate, ids) in vocabulary.iter_records() {
2842 if !ids
2843 .iter()
2844 .any(|id| out[*id as usize / 8] & (1 << (*id % 8)) != 0)
2845 {
2846 continue;
2847 }
2848 let mut equivalent = program.new_matcher().unwrap();
2849 assert!(equivalent.advance(br#"{""#).unwrap());
2850 let mut equivalent_mask = [0; 4];
2851 equivalent
2852 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut equivalent_mask)
2853 .unwrap();
2854 assert_eq!(equivalent_mask, out);
2855 assert!(equivalent.advance(candidate).unwrap());
2856 }
2857 out.fill(0);
2858 matcher
2859 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut out)
2860 .unwrap();
2861 let (owners, capacity) = match &matcher.backend {
2862 MatcherBackend::Incremental(inner) => inner.state.key_lease_tree_debug(),
2863 MatcherBackend::Reference(_) => panic!("incremental program"),
2864 };
2865 assert!(
2866 owners >= 2,
2867 "each simultaneously stepped wrapper cursor owns a lease"
2868 );
2869 assert!(capacity >= 2 * 600);
2870 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
2871 panic!("incremental program")
2872 };
2873 inner.state.discard_prepared_key_leases_tree();
2874 assert_eq!(
2875 inner.state.key_lease_tree_debug(),
2876 (0, 0),
2877 "recursive cleanup releases every cursor-owned lease"
2878 );
2879 }
2880
2881 #[test]
2882 fn neutral_utf8_product_matches_scalar_boundaries() {
2883 for scalar in (0..=0x10ffff).filter_map(char::from_u32) {
2884 let mut buffer = [0; 4];
2885 let bytes = scalar.encode_utf8(&mut buffer).as_bytes();
2886 let result = bytes
2887 .iter()
2888 .try_fold(0, |mode, &byte| neutral_utf8_step(mode, byte));
2889 assert_eq!(
2890 result,
2891 (scalar >= ' ' && scalar != '"' && scalar != '\\').then_some(0)
2892 );
2893 }
2894 for bytes in [
2895 &[0xc0, 0x80][..],
2896 &[0xed, 0xa0, 0x80],
2897 &[0xf4, 0x90, 0x80, 0x80],
2898 &[0x80],
2899 ] {
2900 assert!(bytes
2901 .iter()
2902 .try_fold(0, |mode, &byte| neutral_utf8_step(mode, byte))
2903 .is_none());
2904 }
2905 }
2906
2907 #[test]
2908 fn supported_structured_schema_uses_incremental_backend() {
2909 let structured = program(
2910 r#"{"type":"object","properties":{"x":{"type":"integer"}},"additionalProperties":true}"#,
2911 );
2912 assert_eq!(structured.backend_kind(), "incremental");
2913 let mut matcher = structured.new_matcher().expect("state");
2914 assert!(matcher.advance(br#"{"#).expect("advance"));
2915 assert!(!matcher.is_dead());
2916 }
2917
2918 #[test]
2919 fn large_minimum_array_inside_an_object_routes_around_the_shuffle_cap() {
2920 let schema = r#"{"type":"object","properties":{"a":{"type":"boolean"},"b":{"type":"array","items":{"type":"null"},"minItems":500}}}"#;
2921 let started = std::time::Instant::now();
2922 let program = program(schema);
2923 assert!(
2924 started.elapsed() < std::time::Duration::from_secs(1),
2925 "structured routing must avoid constructing the oversized shuffle automaton"
2926 );
2927 assert!(program.uses_incremental_backend());
2928
2929 let items = std::iter::repeat_n("null", 500)
2930 .collect::<Vec<_>>()
2931 .join(",");
2932 let document = format!(r#"{{"a":true,"b":[{items}]}}"#);
2933 let mut matcher = program.new_matcher().expect("state");
2934 assert!(matcher.advance(document.as_bytes()).expect("document"));
2935 assert!(matcher.is_accepting());
2936
2937 let too_short = format!(r#"{{"a":true,"b":[{}]}}"#, items.replacen(",null", "", 1));
2938 let mut matcher = program.new_matcher().expect("state");
2939 assert!(!matcher.advance(too_short.as_bytes()).expect("document"));
2940 assert!(!matcher.is_accepting());
2941 }
2942
2943 #[test]
2944 fn direct_shuffle_graph_keeps_the_closed_object_regular() {
2945 let schema = r#"{
2946 "type":"object",
2947 "properties":{
2948 "default":{"type":"string"},
2949 "field":{"enum":["year","organization","owners"]},
2950 "transform":{"enum":["upper"]}
2951 },
2952 "required":["field"],
2953 "additionalProperties":false
2954 }"#;
2955 let program = program(schema);
2956 #[cfg(not(feature = "bench-internals"))]
2957 let ProgramBackend::Incremental(plan) = &program.backend;
2958 #[cfg(feature = "bench-internals")]
2959 let plan = match &program.backend {
2960 ProgramBackend::Incremental(plan) => plan,
2961 ProgramBackend::ReferenceBench => panic!("production program must be incremental"),
2962 };
2963 assert!(matches!(
2964 plan.node(plan.root),
2965 crate::structured::plan::NodePlan::Regular(_)
2966 ));
2967
2968 for document in [
2969 br#"{"field":"year"}"#.as_slice(),
2970 br#"{"transform":"upper","field":"owners","default":"x"}"#,
2971 ] {
2972 let mut matcher = program.new_matcher().expect("state");
2973 assert!(matcher.advance(document).expect("document"));
2974 assert!(matcher.is_accepting());
2975 }
2976 }
2977
2978 #[test]
2979 fn negation_and_lowered_conditionals_use_the_incremental_backend() {
2980 type BackendCase<'a> = (&'a str, &'a [(&'a [u8], bool)]);
2981 let cases: &[BackendCase<'_>] = &[
2982 (
2983 r#"{"not":{"const":1}}"#,
2984 &[(b"1", false), (b"12", true), (b"2", true)],
2985 ),
2986 (
2987 r#"{"if":{"type":"integer"},"then":{"minimum":2},"else":{"type":"string"}}"#,
2988 &[
2989 (b"1", false),
2990 (b"2", true),
2991 (br#""x""#, true),
2992 (b"true", false),
2993 ],
2994 ),
2995 ];
2996 for (schema, documents) in cases {
2997 let program = program(schema);
2998 assert!(program.uses_incremental_backend(), "{schema}");
2999 for &(document, accepted) in *documents {
3000 let mut matcher = program.new_matcher().expect("state");
3001 let advanced = matcher.advance(document).expect("advance");
3002 assert!(advanced || !accepted, "{schema}: {document:?}");
3003 assert_eq!(matcher.is_accepting(), accepted, "{schema}: {document:?}");
3004 }
3005 }
3006 }
3007
3008 #[test]
3009 fn local_recursive_root_reference_uses_incremental_backend() {
3010 let schema = r##"{
3011 "$defs": {
3012 "node": {
3013 "type":"object",
3014 "properties":{"next":{"$ref":"#/$defs/node"}},
3015 "additionalProperties":false
3016 }
3017 },
3018 "$ref":"#/$defs/node"
3019 }"##;
3020 let program = program(schema);
3021 assert!(program.uses_incremental_backend());
3022 for document in [
3023 b"{}".as_slice(),
3024 br#"{"next":{}}"#,
3025 br#"{"next":{"next":{}}}"#,
3026 ] {
3027 let mut matcher = program.new_matcher().expect("state");
3028 assert!(matcher.advance(document).expect("document"), "{document:?}");
3029 assert!(matcher.is_accepting(), "{document:?}");
3030 }
3031 for document in [br#"{"next":1}"#.as_slice(), br#"{"next":{"extra":true}}"#] {
3032 let mut matcher = program.new_matcher().expect("state");
3033 assert!(
3034 !matcher.advance(document).expect("document"),
3035 "{document:?}"
3036 );
3037 assert!(!matcher.is_accepting(), "{document:?}");
3038 }
3039 }
3040
3041 #[test]
3042 fn recursive_array_anchor_and_shared_definition_refs_are_incremental() {
3043 let cases: &[(&str, &[u8])] = &[
3044 (
3045 r##"{
3046 "$defs":{"node":{"type":"object","properties":{"children":{"type":"array","items":{"$ref":"#/$defs/node"}}},"additionalProperties":false}},
3047 "$ref":"#/$defs/node"
3048 }"##,
3049 br#"{"children":[{}, {"children":[]}]}"#,
3050 ),
3051 (
3052 r##"{
3053 "$defs":{"node":{"$anchor":"node","type":"object","properties":{"next":{"$ref":"#node"}},"additionalProperties":false}},
3054 "$ref":"#node"
3055 }"##,
3056 br#"{"next":{}}"#,
3057 ),
3058 (
3059 r##"{
3060 "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3061 "type":"object",
3062 "properties":{"left":{"$ref":"#/$defs/node"},"right":{"$ref":"#/$defs/node"}},
3063 "required":["left","right"],
3064 "additionalProperties":false
3065 }"##,
3066 br#"{"left":{"next":{}},"right":{}}"#,
3067 ),
3068 ];
3069 for (schema, document) in cases {
3070 let program = program(schema);
3071 assert!(program.uses_incremental_backend(), "{schema}");
3072 let mut matcher = program.new_matcher().expect("state");
3073 assert!(matcher.advance(document).expect("document"), "{document:?}");
3074 assert!(matcher.is_accepting(), "{document:?}");
3075 }
3076 }
3077
3078 #[test]
3079 fn productive_mutual_object_array_recursion_is_incremental() {
3080 let schema = r##"{
3081 "$defs":{
3082 "object":{"type":"object","properties":{"items":{"$ref":"#/$defs/list"}},"additionalProperties":false},
3083 "list":{"type":"array","items":{"$ref":"#/$defs/object"}}
3084 },
3085 "$ref":"#/$defs/object"
3086 }"##;
3087 let program = program(schema);
3088 assert!(program.uses_incremental_backend());
3089 let mut matcher = program.new_matcher().expect("state");
3090 assert!(matcher
3091 .advance(br#"{"items":[{}, {"items":[]}]}"#)
3092 .expect("document"));
3093 assert!(matcher.is_accepting());
3094 }
3095
3096 #[test]
3097 fn pure_reference_cycles_are_incremental_empty_languages() {
3098 for schema in [
3099 r##"{"$ref":"#"}"##,
3100 r##"{"$defs":{"a":{"$ref":"#/$defs/b"},"b":{"$ref":"#/$defs/a"}},"$ref":"#/$defs/a"}"##,
3101 ] {
3102 let program = StructuredProgram::compile(ir(schema)).expect("positive reference SCC");
3103 assert!(program.uses_incremental_backend());
3104 let mut matcher = program.new_matcher().expect("matcher");
3105 assert!(!matcher.advance(b"null").expect("document"));
3106 }
3107 }
3108
3109 #[test]
3110 fn positive_zero_consumption_cycles_use_bounded_incremental_fixed_points() {
3111 for schema in [
3112 r##"{"$defs":{"x":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3113 r##"{"$defs":{"x":{"$ref":"#/$defs/y"},"y":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3114 ] {
3115 let program = StructuredProgram::compile(ir(schema)).expect("positive union SCC");
3116 let mut matcher = program.new_matcher().expect("matcher");
3117 assert!(matcher.advance(b"null").expect("document"), "{schema}");
3118 assert!(matcher.is_accepting(), "{schema}");
3119 }
3120
3121 let all = StructuredProgram::compile(ir(
3122 r##"{"$defs":{"x":{"allOf":[true,{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3123 ))
3124 .expect("positive intersection SCC");
3125 let mut matcher = all.new_matcher().expect("matcher");
3126 assert!(!matcher.advance(b"null").expect("document"));
3127
3128 let mixed = StructuredProgram::compile(ir(r##"{
3129 "$defs":{
3130 "x":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/y"}]},
3131 "y":{"allOf":[true,{"$ref":"#/$defs/x"}]}
3132 },
3133 "$ref":"#/$defs/x"
3134 }"##))
3135 .expect("mixed positive SCC");
3136 let mut matcher = mixed.new_matcher().expect("matcher");
3137 assert!(matcher.advance(b"null").expect("document"));
3138 assert!(matcher.is_accepting());
3139
3140 let error = match StructuredProgram::compile(ir(
3141 r##"{"$defs":{"x":{"oneOf":[{"type":"null"},{"$ref":"#/$defs/x"}]}},"$ref":"#/$defs/x"}"##,
3142 )) {
3143 Ok(_) => panic!("oneOf recursion compiled"),
3144 Err(error) => error,
3145 };
3146 assert_eq!(error.code, ErrorCode::NonMonotoneRecursion);
3147
3148 for schema in [
3149 r##"{"$defs":{"x":{"type":"object","properties":{"next":{"$ref":"#/$defs/x"}},"additionalProperties":false}},"$ref":"#/$defs/x"}"##,
3150 r##"{"$defs":{"x":{"type":"array","items":{"$ref":"#/$defs/x"}}},"$ref":"#/$defs/x"}"##,
3151 ] {
3152 let program = program(schema);
3153 assert!(program.uses_incremental_backend(), "{schema}");
3154 assert_eq!(program.fallback_reason(), None, "{schema}");
3155 }
3156 }
3157
3158 #[test]
3159 fn large_string_enum_selects_incremental_without_a_fallback_reason() {
3160 let program = StructuredProgram::compile(large_string_enum_ir()).expect("program");
3161 assert_eq!(program.backend_kind(), "incremental");
3162 assert_eq!(program.fallback_reason(), None);
3163 assert_eq!(
3164 program.new_matcher().expect("matcher").backend_name(),
3165 "incremental"
3166 );
3167 }
3168
3169 #[test]
3170 fn large_string_enum_accepts_every_member_and_rejects_near_misses() {
3171 let program = StructuredProgram::compile(large_string_enum_ir()).expect("program");
3172 for index in 0..=MAX_UNROLLED_ENUM {
3173 let document = format!(r#""member-{index:04}""#);
3174 let mut matcher = program.new_matcher().expect("matcher");
3175 assert!(matcher.advance(document.as_bytes()).expect("member"));
3176 assert!(matcher.is_accepting(), "{document}");
3177 }
3178 for document in [
3179 r#""member-9999""#,
3180 r#""member-000""#,
3181 r#""member-00000""#,
3182 r#""member-0000-extra""#,
3183 ] {
3184 let mut matcher = program.new_matcher().expect("matcher");
3185 let accepted =
3186 matcher.advance(document.as_bytes()).expect("near miss") && matcher.is_accepting();
3187 assert!(!accepted, "{document}");
3188 }
3189 }
3190
3191 #[test]
3192 fn large_string_enum_token_rejection_is_atomic_and_decoded_spelling_is_canonical() {
3193 let schema = large_string_enum_schema();
3194 for document in [
3195 b"\"a\"".as_slice(),
3196 b"\"\\u0061\"",
3197 "\"é\"".as_bytes(),
3198 b"\"\\u00e9\"",
3199 "\"😀\"".as_bytes(),
3200 b"\"\\uD83D\\uDE00\"",
3201 ] {
3202 let program = program(&schema);
3203 assert!(program.uses_incremental_backend());
3204 let mut matcher = program.new_matcher().expect("matcher");
3205 assert!(matcher.advance(document).expect("document"), "{document:?}");
3206 assert!(matcher.is_accepting(), "{document:?}");
3207 }
3208
3209 let program = program(&schema);
3210 let mut matcher = program.new_matcher().expect("matcher");
3211 assert!(matcher.advance(b"\"ab").expect("prefix"));
3212 let retained = matcher.retained_session_bytes();
3213 assert!(!matcher.advance(b"z-invalid").expect("rejection"));
3214 assert_eq!(matcher.retained_session_bytes(), retained);
3215 assert!(matcher.advance(b"c\"").expect("alternative"));
3216 assert!(matcher.is_accepting());
3217 }
3218
3219 #[test]
3220 fn large_string_enum_compositions_remain_incremental() {
3221 let value = large_string_enum_schema();
3222 let cases = [
3223 (
3224 format!(
3225 r#"{{"type":"object","properties":{{"v":{value}}},"required":["v"],"additionalProperties":false}}"#
3226 ),
3227 br#"{"v":"a"}"#.as_slice(),
3228 ),
3229 (
3230 format!(
3231 r#"{{"type":"object","patternProperties":{{"^v$":{value}}},"additionalProperties":false}}"#
3232 ),
3233 br#"{"v":"ab"}"#,
3234 ),
3235 (
3236 format!(r#"{{"type":"object","additionalProperties":{value}}}"#),
3237 br#"{"v":"abc"}"#,
3238 ),
3239 (
3240 format!(r#"{{"type":"array","items":{value}}}"#),
3241 br#"["a","ab"]"#,
3242 ),
3243 (
3244 format!(r#"{{"prefixItems":[{value}],"items":false}}"#),
3245 br#"["abc"]"#,
3246 ),
3247 (
3248 format!(r#"{{"type":"array","contains":{value},"minContains":1}}"#),
3249 br#"[null,"a"]"#,
3250 ),
3251 (
3252 format!(r#"{{"anyOf":[{value},{{"type":"null"}}]}}"#),
3253 b"\"a\"",
3254 ),
3255 (
3256 format!(r#"{{"allOf":[{value},{{"type":"string"}}]}}"#),
3257 b"\"ab\"",
3258 ),
3259 (
3260 format!(r#"{{"oneOf":[{value},{{"const":false}}]}}"#),
3261 b"\"abc\"",
3262 ),
3263 (format!(r#"{{"not":{value}}}"#), b"null"),
3264 (
3265 format!(r##"{{"$defs":{{"v":{value}}},"$ref":"#/$defs/v"}}"##),
3266 b"\"a\"",
3267 ),
3268 (
3269 format!(
3270 r##"{{"$defs":{{"node":{{"type":"object","properties":{{"v":{value},"next":{{"$ref":"#/$defs/node"}}}},"additionalProperties":false}}}},"$ref":"#/$defs/node"}}"##
3271 ),
3272 br#"{"v":"a","next":{"v":"ab"}}"#,
3273 ),
3274 (
3275 format!(
3276 r#"{{"type":"object","properties":{{"trigger":true,"v":true}},"dependentSchemas":{{"trigger":{{"properties":{{"v":{value}}},"required":["v"]}}}},"additionalProperties":true}}"#
3277 ),
3278 br#"{"trigger":true,"v":"a"}"#,
3279 ),
3280 (
3281 format!(r#"{{"properties":{{"known":true}},"unevaluatedProperties":{value}}}"#),
3282 br#"{"known":null,"extra":"a"}"#,
3283 ),
3284 (
3285 format!(r#"{{"prefixItems":[true],"unevaluatedItems":{value}}}"#),
3286 br#"[null,"a"]"#,
3287 ),
3288 ];
3289 for (schema, document) in cases {
3290 let program = program(&schema);
3291 assert!(program.uses_incremental_backend(), "{schema}");
3292 let mut matcher = program.new_matcher().expect("matcher");
3293 assert!(matcher.advance(document).expect("document"), "{schema}");
3294 assert!(matcher.is_accepting(), "{schema}");
3295 }
3296 }
3297
3298 #[test]
3299 fn large_string_enum_prefixes_match_the_reference_oracle() {
3300 let schema = large_string_enum_schema();
3301 let schema_ir = ir(&schema);
3302 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3303 assert!(program.uses_incremental_backend());
3304 for document in [b"\"abc\"".as_slice(), b"\"abd\"", b"\"\\u0061\""] {
3305 for end in 0..=document.len() {
3306 let prefix = &document[..end];
3307 for next in [
3308 0u8, b'"', b'\\', b'a', b'b', b'c', b'z', 0x80, 0xc3, 0xe9, 0xff,
3309 ] {
3310 let mut incremental = program.new_matcher().expect("matcher");
3311 let mut reference = ReferenceMatcher::new(schema_ir.clone());
3312 let incremental_prefix =
3313 prefix.is_empty() || incremental.advance(prefix).expect("prefix");
3314 let reference_prefix = prefix.is_empty() || reference.advance(prefix);
3315 assert_eq!(incremental_prefix, reference_prefix, "{prefix:?}");
3316 if incremental_prefix {
3317 assert_eq!(
3318 incremental.advance(&[next]).expect("next"),
3319 reference.advance(&[next]),
3320 "prefix={prefix:?}, next={next:#04x}"
3321 );
3322 assert_eq!(incremental.is_accepting(), reference.is_accepting());
3323 assert_eq!(incremental.is_dead(), reference.is_dead());
3324 }
3325 }
3326 }
3327 }
3328 }
3329
3330 #[test]
3331 fn recursive_ref_every_prefix_matches_reference_oracle() {
3332 let schema = r##"{
3333 "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3334 "$ref":"#/$defs/node"
3335 }"##;
3336 let schema_ir = ir(schema);
3337 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3338 let documents: &[&[u8]] = &[b"{}", br#"{"next":{}}"#, br#"{"next":{"next":{}}}"#];
3339 let deviations: &[&[u8]] = &[
3340 b"\0",
3341 b"{",
3342 b"}",
3343 b"[",
3344 b"]",
3345 b":",
3346 b",",
3347 b"\"",
3348 b"\\",
3349 b" ",
3350 b"\n",
3351 b"\xc2",
3352 b"\x80",
3353 b"\xff",
3354 br#"{"next":"#,
3355 ];
3356
3357 for document in documents {
3358 let mut matcher = program.new_matcher().expect("matcher");
3359 for end in 0..=document.len() {
3360 let prefix = &document[..end];
3361 assert_eq!(
3362 matcher.is_accepting(),
3363 super::super::reference::accepts(&schema_ir, prefix),
3364 "acceptance at {prefix:?}"
3365 );
3366 let mut records: Vec<(Vec<u8>, Vec<u32>)> = deviations
3367 .iter()
3368 .enumerate()
3369 .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
3370 .collect();
3371 if let Some(&next) = document.get(end) {
3372 records.push((vec![next], vec![30]));
3373 records.push((document[end..].to_vec(), vec![31]));
3374 }
3375 records.push((b"}".to_vec(), vec![32, 33]));
3376 let expected = matcher
3377 .allowed_mask_from_records(
3378 40,
3379 records
3380 .iter()
3381 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
3382 )
3383 .expect("mask");
3384 for _ in 0..100 {
3385 let repeated = matcher
3386 .allowed_mask_from_records(
3387 40,
3388 records
3389 .iter()
3390 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
3391 )
3392 .expect("repeated mask");
3393 assert_eq!(repeated, expected, "mask mutated state at {prefix:?}");
3394 }
3395 for (bytes, ids) in &records {
3396 let mut candidate = prefix.to_vec();
3397 candidate.extend_from_slice(bytes);
3398 let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3399 || super::super::reference::can_continue(&schema_ir, &candidate);
3400 for &id in ids {
3401 assert_eq!(
3402 expected.get(TokenId(id)),
3403 oracle,
3404 "prefix {prefix:?}, candidate {bytes:?}"
3405 );
3406 }
3407 }
3408 if let Some(&next) = document.get(end) {
3409 assert!(matcher.advance(&[next]).expect("valid next byte"));
3410 }
3411 }
3412 }
3413 }
3414
3415 #[test]
3416 fn recursive_ref_trie_record_and_reference_masks_are_identical() {
3417 let schema = r##"{
3418 "$defs":{"node":{"type":"object","properties":{"next":{"$ref":"#/$defs/node"}},"additionalProperties":false}},
3419 "$ref":"#/$defs/node"
3420 }"##;
3421 let schema_ir = ir(schema);
3422 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3423 let mut tokens = FxHashMap::default();
3424 let pieces: &[&[u8]] = &[
3425 b"{",
3426 b"}",
3427 b":",
3428 b",",
3429 b"\"next\"",
3430 br#"{"next":"#,
3431 b"{}",
3432 b"}}",
3433 b" ",
3434 b" \n",
3435 b"null",
3436 b"[",
3437 b"\xff",
3438 ];
3439 for (id, piece) in pieces.iter().enumerate() {
3440 tokens.insert(piece.to_vec(), vec![u32::try_from(id).unwrap()]);
3441 }
3442 tokens.insert(b"}".to_vec(), vec![1, 20]);
3443 let width = 32usize;
3444 let handle = VocabularyHandle::new(Arc::new(build_vocabulary(31, tokens).expect("vocab")))
3445 .expect("handle");
3446 let trie = TrieCache::new().bind(&handle).expect("trie");
3447 let mut walked = program.new_matcher().expect("trie matcher");
3448 let mut scanned = program.new_matcher().expect("record matcher");
3449 let mut prefix = Vec::new();
3450 for step in [b"{".as_slice(), b"\"next\"", b":", b"{", b"}", b"}"] {
3451 let record = scanned
3452 .allowed_mask_from_records(width, handle.iter_records())
3453 .expect("record mask");
3454 let mut output = vec![0xff; width.div_ceil(32) * 4];
3455 for _ in 0..100 {
3456 walked
3457 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
3458 .expect("trie mask");
3459 for id in 0..width {
3460 let token = TokenId(u32::try_from(id).unwrap());
3461 assert_eq!(
3462 output[id / 8] & (1u8 << (id % 8)) != 0,
3463 record.get(token),
3464 "trie vs record at {prefix:?}, id {id}"
3465 );
3466 }
3467 }
3468 for (bytes, ids) in handle.iter_records() {
3469 let mut candidate = prefix.clone();
3470 candidate.extend_from_slice(bytes);
3471 let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3472 || super::super::reference::can_continue(&schema_ir, &candidate);
3473 for &id in ids {
3474 assert_eq!(record.get(TokenId(id)), oracle, "reference at id {id}");
3475 }
3476 }
3477 assert!(walked.advance(step).expect("trie step"));
3478 assert!(scanned.advance(step).expect("record step"));
3479 prefix.extend_from_slice(step);
3480 }
3481 assert!(walked.is_accepting() && scanned.is_accepting());
3482 }
3483
3484 #[test]
3485 fn dependent_schemas_use_incremental_backend_and_validate_the_whole_object() {
3486 let program = program(
3487 r#"{"type":"object","dependentSchemas":{"x":{"type":"object","required":["y"],"additionalProperties":true}},"additionalProperties":true}"#,
3488 );
3489 assert_eq!(program.backend_kind(), "incremental");
3490 for (document, expected) in [
3491 (br#"{}"#.as_slice(), true),
3492 (br#"{"y":1}"#.as_slice(), true),
3493 (br#"{"x":1}"#.as_slice(), false),
3494 (br#"{"x":1,"y":2}"#.as_slice(), true),
3495 (br#"{"y":2,"x":1}"#.as_slice(), true),
3496 (br#"{"\u0078":1,"y":2}"#.as_slice(), true),
3497 ] {
3498 let mut matcher = program.new_matcher().expect("state");
3499 let actual = matcher.advance(document).expect("advance") && matcher.is_accepting();
3500 assert_eq!(actual, expected, "{}", String::from_utf8_lossy(document));
3501 }
3502 }
3503
3504 #[test]
3505 fn dependent_schemas_hard_shapes_and_nested_locations_match_reference() {
3506 let cases: &[(&str, &[&[u8]])] = &[
3507 (
3508 r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]},"c":{"properties":{"n":{"type":"integer"}}}},"additionalProperties":true}"#,
3509 &[
3510 br#"{}"#,
3511 br#"{"b":1}"#,
3512 br#"{"a":1}"#,
3513 br#"{"b":1,"a":2}"#,
3514 br#"{"a":2,"b":1}"#,
3515 br#"{"c":1,"n":"x"}"#,
3516 ],
3517 ),
3518 (
3519 r#"{"type":"object","dependentSchemas":{"a":{"patternProperties":{"^x":{"type":"integer"}},"propertyNames":{"pattern":"^[ax]+$"},"minProperties":2,"maxProperties":3}},"additionalProperties":true}"#,
3520 &[
3521 br#"{"a":1,"x":2}"#,
3522 br#"{"x":2,"a":1}"#,
3523 br#"{"a":1,"x":"bad"}"#,
3524 br#"{"a":1,"other":2}"#,
3525 ],
3526 ),
3527 (
3528 r#"{"type":"object","dependentSchemas":{"a":{"properties":{"items":{"type":"array","items":{"type":"integer"}}}}},"additionalProperties":true}"#,
3529 &[
3530 br#"{"items":[1,2],"a":true}"#,
3531 br#"{"a":true,"items":[1,"x"]}"#,
3532 ],
3533 ),
3534 (
3535 r#"{"type":"object","dependentSchemas":{"a":{"allOf":[{"required":["x"]},{"anyOf":[{"required":["y"]},{"not":{"required":["z"]}}]}]}},"additionalProperties":true}"#,
3536 &[
3537 br#"{"a":1,"x":1,"y":1}"#,
3538 br#"{"x":1,"a":1}"#,
3539 br#"{"a":1,"y":1}"#,
3540 ],
3541 ),
3542 (
3543 r#"{"type":"object","properties":{"child":{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}},"dependentSchemas":{"p":{"required":["child"]}},"additionalProperties":true}"#,
3544 &[
3545 br#"{"p":1,"child":{"b":1,"a":1}}"#,
3546 br#"{"child":{"a":1},"p":1}"#,
3547 ],
3548 ),
3549 (
3550 r#"{"type":"array","items":{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}}"#,
3551 &[br#"[{"b":1,"a":1},{}]"#, br#"[{"a":1}]"#],
3552 ),
3553 (
3554 r#"{"anyOf":[{"type":"object","dependentSchemas":{"a":false}},{"type":"array"}]}"#,
3555 &[br#"{}"#, br#"{"a":1}"#, br#"[]"#],
3556 ),
3557 (
3558 r#"{"not":{"type":"object","dependentSchemas":{"a":false},"required":["a"]}}"#,
3559 &[br#"{}"#, br#"{"a":1}"#, b"null"],
3560 ),
3561 (
3562 r#"{"dependentSchemas":{"a":false}}"#,
3563 &[b"null", b"true", br#""text""#, b"12", b"[]", br#"{"a":1}"#],
3564 ),
3565 ];
3566 for (schema, documents) in cases {
3567 let schema_ir = ir(schema);
3568 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3569 assert_eq!(program.backend_kind(), "incremental", "{schema}");
3570 for &document in *documents {
3571 let expected = super::super::reference::accepts(&schema_ir, document);
3572 let mut matcher = program.new_matcher().expect("matcher");
3573 let actual = matcher.advance(document).expect("advance") && matcher.is_accepting();
3574 assert_eq!(actual, expected, "{schema}: {document:?}");
3575 }
3576 }
3577 }
3578
3579 #[test]
3580 fn dependent_schemas_prefixes_match_the_reference() {
3581 type Case<'a> = (&'a str, &'a [(&'a [u8], bool)]);
3582 let cases: &[Case<'_>] = &[
3583 (
3584 r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}"#,
3585 &[
3586 (br#"{}"#, true),
3587 (br#"{"b":1}"#, true),
3588 (br#"{"a":1}"#, false),
3589 (br#"{"a":1,"b":2}"#, true),
3590 (br#"{"b":2,"a":1}"#, true),
3591 ],
3592 ),
3593 (
3594 r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]}},"additionalProperties":true}"#,
3595 &[(br#"{"a":1,"b":2}"#, true), (br#"{"a":1}"#, false)],
3596 ),
3597 (
3598 r#"{"type":"object","dependentSchemas":{"a":{"required":["y"]},"b":{"required":["z"]}},"additionalProperties":true}"#,
3599 &[
3600 (br#"{"a":1,"b":2,"y":1,"z":2}"#, true),
3601 (br#"{"a":1,"b":2,"y":1}"#, false),
3602 (br#"{"a":1,"y":1}"#, true),
3603 ],
3604 ),
3605 (
3606 r#"{"type":"object","dependentSchemas":{"a":{"not":{"required":["z"]}}},"additionalProperties":true}"#,
3607 &[(br#"{"a":1}"#, true), (br#"{"a":1,"z":1}"#, false)],
3608 ),
3609 ];
3610 let mut document_total = 0usize;
3611 let mut prefix_total = 0usize;
3612 for (schema, documents) in cases {
3613 for &(document, valid) in *documents {
3614 document_total += 1;
3615 prefix_total += assert_combinator_prefixes(schema, document, valid);
3616 }
3617 }
3618 println!("dependentSchemas documents={document_total} byte_prefixes={prefix_total}");
3619 }
3620
3621 #[test]
3624 fn a_false_triggered_dependency_dies_before_reference_can_continue_notices() {
3625 let schema =
3626 r#"{"type":"object","dependentSchemas":{"a":false},"additionalProperties":true}"#;
3627 let schema_ir = ir(schema);
3628 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3629 assert!(program.uses_incremental_backend());
3630 let mut matcher = program.new_matcher().expect("matcher");
3631 assert!(matcher.advance(br#"{"a"#).expect("key body still open"));
3632 let before = matcher.is_accepting();
3633 assert!(
3634 !matcher
3635 .advance(b"\"")
3636 .expect("closing quote fixes the doomed trigger"),
3637 "the closing quote that commits key `a` must be rejected outright"
3638 );
3639 assert_eq!(
3640 matcher.is_accepting(),
3641 before,
3642 "rejection must not mutate state"
3643 );
3644
3645 let vocab_tail: &[&[u8]] = &[b":1}", b":1,\"z\":2}", b":null}", b":{}}", b":[1,2]}"];
3646 for &tail in vocab_tail {
3647 let mut completed = b"{\"a\"".to_vec();
3648 completed.extend_from_slice(tail);
3649 assert!(
3650 !super::super::reference::accepts(&schema_ir, &completed),
3651 "{completed:?} should never validate once trigger `a` is fixed"
3652 );
3653 }
3654
3655 let mut record = program.new_matcher().expect("record matcher");
3656 assert!(record.advance(br#"{"a"#).expect("key body still open"));
3657 let candidates: &[&[u8]] = &[b"\"", b"b", b"a"];
3660 let mut tokens = FxHashMap::default();
3661 for (id, bytes) in candidates.iter().enumerate() {
3662 tokens.insert(bytes.to_vec(), vec![u32::try_from(id).unwrap()]);
3663 }
3664 let handle = VocabularyHandle::new(Arc::new(
3665 build_vocabulary(u32::try_from(candidates.len()).unwrap(), tokens).unwrap(),
3666 ))
3667 .unwrap();
3668 let mask = record
3669 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
3670 .unwrap();
3671 for (id, &candidate) in candidates.iter().enumerate() {
3672 let allowed = mask.get(TokenId(u32::try_from(id).unwrap()));
3673 assert_eq!(
3674 allowed,
3675 candidate != b"\"",
3676 "candidate {candidate:?} mask bit"
3677 );
3678 }
3679 }
3680
3681 #[test]
3682 fn dependent_schema_cycles_are_typed_errors_and_annotation_consumers_are_incremental() {
3683 for schema in [
3684 r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"anyOf":[true,{"$ref":"#/$defs/x"}]}}}},"$ref":"#/$defs/x"}"##,
3685 r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"not":{"$ref":"#/$defs/x"}}}}},"$ref":"#/$defs/x"}"##,
3686 r##"{"$defs":{"x":{"type":"object","dependentSchemas":{"a":{"$ref":"#/$defs/y"}}},"y":{"$ref":"#/$defs/x"}},"$ref":"#/$defs/x"}"##,
3687 ] {
3688 let error = match StructuredProgram::compile(ir(schema)) {
3689 Ok(_) => panic!("dependent recursion compiled: {schema}"),
3690 Err(error) => error,
3691 };
3692 assert_eq!(error.code, ErrorCode::NonMonotoneRecursion, "{schema}");
3693 assert!(!error.recoverable, "{schema}");
3694 }
3695 for schema in [
3696 r#"{"type":"object","dependentSchemas":{"a":{"properties":{"x":true}}},"unevaluatedProperties":false}"#,
3697 r#"{"dependentSchemas":{"a":{"items":true}},"unevaluatedItems":false}"#,
3698 ] {
3699 let compiled = StructuredProgram::compile(ir(schema))
3700 .unwrap_or_else(|error| panic!("{schema}: {error:?}"));
3701 assert_eq!(compiled.backend_kind(), "incremental", "{schema}");
3702 }
3703 let dependent_required = ir(r#"{"type":"object","dependentRequired":{"a":["b"]}}"#);
3704 assert_eq!(dependent_required.diagnostics().len(), 0);
3705 let program = StructuredProgram::compile(dependent_required).unwrap();
3706 assert!(program.uses_incremental_backend());
3707 }
3708
3709 #[test]
3710 fn dependent_required_is_non_annotating_under_unevaluated_properties() {
3711 let only_presence =
3712 ir(r#"{"dependentRequired":{"a":["b"]},"unevaluatedProperties":false}"#);
3713 let program = StructuredProgram::compile(only_presence).unwrap();
3714 assert_eq!(program.backend_kind(), "incremental");
3715 let mut matcher = program.new_matcher().unwrap();
3716 assert!(!matcher.advance(br#"{"a":1,"b":2}"#).unwrap() || !matcher.is_accepting());
3717
3718 let annotated = ir(r#"{
3719 "properties":{"a":true,"b":true},
3720 "dependentRequired":{"a":["b"]},
3721 "unevaluatedProperties":false
3722 }"#);
3723 let program = StructuredProgram::compile(annotated).unwrap();
3724 assert_eq!(program.backend_kind(), "incremental");
3725 let mut matcher = program.new_matcher().unwrap();
3726 assert!(matcher.advance(br#"{"a":1,"b":2}"#).unwrap());
3727 assert!(matcher.is_accepting());
3728 }
3729
3730 #[test]
3731 fn dependent_required_combinations_remain_incremental_and_correct() {
3732 let cases = [
3733 (
3734 r#"{"type":"object","properties":{"a":true,"b":true},"dependentRequired":{"a":["b"]},"additionalProperties":false}"#,
3735 br#"{"a":1,"b":2}"#.as_slice(),
3736 true,
3737 ),
3738 (
3739 r#"{"type":"object","required":["a"],"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3740 br#"{"a":1}"#,
3741 false,
3742 ),
3743 (
3744 r#"{"type":"object","patternProperties":{"^b$":true},"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3745 br#"{"b":2,"a":1}"#,
3746 true,
3747 ),
3748 (
3749 r#"{"type":"object","propertyNames":{"minLength":1},"minProperties":1,"maxProperties":2,"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3750 br#"{"a":1,"b":2}"#,
3751 true,
3752 ),
3753 (
3754 r#"{"type":"object","dependentSchemas":{"a":{"required":["c"]}},"dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3755 br#"{"a":1,"b":2,"c":3}"#,
3756 true,
3757 ),
3758 (
3759 r#"{"allOf":[{"dependentRequired":{"a":["b"]}},{"type":"object"}]}"#,
3760 br#"{"a":1}"#,
3761 false,
3762 ),
3763 (
3764 r#"{"anyOf":[{"dependentRequired":{"a":["b"]}},{"type":"array"}]}"#,
3765 br#"[]"#,
3766 true,
3767 ),
3768 (
3769 r#"{"oneOf":[{"dependentRequired":{"a":["b"]}},{"type":"array"}]}"#,
3770 br#"{"a":1,"b":2}"#,
3771 true,
3772 ),
3773 (
3774 r#"{"not":{"dependentRequired":{"a":["b"]}}}"#,
3775 br#"{"a":1}"#,
3776 true,
3777 ),
3778 (
3779 r#"{"if":{"required":["a"]},"then":{"dependentRequired":{"a":["b"]}},"else":true}"#,
3780 br#"{"a":1,"b":2}"#,
3781 true,
3782 ),
3783 ];
3784 for (schema, document, expected) in cases {
3785 let schema_ir = ir(schema);
3786 let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3787 assert!(program.uses_incremental_backend(), "{schema}");
3788 let mut matcher = program.new_matcher().unwrap();
3789 let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3790 assert_eq!(actual, expected, "{schema} / {document:?}");
3791 assert_eq!(
3792 actual,
3793 super::super::reference::accepts(&schema_ir, document)
3794 );
3795 }
3796 }
3797
3798 #[test]
3799 fn dependent_required_hard_presence_graphs_nested_and_recursive_cases() {
3800 let cases = [
3801 (
3802 r#"{"dependentRequired":{"a":["shared"],"x":["shared"]}}"#,
3803 br#"{"a":1,"x":2,"shared":3}"#.as_slice(),
3804 true,
3805 ),
3806 (
3807 r#"{"dependentRequired":{"a":["b"],"b":["a"]}}"#,
3808 br#"{"a":1}"#,
3809 false,
3810 ),
3811 (
3812 r#"{"dependentRequired":{"a":["b"],"b":["c"]}}"#,
3813 br#"{"a":1,"b":2}"#,
3814 false,
3815 ),
3816 (
3817 r#"{"dependentRequired":{"a":["b"],"b":["c"]}}"#,
3818 br#"{"c":3,"b":2,"a":1}"#,
3819 true,
3820 ),
3821 (
3822 r#"{"type":"object","properties":{"nested":{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}},"additionalProperties":true}"#,
3823 br#"{"nested":{"a":1}}"#,
3824 false,
3825 ),
3826 (
3827 r#"{"type":"array","items":{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}}"#,
3828 br#"[{"a":1,"b":2},{"x":3}]"#,
3829 true,
3830 ),
3831 (
3832 r##"{"$defs":{"node":{"type":"object","dependentRequired":{"a":["b"]},"properties":{"next":{"anyOf":[{"type":"null"},{"$ref":"#/$defs/node"}]}},"additionalProperties":true}},"$ref":"#/$defs/node"}"##,
3833 br#"{"a":1,"b":2,"next":{"a":3,"b":4,"next":null}}"#,
3834 true,
3835 ),
3836 ];
3837 for (schema, document, expected) in cases {
3838 let schema_ir = ir(schema);
3839 let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3840 assert!(program.uses_incremental_backend(), "{schema}");
3841 let mut matcher = program.new_matcher().unwrap();
3842 let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3843 assert_eq!(actual, expected, "{schema} / {document:?}");
3844 assert_eq!(
3845 actual,
3846 super::super::reference::accepts(&schema_ir, document)
3847 );
3848 }
3849 }
3850
3851 #[test]
3852 fn dependent_required_remaining_keyword_routing_is_explicit() {
3853 for (schema, document, expected) in [
3854 (
3855 r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":{"type":"integer"}}"#,
3856 br#"{"a":1,"b":2}"#.as_slice(),
3857 true,
3858 ),
3859 (
3860 r#"{"dependentRequired":{"a":["b"]},"unevaluatedItems":false}"#,
3861 br#"{"a":1,"b":2}"#,
3862 true,
3863 ),
3864 ] {
3865 let schema_ir = ir(schema);
3866 let program = StructuredProgram::compile(schema_ir.clone()).unwrap();
3867 assert!(program.uses_incremental_backend());
3868 let mut matcher = program.new_matcher().unwrap();
3869 let actual = matcher.advance(document).unwrap() && matcher.is_accepting();
3870 assert_eq!(actual, expected);
3871 assert_eq!(
3872 actual,
3873 super::super::reference::accepts(&schema_ir, document)
3874 );
3875 }
3876 }
3877
3878 #[test]
3879 fn dependent_object_presence_record_preallocated_trie_and_reference_masks_are_identical() {
3880 for schema in [
3881 r#"{"type":"object","dependentSchemas":{"a":{"required":["b"]},"never":false},"additionalProperties":true}"#,
3882 r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
3883 ] {
3884 let mut mask_comparisons = 0usize;
3885 let schema_ir = ir(schema);
3886 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
3887 let mut tokens = FxHashMap::default();
3888 for (id, bytes) in [
3889 (0, b"{".as_slice()),
3890 (1, b"\"a\""),
3891 (2, b"\"\\u0061\""),
3892 (3, b":"),
3893 (4, b"1"),
3894 (5, b","),
3895 (6, b"\"b\":"),
3896 (7, b"2}"),
3897 (8, b"}"),
3898 (9, b" \t"),
3899 (10, b"\"other\":1"),
3900 (11, b"1,"),
3901 (12, b"}\n,"),
3902 (13, b"\"a"),
3903 (14, b"\\u00"),
3904 (15, b"61\""),
3905 ] {
3906 tokens.insert(bytes.to_vec(), vec![id]);
3907 }
3908 let handle =
3909 VocabularyHandle::new(Arc::new(build_vocabulary(16, tokens).unwrap())).unwrap();
3910 let trie = TrieCache::new().bind(&handle).unwrap();
3911 let mut record = program.new_matcher().unwrap();
3912 let mut walked = program.new_matcher().unwrap();
3913 let mut prefix = Vec::new();
3914 for token in [
3915 b"{".as_slice(),
3916 b"\"b\":",
3917 b"2",
3918 b",",
3919 b"\"\\u0061\"",
3920 b":",
3921 b"1",
3922 b"}",
3923 ] {
3924 let allocating = record
3925 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
3926 .unwrap();
3927 let mut preallocated = [0xff; 4];
3928 record
3929 .write_record_mask_le_bytes_into(
3930 handle.mask_vocab_size(),
3931 handle.iter_records(),
3932 &mut preallocated,
3933 )
3934 .unwrap();
3935 let mut trie_output = [0xff; 4];
3936 walked
3937 .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_output)
3938 .unwrap();
3939 for (bytes, ids) in handle.iter_records() {
3940 let mut candidate = prefix.clone();
3941 candidate.extend_from_slice(bytes);
3942 let oracle = super::super::reference::accepts(&schema_ir, &candidate)
3943 || super::super::reference::can_continue(&schema_ir, &candidate);
3944 for &id in ids {
3945 let id = usize::try_from(id).unwrap();
3946 assert_eq!(
3947 allocating.get(TokenId(id as u32)),
3948 oracle,
3949 "{prefix:?} + {bytes:?}"
3950 );
3951 assert_eq!(preallocated[id / 8] & (1 << (id % 8)) != 0, oracle);
3952 assert_eq!(trie_output[id / 8] & (1 << (id % 8)) != 0, oracle);
3953 mask_comparisons += 3;
3954 }
3955 }
3956 assert!(record.advance(token).unwrap(), "{token:?}");
3957 assert!(walked.advance(token).unwrap(), "{token:?}");
3958 prefix.extend_from_slice(token);
3959 }
3960 assert!(record.is_accepting() && walked.is_accepting());
3961 println!("dependent-object token-mask comparisons={mask_comparisons} schema={schema}");
3962 }
3963 }
3964
3965 #[test]
3966 fn rejected_candidate_rolls_back_incremental_state() {
3967 let program = program(
3968 r#"{"type":"object","required":["x"],"properties":{"x":{"type":"integer"}},"additionalProperties":false}"#,
3969 );
3970 let mut matcher = program.new_matcher().expect("state");
3971 assert!(matcher.advance(br#"{"#).expect("advance"));
3972 let before = matcher.is_accepting();
3973 assert!(!matcher.advance(b"}").expect("advance"));
3974 assert_eq!(matcher.is_accepting(), before);
3975 assert!(matcher.advance(b"\"x\"").expect("advance"));
3976 }
3977
3978 #[test]
3979 fn mask_evaluation_rolls_back_and_preserves_duplicate_ids() {
3980 let program = program(
3981 r#"{"type":"object","properties":{"x":{"type":"boolean"}},"required":["x"],"additionalProperties":false}"#,
3982 );
3983 let mut matcher = program.new_matcher().expect("state");
3984 assert!(matcher.advance(br#"{"x":"#).expect("advance"));
3985 let records = [
3986 (b"true".as_slice(), [3u32, 7].as_slice()),
3987 (b"0".as_slice(), [9u32].as_slice()),
3988 ];
3989 let mask = matcher
3990 .allowed_mask_from_records(10, records.into_iter())
3991 .expect("mask");
3992 assert!(mask.get(TokenId(3)) && mask.get(TokenId(7)));
3993 assert!(!mask.get(TokenId(9)));
3994 assert!(matcher.advance(b"true").expect("advance"));
3995 }
3996
3997 #[test]
3998 fn committed_object_keys_remain_unique() {
3999 let schema = r#"{"type":"object","properties":{"name":{"type":"boolean"}},"required":["name"],"additionalProperties":{"type":"boolean"}}"#;
4000 let ir = Arc::new(schema_to_ir(schema, CompileOptions::default()).expect("schema"));
4001 let program = StructuredProgram::compile(ir).expect("program");
4002 let mut matcher = program.new_matcher().expect("state");
4003 for token in [b"{".as_slice(), b"\"name\"", b":", b"true", b","] {
4004 assert!(matcher.advance(token).expect("advance"));
4005 }
4006 assert!(!matcher.advance(b"\"name\"").expect("duplicate"));
4007 }
4008
4009 #[test]
4010 fn supported_structured_shapes_select_incremental_backend() {
4011 let schemas = [
4012 r#"{"type":"object","properties":{"x":{"type":"integer"}},"additionalProperties":{"type":"string"}}"#,
4013 r#"{"type":"object","patternProperties":{"^x+$":{"type":"integer"}},"additionalProperties":false}"#,
4014 r#"{"type":"object","propertyNames":{"type":"string","pattern":"^a+$"},"minProperties":1,"maxProperties":2,"additionalProperties":true}"#,
4015 r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"integer","minimum":1},"minContains":1,"maxContains":2,"uniqueItems":true}"#,
4016 r#"{"type":"object","properties":{"\u0061":{"type":"array","items":{"type":"object","properties":{"n":{"type":"number"}},"required":["n"],"additionalProperties":false},"uniqueItems":true}},"additionalProperties":false}"#,
4017 ];
4018 for schema in schemas {
4019 assert_eq!(program(schema).backend_kind(), "incremental", "{schema}");
4020 }
4021 }
4022
4023 #[test]
4024 fn plan_limit_is_an_error_not_a_reference_fallback() {
4025 let limits = StructuredLimits {
4026 max_plan_bytes: 0,
4027 ..StructuredLimits::default()
4028 };
4029 let result = StructuredProgram::compile_with_limits(
4030 ir(r#"{"type":"string","pattern":"^a+$"}"#),
4031 limits,
4032 );
4033 let error = match result {
4034 Ok(_) => panic!("zero plan budget compiled"),
4035 Err(error) => error,
4036 };
4037 assert_eq!(error.code, ErrorCode::InternalLimitExceeded);
4038 assert!(!error.recoverable);
4039 }
4040
4041 #[test]
4042 fn incremental_program_has_no_fallback_reason() {
4043 let program = program(r#"{"type":"string"}"#);
4044 assert!(program.uses_incremental_backend());
4045 assert_eq!(program.fallback_reason(), None);
4046 }
4047
4048 #[test]
4049 fn every_supported_node_shape_has_an_explicit_runtime_route() {
4050 let large_enum = large_string_enum_schema();
4051 let schemas = [
4052 r#"{"type":"null"}"#.to_owned(),
4053 r#"{"type":"boolean"}"#.to_owned(),
4054 "false".to_owned(),
4055 r#"{"const":"x"}"#.to_owned(),
4056 r#"{"type":"string","pattern":"^x+$"}"#.to_owned(),
4057 r#"{"type":"integer"}"#.to_owned(),
4058 r#"{"type":"number"}"#.to_owned(),
4059 r#"{"enum":["a","b"]}"#.to_owned(),
4060 large_enum,
4061 r#"{"type":"array","items":{"type":"integer"},"maxItems":100}"#.to_owned(),
4062 r#"{"prefixItems":[{"type":"integer"}],"items":false}"#.to_owned(),
4063 r#"{"prefixItems":[{"type":"integer"}],"items":true}"#.to_owned(),
4064 r#"{"type":"array","items":{"type":"object"},"uniqueItems":true,"contains":{"properties":{"k":{"const":1}}}}"#.to_owned(),
4065 r#"{"type":"object","properties":{"a":{"type":"integer"}},"additionalProperties":false}"#.to_owned(),
4066 r#"{"type":"object","patternProperties":{"^x$":{"type":"integer"}},"additionalProperties":true}"#.to_owned(),
4067 r#"{"anyOf":[{"type":"null"},{"type":"boolean"}]}"#.to_owned(),
4068 r#"{"allOf":[{"type":"integer"},{"minimum":0}]}"#.to_owned(),
4069 r#"{"oneOf":[{"type":"null"},{"type":"boolean"}]}"#.to_owned(),
4070 r#"{"not":{"type":"null"}}"#.to_owned(),
4071 r##"{"$defs":{"x":{"type":"string"}},"$ref":"#/$defs/x"}"##.to_owned(),
4072 r#"{"properties":{"a":true},"unevaluatedProperties":false}"#.to_owned(),
4073 ];
4074 for schema in schemas {
4075 let program = program(&schema);
4076 assert_ne!(
4077 program.fallback_reason(),
4078 Some("unsupported-node-shape"),
4079 "{schema}"
4080 );
4081 assert!(program.uses_incremental_backend(), "{schema}");
4082 }
4083 }
4084
4085 #[test]
4086 fn former_fallbacks_are_incremental_or_typed_errors() {
4087 let mut builder = crate::ir::Builder::new(CompileOptions::default());
4088 let lower = builder
4089 .string_pattern("a+", Some(5), None, Charset::Utf8Any)
4090 .expect("lower");
4091 let upper = builder
4092 .string_pattern("a+", None, Some(2), Charset::Utf8Any)
4093 .expect("upper");
4094 let root = builder
4095 .intersection_of(vec![lower, upper])
4096 .expect("intersection");
4097 let intersection = StructuredProgram::compile(Arc::new(builder.finish(root).expect("ir")))
4098 .expect("program");
4099 assert!(intersection.uses_incremental_backend());
4100 let mut matcher = intersection.new_matcher().expect("matcher");
4101 assert!(!matcher.advance(br#""aaaaa""#).expect("document"));
4102
4103 let mut builder = crate::ir::Builder::new(CompileOptions::default());
4104 let value = builder.string_const("x").expect("value");
4105 let fields = (0..5).map(|index| (format!("p{index}"), value)).collect();
4106 let root = builder
4107 .object(
4108 fields,
4109 &[false; 5],
4110 crate::ir::ObjectClosure::RejectOpenObjects,
4111 )
4112 .expect("object");
4113 let error = match StructuredProgram::compile(Arc::new(builder.finish(root).expect("ir"))) {
4114 Ok(_) => panic!("RejectOpenObjects compiled"),
4115 Err(error) => error,
4116 };
4117 assert_eq!(error.code, ErrorCode::Unsupported);
4118 assert!(!error.recoverable);
4119 }
4120
4121 #[test]
4122 fn resource_errors_roll_back_token_and_mask_state() {
4123 let limits = StructuredLimits {
4124 max_document_bytes: 1,
4125 max_mask_work: 1,
4126 ..StructuredLimits::default()
4127 };
4128 let program = StructuredProgram::compile_with_limits(ir(r#"{"type":"string"}"#), limits)
4129 .expect("program");
4130 let mut matcher = program.new_matcher().expect("matcher");
4131 let error = matcher.advance(b"\"a").expect_err("document cap");
4132 assert!(matches!(
4133 error,
4134 StructuredMatcherError::ResourceLimit {
4135 kind: LimitKind::DocumentBytes,
4136 observed: 2,
4137 limit: 1,
4138 }
4139 ));
4140 assert!(!matcher.is_dead());
4141
4142 let records = [(b"\"".to_vec(), vec![1u32, 2])];
4143 let error = matcher
4144 .allowed_mask_from_records(
4145 3,
4146 records
4147 .iter()
4148 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
4149 )
4150 .expect_err("mask work cap");
4151 assert!(matches!(
4152 error,
4153 StructuredMatcherError::ResourceLimit {
4154 kind: LimitKind::MaskWork,
4155 ..
4156 }
4157 ));
4158 assert!(matcher.advance(b"\"").expect("rollback kept empty state"));
4159 }
4160
4161 #[test]
4162 fn masks_are_order_independent_and_match_the_reference_oracle() {
4163 let schema = r#"{"type":"object","properties":{"n":{"type":"number","minimum":-1,"maximum":1,"multipleOf":0.5},"s":{"type":"string","pattern":"^a+$","minLength":1,"maxLength":2}},"required":["n","s"],"additionalProperties":false}"#;
4164 let ir = ir(schema);
4165 let program = StructuredProgram::compile(ir.clone()).expect("program");
4166 assert_eq!(program.backend_kind(), "incremental");
4167 let prefix = br#"{"n":1,"s":"a"#;
4168 let records = vec![
4169 (b"\"}".to_vec(), vec![0, 6]),
4170 (b"a\"}".to_vec(), vec![1]),
4171 (b"aa\"}".to_vec(), vec![2]),
4172 (b"b\"}".to_vec(), vec![3]),
4173 ];
4174 let mut forward = program.new_matcher().expect("matcher");
4175 let mut reverse = program.new_matcher().expect("matcher");
4176 for &byte in prefix {
4177 assert!(forward.advance(&[byte]).expect("prefix"));
4178 assert!(reverse.advance(&[byte]).expect("prefix"));
4179 }
4180 let forward_mask = mask(&mut forward, 7, &records);
4181 let mut reversed = records.clone();
4182 reversed.reverse();
4183 let reverse_mask = mask(&mut reverse, 7, &reversed);
4184 assert_eq!(forward_mask, reverse_mask);
4185
4186 for (bytes, ids) in &records {
4187 let mut candidate = prefix.to_vec();
4188 candidate.extend_from_slice(bytes);
4189 let expected = super::super::reference::accepts(&ir, &candidate)
4190 || super::super::reference::can_continue(&ir, &candidate);
4191 for &id in ids {
4192 assert_eq!(forward_mask.get(TokenId(id)), expected, "{bytes:?}");
4193 }
4194 }
4195 assert!(forward.advance(b"\"}").expect("mask rolled back"));
4196 assert!(forward.is_accepting());
4197 }
4198
4199 #[test]
4200 fn shared_program_matchers_are_independent() {
4201 let program = program(r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true}"#);
4202 let mut first = program.new_matcher().expect("first");
4203 let second = program.new_matcher().expect("second");
4204 assert!(first.advance(b"[").expect("advance"));
4205 assert!(!first.is_dead());
4206 assert!(!second.is_accepting());
4207 assert!(!second.is_dead());
4208 }
4209
4210 #[cfg(feature = "bench-internals")]
4211 #[test]
4212 fn mask_work_metrics_count_actual_walk_and_reset_per_operation() {
4213 let program = program(r#"{"type":"string","pattern":"^a+$"}"#);
4214 let mut tokens = FxHashMap::default();
4215 for (id, bytes) in [
4216 (0u32, b"a".as_slice()),
4217 (1, b"aa"),
4218 (2, b"b"),
4219 (3, b"\""),
4220 (4, b"\\"),
4221 ] {
4222 tokens.insert(bytes.to_vec(), vec![id]);
4223 }
4224 let vocabulary =
4225 VocabularyHandle::new(Arc::new(build_vocabulary(5, tokens).expect("vocabulary")))
4226 .expect("handle");
4227 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4228 let mut matcher = program.new_matcher().expect("matcher");
4229 assert!(matcher.advance(b"\"").expect("open string"));
4230 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4231
4232 matcher
4233 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4234 .expect("first mask");
4235 let first = matcher.mask_work_metrics_for_bench();
4236 assert!(first.trie_nodes > 0);
4237 assert!(first.trie_edges > 0);
4238 assert_eq!(first.pushed_bytes, first.trie_edges);
4239 assert_eq!(first.checkpoints, first.trie_nodes.saturating_add(1));
4240 assert_eq!(first.rollbacks, first.checkpoints);
4241
4242 matcher
4243 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4244 .expect("second mask");
4245 assert_eq!(matcher.mask_work_metrics_for_bench(), first);
4246 }
4247
4248 #[cfg(feature = "bench-internals")]
4249 #[test]
4250 fn mask_work_metrics_are_isolated_between_matchers() {
4251 let program = program(
4252 r##"{"$dynamicAnchor":"node","type":"object","properties":{"child":{"$dynamicRef":"#node"}}}"##,
4253 );
4254 let mut tokens = FxHashMap::default();
4255 tokens.insert(b"{".to_vec(), vec![0]);
4256 tokens.insert(b"}".to_vec(), vec![1]);
4257 let vocabulary =
4258 VocabularyHandle::new(Arc::new(build_vocabulary(2, tokens).expect("vocabulary")))
4259 .expect("handle");
4260 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4261 let mut first = program.new_matcher().expect("first");
4262 let second = program.new_matcher().expect("second");
4263 assert!(first.advance(br#"{"child":"#).expect("child prefix"));
4264 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4265
4266 first
4267 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4268 .expect("dynamic mask");
4269 let first_metrics = first.mask_work_metrics_for_bench();
4270 assert!(first_metrics.dynamic_resolutions > 0);
4271 assert!(first_metrics.dynamic_scope_steps > 0);
4272 first
4273 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
4274 .expect("warm dynamic mask");
4275 let warm_metrics = first.mask_work_metrics_for_bench();
4276 assert!(warm_metrics.dynamic_resolutions > 0);
4277 assert_eq!(warm_metrics.dynamic_scope_steps, 0);
4278 assert_eq!(
4279 second.mask_work_metrics_for_bench(),
4280 MaskWorkMetrics::default()
4281 );
4282 }
4283
4284 #[test]
4287 fn incremental_matcher_agrees_with_the_reference_oracle_across_every_prefix() {
4288 let cases: &[(&str, &[u8])] = &[
4289 (
4290 r#"{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"boolean"}}}"#,
4291 br#"{"a":{"b":true},"c":{"d":false}}"#,
4292 ),
4293 (
4294 r#"{"type":"object","patternProperties":{"^x":{"type":"integer"}},"additionalProperties":{"type":"string"}}"#,
4295 br#"{"x1":5,"other":"ok"}"#,
4296 ),
4297 (
4298 r#"{"type":"object","propertyNames":{"type":"string","pattern":"^a$"},"additionalProperties":true}"#,
4299 br#"{"a":1}"#,
4300 ),
4301 (
4302 r#"{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":4}"#,
4303 br#"[1,22,333]"#,
4304 ),
4305 (r#"{"type":"string","pattern":"^a.c$"}"#, br#""abc""#),
4306 (
4307 r#"{"type":"object","minProperties":1,"maxProperties":2,"additionalProperties":{"type":"boolean"}}"#,
4308 br#"{"p":true,"q":false}"#,
4309 ),
4310 (
4311 r#"{"type":"object","dependentSchemas":{"a":{"type":"object","required":["b"],"additionalProperties":true}},"additionalProperties":true}"#,
4312 br#"{"b":2,"\u0061":1}"#,
4313 ),
4314 (
4315 r#"{"type":"object","dependentRequired":{"a":["b"]},"additionalProperties":true}"#,
4316 br#"{"b":2,"\u0061":1}"#,
4317 ),
4318 (
4319 r#"{"dependentSchemas":{"a":false}}"#,
4320 br#"[null,true,{"a":1}]"#,
4321 ),
4322 ];
4323 let alphabet: Vec<u8> = (0u8..=127).collect();
4324 let mut prefix_count = 0usize;
4325 let mut candidate_byte_count = 0usize;
4326 for (schema, doc) in cases {
4327 let ir = ir(schema);
4328 let program = StructuredProgram::compile(ir.clone()).expect("program");
4329 assert_eq!(
4330 program.backend_kind(),
4331 "incremental",
4332 "{schema} must be incremental"
4333 );
4334 let records: Vec<(Vec<u8>, Vec<u32>)> = alphabet
4335 .iter()
4336 .enumerate()
4337 .map(|(i, &b)| (vec![b], vec![i as u32]))
4338 .collect();
4339 let mut m = program.new_matcher().expect("matcher");
4340 for end in 0..doc.len() {
4341 prefix_count += 1;
4342 candidate_byte_count += alphabet.len();
4343 let prefix = &doc[..end];
4344 assert_eq!(
4345 m.is_accepting(),
4346 super::super::reference::accepts(&ir, prefix),
4347 "is_accepting diverged at {prefix:?} for {schema}"
4348 );
4349 let msk = mask(&mut m, alphabet.len(), &records);
4350 for (i, &b) in alphabet.iter().enumerate() {
4351 if msk.get(TokenId(i as u32)) {
4352 let mut cand = prefix.to_vec();
4353 cand.push(b);
4354 let oracle = super::super::reference::accepts(&ir, &cand)
4355 || super::super::reference::can_continue(&ir, &cand);
4356 assert!(
4357 oracle,
4358 "over-acceptance: byte {b:#04x} allowed at {prefix:?} but the oracle forbids it, {schema}"
4359 );
4360 }
4361 }
4362 let next = doc[end];
4363 assert!(
4364 msk.get(TokenId(usize::from(next) as u32)),
4365 "under-acceptance: valid next byte {next:#04x} masked out at {prefix:?}, {schema}"
4366 );
4367 assert!(
4368 m.advance(&[next]).expect("advance"),
4369 "valid byte {next:#04x} rejected at {prefix:?}, {schema}"
4370 );
4371 }
4372 assert!(
4373 m.is_accepting(),
4374 "complete valid document not accepting: {schema}"
4375 );
4376 }
4377 println!(
4378 "every-prefix differential: prefixes={prefix_count} candidate_bytes={candidate_byte_count}"
4379 );
4380 }
4381
4382 #[test]
4383 fn uniqueitems_speculative_mask_must_not_crash() {
4384 let program = program(
4385 r#"{"type":"array","items":{"type":"number"},"uniqueItems":true,"contains":{"const":1}}"#,
4386 );
4387 let mut m = program.new_matcher().expect("matcher");
4388 assert!(m.advance(b"[").expect("advance"));
4389 assert!(m.advance(b"1").expect("advance"));
4390 let mut records: Vec<(Vec<u8>, Vec<u32>)> = (0u8..=127)
4391 .map(|byte| (vec![byte], vec![u32::from(byte)]))
4392 .collect();
4393 records.push((b" ]".to_vec(), vec![127]));
4394 for _ in 0..100 {
4395 m.allowed_mask_from_records(
4396 128,
4397 records
4398 .iter()
4399 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
4400 )
4401 .expect("speculative mask");
4402 }
4403 assert!(!m.advance(b"x").expect("invalid token"));
4404 assert!(m.advance(b"\t").expect("whitespace"));
4405 assert!(m.advance(b"]").expect("close"));
4406 assert!(m.is_accepting());
4407 }
4408
4409 #[cfg(feature = "bench-internals")]
4410 #[test]
4411 fn duplicate_finite_string_item_is_rejected_at_its_first_impossible_byte() {
4412 let base_program = program(
4413 r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
4414 );
4415 let tokens = (0u8..=255)
4416 .map(|byte| (vec![byte], vec![u32::from(byte)]))
4417 .collect();
4418 let vocabulary = VocabularyHandle::new(Arc::new(
4419 build_vocabulary(256, tokens).expect("byte vocabulary"),
4420 ))
4421 .expect("vocabulary handle");
4422 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4423
4424 for (name, second_prefix, rejected) in [
4425 ("raw", b"\"".as_slice(), b't'),
4426 ("escaped", b"\"\\u007".as_slice(), b'4'),
4427 ] {
4428 let mut prefix = b"[\"treasury\",".to_vec();
4429 prefix.extend_from_slice(second_prefix);
4430 let (mask, _) = assert_three_route_mask_and_advance(
4431 &base_program,
4432 &vocabulary,
4433 &trie,
4434 &prefix,
4435 name,
4436 );
4437 assert_eq!(
4438 mask[usize::from(rejected) / 8] & (1 << (rejected % 8)),
4439 0,
4440 "{name}: first byte completing the duplicate must be masked"
4441 );
4442
4443 let mut direct = base_program.new_matcher().expect("direct matcher");
4444 assert!(direct.advance(&prefix).expect("prefix"));
4445 assert!(!direct.advance(&[rejected]).expect("duplicate byte"));
4446 assert!(!direct.is_dead(), "rejection must preserve committed state");
4447 }
4448
4449 let distinct = b"[\"treasury\",\"risk";
4450 let (mask, _) = assert_three_route_mask_and_advance(
4451 &base_program,
4452 &vocabulary,
4453 &trie,
4454 distinct,
4455 "distinct",
4456 );
4457 assert_ne!(
4458 mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4459 0,
4460 "distinct item must remain closable"
4461 );
4462
4463 let nested = program(
4464 r#"{"type":"object","properties":{"approvals":{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}},"required":["approvals"],"unevaluatedProperties":false}"#,
4465 );
4466 let nested_prefix = b"{\"approvals\":[\"tr\\u0065\\u0061\\u0073\\u0075\\u0072\\u0079\",\"";
4467 let (mask, _) = assert_three_route_mask_and_advance(
4468 &nested,
4469 &vocabulary,
4470 &trie,
4471 nested_prefix,
4472 "nested-payment-shape",
4473 );
4474 assert_eq!(
4475 mask[usize::from(b't') / 8] & (1 << (b't' % 8)),
4476 0,
4477 "nested payment-shaped array must reject the duplicate at t"
4478 );
4479
4480 let escape_program =
4481 program(r#"{"type":"array","items":{"enum":["tram","tree"]},"uniqueItems":true}"#);
4482 let escape_prefix = b"[\"tree\",\"tr\\u006";
4483 let (mask, _) = assert_three_route_mask_and_advance(
4484 &escape_program,
4485 &vocabulary,
4486 &trie,
4487 escape_prefix,
4488 "pending-enum-escape",
4489 );
4490 assert_eq!(
4491 mask[usize::from(b'5') / 8] & (1 << (b'5' % 8)),
4492 0,
4493 "escape resolving only to the seen `tree` must be rejected"
4494 );
4495 assert_ne!(
4496 mask[usize::from(b'1') / 8] & (1 << (b'1' % 8)),
4497 0,
4498 "escape resolving to unseen `tram` must remain available"
4499 );
4500
4501 let completed_member = b"[\"treasury";
4502 let (mask, _) = assert_three_route_mask_and_advance(
4503 &base_program,
4504 &vocabulary,
4505 &trie,
4506 completed_member,
4507 "completed-enum-member",
4508 );
4509 assert_ne!(
4510 mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4511 0,
4512 "a complete first item must retain its closing quote"
4513 );
4514 assert_eq!(
4515 mask[usize::from(b'\\') / 8] & (1 << (b'\\' % 8)),
4516 0,
4517 "an enum member with no longer completion must reject a new escape"
4518 );
4519
4520 let before_third_item = b"[\"treasury\",\"risk\",";
4521 let (mask, _) = assert_three_route_mask_and_advance(
4522 &base_program,
4523 &vocabulary,
4524 &trie,
4525 before_third_item,
4526 "before-third-enum-item",
4527 );
4528 assert_ne!(
4529 mask[usize::from(b'"') / 8] & (1 << (b'"' % 8)),
4530 0,
4531 "an unseen member must permit the next opening quote"
4532 );
4533
4534 let third_item = b"[\"treasury\",\"risk\",\"";
4535 let (mask, _) = assert_three_route_mask_and_advance(
4536 &base_program,
4537 &vocabulary,
4538 &trie,
4539 third_item,
4540 "third-enum-item",
4541 );
4542 assert_eq!(
4543 mask[usize::from(b'!') / 8] & (1 << (b'!' % 8)),
4544 0,
4545 "a third enum item must reject a byte outside every remaining enum member"
4546 );
4547 assert_ne!(
4548 mask[usize::from(b'o') / 8] & (1 << (b'o' % 8)),
4549 0,
4550 "a third enum item must retain the unseen `ops` prefix"
4551 );
4552 for byte in [b'r', b't'] {
4553 assert_eq!(
4554 mask[usize::from(byte) / 8] & (1 << (byte % 8)),
4555 0,
4556 "a third enum item must reject seen member prefix {byte:?}"
4557 );
4558 }
4559
4560 let exhausted = program(
4561 r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"uniqueItems":true,"maxItems":4}"#,
4562 );
4563 let mut exhausted_matcher = exhausted.new_matcher().expect("exhausted matcher");
4564 assert!(exhausted_matcher
4565 .advance(br#"["treasury","risk","ops""#)
4566 .unwrap());
4567 assert!(!exhausted_matcher.advance(b",").unwrap());
4568 assert!(!exhausted_matcher.is_dead());
4569 assert!(exhausted_matcher.advance(b"]").unwrap());
4570 assert!(exhausted_matcher.is_accepting());
4571
4572 let wrapped = program(
4573 r#"{
4574 "type":"object",
4575 "properties":{
4576 "rail":{"enum":["ach","wire","internal"]},
4577 "approvals":{
4578 "type":"array",
4579 "items":{"enum":["treasury","ops","risk"]},
4580 "contains":{"const":"treasury"},
4581 "uniqueItems":true,
4582 "minItems":1,
4583 "maxItems":3
4584 }
4585 },
4586 "required":["approvals"],
4587 "if":{"properties":{"rail":{"const":"wire"}},"required":["rail"]},
4588 "then":{"required":["rail"]},
4589 "else":{"not":{"required":["rail"]}},
4590 "allOf":[
4591 {"if":{"properties":{"rail":{"const":"internal"}},"required":["rail"]},"then":{"required":["rail"]}},
4592 {"if":{"properties":{"rail":{"const":"ach"}},"required":["rail"]},"then":{"required":["rail"]}}
4593 ],
4594 "unevaluatedProperties":false
4595 }"#,
4596 );
4597 for (spelling, treasury, risk) in [
4598 ("raw-raw", b"treasury".as_slice(), b"risk".as_slice()),
4599 (
4600 "escaped-raw",
4601 br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4602 b"risk".as_slice(),
4603 ),
4604 (
4605 "raw-escaped",
4606 b"treasury".as_slice(),
4607 br"r\u0069\u0073\u006B".as_slice(),
4608 ),
4609 (
4610 "escaped-escaped",
4611 br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4612 br"r\u0069\u0073\u006B".as_slice(),
4613 ),
4614 ] {
4615 for (scope, case, head, tail) in [
4616 (
4617 "direct",
4618 &base_program,
4619 b"[\"".as_slice(),
4620 br#"ops"]"#.as_slice(),
4621 ),
4622 (
4623 "wrapper",
4624 &wrapped,
4625 b"{\"approvals\":[\"".as_slice(),
4626 br#"ops"]}"#.as_slice(),
4627 ),
4628 ] {
4629 let label = format!("{scope}-{spelling}");
4630 let mut prefix = head.to_vec();
4631 prefix.extend_from_slice(treasury);
4632 prefix.extend_from_slice(b"\",\"");
4633 prefix.extend_from_slice(risk);
4634 prefix.extend_from_slice(b"\",\"");
4635 let (mask, _) =
4636 assert_three_route_mask_and_advance(case, &vocabulary, &trie, &prefix, &label);
4637 let bit = |byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4638 assert!(bit(b'o'), "{label}: `ops` must remain unseen");
4639 for rejected in [b't', b'r', b'!'] {
4640 assert!(!bit(rejected), "{label}: rejected byte {rejected:?}");
4641 }
4642
4643 let mut witness = prefix;
4644 witness.extend_from_slice(tail);
4645 let mut one_call = case.new_matcher().expect("one-call matcher");
4646 assert!(one_call.advance(&witness).unwrap(), "{label}: witness");
4647 assert!(one_call.is_accepting(), "{label}: accepting");
4648
4649 let mut bytewise = case.new_matcher().expect("bytewise matcher");
4650 for (index, &byte) in witness.iter().enumerate() {
4651 assert!(
4652 bytewise.advance(&[byte]).unwrap(),
4653 "{label}: bytewise witness rejected {byte:?} at {index}"
4654 );
4655 }
4656 assert!(bytewise.is_accepting(), "{label}: bytewise accepting");
4657
4658 #[cfg(not(debug_assertions))]
4661 {
4662 let mut masked = case.new_matcher().expect("masked byte matcher");
4663 for (index, &byte) in witness.iter().enumerate() {
4664 let mut next = vec![0xa5; vocabulary.mask_vocab_size().div_ceil(32) * 4];
4665 masked
4666 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut next)
4667 .unwrap();
4668 assert!(
4669 next[usize::from(byte) / 8] & (1 << (byte % 8)) != 0,
4670 "{label}: witness byte {byte:?} masked at {index}"
4671 );
4672 assert!(masked.advance(&[byte]).unwrap());
4673 }
4674 assert!(masked.is_accepting(), "{label}: masked replay accepting");
4675 }
4676 }
4677 }
4678 let wrapped_prefix = b"{\"approvals\":[\"treasury\",\"risk\",\"";
4679 let (mask, _) = assert_three_route_mask_and_advance(
4680 &wrapped,
4681 &vocabulary,
4682 &trie,
4683 wrapped_prefix,
4684 "wrapped-third-enum-item",
4685 );
4686 assert_ne!(
4687 mask[usize::from(b'o') / 8] & (1 << (b'o' % 8)),
4688 0,
4689 "wrappers must retain the remaining unseen `ops` member"
4690 );
4691 assert_eq!(
4692 mask[usize::from(b'!') / 8] & (1 << (b'!' % 8)),
4693 0,
4694 "wrappers must reject a prefix outside the remaining enum language"
4695 );
4696 }
4697
4698 #[cfg(feature = "bench-internals")]
4699 #[test]
4700 fn payment_approval_enum_retains_unseen_member_after_escaped_items() {
4701 let payment = program(
4702 r##"{
4703 "$schema":"https://json-schema.org/draft/2020-12/schema",
4704 "$id":"https://example.test/payment-instruction",
4705 "type":"object",
4706 "unevaluatedProperties":false,
4707 "required":["rail","amountMinor","currency","approvals","allocation"],
4708 "properties":{
4709 "rail":{"enum":["ach","wire","internal"]},
4710 "amountMinor":{"type":"integer","multipleOf":100,"minimum":100,"maximum":900},
4711 "currency":{"enum":["USD","EUR","RUB"],"not":{"const":"RUB"}},
4712 "routingNumber":{"type":"string","pattern":"^[0-9]{4}$"},
4713 "iban":{"type":"string","pattern":"^[A-Z]{2}[0-9]{4}$"},
4714 "accountRef":{"type":"string","pattern":"^acct-[0-9]{2}$"},
4715 "fxRate":{"type":"integer","minimum":1,"maximum":9},
4716 "settlementDate":{"type":"string","pattern":"^20[0-9]{2}$"},
4717 "approvals":{
4718 "type":"array",
4719 "items":{"enum":["treasury","ops","risk"]},
4720 "contains":{"const":"treasury"},
4721 "uniqueItems":true,
4722 "minItems":1,
4723 "maxItems":3
4724 },
4725 "allocation":{"$ref":"#/$defs/split"},
4726 "tags":{
4727 "type":"object",
4728 "propertyNames":{"pattern":"^[a-z]{2,6}$"},
4729 "patternProperties":{"^[a-z]{2,6}$":{"type":"string","maxLength":6}},
4730 "additionalProperties":false,
4731 "maxProperties":2
4732 }
4733 },
4734 "dependentRequired":{"fxRate":["settlementDate"]},
4735 "if":{"properties":{"rail":{"const":"wire"}},"required":["rail"]},
4736 "then":{"required":["iban"]},
4737 "else":{"not":{"required":["iban"]}},
4738 "allOf":[
4739 {"if":{"properties":{"rail":{"const":"internal"}},"required":["rail"]},"then":{"required":["accountRef"]}},
4740 {"if":{"properties":{"rail":{"const":"ach"}},"required":["rail"]},"then":{"required":["routingNumber"]}}
4741 ],
4742 "$defs":{
4743 "split":{
4744 "$dynamicAnchor":"split",
4745 "type":"object",
4746 "additionalProperties":false,
4747 "required":["pct"],
4748 "properties":{
4749 "pct":{"type":"integer","minimum":1,"maximum":99},
4750 "sub":{"$dynamicRef":"#split"}
4751 }
4752 }
4753 }
4754 }"##,
4755 );
4756 let records = (0u8..=255)
4757 .map(|byte| (vec![byte], vec![u32::from(byte)]))
4758 .collect();
4759 let vocabulary = VocabularyHandle::new(Arc::new(
4760 build_vocabulary(256, records).expect("byte vocabulary"),
4761 ))
4762 .expect("vocabulary handle");
4763 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4764 let document_head = b"{\n\n\"amountMinor\":100,\n\n\"currency\":\"USD\",\n\n\"accountRef\":\"acct-11\",\n\n\"fxRate\":1,\n\n\"routingNumber\":\"1123\",\n\n\"rail\":\"ach\",\n\n\"settlementDate\":\"2016\",\n\n\"tags\":{\n\n\"amount\":\"USD\",\n\n\"routin\":\"acct-1\"\n\n},\n\n\"approvals\":[\n\n\"";
4765
4766 for (label, treasury, risk) in [
4767 ("raw-raw", b"treasury".as_slice(), b"risk".as_slice()),
4768 (
4769 "escaped-raw",
4770 br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4771 b"risk".as_slice(),
4772 ),
4773 (
4774 "raw-escaped",
4775 b"treasury".as_slice(),
4776 br"r\u0069\u0073\u006B".as_slice(),
4777 ),
4778 (
4779 "escaped-escaped",
4780 br"tr\u0065\u0061\u0073\u0075\u0072\u0079".as_slice(),
4781 br"r\u0069\u0073\u006B".as_slice(),
4782 ),
4783 ] {
4784 let mut prefix = document_head.to_vec();
4785 prefix.extend_from_slice(treasury);
4786 prefix.extend_from_slice(b"\",\"");
4787 prefix.extend_from_slice(risk);
4788 prefix.extend_from_slice(b"\",\"");
4789 let (mask, _) =
4790 assert_three_route_mask_and_advance(&payment, &vocabulary, &trie, &prefix, label);
4791 let bit = |byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4792 assert!(bit(b'o'), "{label}: unseen `ops` must remain reachable");
4793 for rejected in [b't', b'r', b'!'] {
4794 assert!(!bit(rejected), "{label}: rejected byte {rejected:?}");
4795 }
4796
4797 let mut bytewise = payment.new_matcher().expect("bytewise payment matcher");
4798 for (index, &byte) in prefix.iter().enumerate() {
4799 assert!(
4800 bytewise.advance(&[byte]).unwrap(),
4801 "{label}: bytewise prefix rejected index {index}, byte {byte:?}, after {}",
4802 String::from_utf8_lossy(&prefix[..index])
4803 );
4804 }
4805
4806 let mut witness = prefix;
4807 witness.extend_from_slice(br#"ops"],"allocation":{"pct":60}}"#);
4808 let mut matcher = payment.new_matcher().expect("payment matcher");
4809 assert!(matcher.advance(&witness).unwrap(), "{label}: witness");
4810 assert!(matcher.is_accepting(), "{label}: final payment");
4811 }
4812 }
4813
4814 #[cfg(feature = "bench-internals")]
4815 #[test]
4816 fn pending_scalar_bytes_preserve_pattern_liveness() {
4817 let records = (0u8..=255)
4818 .map(|byte| (vec![byte], vec![u32::from(byte)]))
4819 .collect();
4820 let vocabulary = VocabularyHandle::new(Arc::new(
4821 build_vocabulary(256, records).expect("byte vocabulary"),
4822 ))
4823 .expect("vocabulary handle");
4824 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4825 let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4826 let digits = *b"0123456789abcdefABCDEF";
4827 let escaped = program(r#"{"type":"string","pattern":"^ac[0-9]{2}$"}"#);
4828 for (label, prefix, expected) in [
4829 ("escape-start", br#""a\u"#.as_slice(), b"0".as_slice()),
4830 ("escape-zero", br#""a\u0"#.as_slice(), b"0".as_slice()),
4831 ("escape-byte", br#""a\u00"#.as_slice(), b"6".as_slice()),
4832 ("escape-nibble", br#""a\u006"#.as_slice(), b"3".as_slice()),
4833 ] {
4834 let (mask, _) =
4835 assert_three_route_mask_and_advance(&escaped, &vocabulary, &trie, prefix, label);
4836 for digit in digits {
4837 assert_eq!(
4838 bit(&mask, digit),
4839 expected.contains(&digit),
4840 "{label}: hex byte {digit:?}"
4841 );
4842 }
4843 assert!(mask.iter().any(|byte| *byte != 0), "{label}: empty mask");
4844 }
4845 let mut dead_escape = escaped.new_matcher().expect("escaped matcher");
4846 assert!(dead_escape.advance(br#""a\u00"#).unwrap());
4847 assert!(!dead_escape.advance(b"a").unwrap());
4848 assert!(!dead_escape.is_dead());
4849
4850 let already_complete = program(r#"{"type":"string","pattern":"^ac[0-9]{2}$"}"#);
4851 let prefix = br#""ac11"#;
4852 let (mask, _) = assert_three_route_mask_and_advance(
4853 &already_complete,
4854 &vocabulary,
4855 &trie,
4856 prefix,
4857 "complete-pattern-cannot-open-an-extra-escape",
4858 );
4859 assert!(bit(&mask, b'"'));
4860 assert!(!bit(&mask, b'\\'));
4861 let mut rejected_surrogate = already_complete
4862 .new_matcher()
4863 .expect("surrogate rejection matcher");
4864 assert!(rejected_surrogate.advance(prefix).unwrap());
4865 assert!(!rejected_surrogate.advance(b"\\").unwrap());
4866 assert!(!rejected_surrogate.is_dead());
4867
4868 let supplementary = program(r#"{"type":"string","pattern":"^a😀$"}"#);
4869 let (mask, _) = assert_three_route_mask_and_advance(
4870 &supplementary,
4871 &vocabulary,
4872 &trie,
4873 br#""a\u"#,
4874 "live-surrogate-start",
4875 );
4876 assert!(bit(&mask, b'D') && bit(&mask, b'd'));
4877 assert!(!bit(&mask, b'0'));
4878 let mut supplementary_witness = supplementary
4879 .new_matcher()
4880 .expect("supplementary witness matcher");
4881 assert!(supplementary_witness
4882 .advance(br#""a\uD83D\uDE00""#)
4883 .unwrap());
4884 assert!(supplementary_witness.is_accepting());
4885
4886 for (label, schema, prefix) in [
4887 (
4888 "unconstrained-string-escape",
4889 r#"{"type":"string"}"#,
4890 br#""a\u"#.as_slice(),
4891 ),
4892 (
4893 "length-headroom-escape",
4894 r#"{"type":"string","maxLength":5}"#,
4895 br#""ab\u"#.as_slice(),
4896 ),
4897 (
4898 "single-scalar-pattern-escape",
4899 r#"{"type":"string","pattern":"^a.$"}"#,
4900 br#""a\u"#.as_slice(),
4901 ),
4902 (
4903 "open-object-key-escape",
4904 r#"{"type":"object"}"#,
4905 br#"{"a\u"#.as_slice(),
4906 ),
4907 (
4908 "plain-array-string-escape",
4909 r#"{"type":"array","items":{"type":"string"}}"#,
4910 br#"["a\u"#.as_slice(),
4911 ),
4912 ] {
4913 let control = program(schema);
4914 let (mask, _) =
4915 assert_three_route_mask_and_advance(&control, &vocabulary, &trie, prefix, label);
4916 for digit in digits {
4917 assert!(bit(&mask, digit), "{label}: rejected {digit:?}");
4918 }
4919 }
4920
4921 let bmp_enum = program(r#"{"type":"array","items":{"enum":["c","d"]},"uniqueItems":true}"#);
4922 let (mask, _) = assert_three_route_mask_and_advance(
4923 &bmp_enum,
4924 &vocabulary,
4925 &trie,
4926 br#"["\u"#,
4927 "bmp-enum-surrogate-rejection",
4928 );
4929 for digit in digits {
4930 assert_eq!(bit(&mask, digit), digit == b'0', "BMP enum: {digit:?}");
4931 }
4932
4933 let supplementary_enum =
4934 program(r#"{"type":"array","items":{"enum":["\ud83d\ude00","x"]},"uniqueItems":true}"#);
4935 let (mask, _) = assert_three_route_mask_and_advance(
4936 &supplementary_enum,
4937 &vocabulary,
4938 &trie,
4939 br#"["\u"#,
4940 "supplementary-enum-surrogate-reachable",
4941 );
4942 assert!(bit(&mask, b'D') && bit(&mask, b'd'));
4943
4944 let raw = program(r#"{"type":"string","pattern":"^aé$"}"#);
4945 let (mask, _) =
4946 assert_three_route_mask_and_advance(&raw, &vocabulary, &trie, b"\"a", "raw-utf8-lead");
4947 assert!(bit(&mask, 0xc3));
4948 assert!(!bit(&mask, 0xc2));
4949 let (mask, _) = assert_three_route_mask_and_advance(
4950 &raw,
4951 &vocabulary,
4952 &trie,
4953 b"\"a\xc3",
4954 "raw-utf8-continuation",
4955 );
4956 assert!(bit(&mask, 0xa9));
4957 assert!(!bit(&mask, 0xa8));
4958 }
4959
4960 #[cfg(feature = "bench-internals")]
4961 #[test]
4962 fn object_key_partial_scalars_and_pattern_unions_preserve_liveness() {
4963 let records = (0u8..=255)
4964 .map(|byte| (vec![byte], vec![u32::from(byte)]))
4965 .collect();
4966 let vocabulary = VocabularyHandle::new(Arc::new(
4967 build_vocabulary(256, records).expect("byte vocabulary"),
4968 ))
4969 .expect("vocabulary handle");
4970 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
4971 let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
4972 let digits = *b"0123456789abcdefABCDEF";
4973
4974 let exact = program(
4975 r#"{"type":"object","properties":{"edges":{"type":"integer"},"force":{"type":"array","uniqueItems":true}},"additionalProperties":false}"#,
4976 );
4977 for (label, prefix) in [
4978 ("exact-first-escape", br#"{"e\u"#.as_slice()),
4979 ("exact-second-escape", br#"{"ed\u"#.as_slice()),
4980 ] {
4981 let (mask, _) =
4982 assert_three_route_mask_and_advance(&exact, &vocabulary, &trie, prefix, label);
4983 for digit in digits {
4984 assert_eq!(bit(&mask, digit), digit == b'0', "{label}: {digit:?}");
4985 }
4986 }
4987
4988 let named_pattern = program(
4989 r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2}$"},"patternProperties":{"^[a-z]{2}$":{"type":"integer"}},"additionalProperties":false}"#,
4990 );
4991 let (mask, _) = assert_three_route_mask_and_advance(
4992 &named_pattern,
4993 &vocabulary,
4994 &trie,
4995 br#"{"a\u"#,
4996 "property-name-and-pattern",
4997 );
4998 for digit in digits {
4999 assert_eq!(bit(&mask, digit), digit == b'0', "pattern key: {digit:?}");
5000 }
5001
5002 let no_keys = program(
5003 r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2}$"},"additionalProperties":false}"#,
5004 );
5005 let (mask, _) = assert_three_route_mask_and_advance(
5006 &no_keys,
5007 &vocabulary,
5008 &trie,
5009 b"{",
5010 "empty-key-language",
5011 );
5012 assert!(!bit(&mask, b'"'));
5013 assert!(bit(&mask, b'}'));
5014
5015 let two_patterns = program(
5016 r#"{"type":"object","patternProperties":{"^a[a-z]$":{"type":"integer"},"^b[a-z]$":{"type":"integer"}},"additionalProperties":false}"#,
5017 );
5018 let (mask, _) = assert_three_route_mask_and_advance(
5019 &two_patterns,
5020 &vocabulary,
5021 &trie,
5022 br#"{""#,
5023 "two-pattern-union",
5024 );
5025 assert!(bit(&mask, b'a') && bit(&mask, b'b') && bit(&mask, b'\\'));
5026 assert!(!bit(&mask, b'z'));
5027
5028 let unevaluated_patterns = program(
5029 r#"{"type":"object","patternProperties":{"^a[a-z]$":{"type":"integer"},"^b[a-z]$":{"type":"integer"}},"unevaluatedProperties":false}"#,
5030 );
5031 let (mask, _) = assert_three_route_mask_and_advance(
5032 &unevaluated_patterns,
5033 &vocabulary,
5034 &trie,
5035 br#"{""#,
5036 "unevaluated-two-pattern-union",
5037 );
5038 assert!(bit(&mask, b'a') && bit(&mask, b'b'));
5039 assert!(!bit(&mask, b'z'));
5040
5041 let covered_by_false_pattern = program(
5042 r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^a.$":false},"additionalProperties":false}"#,
5043 );
5044 let (mask, _) = assert_three_route_mask_and_advance(
5045 &covered_by_false_pattern,
5046 &vocabulary,
5047 &trie,
5048 b"{",
5049 "positive-language-covered-by-false-pattern",
5050 );
5051 assert!(!bit(&mask, b'"'));
5052 assert!(bit(&mask, b'}'));
5053
5054 let partially_subtracted_pattern = program(
5055 r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^ab$":false},"additionalProperties":false}"#,
5056 );
5057 let (mask, _) = assert_three_route_mask_and_advance(
5058 &partially_subtracted_pattern,
5059 &vocabulary,
5060 &trie,
5061 br#"{"a"#,
5062 "positive-language-minus-one-false-key",
5063 );
5064 assert!(!bit(&mask, b'b'));
5065 assert!(bit(&mask, b'c'));
5066 let (mask, _) = assert_three_route_mask_and_advance(
5067 &partially_subtracted_pattern,
5068 &vocabulary,
5069 &trie,
5070 br#"{"aa"#,
5071 "complete-key-cannot-open-an-extra-escape",
5072 );
5073 assert!(bit(&mask, b'"'));
5074 assert!(!bit(&mask, b'\\'));
5075
5076 let unevaluated_items =
5077 program(r#"{"type":"array","prefixItems":[{"enum":["p"]}],"unevaluatedItems":false}"#);
5078 let (mask, _) = assert_three_route_mask_and_advance(
5079 &unevaluated_items,
5080 &vocabulary,
5081 &trie,
5082 br#"["p""#,
5083 "unevaluated-items-exhausted",
5084 );
5085 assert!(bit(&mask, b']'));
5086 assert!(!bit(&mask, b','));
5087
5088 let ascii_records: FxHashMap<Vec<u8>, Vec<u32>> = (0x20u8..=0x7e)
5089 .chain([b'\t', b'\n', b'\r'])
5090 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5091 .collect();
5092 let ascii_vocabulary = VocabularyHandle::new(Arc::new(
5093 build_vocabulary(256, ascii_records).expect("ASCII vocabulary"),
5094 ))
5095 .expect("ASCII vocabulary handle");
5096 let ascii_trie = TrieCache::new()
5097 .bind(&ascii_vocabulary)
5098 .expect("ASCII trie");
5099 let (ascii_mask, _) = assert_three_route_mask_and_advance(
5100 &unevaluated_items,
5101 &ascii_vocabulary,
5102 &ascii_trie,
5103 br#"["p""#,
5104 "unevaluated-items-sparse-vocabulary",
5105 );
5106 assert!(!bit(&ascii_mask, b','));
5107 let mut whole = unevaluated_items
5108 .new_matcher()
5109 .expect("whole-token unevaluatedItems matcher");
5110 assert!(!whole.advance(br#"["p","#).unwrap());
5111 }
5112
5113 #[cfg(feature = "bench-internals")]
5114 #[test]
5115 fn arrays_numbers_and_negation_refuse_known_dead_prefixes() {
5116 let records = (0u8..=255)
5117 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5118 .collect();
5119 let vocabulary = VocabularyHandle::new(Arc::new(
5120 build_vocabulary(256, records).expect("byte vocabulary"),
5121 ))
5122 .expect("vocabulary handle");
5123 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5124 let bit = |mask: &[u8], byte: u8| mask[usize::from(byte) / 8] & (1 << (byte % 8)) != 0;
5125
5126 let max_contains = program(
5127 r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5128 );
5129 let (mask, _) = assert_three_route_mask_and_advance(
5130 &max_contains,
5131 &vocabulary,
5132 &trie,
5133 br#"["p",""#,
5134 "max-contains-second-item-prefix",
5135 );
5136 assert!(!bit(&mask, b'p'));
5137 assert!(bit(&mask, b'q'));
5138 let mut direct = max_contains.new_matcher().expect("maxContains matcher");
5139 assert!(direct.advance(br#"["p",""#).unwrap());
5140 assert!(!direct.advance(b"p").unwrap());
5141
5142 let exclusive = program(r#"{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1}"#);
5143 let (mask, _) = assert_three_route_mask_and_advance(
5144 &exclusive,
5145 &vocabulary,
5146 &trie,
5147 b" ",
5148 "exclusive-unit-interval",
5149 );
5150 assert!(bit(&mask, b'0'));
5151 assert!(bit(&mask, b'1'));
5152 let mut exponent_witness = exclusive.new_matcher().expect("number witness");
5153 assert!(exponent_witness.advance(b"1e-1").unwrap());
5154 assert!(exponent_witness.is_accepting());
5155
5156 let negated_limit = program(r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#);
5157 let (mask, _) = assert_three_route_mask_and_advance(
5158 &negated_limit,
5159 &vocabulary,
5160 &trie,
5161 br#""ab"#,
5162 "negated-max-length",
5163 );
5164 assert!(!bit(&mask, b'c'));
5165
5166 let impossible_unique = program(
5167 r#"{"type":"array","items":{"enum":["p","q"]},"uniqueItems":true,"minItems":3,"maxItems":3}"#,
5168 );
5169 let (mask, _) = assert_three_route_mask_and_advance(
5170 &impossible_unique,
5171 &vocabulary,
5172 &trie,
5173 b"",
5174 "impossible-finite-unique-array",
5175 );
5176 assert!(!bit(&mask, b'['));
5177 for whitespace in b" \t\r\n" {
5178 assert!(!bit(&mask, *whitespace));
5179 }
5180 }
5181
5182 #[cfg(feature = "bench-internals")]
5183 #[test]
5184 fn realistic_depth_mask_advance_and_witness_reachability_corpus_gate() {
5185 struct Fixture {
5186 name: &'static str,
5187 schema: &'static str,
5188 witness: &'static [u8],
5189 selected_prefix: &'static [u8],
5190 }
5191
5192 let fixtures = [
5193 Fixture {
5194 name: "closed-exact-object",
5195 schema: r#"{"type":"object","properties":{"nodes":{"type":"array"},"edges":{"type":"array"}},"required":["nodes","edges"],"additionalProperties":false}"#,
5196 witness: br#"{"nodes":[],"edges":[]}"#,
5197 selected_prefix: br#"{""#,
5198 },
5199 Fixture {
5200 name: "pydantic-defs-ref-nested-arrays",
5201 schema: r##"{"$defs":{"Node":{"type":"object","properties":{"id":{"type":"integer"}},"required":["id"],"additionalProperties":false},"Edge":{"type":"object","properties":{"source":{"type":"integer"},"target":{"type":"integer"}},"required":["source","target"],"additionalProperties":false}},"type":"object","properties":{"nodes":{"type":"array","items":{"$ref":"#/$defs/Node"}},"edges":{"type":"array","items":{"$ref":"#/$defs/Edge"}}},"required":["nodes","edges"],"additionalProperties":false}"##,
5202 witness: br#"{"nodes":[{"id":1}],"edges":[{"source":1,"target":2}]}"#,
5203 selected_prefix: br#"{"nodes":[{"id":1}],"edges":[{"source":1,"target":"#,
5204 },
5205 Fixture {
5206 name: "property-name-two-pattern-union",
5207 schema: r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2,6}$"},"patternProperties":{"^a[a-z]{1,5}$":{"type":"string"},"^r[a-z]{1,5}$":{"type":"string"}},"additionalProperties":false,"maxProperties":2}"#,
5208 witness: br#"{"amount":"USD","routin":"acct"}"#,
5209 selected_prefix: br#"{"amount":"USD","routin"#,
5210 },
5211 Fixture {
5212 name: "conditional-dependent-unevaluated-oneof",
5213 schema: r#"{"type":"object","properties":{"kind":{"enum":["a","b"]},"value":{"oneOf":[{"type":"integer"},{"type":"string","pattern":"^ok$"}]},"flag":{"type":"boolean"}},"required":["kind","value"],"dependentSchemas":{"flag":{"required":["value"]}},"allOf":[{"if":{"properties":{"kind":{"const":"a"}},"required":["kind"]},"then":{"not":{"properties":{"value":{"const":0}},"required":["value"]}},"else":{"anyOf":[{"required":["flag"]},{"not":{"required":["flag"]}}]}}],"unevaluatedProperties":false}"#,
5214 witness: br#"{"kind":"a","value":1,"flag":true}"#,
5215 selected_prefix: br#"{"kind":"a","value":1,"flag":t"#,
5216 },
5217 Fixture {
5218 name: "unique-contains-third-item",
5219 schema: r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
5220 witness: br#"["treasury","risk","ops"]"#,
5221 selected_prefix: br#"["treasury","risk",""#,
5222 },
5223 Fixture {
5224 name: "unevaluated-items-boundary",
5225 schema: r#"{"prefixItems":[{"enum":["p"]}],"contains":{"const":"q"},"unevaluatedItems":false}"#,
5226 witness: br#"["p","q"]"#,
5227 selected_prefix: br#"["p","q"#,
5228 },
5229 Fixture {
5230 name: "dynamic-reference-recursion",
5231 schema: r##"{"$dynamicAnchor":"node","type":"object","properties":{"value":{"type":"integer"},"child":{"$dynamicRef":"#node"}},"required":["value"],"additionalProperties":false}"##,
5232 witness: br#"{"value":1,"child":{"value":2,"child":{"value":3}}}"#,
5233 selected_prefix: br#"{"value":1,"child":{"value":2,"child":{"value":"#,
5234 },
5235 Fixture {
5236 name: "exclusive-decimal-multiple",
5237 schema: r#"{"type":"number","exclusiveMinimum":0,"exclusiveMaximum":1,"multipleOf":0.1}"#,
5238 witness: b"0.5",
5239 selected_prefix: b"0.",
5240 },
5241 Fixture {
5242 name: "max-contains-cap",
5243 schema: r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5244 witness: br#"["p","q"]"#,
5245 selected_prefix: br#"["p",""#,
5246 },
5247 Fixture {
5248 name: "negated-constant-at-max-length",
5249 schema: r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#,
5250 witness: br#""abd""#,
5251 selected_prefix: br#""ab"#,
5252 },
5253 ];
5254
5255 let mut records: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5256 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5257 .collect();
5258 records.insert(b"e".to_vec(), vec![u32::from(b'e'), 256]);
5259 records.insert(b"name".to_vec(), vec![257, 258]);
5260 records.insert(b"ops".to_vec(), vec![259, 260]);
5261 let vocabulary = VocabularyHandle::new(Arc::new(
5262 build_vocabulary(261, records).expect("corpus vocabulary"),
5263 ))
5264 .expect("corpus vocabulary handle");
5265 let trie = TrieCache::new().bind(&vocabulary).expect("corpus trie");
5266 let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5267
5268 for fixture in fixtures {
5269 assert!(fixture.witness.starts_with(fixture.selected_prefix));
5270 let case = program(fixture.schema);
5271 assert_three_route_mask_and_advance(
5272 &case,
5273 &vocabulary,
5274 &trie,
5275 fixture.selected_prefix,
5276 fixture.name,
5277 );
5278
5279 let mut matcher = case.new_matcher().expect("corpus matcher");
5280 for (index, &next) in fixture.witness.iter().enumerate() {
5281 let mut first = vec![0xa5; output_len];
5282 matcher
5283 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut first)
5284 .unwrap_or_else(|error| panic!("{} prefix {index}: {error}", fixture.name));
5285 assert!(
5286 first.iter().any(|byte| *byte != 0),
5287 "{} produced an empty mask at valid prefix {:?}",
5288 fixture.name,
5289 &fixture.witness[..index]
5290 );
5291 assert!(
5292 first[usize::from(next) / 8] & (1 << (next % 8)) != 0,
5293 "{} masked witness byte {next:?} at prefix {:?}",
5294 fixture.name,
5295 &fixture.witness[..index]
5296 );
5297 assert!(
5298 matcher.advance(&[next]).unwrap(),
5299 "{} rejected witness byte {next:?} at {index}",
5300 fixture.name
5301 );
5302 assert_incremental_accounting(&matcher, fixture.name);
5303 }
5304 assert!(matcher.is_accepting(), "{} witness", fixture.name);
5305 }
5306 }
5307
5308 #[cfg(feature = "bench-internals")]
5309 #[test]
5310 fn finite_admitted_frontier_successors_are_completable() {
5311 use std::collections::{HashSet, VecDeque};
5312
5313 let fixtures = [
5314 (
5315 "max-contains-frontier",
5316 r#"{"type":"array","items":{"enum":["p","q"]},"contains":{"const":"p"},"maxContains":1,"maxItems":3}"#,
5317 br#"["p",""#.as_slice(),
5318 ),
5319 (
5320 "unique-contains-frontier",
5321 r#"{"type":"array","items":{"enum":["treasury","ops","risk"]},"contains":{"const":"treasury"},"uniqueItems":true,"minItems":1,"maxItems":3}"#,
5322 br#"["treasury","risk",""#.as_slice(),
5323 ),
5324 (
5325 "pattern-difference-frontier",
5326 r#"{"type":"object","patternProperties":{"^a[a-z]$":true,"^ab$":false},"additionalProperties":false}"#,
5327 br#"{"a"#.as_slice(),
5328 ),
5329 (
5330 "negated-max-length-frontier",
5331 r#"{"type":"string","maxLength":3,"not":{"const":"abc"}}"#,
5332 br#""ab"#.as_slice(),
5333 ),
5334 ];
5335 let records: FxHashMap<Vec<u8>, Vec<u32>> = (0x20u8..=0x7e)
5336 .chain([b'\t', b'\n', b'\r'])
5337 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5338 .collect();
5339 let vocabulary = VocabularyHandle::new(Arc::new(
5340 build_vocabulary(256, records).expect("ASCII liveness vocabulary"),
5341 ))
5342 .expect("ASCII liveness vocabulary handle");
5343 let trie = TrieCache::new()
5344 .bind(&vocabulary)
5345 .expect("ASCII liveness trie");
5346 let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5347 let fixture_filter = std::env::var("MF_LIVENESS_FIXTURE").ok();
5348
5349 for (label, schema, initial) in fixtures {
5350 if fixture_filter
5351 .as_deref()
5352 .is_some_and(|wanted| wanted != label)
5353 {
5354 continue;
5355 }
5356 let case = program(schema);
5357 let mut frontier = VecDeque::from([(initial.to_vec(), 0usize)]);
5358 let mut visited = HashSet::from([initial.to_vec()]);
5359 while let Some((prefix, frontier_depth)) = frontier.pop_front() {
5360 let mut matcher = case.new_matcher().expect("frontier matcher");
5361 assert!(matcher.advance(&prefix).unwrap(), "{label}: {prefix:?}");
5362 let mut output = vec![0xa5; output_len];
5363 matcher
5364 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
5365 .unwrap_or_else(|error| panic!("{label} {prefix:?}: {error}"));
5366 for (token, ids) in vocabulary.iter_records() {
5367 let admitted = ids
5368 .iter()
5369 .any(|id| output[*id as usize / 8] & (1 << (*id % 8)) != 0);
5370 let mut candidate = prefix.clone();
5371 candidate.extend_from_slice(token);
5372 let mut exact = case.new_matcher().expect("frontier transition matcher");
5373 assert_eq!(
5374 admitted,
5375 exact.advance(&candidate).unwrap(),
5376 "{label}: mask/whole-token advance disagreement: {candidate:?}"
5377 );
5378 }
5379 if std::env::var_os("MF_LIVENESS_TRANSITIONS_ONLY").is_some() {
5380 continue;
5381 }
5382 for (token, ids) in vocabulary.iter_records() {
5383 let admitted = ids
5384 .iter()
5385 .any(|id| output[*id as usize / 8] & (1 << (*id % 8)) != 0);
5386 if !admitted {
5387 continue;
5388 }
5389 let mut candidate = prefix.clone();
5390 candidate.extend_from_slice(token);
5391 let mut budget = 50_000;
5392 assert_eq!(
5393 closer_first_completion_search(&case, &candidate, 24, &mut budget),
5394 CompletionSearch::Found,
5395 "{label}: admitted successor has no proven completion: {candidate:?}"
5396 );
5397 if frontier_depth < 1 && visited.insert(candidate.clone()) {
5398 frontier.push_back((candidate, frontier_depth + 1));
5399 }
5400 }
5401 assert!(visited.len() <= 512, "{label}: unbounded test frontier");
5402 }
5403 }
5404 }
5405
5406 #[cfg(feature = "bench-internals")]
5407 #[test]
5408 fn closed_object_key_slice_never_emits_a_token_that_exact_advance_rejects() {
5409 let program = program(
5412 r#"{
5413 "type":"object",
5414 "properties":{
5415 "productId":{"type":"integer"},
5416 "tags":{"type":"array","items":{"type":"string"},"uniqueItems":true}
5417 },
5418 "required":["productId"],
5419 "additionalProperties":false
5420 }"#,
5421 );
5422 let mut tokens: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5423 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5424 .collect();
5425 tokens.insert(b"name".to_vec(), vec![256]);
5426 let vocabulary = VocabularyHandle::new(Arc::new(
5427 build_vocabulary(257, tokens).expect("key-slice vocabulary"),
5428 ))
5429 .expect("vocabulary handle");
5430 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5431 let (mask, _) = assert_three_route_mask_and_advance(
5432 &program,
5433 &vocabulary,
5434 &trie,
5435 b"\n{ \"",
5436 "closed-key-after-whitespace",
5437 );
5438 assert_eq!(
5439 mask[256 / 8] & (1 << (256 % 8)),
5440 0,
5441 "an impossible multi-byte key prefix must not be emitted"
5442 );
5443 assert_ne!(
5444 mask[usize::from(b'p') / 8] & (1 << (b'p' % 8)),
5445 0,
5446 "the viable `productId` prefix must remain"
5447 );
5448 }
5449
5450 #[cfg(feature = "bench-internals")]
5451 #[test]
5452 fn pydantic_knowledge_graph_root_key_mask_matches_every_transition() {
5453 let schema = program(
5454 r##"{
5455 "$defs":{
5456 "Edge":{
5457 "additionalProperties":false,
5458 "description":"One directed relation between two entities.",
5459 "properties":{
5460 "source":{"description":"Unique source of the edge","maximum":9,"minimum":1,"title":"Source","type":"integer"},
5461 "target":{"description":"Unique target of the edge","maximum":9,"minimum":1,"title":"Target","type":"integer"},
5462 "label":{"description":"Label of the edge","maxLength":10,"title":"Label","type":"string"}
5463 },
5464 "required":["source","target","label"],
5465 "title":"Edge",
5466 "type":"object"
5467 },
5468 "Node":{
5469 "additionalProperties":false,
5470 "description":"One entity in the graph.",
5471 "properties":{
5472 "id":{"description":"Unique identifier of the node","maximum":9,"minimum":1,"title":"Id","type":"integer"},
5473 "label":{"description":"Label of the node","maxLength":10,"title":"Label","type":"string"}
5474 },
5475 "required":["id","label"],
5476 "title":"Node",
5477 "type":"object"
5478 }
5479 },
5480 "additionalProperties":false,
5481 "description":"A graph of entities and the relations between them.",
5482 "properties":{
5483 "nodes":{"items":{"$ref":"#/$defs/Node"},"maxItems":3,"minItems":1,"title":"Nodes","type":"array"},
5484 "edges":{"items":{"$ref":"#/$defs/Edge"},"maxItems":2,"minItems":1,"title":"Edges","type":"array"}
5485 },
5486 "required":["nodes","edges"],
5487 "title":"KnowledgeGraph",
5488 "type":"object"
5489 }"##,
5490 );
5491 let mut records: FxHashMap<Vec<u8>, Vec<u32>> = (0u8..=255)
5492 .map(|byte| (vec![byte], vec![u32::from(byte)]))
5493 .collect();
5494 for (id, token) in [
5495 (256, b"name".as_slice()),
5496 (257, b"nodes"),
5497 (259, b"edges"),
5498 (260, b"node"),
5499 (261, b"edge"),
5500 (262, b"nodes\""),
5501 (263, b"nodes\":"),
5502 (264, b"edges\":["),
5503 (265, b"\\u006e"),
5504 (266, b"\\u0065"),
5505 (267, b"\\u2"),
5506 (268, b"\":"),
5507 (269, b"nodes\":["),
5508 (270, b"n\\u006fdes\":"),
5509 (271, b"\xc3\xa9"),
5510 ] {
5511 records.insert(token.to_vec(), vec![id]);
5512 }
5513 records.get_mut(b"nodes".as_slice()).unwrap().push(258);
5514 records.get_mut(&[0xef][..]).unwrap().push(272);
5515 let vocabulary = VocabularyHandle::new(Arc::new(
5516 build_vocabulary(280, records).expect("knowledge graph vocabulary"),
5517 ))
5518 .expect("vocabulary handle");
5519 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5520 let prefix = b"{ \"";
5521 let (mask, _) = assert_three_route_mask_and_advance(
5522 &schema,
5523 &vocabulary,
5524 &trie,
5525 prefix,
5526 "knowledge-graph-root-key",
5527 );
5528 let bit = |id: u32| mask[id as usize / 8] & (1 << (id % 8)) != 0;
5529
5530 for byte in 0x20u8..=0x7e {
5531 assert_eq!(
5532 bit(u32::from(byte)),
5533 matches!(byte, b'n' | b'e' | b'\\'),
5534 "printable root key byte {byte:?}"
5535 );
5536 }
5537 for byte in 0u8..0x20 {
5538 assert!(!bit(u32::from(byte)), "control key byte {byte:#04x}");
5539 }
5540 assert!(!bit(256), "raw token `name` must be rejected");
5541 assert!(bit(257) && bit(258), "all aliases for `nodes` must agree");
5542 assert!(bit(259));
5543
5544 let mut decoder = super::super::lexer::JsonStringDecoder::new();
5545 assert_eq!(
5546 decoder.push(0xef),
5547 super::super::lexer::DecodeStep::Continue,
5548 "0xEF is a lexically pending UTF-8 lead byte"
5549 );
5550 assert!(!bit(0xef), "0xEF cannot reach either finite decoded key");
5551 assert!(!bit(272), "the 0xEF alias must agree");
5552
5553 for (token, ids) in vocabulary.iter_records() {
5554 let mut matcher = schema.new_matcher().expect("transition matcher");
5555 assert!(matcher.advance(prefix).unwrap());
5556 let before = (
5557 matcher.is_accepting(),
5558 matcher.is_dead(),
5559 matcher.eos_legal(),
5560 );
5561 let accepted = matcher.advance(token).unwrap();
5562 for id in ids {
5563 assert_eq!(bit(*id), accepted, "token {token:?}, id {id}");
5564 }
5565 if !accepted {
5566 assert_eq!(
5567 (
5568 matcher.is_accepting(),
5569 matcher.is_dead(),
5570 matcher.eos_legal(),
5571 ),
5572 before,
5573 "rejected token mutated committed state: {token:?}"
5574 );
5575 }
5576 assert_incremental_accounting(&matcher, "knowledge-graph-transition");
5577 }
5578
5579 for document in [
5580 br#"{"nodes":[{"id":1,"label":"a"}],"edges":[{"source":1,"target":1,"label":"x"}]}"#
5581 .as_slice(),
5582 br#"{"edges":[{"source":1,"target":1,"label":"x"}],"nodes":[{"label":"a","id":1}]}"#
5583 .as_slice(),
5584 ] {
5585 let mut matcher = schema.new_matcher().expect("witness matcher");
5586 assert!(matcher.advance(document).unwrap());
5587 assert!(matcher.is_accepting());
5588 assert_incremental_accounting(&matcher, "knowledge-graph-witness");
5589 }
5590 }
5591
5592 #[test]
5593 fn unique_contains_trie_masks_are_transactional_after_each_committed_item() {
5594 let ir = ir(
5595 r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true,"contains":{"const":0}}"#,
5596 );
5597 let program = StructuredProgram::compile(ir.clone()).expect("program");
5598 let mut tokens = FxHashMap::default();
5599 for (id, bytes) in [
5600 (0, b"[".as_slice()),
5601 (1, b"0"),
5602 (2, b",1"),
5603 (3, b",2"),
5604 (4, b" "),
5605 (5, b","),
5606 (6, b"]"),
5607 (7, b",1]"),
5608 (8, b",3]"),
5609 (9, b"x"),
5610 ] {
5611 tokens.insert(bytes.to_vec(), vec![id]);
5612 }
5613 let handle = VocabularyHandle::new(Arc::new(build_vocabulary(10, tokens).expect("vocab")))
5614 .expect("handle");
5615 let trie = TrieCache::new().bind(&handle).expect("trie");
5616 let mut trie_matcher = program.new_matcher().expect("trie matcher");
5617 let mut record_matcher = program.new_matcher().expect("record matcher");
5618 let mut output = [0u8; 4];
5619
5620 for token in [b"[".as_slice(), b"0", b",1", b",2"] {
5621 assert!(trie_matcher.advance(token).expect("trie commit"));
5622 assert!(record_matcher.advance(token).expect("record commit"));
5623 for (bytes, ids) in handle.iter_records() {
5624 record_matcher
5625 .allowed_mask_from_records(handle.mask_vocab_size(), [(bytes, ids)].into_iter())
5626 .unwrap_or_else(|error| {
5627 panic!("candidate {bytes:?} after {token:?}: {error:?}")
5628 });
5629 }
5630 let expected = record_matcher
5631 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
5632 .unwrap_or_else(|error| panic!("record mask after {token:?}: {error:?}"));
5633 for _ in 0..128 {
5634 trie_matcher
5635 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
5636 .expect("trie mask");
5637 }
5638 for id in 0..handle.mask_vocab_size() {
5639 let bit = output[id / 8] & (1u8 << (id % 8)) != 0;
5640 assert_eq!(bit, expected.get(TokenId(u32::try_from(id).expect("id"))));
5641 }
5642 }
5643 let dup_records = [(b",1]".as_slice(), [7u32].as_slice())];
5646 let distinct_records = [(b",3]".as_slice(), [8u32].as_slice())];
5647 let dup_mask = record_matcher
5648 .allowed_mask_from_records(handle.mask_vocab_size(), dup_records.into_iter())
5649 .expect("dup mask");
5650 assert!(!dup_mask.get(TokenId(7)), "duplicate 1 must stay rejected");
5651 let distinct_mask = record_matcher
5652 .allowed_mask_from_records(handle.mask_vocab_size(), distinct_records.into_iter())
5653 .expect("distinct mask");
5654 assert!(
5655 distinct_mask.get(TokenId(8)),
5656 "distinct value 3 must stay accepted"
5657 );
5658 let mut trie_out = [0u8; 4];
5659 trie_matcher
5660 .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
5661 .expect("trie mask after probes");
5662 let trie_bit = |id: u32| trie_out[id as usize / 8] & (1u8 << (id % 8)) != 0;
5663 assert!(!trie_bit(7), "trie: duplicate 1 must stay rejected");
5664 assert!(trie_bit(8), "trie: distinct 3 must stay accepted");
5665 assert!(trie_matcher
5667 .advance(b",3]")
5668 .expect("distinct value 3 accepted"));
5669 assert!(trie_matcher.is_accepting());
5670 assert!(record_matcher
5671 .advance(b",3]")
5672 .expect("distinct value 3 accepted"));
5673 assert!(record_matcher.is_accepting());
5674 }
5675
5676 #[test]
5677 fn trie_output_matches_record_scan_and_clears_dirty_output() {
5678 let program = program(
5679 r#"{"type":"object","properties":{"x":{"type":"boolean"}},"required":["x"],"additionalProperties":false}"#,
5680 );
5681 let mut tokens = FxHashMap::default();
5682 tokens.insert(b"{".to_vec(), vec![0]);
5683 tokens.insert(b"\"x\"".to_vec(), vec![1]);
5684 tokens.insert(b":".to_vec(), vec![2]);
5685 tokens.insert(b"true".to_vec(), vec![3, 7]);
5686 tokens.insert(b"false".to_vec(), vec![4]);
5687 tokens.insert(b"}".to_vec(), vec![5]);
5688 let vocabulary = Arc::new(build_vocabulary(8, tokens).expect("vocabulary"));
5689 let handle = VocabularyHandle::new(vocabulary).expect("handle");
5690 let trie = TrieCache::new().bind(&handle).expect("trie");
5691 let mut records = program.new_matcher().expect("records matcher");
5692 let mut walked = program.new_matcher().expect("trie matcher");
5693 for token in [b"{".as_slice(), b"\"x\"", b":"] {
5694 assert!(records.advance(token).expect("advance"));
5695 assert!(walked.advance(token).expect("advance"));
5696 }
5697 let expected = records
5698 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
5699 .expect("record mask");
5700 let mut output =
5701 vec![0xff; handle.mask_vocab_size().div_ceil(32) * std::mem::size_of::<u32>()];
5702 walked
5703 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
5704 .expect("trie mask");
5705 for id in 0..handle.mask_vocab_size() {
5706 let token = TokenId(u32::try_from(id).expect("test token id"));
5707 let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
5708 assert_eq!(observed, expected.get(token), "token {id}");
5709 }
5710 assert!(walked.advance(b"true").expect("traversal rolled back"));
5711 }
5712
5713 #[test]
5714 fn trie_record_scan_and_reference_masks_are_bit_identical_at_a_committed_prefix() {
5715 let schema = r#"{"type":"object","additionalProperties":{"type":"boolean"}}"#;
5716 let ir = ir(schema);
5717 let program = StructuredProgram::compile(ir.clone()).expect("program");
5718 assert_eq!(program.backend_kind(), "incremental");
5719 let mut tokens = FxHashMap::default();
5720 let pieces: &[&[u8]] = &[
5721 b"{", b"}", b"\"", b":", b",", b"k", b"0", b"1", b"true", b"false", b"tru", b"e",
5722 ];
5723 for (i, p) in pieces.iter().enumerate() {
5724 tokens.insert(p.to_vec(), vec![i as u32]);
5725 }
5726 tokens.insert(b"}".to_vec(), vec![1, 12]);
5727 let width = 16usize;
5728 let handle = VocabularyHandle::new(Arc::new(build_vocabulary(15, tokens).expect("vocab")))
5729 .expect("handle");
5730 let trie = TrieCache::new().bind(&handle).expect("trie");
5731 let prefix: &[u8] = br#"{"k0":true,"k1":false"#;
5732
5733 let mut walked = program.new_matcher().expect("walked");
5734 let mut scanned = program.new_matcher().expect("scanned");
5735 for &b in prefix {
5736 assert!(walked.advance(&[b]).expect("advance"));
5737 assert!(scanned.advance(&[b]).expect("advance"));
5738 }
5739 let mut trie_out = vec![0u8; width.div_ceil(32) * 4];
5740 walked
5741 .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
5742 .expect("trie mask");
5743 let record_mask = scanned
5744 .allowed_mask_from_records(width, handle.iter_records())
5745 .expect("record mask");
5746
5747 let mut id_bytes: std::collections::HashMap<usize, Vec<u8>> =
5748 std::collections::HashMap::new();
5749 for (i, p) in pieces.iter().enumerate() {
5750 id_bytes.insert(i, p.to_vec());
5751 }
5752 id_bytes.insert(12, b"}".to_vec());
5753 let mut trie_sum = 0u64;
5754 let mut record_sum = 0u64;
5755 let mut ref_sum = 0u64;
5756 for id in 0..width {
5757 let token = TokenId(u32::try_from(id).expect("id"));
5758 let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
5759 let record_bit = record_mask.get(token);
5760 let ref_bit = match id_bytes.get(&id) {
5761 Some(piece) => {
5762 let mut cand = prefix.to_vec();
5763 cand.extend_from_slice(piece);
5764 super::super::reference::accepts(&ir, &cand)
5765 || super::super::reference::can_continue(&ir, &cand)
5766 }
5767 None => false,
5768 };
5769 assert_eq!(trie_bit, record_bit, "trie vs record at id {id}");
5770 assert_eq!(trie_bit, ref_bit, "trie vs reference at id {id}");
5771 trie_sum |= u64::from(trie_bit) << id;
5772 record_sum |= u64::from(record_bit) << id;
5773 ref_sum |= u64::from(ref_bit) << id;
5774 }
5775 assert_eq!(trie_sum, record_sum, "checksum trie==record");
5776 assert_eq!(trie_sum, ref_sum, "checksum trie==reference");
5777 }
5778
5779 #[test]
5780 fn adaptive_full_trie_and_record_scan_match_on_string_boundaries() {
5781 let schemas_and_prefixes: &[(&str, &[&[u8]])] = &[
5782 (
5783 r#"{"anyOf":[{"type":"object","properties":{"s":{"type":"string","maxLength":1}}},{"type":"object","properties":{"s":{"type":"string","maxLength":3}}}]}"#,
5784 &[br#"{"s":"a"#, br#"{"s":"aa"#],
5785 ),
5786 (
5787 r#"{"type":"object","unevaluatedProperties":{"type":"string","maxLength":1}}"#,
5788 &[br#"{"s":"a"#],
5789 ),
5790 (
5791 r#"{"type":"string","maxLength":40}"#,
5792 &[b"\"", b"\"abcdefgh"],
5793 ),
5794 (
5795 r#"{"type":"string","maxLength":2}"#,
5796 &[b"\"", b"\"a", b"\"aa"],
5797 ),
5798 (
5799 r#"{"type":"object","properties":{"s":{"type":"string","maxLength":2}}}"#,
5800 &[br#"{"s":""#, br#"{"s":"a"#, br#"{"s":"aa"#],
5801 ),
5802 (
5803 r#"{"type":"object","properties":{"s":{"type":"string","maxLength":2}},"unevaluatedProperties":false}"#,
5804 &[br#"{"s":""#, br#"{"s":"a"#, br#"{"s":"aa"#],
5805 ),
5806 (
5807 r#"{"allOf":[{"type":"object"},{"propertyNames":{"maxLength":2}}]}"#,
5808 &[br#"{""#, br#"{"a"#, br#"{"aa"#],
5809 ),
5810 (r#"{"type":"string"}"#, &[b"\"", b"\"a", br#""\u00"#]),
5811 (
5812 r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","pattern":"z$"}]}"#,
5813 &[b"\"a"],
5814 ),
5815 (
5816 r#"{"type":"string","minLength":1,"maxLength":2,"pattern":"^a"}"#,
5817 &[b"\"", b"\"a"],
5818 ),
5819 (
5820 r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","format":"email"}]}"#,
5821 &[b"\"a"],
5822 ),
5823 ];
5824 let mut tokens = FxHashMap::default();
5825 tokens.insert(b"a".to_vec(), vec![0, 1]);
5826 tokens.insert(br#"\u0061"#.to_vec(), vec![2]);
5827 tokens.insert(br#"\uD83D"#.to_vec(), vec![9]);
5828 tokens.insert(br#"\uDE00"#.to_vec(), vec![10]);
5829 tokens.insert(vec![0xf0, 0x9f], vec![3]);
5830 tokens.insert(vec![0x98, 0x80], vec![4]);
5831 tokens.insert(b"\xf0\x9f\x98\x80".to_vec(), vec![5]);
5832 tokens.insert(b"\"".to_vec(), vec![6]);
5833 tokens.insert(b"\\".to_vec(), vec![7]);
5834 tokens.insert(vec![0x1f], vec![8]);
5835 tokens.insert(b"z".to_vec(), vec![100]);
5836 tokens.insert(b"zz".to_vec(), vec![101]);
5837 tokens.insert("\u{e9}".as_bytes().to_vec(), vec![102]);
5838 tokens.insert("\u{20ac}".as_bytes().to_vec(), vec![103]);
5839 tokens.insert("e\u{301}".as_bytes().to_vec(), vec![104]);
5840 tokens.insert(vec![b'a'; 40], vec![105]);
5841 tokens.insert(br#"\uD83D\uDE00"#.to_vec(), vec![106]);
5842 tokens.insert(vec![b'a'; 33], vec![107]);
5843 tokens.insert(vec![b'a'; 65], vec![108]);
5844 tokens.insert(vec![b'a'; 129], vec![109]);
5845 let vocabulary =
5846 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).expect("vocabulary")))
5847 .expect("handle");
5848 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
5849 let mask_bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
5850
5851 for &(schema, prefixes) in schemas_and_prefixes {
5852 let program = program(schema);
5853 for &prefix in prefixes {
5854 let mut adaptive = program.new_matcher().expect("adaptive matcher");
5855 let mut full = program.new_matcher().expect("full matcher");
5856 let mut records = program.new_matcher().expect("record matcher");
5857 assert!(adaptive.advance(prefix).expect("adaptive prefix"));
5858 assert!(full.advance(prefix).expect("full prefix"));
5859 assert!(records.advance(prefix).expect("record prefix"));
5860 let mut adaptive_out = vec![0u8; mask_bytes];
5861 let mut full_out = vec![0u8; mask_bytes];
5862 let mut record_out = vec![0u8; mask_bytes];
5863 adaptive
5864 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
5865 .expect("adaptive mask");
5866 #[cfg(not(feature = "bench-internals"))]
5867 let MatcherBackend::Incremental(incremental) = &mut full.backend;
5868 #[cfg(feature = "bench-internals")]
5869 let incremental = match &mut full.backend {
5870 MatcherBackend::Incremental(incremental) => incremental,
5871 MatcherBackend::Reference(_) => panic!("expected incremental matcher"),
5872 };
5873 walk_full_trie_mask_for_test(
5874 incremental,
5875 &vocabulary,
5876 &trie,
5877 &mut full_out,
5878 program.limits,
5879 )
5880 .expect("full trie mask");
5881 records
5882 .write_record_mask_le_bytes_into(
5883 vocabulary.mask_vocab_size(),
5884 vocabulary.iter_records(),
5885 &mut record_out,
5886 )
5887 .expect("record mask");
5888 assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, "adaptive/full");
5889 assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, "adaptive/record");
5890 }
5891 }
5892 }
5893
5894 #[test]
5895 fn slice_proof_cache_is_exact_bounded_and_evicts() {
5896 let mut cache = SliceProofCache::default();
5897 let bytes = 2 * std::mem::size_of::<SliceProofEntry>();
5898 let key = |tag, state| SliceProofKey {
5899 vocabulary: VocabFingerprint::from_bytes([tag; 32]),
5900 node: NodeId(3),
5901 state: StateId(state),
5902 body_prefix: false,
5903 reject_invalid: false,
5904 };
5905 cache.insert(key(1, 4), true, bytes);
5906 cache.insert(key(2, 4), false, bytes);
5907 assert_eq!(cache.get(key(1, 4)), Some(true));
5908 assert_eq!(cache.get(key(2, 4)), Some(false));
5909 assert_eq!(cache.get(key(1, 5)), None);
5910 assert_eq!(
5911 cache.get(SliceProofKey {
5912 body_prefix: true,
5913 ..key(1, 4)
5914 }),
5915 None
5916 );
5917 cache.insert(key(3, 4), true, bytes);
5918 assert_eq!(cache.entries.len(), 2);
5919 assert_eq!(cache.get(key(1, 4)), None);
5920 let mut disabled = SliceProofCache::default();
5921 disabled.insert(key(1, 4), true, 0);
5922 assert!(disabled.entries.is_empty());
5923 }
5924
5925 #[cfg(feature = "bench-internals")]
5926 #[test]
5927 fn slice_proof_cache_measurements_track_hits_misses_and_entries() {
5928 let mut cache = SliceProofCache::default();
5929 let key = SliceProofKey {
5930 vocabulary: VocabFingerprint::from_bytes([1; 32]),
5931 node: NodeId(3),
5932 state: StateId(4),
5933 body_prefix: false,
5934 reject_invalid: false,
5935 };
5936 cache.insert(key, true, std::mem::size_of::<SliceProofEntry>());
5937 cache.reset_measurements();
5938 assert_eq!(cache.get(key), Some(true));
5939 assert_eq!(
5940 cache.get(SliceProofKey {
5941 state: StateId(5),
5942 ..key
5943 }),
5944 None
5945 );
5946 assert_eq!(
5947 cache.measurements(),
5948 SliceProofMetrics {
5949 hits: 1,
5950 misses: 1,
5951 entries: 1,
5952 }
5953 );
5954 }
5955
5956 #[cfg(feature = "bench-internals")]
5957 #[test]
5958 fn near_limit_scalar_escape_boundary_matrix_matches_all_routes_and_advances() {
5959 struct TokenCase {
5960 name: &'static str,
5961 bytes: Vec<u8>,
5962 decoded_key_bytes: Option<usize>,
5963 string_content_bytes: usize,
5964 }
5965
5966 let cases = vec![
5967 TokenCase {
5968 name: "ascii",
5969 bytes: b"a".to_vec(),
5970 decoded_key_bytes: Some(1),
5971 string_content_bytes: 1,
5972 },
5973 TokenCase {
5974 name: "utf8-2",
5975 bytes: "¢".as_bytes().to_vec(),
5976 decoded_key_bytes: Some(2),
5977 string_content_bytes: 2,
5978 },
5979 TokenCase {
5980 name: "utf8-3",
5981 bytes: "€".as_bytes().to_vec(),
5982 decoded_key_bytes: Some(3),
5983 string_content_bytes: 3,
5984 },
5985 TokenCase {
5986 name: "utf8-4",
5987 bytes: "😀".as_bytes().to_vec(),
5988 decoded_key_bytes: Some(4),
5989 string_content_bytes: 4,
5990 },
5991 TokenCase {
5992 name: "combining-mark",
5993 bytes: "\u{301}".as_bytes().to_vec(),
5994 decoded_key_bytes: Some(2),
5995 string_content_bytes: 2,
5996 },
5997 TokenCase {
5998 name: "json-escape",
5999 bytes: br#"\n"#.to_vec(),
6000 decoded_key_bytes: Some(1),
6001 string_content_bytes: 2,
6002 },
6003 TokenCase {
6004 name: "surrogate-pair",
6005 bytes: br#"\uD83D\uDE00"#.to_vec(),
6006 decoded_key_bytes: Some(4),
6007 string_content_bytes: 12,
6008 },
6009 TokenCase {
6010 name: "partial-utf8",
6011 bytes: vec![0xf0, 0x9f],
6012 decoded_key_bytes: None,
6013 string_content_bytes: 2,
6014 },
6015 TokenCase {
6016 name: "cross-quote",
6017 bytes: b"a\"".to_vec(),
6018 decoded_key_bytes: Some(1),
6019 string_content_bytes: 1,
6020 },
6021 TokenCase {
6022 name: "cross-quote-colon",
6023 bytes: br#"a":"#.to_vec(),
6024 decoded_key_bytes: Some(1),
6025 string_content_bytes: 1,
6026 },
6027 TokenCase {
6028 name: "cross-quote-colon-value",
6029 bytes: br#"a":0"#.to_vec(),
6030 decoded_key_bytes: Some(1),
6031 string_content_bytes: 1,
6032 },
6033 ];
6034 let key_program = program(r#"{"type":"object","additionalProperties":true}"#);
6035 let any_string_program = program(r#"{"type":"object","unevaluatedProperties":true}"#);
6036 let canonical_program =
6037 program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6038 let dependent_program = program(
6039 r#"{"type":"object","dependentSchemas":{"pa":{"type":"object"}},"additionalProperties":true}"#,
6040 );
6041 let property_names_program = program(
6042 r#"{"type":"object","propertyNames":{"type":"string","maxLength":64},"additionalProperties":true}"#,
6043 );
6044 let key_prefix = br#"{"p"#;
6045 let string_prefix = br#"{"s":"p"#;
6046 let canonical_prefix = br#"["p"#;
6047
6048 for case in cases {
6049 let vocabulary = VocabularyHandle::new(Arc::new(
6050 build_vocabulary(7, FxHashMap::from_iter([(case.bytes.clone(), vec![0])])).unwrap(),
6051 ))
6052 .unwrap();
6053 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6054
6055 let document_exact = key_prefix.len() + case.bytes.len();
6056 for limit in around(document_exact) {
6057 assert_near_limit_three_routes(
6058 &key_program,
6059 &vocabulary,
6060 &trie,
6061 key_prefix,
6062 NearLimitTuning::Document(limit),
6063 &format!("max_document_bytes/{}/{limit}", case.name),
6064 );
6065 }
6066
6067 if let Some(decoded) = case.decoded_key_bytes {
6068 let key_exact = 1 + decoded;
6069 for limit in around(key_exact) {
6070 assert_near_limit_three_routes(
6071 &key_program,
6072 &vocabulary,
6073 &trie,
6074 key_prefix,
6075 NearLimitTuning::Key(limit),
6076 &format!("max_key_bytes/{}/{limit}", case.name),
6077 );
6078 }
6079 }
6080
6081 let string_exact = 1 + case.string_content_bytes;
6082 for limit in around(string_exact) {
6083 assert_near_limit_three_routes(
6084 &any_string_program,
6085 &vocabulary,
6086 &trie,
6087 string_prefix,
6088 NearLimitTuning::String(limit),
6089 &format!("max_string_bytes/{}/{limit}", case.name),
6090 );
6091 }
6092
6093 let undo_exact = minimum_successful_limit(
6094 &key_program,
6095 &vocabulary,
6096 &trie,
6097 key_prefix,
6098 StructuredLimits::default().max_undo_entries,
6099 NearLimitTuning::Undo,
6100 );
6101 for limit in around(undo_exact) {
6102 assert_near_limit_three_routes(
6103 &key_program,
6104 &vocabulary,
6105 &trie,
6106 key_prefix,
6107 NearLimitTuning::Undo(limit),
6108 &format!("max_undo_entries/{}/{limit}", case.name),
6109 );
6110 }
6111
6112 let session_exact = minimum_successful_limit(
6113 &key_program,
6114 &vocabulary,
6115 &trie,
6116 key_prefix,
6117 StructuredLimits::default().max_session_bytes,
6118 NearLimitTuning::Session,
6119 );
6120 for limit in around(session_exact) {
6121 assert_near_limit_three_routes(
6122 &key_program,
6123 &vocabulary,
6124 &trie,
6125 key_prefix,
6126 NearLimitTuning::Session(limit),
6127 &format!("max_session_bytes/{}/{limit}", case.name),
6128 );
6129 }
6130
6131 let canonical_exact = minimum_successful_limit(
6132 &canonical_program,
6133 &vocabulary,
6134 &trie,
6135 canonical_prefix,
6136 StructuredLimits::default().max_unique_canonical_bytes,
6137 NearLimitTuning::Canonical,
6138 );
6139 for limit in around(canonical_exact) {
6140 assert_near_limit_three_routes(
6141 &canonical_program,
6142 &vocabulary,
6143 &trie,
6144 canonical_prefix,
6145 NearLimitTuning::Canonical(limit),
6146 &format!("canonical_uniqueItems_storage/{}/{limit}", case.name),
6147 );
6148 }
6149
6150 for (storage_name, storage_program) in [
6151 ("dependent_observer_storage", &dependent_program),
6152 ("propertyNames_cursor_storage", &property_names_program),
6153 ] {
6154 let exact = minimum_successful_limit(
6155 storage_program,
6156 &vocabulary,
6157 &trie,
6158 key_prefix,
6159 StructuredLimits::default().max_session_bytes,
6160 NearLimitTuning::Session,
6161 );
6162 for limit in around(exact) {
6163 assert_near_limit_three_routes(
6164 storage_program,
6165 &vocabulary,
6166 &trie,
6167 key_prefix,
6168 NearLimitTuning::Session(limit),
6169 &format!("{storage_name}/{}/{limit}", case.name),
6170 );
6171 }
6172 }
6173 }
6174 }
6175
6176 #[cfg(feature = "bench-internals")]
6177 #[test]
6178 fn active_unique_items_canonical_builder_forces_exact_fallback() {
6179 let program = program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6180 let vocabulary = VocabularyHandle::new(Arc::new(
6181 build_vocabulary(3, FxHashMap::from_iter([(b"abc".to_vec(), vec![0])])).unwrap(),
6182 ))
6183 .unwrap();
6184 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6185 let mut matcher = program.new_matcher().unwrap();
6186 assert!(matcher.advance(br#"["p"#).unwrap());
6187 {
6188 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
6189 panic!("incremental matcher")
6190 };
6191 let (selected, _) = select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6192 assert!(
6193 selected.is_none(),
6194 "an active canonical builder must conservatively use the exact walker"
6195 );
6196 }
6197 let mut adaptive = [0; 4];
6198 let mut full = [0; 4];
6199 let mut records = [0; 4];
6200 matcher
6201 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
6202 .unwrap();
6203 matcher
6204 .write_full_trie_mask_for_bench(&vocabulary, &trie, &mut full)
6205 .unwrap();
6206 matcher
6207 .write_record_mask_le_bytes_into(
6208 vocabulary.mask_vocab_size(),
6209 vocabulary.iter_records(),
6210 &mut records,
6211 )
6212 .unwrap();
6213 assert_eq!(adaptive, full);
6214 assert_eq!(adaptive, records);
6215 assert_incremental_accounting(&matcher, "canonical exact fallback");
6216 }
6217
6218 #[test]
6219 fn sliced_key_resource_errors_match_record_scan_and_clear_output() {
6220 let schemas = [
6221 r#"{"type":"object","additionalProperties":true}"#,
6222 r#"{"type":"object","unevaluatedProperties":true}"#,
6223 r#"{"allOf":[{"type":"object"},{"additionalProperties":true}]}"#,
6224 r#"{"type":"array","uniqueItems":true,"items":{"type":"object"}}"#,
6225 r#"{"type":"array","contains":{"type":"object"},"items":{"type":"object"}}"#,
6226 ];
6227 for schema in schemas {
6228 let prefix: &[u8] = if schema.contains("array") {
6229 br#"[{"a"#
6230 } else {
6231 br#"{"a"#
6232 };
6233 for budget in [2, 3, 4] {
6234 for document_limit in [false, true] {
6235 let mut limits = StructuredLimits::default();
6236 if document_limit {
6237 limits.max_document_bytes = prefix.len() + budget - 1;
6238 } else {
6239 limits.max_key_bytes = budget;
6240 }
6241 let program =
6242 StructuredProgram::compile_with_limits(ir(schema), limits).unwrap();
6243 let mut tokens = FxHashMap::default();
6244 tokens.insert(b"abc".to_vec(), vec![0, 1]);
6245 let vocabulary =
6246 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap()))
6247 .unwrap();
6248 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6249 let mut fast = program.new_matcher().unwrap();
6250 let mut scan = program.new_matcher().unwrap();
6251 assert!(fast.advance(prefix).unwrap());
6252 assert!(scan.advance(prefix).unwrap());
6253 let mut fast_out = [0xa5; 4];
6254 let mut scan_out = [0xa5; 4];
6255 let observed =
6256 fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6257 let expected = scan.write_record_mask_le_bytes_into(
6258 vocabulary.mask_vocab_size(),
6259 vocabulary.iter_records(),
6260 &mut scan_out,
6261 );
6262 assert_eq!(
6263 observed.is_ok(),
6264 expected.is_ok(),
6265 "{schema}, budget={budget}, document={document_limit}"
6266 );
6267 assert_eq!(fast_out, scan_out);
6268 if observed.is_err() {
6269 assert_eq!(fast_out, [0; 4]);
6270 } else if fast_out[0] & 1 != 0 {
6271 assert!(fast.advance(b"abc").unwrap());
6272 }
6273 }
6274 }
6275 }
6276 }
6277
6278 #[test]
6279 fn wrapped_any_string_byte_limits_match_record_errors_and_advance() {
6280 for limit in [3, 4, 5] {
6281 let limits = StructuredLimits {
6282 max_string_bytes: limit,
6283 ..StructuredLimits::default()
6284 };
6285 let program = StructuredProgram::compile_with_limits(
6286 ir(r#"{"type":"object","unevaluatedProperties":true}"#),
6287 limits,
6288 )
6289 .unwrap();
6290 let mut tokens = FxHashMap::default();
6291 tokens.insert(b"abc".to_vec(), vec![0, 1]);
6292 let vocabulary =
6293 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6294 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6295 let mut fast = program.new_matcher().unwrap();
6296 let mut scan = program.new_matcher().unwrap();
6297 assert!(fast.advance(br#"{"s":"a"#).unwrap());
6298 assert!(scan.advance(br#"{"s":"a"#).unwrap());
6299 let mut fast_out = [0xa5; 4];
6300 let mut scan_out = [0xa5; 4];
6301 let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6302 let expected = scan.write_record_mask_le_bytes_into(
6303 vocabulary.mask_vocab_size(),
6304 vocabulary.iter_records(),
6305 &mut scan_out,
6306 );
6307 assert_eq!(observed.is_ok(), expected.is_ok());
6308 assert_eq!(observed.is_ok(), limit >= 4);
6309 assert_eq!(fast_out, scan_out);
6310 if observed.is_ok() {
6311 assert!(fast_out[0] & 1 != 0);
6312 assert!(fast.advance(b"abc").unwrap());
6313 } else {
6314 assert_eq!(fast_out, [0; 4]);
6315 assert!(!fast.is_dead());
6316 }
6317 }
6318 }
6319
6320 #[test]
6321 fn unique_item_canonical_limits_match_record_masks_and_advance() {
6322 let mut tokens = FxHashMap::default();
6323 tokens.insert(b"abc".to_vec(), vec![0, 1]);
6324 let vocabulary =
6325 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6326 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6327 let mut observed_success = false;
6328 let mut observed_limit = false;
6329 for canonical_limit in [16, 32, 64, 128, 1024] {
6330 let program =
6331 program(r#"{"type":"array","uniqueItems":true,"items":{"type":"string"}}"#);
6332 let mut fast = program.new_matcher().unwrap();
6333 let mut scan = program.new_matcher().unwrap();
6334 assert!(fast.advance(b"[\"").unwrap());
6335 assert!(scan.advance(b"[\"").unwrap());
6336 match &mut fast.backend {
6337 MatcherBackend::Incremental(inner) => {
6338 inner
6339 .state
6340 .set_unique_canonical_limit_for_test(canonical_limit);
6341 }
6342 #[cfg(feature = "bench-internals")]
6343 MatcherBackend::Reference(_) => panic!("incremental matcher"),
6344 }
6345 match &mut scan.backend {
6346 MatcherBackend::Incremental(inner) => {
6347 inner
6348 .state
6349 .set_unique_canonical_limit_for_test(canonical_limit);
6350 }
6351 #[cfg(feature = "bench-internals")]
6352 MatcherBackend::Reference(_) => panic!("incremental matcher"),
6353 }
6354 let mut fast_out = [0xa5; 4];
6355 let mut scan_out = [0xa5; 4];
6356 let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6357 let expected = scan.write_record_mask_le_bytes_into(
6358 vocabulary.mask_vocab_size(),
6359 vocabulary.iter_records(),
6360 &mut scan_out,
6361 );
6362 assert_eq!(
6363 observed.is_ok(),
6364 expected.is_ok(),
6365 "limit={canonical_limit}"
6366 );
6367 assert_eq!(fast_out, scan_out, "limit={canonical_limit}");
6368 if observed.is_ok() && fast_out[0] & 1 != 0 {
6369 observed_success = true;
6370 assert!(fast.advance(b"abc").unwrap());
6371 }
6372 if observed.is_err() {
6373 observed_limit = true;
6374 assert_eq!(fast_out, [0; 4]);
6375 assert!(!fast.is_dead());
6376 }
6377 }
6378 assert!(observed_success);
6379 assert!(observed_limit);
6380 }
6381
6382 #[test]
6383 fn session_limits_match_record_masks_and_advance() {
6384 let mut tokens = FxHashMap::default();
6385 tokens.insert(b"abcdefghijklmnopqrstuvwxyz0123456789".to_vec(), vec![0, 1]);
6386 let vocabulary =
6387 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
6388 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6389 let program = program(r#"{"type":"object","additionalProperties":true}"#);
6390 let mut observed_success = false;
6391 let mut observed_limit = false;
6392 for additional in [0, 1, 32, 65_536] {
6393 let mut fast = program.new_matcher().unwrap();
6394 let mut scan = program.new_matcher().unwrap();
6395 assert!(fast.advance(b"{\"").unwrap());
6396 assert!(scan.advance(b"{\"").unwrap());
6397 let limit = fast
6398 .retained_session_bytes()
6399 .checked_add(additional)
6400 .unwrap();
6401 match &mut fast.backend {
6402 MatcherBackend::Incremental(inner) => {
6403 inner.state.set_session_limit_for_test(limit);
6404 }
6405 #[cfg(feature = "bench-internals")]
6406 MatcherBackend::Reference(_) => panic!("incremental matcher"),
6407 }
6408 match &mut scan.backend {
6409 MatcherBackend::Incremental(inner) => {
6410 inner.state.set_session_limit_for_test(limit);
6411 }
6412 #[cfg(feature = "bench-internals")]
6413 MatcherBackend::Reference(_) => panic!("incremental matcher"),
6414 }
6415 let mut fast_out = [0xa5; 4];
6416 let mut scan_out = [0xa5; 4];
6417 let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
6418 let expected = scan.write_record_mask_le_bytes_into(
6419 vocabulary.mask_vocab_size(),
6420 vocabulary.iter_records(),
6421 &mut scan_out,
6422 );
6423 assert_eq!(
6424 observed.is_ok(),
6425 expected.is_ok(),
6426 "additional={additional}"
6427 );
6428 assert_eq!(fast_out, scan_out, "additional={additional}");
6429 if observed.is_ok() && fast_out[0] & 1 != 0 {
6430 observed_success = true;
6431 assert!(fast
6432 .advance(b"abcdefghijklmnopqrstuvwxyz0123456789")
6433 .unwrap());
6434 }
6435 if observed.is_err() {
6436 observed_limit = true;
6437 assert_eq!(fast_out, [0; 4]);
6438 assert!(!fast.is_dead());
6439 }
6440 }
6441 assert!(observed_success);
6442 assert!(observed_limit);
6443 }
6444
6445 #[test]
6446 #[cfg(feature = "bench-internals")]
6447 fn bounded_string_slice_fires_and_warm_retention_stabilizes() {
6448 let program =
6449 program(r#"{"type":"object","properties":{"s":{"type":"string","maxLength":3}}}"#);
6450 let tokens = (0..100u32)
6451 .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6452 .chain([
6453 (b"a".to_vec(), vec![100]),
6454 (b"\"".to_vec(), vec![101]),
6455 ("\u{1f600}".as_bytes().to_vec(), vec![102]),
6456 ])
6457 .collect();
6458 let vocabulary =
6459 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6460 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6461 let mut matcher = program.new_matcher().unwrap();
6462 assert!(matcher.advance(br#"{"s":"a"#).unwrap());
6463 let mut output = [0; 16];
6464 matcher
6465 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6466 .unwrap();
6467 let retained = match &matcher.backend {
6468 MatcherBackend::Incremental(inner) => inner.state.retained_bytes(),
6469 _ => panic!("incremental program"),
6470 };
6471 for _ in 0..8 {
6472 matcher
6473 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6474 .unwrap();
6475 assert!(
6476 matcher.mask_work_metrics_for_bench().trie_nodes
6477 < trie.trie().node_count() as u64 / 2
6478 );
6479 let MatcherBackend::Incremental(inner) = &matcher.backend else {
6480 panic!("incremental program")
6481 };
6482 assert_eq!(inner.state.retained_bytes(), retained);
6483 }
6484 }
6485
6486 #[test]
6487 #[cfg(feature = "bench-internals")]
6488 fn wrapper_slices_fire_and_match_both_exact_walks_after_warmup() {
6489 let cases = [
6490 (
6491 r#"{"type":"object","properties":{"mode":{"type":"string"},"text":{"type":"string"}},"dependentSchemas":{"mode":{"required":["text"],"properties":{"text":{"maxLength":12}}}}}"#,
6492 br#"{"mo"#.as_slice(),
6493 ),
6494 (
6495 r#"{"type":"object","properties":{"mode":{"type":"string"},"text":{"type":"string"}},"dependentSchemas":{"mode":{"required":["text"],"properties":{"text":{"maxLength":12}}}}}"#,
6496 br#"{"mode":"text","text":"rea"#.as_slice(),
6497 ),
6498 (
6499 r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6500 br#"{"owner":"ops","team":""#.as_slice(),
6501 ),
6502 (
6503 r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6504 br#"{"owner":"ops","team":"core"#.as_slice(),
6505 ),
6506 (
6507 r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6508 br#"{"owner":"o"#.as_slice(),
6509 ),
6510 (
6511 r#"{"type":"object","properties":{"owner":{"type":"string","maxLength":8}},"allOf":[{"properties":{"team":{"type":"string","maxLength":8}}}],"unevaluatedProperties":false}"#,
6512 br#"{"owner":"1234567"#.as_slice(),
6513 ),
6514 (
6515 r#"{"type":"object","unevaluatedProperties":true}"#,
6516 br#"{"a"#.as_slice(),
6517 ),
6518 (
6519 r#"{"allOf":[{"type":"object"},{"additionalProperties":true}]}"#,
6520 br#"{"a"#,
6521 ),
6522 (
6523 r##"{"$dynamicAnchor":"node","type":"object","properties":{"child":{"$dynamicRef":"#node"}},"additionalProperties":true}"##,
6524 br#"{"child":{"a"#,
6525 ),
6526 (
6527 r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
6528 br#"["rea"#,
6529 ),
6530 (
6531 r#"{"type":"array","items":{"type":"string"},"contains":{"const":"ok"},"minContains":2,"maxItems":2}"#,
6532 br#"["o"#,
6533 ),
6534 (
6535 r#"{"type":"array","contains":{"type":"string"},"unevaluatedItems":false}"#,
6536 br#"["rea"#,
6537 ),
6538 (
6539 r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"unevaluatedItems":false}"#,
6540 br#"["#,
6541 ),
6542 (
6543 r#"{"type":"array","prefixItems":[{"type":"string","maxLength":12}],"contains":{"type":"string","pattern":"^ok"},"unevaluatedItems":false}"#,
6544 br#"["#,
6545 ),
6546 ];
6547 let tokens = (0..100u32)
6548 .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6549 .chain([
6550 (b"\"".to_vec(), vec![100]),
6551 (br#"\u0061"#.to_vec(), vec![101]),
6552 (vec![0xf0, 0x9f], vec![102]),
6553 ])
6554 .collect();
6555 let vocabulary =
6556 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6557 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6558 for (schema, prefix) in cases {
6559 let program = program(schema);
6560 let mut matcher = program.new_matcher().unwrap();
6561 assert!(matcher.advance(prefix).unwrap());
6562 let mut fast = [0; 16];
6563 let mut full = [0; 16];
6564 let mut scan = [0; 16];
6565 matcher
6566 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
6567 .unwrap();
6568 assert!(
6569 matcher.mask_work_metrics_for_bench().trie_nodes
6570 < trie.trie().node_count() as u64 / 2,
6571 "cold {schema}"
6572 );
6573 let cold = fast;
6574 matcher
6575 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
6576 .unwrap();
6577 assert!(
6578 matcher.mask_work_metrics_for_bench().trie_nodes
6579 < trie.trie().node_count() as u64 / 2,
6580 "{schema}"
6581 );
6582 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
6583 panic!("incremental program")
6584 };
6585 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
6586 .unwrap();
6587 matcher
6588 .write_record_mask_le_bytes_into(
6589 vocabulary.mask_vocab_size(),
6590 vocabulary.iter_records(),
6591 &mut scan,
6592 )
6593 .unwrap();
6594 assert_eq!(fast, full, "{schema}");
6595 assert_eq!(fast, scan, "{schema}");
6596 assert_eq!(cold, full, "cold {schema}");
6597 }
6598 }
6599
6600 #[test]
6601 #[cfg(feature = "bench-internals")]
6602 fn safe_interior_array_certificate_matrix_matches_all_routes_and_advances() {
6603 #[derive(Clone, Copy)]
6604 struct Case {
6605 name: &'static str,
6606 schema: &'static str,
6607 prefix: &'static [u8],
6608 fast: bool,
6609 }
6610 let cases = [
6611 Case {
6612 name: "empty-array-structural-position",
6613 schema: r#"{"type":"array","items":{"type":"string"}}"#,
6614 prefix: b"[",
6615 fast: false,
6616 },
6617 Case {
6618 name: "prefix-item",
6619 schema: r#"{"prefixItems":[{"type":"string"}],"items":false}"#,
6620 prefix: br#"["pre"#,
6621 fast: true,
6622 },
6623 Case {
6624 name: "prefix-to-tail",
6625 schema: r#"{"prefixItems":[{"type":"string"}],"items":{"type":"string"}}"#,
6626 prefix: br#"["a","ta"#,
6627 fast: true,
6628 },
6629 Case {
6630 name: "nested-array-item",
6631 schema: r#"{"type":"array","items":{"type":"array","items":{"type":"string"}}}"#,
6632 prefix: br#"[["ne"#,
6633 fast: true,
6634 },
6635 Case {
6636 name: "contains-observer",
6637 schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"minContains":1}"#,
6638 prefix: br#"["ok"#,
6639 fast: true,
6640 },
6641 Case {
6642 name: "unevaluated-items-observer",
6643 schema: r#"{"prefixItems":[true],"contains":{"type":"string"},"unevaluatedItems":false}"#,
6644 prefix: br#"[1,"ok"#,
6645 fast: true,
6646 },
6647 Case {
6648 name: "maximum-items-interior",
6649 schema: r#"{"type":"array","items":{"type":"string"},"maxItems":2}"#,
6650 prefix: br#"["a"#,
6651 fast: true,
6652 },
6653 Case {
6654 name: "canonical-unique-items-fallback",
6655 schema: r#"{"type":"array","items":{"type":"string"},"uniqueItems":true}"#,
6656 prefix: br#"["a"#,
6657 fast: false,
6658 },
6659 ];
6660 let mut tokens = (0..100u32)
6661 .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6662 .collect::<FxHashMap<_, _>>();
6663 for (id, bytes) in [
6664 (100, b"\"".as_slice()),
6665 (101, b"\","),
6666 (102, b"\"]"),
6667 (103, b","),
6668 (104, b"]"),
6669 (105, br#"\u0061"#),
6670 (106, b"\xf0\x9f"),
6671 ] {
6672 tokens.insert(bytes.to_vec(), vec![id]);
6673 }
6674 let vocabulary =
6675 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6676 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6677 let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
6678
6679 for case in cases {
6680 let program = program(case.schema);
6681 let mut adaptive = program.new_matcher().unwrap();
6682 let mut full = program.new_matcher().unwrap();
6683 let mut record = program.new_matcher().unwrap();
6684 for matcher in [&mut adaptive, &mut full, &mut record] {
6685 assert!(matcher.advance(case.prefix).unwrap(), "{}", case.name);
6686 }
6687 {
6688 let MatcherBackend::Incremental(inner) = &mut adaptive.backend else {
6689 panic!("incremental matcher")
6690 };
6691 let (selected, _) =
6692 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6693 assert_eq!(selected.is_some(), case.fast, "{}", case.name);
6694 }
6695 let mut adaptive_out = vec![0; output_len];
6696 let mut full_out = vec![0; output_len];
6697 let mut record_out = vec![0; output_len];
6698 adaptive
6699 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6700 .unwrap();
6701 if case.fast {
6702 assert!(
6703 adaptive.mask_work_metrics_for_bench().trie_nodes
6704 < trie.trie().node_count() as u64,
6705 "{} fast path did not fire",
6706 case.name
6707 );
6708 }
6709 {
6710 let MatcherBackend::Incremental(inner) = &mut full.backend else {
6711 panic!("incremental matcher")
6712 };
6713 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6714 walk_full_trie_mask_for_test(
6715 inner,
6716 &vocabulary,
6717 &trie,
6718 &mut full_out,
6719 program.limits,
6720 )
6721 .unwrap();
6722 }
6723 {
6724 let MatcherBackend::Incremental(inner) = &mut record.backend else {
6725 panic!("incremental matcher")
6726 };
6727 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6728 }
6729 record
6730 .write_record_mask_le_bytes_into(
6731 vocabulary.mask_vocab_size(),
6732 vocabulary.iter_records(),
6733 &mut record_out,
6734 )
6735 .unwrap();
6736 assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, case.name);
6737 assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, case.name);
6738
6739 for (candidate, ids) in vocabulary.iter_records() {
6740 if !ids
6741 .iter()
6742 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6743 {
6744 continue;
6745 }
6746 let mut equivalent = program.new_matcher().unwrap();
6747 assert!(equivalent.advance(case.prefix).unwrap(), "{}", case.name);
6748 let mut prepared = vec![0; output_len];
6749 equivalent
6750 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6751 .unwrap();
6752 assert_eq!(prepared, adaptive_out, "{}", case.name);
6753 assert!(
6754 equivalent.advance(candidate).unwrap(),
6755 "{} emitted {ids:?}",
6756 case.name
6757 );
6758 assert_incremental_accounting(&equivalent, case.name);
6759 }
6760 for matcher in [&adaptive, &full, &record] {
6761 assert_incremental_accounting(matcher, case.name);
6762 }
6763 }
6764
6765 let tail = cases
6766 .iter()
6767 .find(|case| case.name == "prefix-to-tail")
6768 .unwrap();
6769 let program = program(tail.schema);
6770 let mut matcher = program.new_matcher().unwrap();
6771 assert!(matcher.advance(tail.prefix).unwrap());
6772 let mut output = vec![0; output_len];
6773 matcher
6774 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
6775 .unwrap();
6776 assert_ne!(output[101 / 8] & (1 << (101 % 8)), 0);
6777 }
6778
6779 #[test]
6780 #[cfg(feature = "bench-internals")]
6781 fn combined_unevaluated_certificate_matches_all_routes_and_advances() {
6782 let schema = r#"{
6783 "type":"object",
6784 "properties":{
6785 "mode":{"enum":["fast","slow"]},
6786 "count":{"type":"integer","minimum":0,"maximum":9},
6787 "code":{"type":"string","pattern":"^[A-Z]{2}[0-9]{2}$"},
6788 "items":{"type":"array","items":{"type":"string"},"contains":{"const":"required"},"uniqueItems":true,"minItems":1,"maxItems":3},
6789 "meta":{"type":"object","propertyNames":{"pattern":"^[a-z]+$"},"patternProperties":{"^[a-z]+$":{"type":"string"}},"additionalProperties":false}
6790 },
6791 "required":["mode","count","code","items","meta"],
6792 "dependentRequired":{"mode":["code"]},
6793 "if":{"properties":{"mode":{"const":"fast"}},"required":["mode"]},
6794 "then":{"properties":{"count":{"maximum":5}}},
6795 "else":{"properties":{"count":{"minimum":6}}},
6796 "allOf":[
6797 {"if":{"properties":{"code":{"pattern":"^OK"}},"required":["code"]},"then":{"properties":{"meta":{"minProperties":1}}},"else":{"properties":{"meta":{"maxProperties":2}}}},
6798 {"if":{"properties":{"count":{"maximum":5}},"required":["count"]},"then":{"properties":{"items":{"maxItems":3}}},"else":{"properties":{"items":{"minItems":2}}}}
6799 ],
6800 "unevaluatedProperties":false
6801 }"#;
6802 let tokens = (0..100u32)
6803 .map(|id| (format!("word{id}").into_bytes(), vec![id]))
6804 .chain([(b"\"".to_vec(), vec![100])])
6805 .collect();
6806 let vocabulary =
6807 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
6808 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6809 let program = program(schema);
6810 let prefix = br#"{"mo"#;
6811 let mut adaptive = program.new_matcher().unwrap();
6812 let mut full = program.new_matcher().unwrap();
6813 let mut record = program.new_matcher().unwrap();
6814 for matcher in [&mut adaptive, &mut full, &mut record] {
6815 assert!(matcher.advance(prefix).unwrap());
6816 }
6817 let mut adaptive_out = [0; 16];
6818 let mut full_out = [0; 16];
6819 let mut record_out = [0; 16];
6820 adaptive
6821 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6822 .unwrap();
6823 assert!(
6824 adaptive.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64 / 2,
6825 "combined unevaluated certificate did not activate"
6826 );
6827 {
6828 let MatcherBackend::Incremental(inner) = &mut full.backend else {
6829 panic!("incremental matcher")
6830 };
6831 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6832 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full_out, program.limits)
6833 .unwrap();
6834 }
6835 {
6836 let MatcherBackend::Incremental(inner) = &mut record.backend else {
6837 panic!("incremental matcher")
6838 };
6839 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
6840 }
6841 record
6842 .write_record_mask_le_bytes_into(
6843 vocabulary.mask_vocab_size(),
6844 vocabulary.iter_records(),
6845 &mut record_out,
6846 )
6847 .unwrap();
6848 assert_mask_bytes_equal(
6849 &adaptive_out,
6850 &full_out,
6851 &vocabulary,
6852 "combined unevaluated",
6853 );
6854 assert_mask_bytes_equal(
6855 &adaptive_out,
6856 &record_out,
6857 &vocabulary,
6858 "combined unevaluated",
6859 );
6860
6861 let first = adaptive_out;
6862 adaptive
6863 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6864 .unwrap();
6865 assert_eq!(adaptive_out, first, "speculative rollback changed the mask");
6866 for (candidate, ids) in vocabulary.iter_records() {
6867 if !ids
6868 .iter()
6869 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6870 {
6871 continue;
6872 }
6873 let mut equivalent = program.new_matcher().unwrap();
6874 assert!(equivalent.advance(prefix).unwrap());
6875 let mut prepared = [0; 16];
6876 equivalent
6877 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6878 .unwrap();
6879 assert_eq!(prepared, adaptive_out);
6880 assert!(equivalent.advance(candidate).unwrap(), "emitted {ids:?}");
6881 assert_incremental_accounting(&equivalent, "combined unevaluated advance");
6882 }
6883 for matcher in [&adaptive, &full, &record] {
6884 assert_incremental_accounting(matcher, "combined unevaluated route");
6885 }
6886 }
6887
6888 #[cfg(feature = "bench-internals")]
6889 #[test]
6890 fn lexical_prefix_walk_matches_exact_routes_and_every_emitted_token_advances() {
6891 let tokens = FxHashMap::from_iter([
6892 (b"abc\"".to_vec(), vec![0]),
6893 (b"abc\"}".to_vec(), vec![1]),
6894 (b"ab\\u0063\"}".to_vec(), vec![2]),
6895 ("é\"}".as_bytes().to_vec(), vec![3]),
6896 (vec![0xf0, 0x9f], vec![4]),
6897 (vec![0xf0, 0x9f, b'"'], vec![5]),
6898 (b"ab\n\"".to_vec(), vec![6]),
6899 (b"\\\"still".to_vec(), vec![7]),
6900 (b"\"}".to_vec(), vec![8]),
6901 ]);
6902 let vocabulary =
6903 VocabularyHandle::new(Arc::new(build_vocabulary(31, tokens).unwrap())).unwrap();
6904 let trie = TrieCache::new().bind(&vocabulary).unwrap();
6905 let program = program(
6906 r#"{"type":"object","properties":{"note":{"type":"string"}},"required":["note"]}"#,
6907 );
6908 let prefix = br#"{"note":""#;
6909 let mut adaptive = program.new_matcher().unwrap();
6910 let mut full = program.new_matcher().unwrap();
6911 let mut record = program.new_matcher().unwrap();
6912 for matcher in [&mut adaptive, &mut full, &mut record] {
6913 assert!(matcher.advance(prefix).unwrap());
6914 }
6915 if let MatcherBackend::Incremental(inner) = &mut adaptive.backend {
6916 let mark = inner.state.checkpoint();
6917 for &byte in b"abc\"" {
6918 assert!(inner.state.try_push_byte_for_mask(byte).unwrap());
6919 }
6920 assert!(inner.state.next_byte_is_lexically_impossible(b'a'));
6921 assert!(!inner.state.next_byte_is_lexically_impossible(b'}'));
6922 inner.state.rollback(mark);
6923 }
6924 let mut adaptive_out = [0u8; 4];
6925 let mut full_out = [0u8; 4];
6926 let mut record_out = [0u8; 4];
6927 adaptive
6928 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6929 .unwrap();
6930 let metrics = adaptive.mask_work_metrics_for_bench();
6931 assert!(metrics.local_dfa_transitions > 0);
6932 let (event_capacity, path_capacity) = match &adaptive.backend {
6933 MatcherBackend::Incremental(inner) => {
6934 (inner.events.capacity(), inner.lexical_path.capacity())
6935 }
6936 MatcherBackend::Reference(_) => panic!("incremental matcher"),
6937 };
6938 let MatcherBackend::Incremental(inner) = &mut full.backend else {
6939 panic!("incremental matcher")
6940 };
6941 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full_out, program.limits)
6942 .unwrap();
6943 record
6944 .write_record_mask_le_bytes_into(
6945 vocabulary.mask_vocab_size(),
6946 vocabulary.iter_records(),
6947 &mut record_out,
6948 )
6949 .unwrap();
6950 assert_eq!(adaptive_out, full_out);
6951 assert_eq!(adaptive_out, record_out);
6952
6953 adaptive
6954 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
6955 .unwrap();
6956 let MatcherBackend::Incremental(inner) = &adaptive.backend else {
6957 panic!("incremental matcher")
6958 };
6959 assert_eq!(inner.events.capacity(), event_capacity);
6960 assert_eq!(inner.lexical_path.capacity(), path_capacity);
6961
6962 for (bytes, ids) in vocabulary.iter_records() {
6963 if !ids
6964 .iter()
6965 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
6966 {
6967 continue;
6968 }
6969 let mut equivalent = program.new_matcher().unwrap();
6970 assert!(equivalent.advance(prefix).unwrap());
6971 let mut prepared = [0u8; 4];
6972 equivalent
6973 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
6974 .unwrap();
6975 assert_eq!(prepared, adaptive_out);
6976 assert!(equivalent.advance(bytes).unwrap(), "emitted token {ids:?}");
6977 }
6978 }
6979
6980 #[cfg(feature = "bench-internals")]
6981 #[test]
6982 fn contains_product_uncertain_states_use_the_exact_walker() {
6983 let cases = [
6984 (
6985 "fresh-item",
6986 r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
6987 b"[".as_slice(),
6988 ),
6989 (
6990 "at-maximum",
6991 r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":1}"#,
6992 br#"["ok","a"#,
6993 ),
6994 (
6995 "multiple-structured-candidates",
6996 r#"{"type":"array","maxItems":1,"items":{"type":"array","maxItems":1,"items":{"type":"string"},"contains":{"type":"string","pattern":"^a"},"minContains":1},"contains":{"type":"array","contains":{"type":"string","pattern":"^a"},"minContains":1},"minContains":1}"#,
6997 br#"[["a"#,
6998 ),
6999 ];
7000 let vocabulary = VocabularyHandle::new(Arc::new(
7001 build_vocabulary(
7002 8,
7003 FxHashMap::from_iter([
7004 (b"word".to_vec(), vec![0]),
7005 (b"x".to_vec(), vec![1]),
7006 (b"\"".to_vec(), vec![2]),
7007 ]),
7008 )
7009 .unwrap(),
7010 ))
7011 .unwrap();
7012 let trie = TrieCache::new().bind(&vocabulary).unwrap();
7013 for (name, schema, prefix) in cases {
7014 let program = program(schema);
7015 let mut matcher = program.new_matcher().unwrap();
7016 assert!(matcher.advance(prefix).unwrap(), "{name}");
7017 {
7018 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7019 panic!("incremental matcher")
7020 };
7021 let (selected, _) =
7022 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
7023 assert!(selected.is_none(), "{name} must remain exact");
7024 }
7025 let mut adaptive = [0; 4];
7026 let mut full = [0; 4];
7027 let mut record = [0; 4];
7028 matcher
7029 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive)
7030 .unwrap();
7031 matcher
7032 .write_full_trie_mask_for_bench(&vocabulary, &trie, &mut full)
7033 .unwrap();
7034 matcher
7035 .write_record_mask_le_bytes_into(
7036 vocabulary.mask_vocab_size(),
7037 vocabulary.iter_records(),
7038 &mut record,
7039 )
7040 .unwrap();
7041 assert_eq!(adaptive, full, "{name}");
7042 assert_eq!(adaptive, record, "{name}");
7043 assert_incremental_accounting(&matcher, name);
7044 }
7045 }
7046
7047 #[cfg(feature = "bench-internals")]
7048 #[test]
7049 fn invalid_body_prefixes_skip_the_parser_only_with_a_cold_rejection_proof() {
7050 let tokens = (0..100u32)
7051 .map(|id| (format!("prefix{id}\n").into_bytes(), vec![id]))
7052 .chain([
7053 (b"a".to_vec(), vec![100]),
7054 (b"a\"\n".to_vec(), vec![101]),
7055 (b"\xf0\x9f".to_vec(), vec![102]),
7056 ])
7057 .collect();
7058 let vocabulary =
7059 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
7060 let trie = TrieCache::new().bind(&vocabulary).unwrap();
7061 for (schema, prefix) in [
7062 (
7063 r#"{"type":"object","unevaluatedProperties":true}"#,
7064 br#"{"a"#.as_slice(),
7065 ),
7066 (
7067 r#"{"type":"object","properties":{"s":{"type":"string"}}}"#,
7068 br#"{"s":"a"#.as_slice(),
7069 ),
7070 ] {
7071 let program = program(schema);
7072 let mut matcher = program.new_matcher().unwrap();
7073 assert!(matcher.advance(prefix).unwrap());
7074 let mut fast = [0; 16];
7075 let mut full = [0; 16];
7076 let mut scan = [0; 16];
7077 matcher
7078 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
7079 .unwrap();
7080 assert!(
7081 matcher.mask_work_metrics_for_bench().trie_nodes < 10,
7082 "{schema}"
7083 );
7084 assert!(
7085 matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64
7086 );
7087 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7088 panic!("incremental program")
7089 };
7090 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits)
7091 .unwrap();
7092 matcher
7093 .write_record_mask_le_bytes_into(
7094 vocabulary.mask_vocab_size(),
7095 vocabulary.iter_records(),
7096 &mut scan,
7097 )
7098 .unwrap();
7099 assert_eq!(fast, full, "{schema}");
7100 assert_eq!(fast, scan, "{schema}");
7101 assert_ne!(
7102 fast[101 / 8] & (1 << (101 % 8)),
7103 0,
7104 "whitespace after closing quote"
7105 );
7106 }
7107 }
7108
7109 #[test]
7110 fn invalid_body_filter_preserves_errors_before_lexical_rejection() {
7111 let tokens = [(b"abc\n".to_vec(), vec![0])].into_iter().collect();
7112 let vocabulary =
7113 VocabularyHandle::new(Arc::new(build_vocabulary(7, tokens).unwrap())).unwrap();
7114 let trie = TrieCache::new().bind(&vocabulary).unwrap();
7115 let prefix = br#"{"a"#;
7116 for remaining in 1..=5 {
7117 let limits = StructuredLimits {
7118 max_document_bytes: prefix.len() + remaining,
7119 ..StructuredLimits::default()
7120 };
7121 let program = StructuredProgram::compile_with_limits(
7122 ir(r#"{"type":"object","unevaluatedProperties":true}"#),
7123 limits,
7124 )
7125 .unwrap();
7126 let mut fast = program.new_matcher().unwrap();
7127 let mut scan = program.new_matcher().unwrap();
7128 assert!(fast.advance(prefix).unwrap());
7129 assert!(scan.advance(prefix).unwrap());
7130 let mut fast_out = [0xff; 4];
7131 let mut scan_out = [0xff; 4];
7132 let observed = fast.write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast_out);
7133 let expected = scan.write_record_mask_le_bytes_into(
7134 vocabulary.mask_vocab_size(),
7135 vocabulary.iter_records(),
7136 &mut scan_out,
7137 );
7138 assert_eq!(observed.is_ok(), expected.is_ok(), "remaining={remaining}");
7139 assert_eq!(fast_out, scan_out, "remaining={remaining}");
7140 assert_eq!(fast_out, [0; 4]);
7141 }
7142 }
7143
7144 #[cfg(feature = "bench-internals")]
7145 #[test]
7146 fn escaped_and_partial_body_slice_fires_on_a_cold_wrapper() {
7147 let tokens = (0..100u32)
7148 .map(|id| (format!("\\u0061{id}").into_bytes(), vec![id]))
7149 .chain([
7150 (b"\"".to_vec(), vec![100]),
7151 (vec![0xf0, 0x9f], vec![101]),
7152 (b"\"\n".to_vec(), vec![102]),
7153 (b"\xff".to_vec(), vec![103]),
7154 (b"plain".to_vec(), vec![104]),
7155 ])
7156 .collect();
7157 let vocabulary =
7158 VocabularyHandle::new(Arc::new(build_vocabulary(127, tokens).unwrap())).unwrap();
7159 let trie = TrieCache::new().bind(&vocabulary).unwrap();
7160 let program = program(r#"{"type":"object","unevaluatedProperties":true}"#);
7161 let mut matcher = program.new_matcher().unwrap();
7162 assert!(matcher.advance(br#"{"a"#).unwrap());
7163 let mut fast = [0; 16];
7164 let mut full = [0; 16];
7165 let mut scan = [0; 16];
7166 matcher
7167 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut fast)
7168 .unwrap();
7169 assert!(matcher.mask_work_metrics_for_bench().trie_nodes < 20);
7170 assert!(matcher.mask_work_metrics_for_bench().trie_nodes < trie.trie().node_count() as u64);
7171 let MatcherBackend::Incremental(inner) = &mut matcher.backend else {
7172 panic!("incremental program")
7173 };
7174 walk_full_trie_mask_for_test(inner, &vocabulary, &trie, &mut full, program.limits).unwrap();
7175 matcher
7176 .write_record_mask_le_bytes_into(
7177 vocabulary.mask_vocab_size(),
7178 vocabulary.iter_records(),
7179 &mut scan,
7180 )
7181 .unwrap();
7182 assert_eq!(fast, full);
7183 assert_eq!(fast, scan);
7184 }
7185
7186 #[cfg(feature = "bench-internals")]
7187 #[test]
7188 fn adaptive_slice_removes_at_least_eighty_percent_of_string_edges() {
7189 let program = program(
7190 r#"{"allOf":[{"type":"string","pattern":"^a"},{"type":"string","pattern":"z$"}]}"#,
7191 );
7192 let mut tokens = FxHashMap::default();
7193 for id in 0..512u32 {
7194 tokens.insert(format!("token{id:04}").into_bytes(), vec![id]);
7195 }
7196 tokens.insert(b"\"".to_vec(), vec![600]);
7197 tokens.insert(b"\\".to_vec(), vec![601]);
7198 let vocabulary =
7199 VocabularyHandle::new(Arc::new(build_vocabulary(700, tokens).expect("vocabulary")))
7200 .expect("handle");
7201 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7202 let mut adaptive = program.new_matcher().expect("adaptive");
7203 let mut full = program.new_matcher().expect("full");
7204 assert!(adaptive.advance(b"\"a").expect("prefix"));
7205 assert!(full.advance(b"\"a").expect("prefix"));
7206 let bytes = vocabulary.mask_vocab_size().div_ceil(32) * 4;
7207 let mut adaptive_out = vec![0u8; bytes];
7208 let mut full_out = vec![0u8; bytes];
7209 adaptive
7210 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
7211 .expect("adaptive warmup");
7212 adaptive_out.fill(0);
7213 adaptive
7214 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
7215 .expect("adaptive mask");
7216 let adaptive_metrics = adaptive.mask_work_metrics_for_bench();
7217 let incremental = match &mut full.backend {
7218 MatcherBackend::Incremental(incremental) => incremental,
7219 MatcherBackend::Reference(_) => panic!("expected incremental matcher"),
7220 };
7221 incremental.reset_mask_metrics();
7222 walk_full_trie_mask_for_test(
7223 incremental,
7224 &vocabulary,
7225 &trie,
7226 &mut full_out,
7227 program.limits,
7228 )
7229 .expect("full mask");
7230 incremental.finish_mask_metrics();
7231 let full_metrics = incremental.mask_work_metrics;
7232 assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, "adaptive/full");
7233 assert!(
7234 adaptive_metrics.trie_edges.saturating_mul(5) <= full_metrics.trie_edges,
7235 "adaptive={} full={}",
7236 adaptive_metrics.trie_edges,
7237 full_metrics.trie_edges
7238 );
7239 }
7240
7241 #[test]
7242 fn concurrent_matchers_share_catalogue_without_sharing_state() {
7243 let program = program(r#"{"type":"string"}"#);
7244 let mut tokens = FxHashMap::default();
7245 tokens.insert(b"a".to_vec(), vec![0]);
7246 tokens.insert(b"b".to_vec(), vec![1]);
7247 tokens.insert(b"\"".to_vec(), vec![2]);
7248 let vocabulary =
7249 VocabularyHandle::new(Arc::new(build_vocabulary(3, tokens).expect("vocabulary")))
7250 .expect("handle");
7251 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7252 let expected = {
7253 let mut matcher = program.new_matcher().expect("expected matcher");
7254 assert!(matcher.advance(b"\"").expect("prefix"));
7255 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7256 matcher
7257 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7258 .expect("expected mask");
7259 output
7260 };
7261 let threads: Vec<_> = (0..8)
7262 .map(|_| {
7263 let program = program.clone();
7264 let vocabulary = vocabulary.clone();
7265 let trie = trie.clone();
7266 std::thread::spawn(move || {
7267 let mut matcher = program.new_matcher().expect("thread matcher");
7268 assert!(matcher.advance(b"\"").expect("thread prefix"));
7269 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7270 matcher
7271 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7272 .expect("thread mask");
7273 output
7274 })
7275 })
7276 .collect();
7277 for thread in threads {
7278 assert_eq!(thread.join().expect("thread"), expected);
7279 }
7280 }
7281
7282 fn assert_mask_bytes_equal(
7283 left: &[u8],
7284 right: &[u8],
7285 vocabulary: &VocabularyHandle,
7286 label: &str,
7287 ) {
7288 if left == right {
7289 return;
7290 }
7291 let differences: Vec<_> = (0..vocabulary.mask_vocab_size())
7292 .filter(|id| left[id / 8] & (1 << (id % 8)) != right[id / 8] & (1 << (id % 8)))
7293 .map(|id| (id, vocabulary.token_bytes(u32::try_from(id).unwrap())))
7294 .collect();
7295 panic!("{label} mismatch: {differences:?}");
7296 }
7297
7298 #[test]
7299 fn vocabulary_a_with_trie_b_is_artifact_mismatch_and_leaves_output_zeroed() {
7300 let program = program(r#"{"type":"object","additionalProperties":{"type":"boolean"}}"#);
7301 let mut tokens_a = FxHashMap::default();
7302 tokens_a.insert(b"{".to_vec(), vec![0]);
7303 tokens_a.insert(b"}".to_vec(), vec![1]);
7304 let mut tokens_b = FxHashMap::default();
7305 tokens_b.insert(b"{".to_vec(), vec![0]);
7306 tokens_b.insert(b"\"".to_vec(), vec![1]);
7307 tokens_b.insert(b"}".to_vec(), vec![2]);
7308 let handle_a =
7309 VocabularyHandle::new(Arc::new(build_vocabulary(3, tokens_a).expect("vocab a")))
7310 .expect("handle a");
7311 let handle_b =
7312 VocabularyHandle::new(Arc::new(build_vocabulary(4, tokens_b).expect("vocab b")))
7313 .expect("handle b");
7314 let trie_b = TrieCache::new().bind(&handle_b).expect("trie b");
7315 let mut matcher = program.new_matcher().expect("matcher");
7316 let mut output = vec![0xff; handle_a.mask_vocab_size().div_ceil(32) * 4];
7317 let error = matcher
7318 .write_mask_le_bytes_into(&handle_a, Some(&trie_b), &mut output)
7319 .expect_err("vocab a paired with trie b must mismatch");
7320 assert!(matches!(
7321 error,
7322 StructuredMatcherError::Compile(CompileError {
7323 code: ErrorCode::ArtifactMismatch,
7324 ..
7325 })
7326 ));
7327 assert!(
7328 output.iter().all(|&b| b == 0),
7329 "output must be cleared on mismatch"
7330 );
7331 }
7332
7333 #[test]
7334 fn direct_output_rejects_a_wrong_length_before_mutation() {
7335 let program = program(r#"{"type":"string"}"#);
7336 let vocabulary = Arc::new(build_vocabulary(1, FxHashMap::default()).expect("vocabulary"));
7337 let handle = VocabularyHandle::new(vocabulary).expect("handle");
7338 let trie = TrieCache::new().bind(&handle).expect("trie");
7339 let mut matcher = program.new_matcher().expect("matcher");
7340 let mut output = [0xa5u8; 2];
7341 let error = matcher
7342 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7343 .expect_err("wrong output length");
7344 assert!(matches!(
7345 error,
7346 StructuredMatcherError::Compile(CompileError {
7347 code: ErrorCode::ArtifactOutOfBounds,
7348 ..
7349 })
7350 ));
7351 assert_eq!(output, [0xa5; 2]);
7352 }
7353
7354 fn assert_combinator_prefixes(schema: &str, document: &[u8], valid: bool) -> usize {
7355 let schema_ir = ir(schema);
7356 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7357 assert!(program.uses_incremental_backend(), "{schema}");
7358 let mut matcher = program.new_matcher().expect("matcher");
7359 let mut prefix = Vec::new();
7360 for &byte in document {
7361 let before_accepting = matcher.is_accepting();
7362 let before_dead = matcher.is_dead();
7363 let mut candidate = prefix.clone();
7364 candidate.push(byte);
7365 let allowed = matcher.advance(&[byte]).expect("incremental byte");
7366 let expected = super::super::reference::accepts(&schema_ir, &candidate)
7367 || super::super::reference::can_continue(&schema_ir, &candidate);
7368 assert_eq!(allowed, expected, "prefix {candidate:?}, schema {schema}");
7369 if !allowed {
7370 assert_eq!(matcher.is_accepting(), before_accepting);
7371 assert_eq!(matcher.is_dead(), before_dead);
7372 let records = [([byte], [0u32])];
7373 let mask = matcher
7374 .allowed_mask_from_records(
7375 1,
7376 records
7377 .iter()
7378 .map(|(bytes, ids)| (bytes.as_slice(), ids.as_slice())),
7379 )
7380 .expect("rejected-byte mask");
7381 assert!(!mask.get(TokenId(0)));
7382 return candidate.len();
7383 }
7384 prefix.push(byte);
7385 assert_eq!(
7386 matcher.is_accepting(),
7387 super::super::reference::accepts(&schema_ir, &prefix),
7388 "acceptance at {prefix:?}, schema {schema}"
7389 );
7390 }
7391 assert_eq!(matcher.is_accepting(), valid, "{document:?}, {schema}");
7392 prefix.len()
7393 }
7394
7395 #[test]
7396 fn non_regular_combinator_hard_correctness_matrix_matches_reference_at_every_prefix() {
7397 type CombinatorCase<'a> = (&'a str, &'a [(&'a [u8], bool)]);
7398 let cases: &[CombinatorCase<'_>] = &[
7399 (
7400 r#"{"allOf":[{"type":"object","properties":{"id":{"type":"integer"}},"required":["id"],"additionalProperties":true},{"type":"object","patternProperties":{"^x":{"type":"string"}},"additionalProperties":true}]}"#,
7401 &[
7402 (r#"{"xé":"λ","id":1}"#.as_bytes(), true),
7403 (br#"{"id":1,"x":"\u03bb"}"#, true),
7404 ],
7405 ),
7406 (
7407 r#"{"allOf":[{"type":"array","items":{"type":"integer"},"contains":{"const":2},"minContains":1},{"type":"array","uniqueItems":true,"minItems":2}]}"#,
7408 &[(b"[1,2]", true), (b"[1,3]", false)],
7409 ),
7410 (
7411 r#"{"anyOf":[{"enum":["aaaaaaaaab","aaaaaaaaac"]},{"type":"string","pattern":"^aaaaaaaaad+$"},{"type":"array","items":{"type":"integer"},"uniqueItems":true}]}"#,
7412 &[
7413 (br#""aaaaaaaaac""#, true),
7414 (br#""aaaaaaaaadd""#, true),
7415 (b"[1,2]", true),
7416 (br#""aaaaaaaaa""#, false),
7417 ],
7418 ),
7419 (
7420 r#"{"oneOf":[{"const":1},{"type":"number"},{"type":"object","additionalProperties":{"type":"integer"}}]}"#,
7421 &[(b"1", false), (b"12", true), (br#"{"x":1}"#, true)],
7422 ),
7423 (
7424 r#"{"oneOf":[{"type":"string","pattern":"^.*$"},{"type":"string","pattern":"^.*$"},{"type":"array","uniqueItems":true}]}"#,
7425 &[(br#""a""#, false), (b"[]", true)],
7426 ),
7427 (
7428 r#"{"allOf":[{"anyOf":[{"oneOf":[{"type":"object","required":["a"],"additionalProperties":true},{"type":"array","uniqueItems":true}]},{"type":"null"}]},{"anyOf":[{"type":"object","additionalProperties":true},{"type":"array","contains":{"const":1}},{"type":"null"}]}]}"#,
7429 &[
7430 (r#" { "a" : "λ" } "#.as_bytes(), true),
7431 (b" [ 1 ] ", true),
7432 (b"null", true),
7433 (br#"{}"#, false),
7434 ],
7435 ),
7436 (
7437 r##"{"$defs":{"node":{"anyOf":[{"type":"null"},{"type":"object","properties":{"value":{"type":"integer"},"next":{"$ref":"#/$defs/node"}},"required":["value","next"],"additionalProperties":false}]}},"$ref":"#/$defs/node"}"##,
7438 &[
7439 (b"null", true),
7440 (br#"{"value":1,"next":{"value":2,"next":null}}"#, true),
7441 (br#"{"value":1,"next":]"#, false),
7442 ],
7443 ),
7444 ];
7445 for (schema, documents) in cases {
7446 for (document, valid) in *documents {
7447 assert_combinator_prefixes(schema, document, *valid);
7448 }
7449 }
7450 }
7451
7452 #[test]
7453 fn negation_and_conditional_prefixes_match_the_reference() {
7454 type Case<'a> = (&'a str, &'a [(&'a [u8], bool)]);
7455 let cases: &[Case<'_>] = &[
7456 (
7457 r#"{"not":{"const":1}}"#,
7458 &[(b"1", false), (b"12", true), (b"2", true)],
7459 ),
7460 (
7461 r#"{"not":{"enum":[null,true,"x"]}}"#,
7462 &[(b"null", false), (b"false", true), (br#""x""#, false)],
7463 ),
7464 (
7465 r#"{"not":{"type":"object","required":["x"],"additionalProperties":true}}"#,
7466 &[(br#"{"x":1}"#, false), (br#"{"y":1}"#, true), (b"[]", true)],
7467 ),
7468 (
7469 r#"{"not":{"type":"array","uniqueItems":true}}"#,
7470 &[(b"[1,2]", false), (b"[1,1]", true), (b"{}", true)],
7471 ),
7472 (
7473 r#"{"not":{"type":"array","contains":{"const":1}}}"#,
7474 &[(b"[1]", false), (b"[2]", true), (b"{}", true)],
7475 ),
7476 (
7477 r#"{"not":{"type":"number","minimum":-1,"maximum":2}}"#,
7478 &[(b"-0", false), (b"2.0", false), (b"3", true)],
7479 ),
7480 (
7481 r#"{"not":{"type":"number"}}"#,
7482 &[(b"1e2", false), (b"-0e+9", false), (br#""1e2""#, true)],
7483 ),
7484 (
7485 r#"{"not":{"type":"string","pattern":"^λ+$"}}"#,
7486 &[
7487 (r#""λ""#.as_bytes(), false),
7488 (br#""\u03bb""#, false),
7489 (br#""x""#, true),
7490 ],
7491 ),
7492 (
7493 r#"{"not":{"allOf":[{"type":"object"},{"required":["x"]}]}}"#,
7494 &[(br#"{"x":1}"#, false), (b"{}", true), (b"[]", true)],
7495 ),
7496 (
7497 r#"{"not":{"anyOf":[{"type":"null"},{"const":1}]}}"#,
7498 &[(b"null", false), (b"1", false), (b"2", true)],
7499 ),
7500 (
7501 r#"{"not":{"oneOf":[{"const":1},{"type":"number"}]}}"#,
7502 &[(b"1", true), (b"2", false), (b"null", true)],
7503 ),
7504 (
7505 r#"{"not":{"not":{"type":"string"}}}"#,
7506 &[(br#""a""#, true), (b"1", false)],
7507 ),
7508 (
7509 r#"{"if":{"type":"object","required":["kind"],"properties":{"kind":{"const":"a"}},"additionalProperties":true},"then":{"required":["a"]},"else":{"required":["b"]}}"#,
7510 &[
7511 (br#"{"kind":"a","a":1}"#, true),
7512 (br#"{"a":1,"kind":"a"}"#, true),
7513 (br#"{"kind":"b","b":1}"#, true),
7514 (br#"{"kind":"a","b":1}"#, false),
7515 ],
7516 ),
7517 (
7518 r#"{"if":{"type":"integer"},"then":{"minimum":2}}"#,
7519 &[(b"1", false), (b"2", true), (br#""x""#, true)],
7520 ),
7521 (
7522 r#"{"if":{"type":"integer"},"else":{"type":"string"}}"#,
7523 &[(b"1", true), (br#""x""#, true)],
7524 ),
7525 (
7526 r#"{"if":true,"then":{"type":"integer"},"else":false}"#,
7527 &[(b"1", true)],
7528 ),
7529 (
7530 r#"{"if":false,"then":false,"else":{"type":"string"}}"#,
7531 &[(br#""x""#, true)],
7532 ),
7533 (
7534 r#"{"type":"array","items":{"if":{"type":"integer"},"then":{"minimum":0},"else":{"type":"string"}}}"#,
7535 &[(br#"[0,"x"]"#, true)],
7536 ),
7537 (
7538 r##"{"$defs":{"condition":{"type":"object","required":["kind"],"properties":{"kind":{"const":"a"}},"additionalProperties":true},"branch":{"required":["a"]}},"if":{"$ref":"#/$defs/condition"},"then":{"$ref":"#/$defs/branch"},"else":{"required":["b"]}}"##,
7539 &[
7540 (br#"{"kind":"a","a":1}"#, true),
7541 (br#"{"kind":"b","b":1}"#, true),
7542 ],
7543 ),
7544 (
7545 r#"{"if":{"allOf":[{"type":"object"},{"anyOf":[{"required":["x"]},{"required":["y"]}]}]},"then":{"oneOf":[{"required":["a"]},{"required":["b"]}]},"else":false}"#,
7546 &[
7547 (br#"{"x":1,"a":1}"#, true),
7548 (br#"{"y":1,"a":1,"b":1}"#, false),
7549 ],
7550 ),
7551 ];
7552 let mut document_total = 0usize;
7553 let mut prefix_total = 0usize;
7554 for (schema, documents) in cases {
7555 for &(document, valid) in *documents {
7556 document_total += 1;
7557 prefix_total += assert_combinator_prefixes(schema, document, valid);
7558 }
7559 }
7560 println!("documents={document_total} byte_prefixes={prefix_total}");
7561 }
7562
7563 #[test]
7564 fn combinator_trie_record_and_reference_masks_are_identical() {
7565 let schema = r#"{"oneOf":[{"const":1},{"type":"number"},{"type":"object","additionalProperties":{"type":"integer"}}]}"#;
7566 let schema_ir = ir(schema);
7567 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7568 assert!(program.uses_incremental_backend());
7569 let mut tokens = FxHashMap::default();
7570 for (id, bytes) in [
7571 (0u32, b"1".as_slice()),
7572 (1, b"12"),
7573 (2, br#"{"x":1}"#),
7574 (3, br#""x""#),
7575 ] {
7576 tokens.insert(bytes.to_vec(), vec![id]);
7577 }
7578 let vocabulary = crate::index::VocabularyHandle::new(Arc::new(
7579 build_vocabulary(4, tokens).expect("vocabulary"),
7580 ))
7581 .expect("handle");
7582 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7583 let mut record_matcher = program.new_matcher().expect("record matcher");
7584 let mut trie_matcher = program.new_matcher().expect("trie matcher");
7585 let record = record_matcher
7586 .allowed_mask_from_records(vocabulary.mask_vocab_size(), vocabulary.iter_records())
7587 .expect("record mask");
7588 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7589 trie_matcher
7590 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7591 .expect("trie mask");
7592 for id in 0..vocabulary.mask_vocab_size() {
7593 let token = TokenId(u32::try_from(id).unwrap());
7594 let trie_bit = output[id / 8] & (1 << (id % 8)) != 0;
7595 assert_eq!(trie_bit, record.get(token), "trie/record id {id}");
7596 let bytes = vocabulary
7597 .iter_records()
7598 .find(|(_, ids)| ids.contains(&(id as u32)))
7599 .map(|(bytes, _)| bytes);
7600 let oracle = bytes.is_some_and(|bytes| {
7601 super::super::reference::accepts(&schema_ir, bytes)
7602 || super::super::reference::can_continue(&schema_ir, bytes)
7603 });
7604 assert_eq!(trie_bit, oracle, "reference id {id}");
7605 }
7606 }
7607
7608 #[test]
7609 fn negation_trie_record_and_reference_masks_are_identical() {
7610 let schema = r#"{"not":{"const":1}}"#;
7611 let schema_ir = ir(schema);
7612 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7613 assert!(program.uses_incremental_backend());
7614 let mut tokens = FxHashMap::default();
7615 for (id, bytes) in [
7616 (0u32, b"1".as_slice()),
7617 (1, b"12"),
7618 (2, b"1 "),
7619 (3, b"2"),
7620 (4, br#""x""#),
7621 (5, b"[1]"),
7622 ] {
7623 tokens.insert(bytes.to_vec(), vec![id]);
7624 }
7625 let vocabulary = crate::index::VocabularyHandle::new(Arc::new(
7626 build_vocabulary(6, tokens).expect("vocabulary"),
7627 ))
7628 .expect("handle");
7629 let trie = TrieCache::new().bind(&vocabulary).expect("trie");
7630 let mut records = program.new_matcher().expect("record matcher");
7631 let mut walked = program.new_matcher().expect("trie matcher");
7632 let record = records
7633 .allowed_mask_from_records(vocabulary.mask_vocab_size(), vocabulary.iter_records())
7634 .expect("record mask");
7635 let mut direct_output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7636 records
7637 .write_record_mask_le_bytes_into(
7638 vocabulary.mask_vocab_size(),
7639 vocabulary.iter_records(),
7640 &mut direct_output,
7641 )
7642 .expect("direct record output");
7643 let mut output = vec![0u8; vocabulary.mask_vocab_size().div_ceil(32) * 4];
7644 walked
7645 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut output)
7646 .expect("trie mask");
7647 for id in 0..vocabulary.mask_vocab_size() {
7648 let token = TokenId(u32::try_from(id).unwrap());
7649 let trie_bit = output[id / 8] & (1 << (id % 8)) != 0;
7650 let direct_bit = direct_output[id / 8] & (1 << (id % 8)) != 0;
7651 assert_eq!(direct_bit, record.get(token), "direct/record id {id}");
7652 assert_eq!(trie_bit, record.get(token), "trie/record id {id}");
7653 let bytes = vocabulary
7654 .iter_records()
7655 .find(|(_, ids)| ids.contains(&u32::try_from(id).unwrap()))
7656 .map(|(bytes, _)| bytes);
7657 let oracle = bytes.is_some_and(|bytes| {
7658 super::super::reference::accepts(&schema_ir, bytes)
7659 || super::super::reference::can_continue(&schema_ir, bytes)
7660 });
7661 assert_eq!(trie_bit, oracle, "reference id {id}");
7662 }
7663 }
7664
7665 #[test]
7666 fn trie_scratch_limit_rejects_without_changing_matcher_state() {
7667 let limits = StructuredLimits {
7668 max_mask_scratch_bytes: 0,
7669 ..StructuredLimits::default()
7670 };
7671 let program = StructuredProgram::compile_with_limits(ir(r#"{"type":"string"}"#), limits)
7672 .expect("program");
7673 let mut tokens = FxHashMap::default();
7674 tokens.insert(b"\"".to_vec(), vec![0]);
7675 let handle =
7676 VocabularyHandle::new(Arc::new(build_vocabulary(1, tokens).expect("vocabulary")))
7677 .expect("handle");
7678 let trie = TrieCache::new().bind(&handle).expect("trie");
7679 let mut matcher = program.new_matcher().expect("matcher");
7680 let mut output = [0xa5; 4];
7681 let error = matcher
7682 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7683 .expect_err("scratch limit");
7684 assert!(matches!(
7685 error,
7686 StructuredMatcherError::ResourceLimit {
7687 kind: LimitKind::MaskScratchBytes,
7688 ..
7689 }
7690 ));
7691 assert_eq!(output, [0; 4]);
7692 assert!(!matcher.is_dead());
7693 }
7694
7695 #[test]
7696 fn trie_scratch_capacity_stabilizes_after_warm_masks() {
7697 let program = program(r#"{"type":"string"}"#);
7698 let mut tokens = FxHashMap::default();
7699 tokens.insert(b"\"a".to_vec(), vec![0]);
7700 tokens.insert(b"\"b".to_vec(), vec![1]);
7701 let handle =
7702 VocabularyHandle::new(Arc::new(build_vocabulary(2, tokens).expect("vocabulary")))
7703 .expect("handle");
7704 let trie = TrieCache::new().bind(&handle).expect("trie");
7705 let mut matcher = program.new_matcher().expect("matcher");
7706 let mut output = [0; 4];
7707 matcher
7708 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7709 .expect("warmup");
7710 let capacity = match &matcher.backend {
7711 MatcherBackend::Incremental(incremental) => incremental.events.capacity(),
7712 #[cfg(feature = "bench-internals")]
7713 MatcherBackend::Reference(_) => panic!("production program selected reference backend"),
7714 };
7715 for _ in 0..32 {
7716 matcher
7717 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7718 .expect("warm mask");
7719 }
7720 let after = match &matcher.backend {
7721 MatcherBackend::Incremental(incremental) => incremental.events.capacity(),
7722 #[cfg(feature = "bench-internals")]
7723 MatcherBackend::Reference(_) => panic!("production program selected reference backend"),
7724 };
7725 assert_eq!(after, capacity);
7726 }
7727
7728 #[test]
7731 fn byte_level_vocabulary_trie_mask_matches_record_scan_across_a_full_generation() {
7732 let program = program(
7733 r#"{"type":"object","properties":{"id":{"type":"integer"},"ok":{"type":"boolean"}},"required":["id","ok"],"additionalProperties":false}"#,
7734 );
7735 let mut tokens = FxHashMap::default();
7736 let pieces: &[&[u8]] = &[
7737 b"{", b"}", b"\"", b":", b",", b" ", b"id", b"ok", b"i", b"d", b"o", b"k", b"\"id\"",
7738 b"\"ok\"", b"true", b"false", b"tru", b"e", b"0", b"1", b"42", b"-",
7739 ];
7740 for (i, piece) in pieces.iter().enumerate() {
7741 tokens.insert(piece.to_vec(), vec![i as u32]);
7742 }
7743 tokens.insert(b"}".to_vec(), vec![1, 40]);
7745 let width = 64usize;
7746 let handle =
7747 VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7748 .expect("handle");
7749 let trie = TrieCache::new().bind(&handle).expect("trie");
7750
7751 let doc: &[&[u8]] = &[
7752 b"{", b"\"id\"", b":", b"42", b",", b"\"ok\"", b":", b"true", b"}",
7753 ];
7754 let mut records = program.new_matcher().expect("records");
7755 let mut walked = program.new_matcher().expect("walked");
7756 let byte_len = width.div_ceil(32) * std::mem::size_of::<u32>();
7757 for step in doc {
7758 let expected = records
7759 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7760 .expect("record mask");
7761 let mut output = vec![0xff; byte_len];
7762 walked
7763 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7764 .expect("trie mask");
7765 for id in 0..handle.mask_vocab_size() {
7766 let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
7767 let token = TokenId(u32::try_from(id).expect("token id"));
7768 assert_eq!(observed, expected.get(token), "id {id} at step {step:?}");
7769 }
7770 assert!(records.advance(step).expect("records advance"), "{step:?}");
7771 assert!(walked.advance(step).expect("walked advance"), "{step:?}");
7772 }
7773 assert!(walked.is_accepting());
7774 assert!(records.is_accepting());
7775 }
7776
7777 #[test]
7780 fn nested_string_token_ending_accepting_keeps_the_frame_and_matches_the_record_scan() {
7781 let program = program(
7782 r#"{"type":"object","properties":{"n":{"type":"string"}},"required":["n"],"additionalProperties":false}"#,
7783 );
7784 let mut tokens = FxHashMap::default();
7785 let pieces: &[&[u8]] = &[
7788 b"{", b"}", b"\"", b":", b",", b"n", b"\"n\"", b"ab", b"abc", b"c", b"z", b" ",
7789 ];
7790 for (i, piece) in pieces.iter().enumerate() {
7791 tokens.insert(piece.to_vec(), vec![i as u32]);
7792 }
7793 let width = 64usize;
7794 let handle =
7795 VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7796 .expect("handle");
7797 let trie = TrieCache::new().bind(&handle).expect("trie");
7798
7799 let doc: &[&[u8]] = &[
7800 b"{", b"\"n\"", b":", b"\"", b"ab", b"abc", b"c", b"\"", b"}",
7801 ];
7802 let mut records = program.new_matcher().expect("records");
7803 let mut walked = program.new_matcher().expect("walked");
7804 let byte_len = width.div_ceil(32) * std::mem::size_of::<u32>();
7805 for step in doc {
7806 let expected = records
7807 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7808 .expect("record mask");
7809 let mut output = vec![0xff; byte_len];
7810 walked
7811 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7812 .expect("trie mask");
7813 for id in 0..handle.mask_vocab_size() {
7814 let observed = output[id / 8] & (1u8 << (id % 8)) != 0;
7815 let token = TokenId(u32::try_from(id).expect("token id"));
7816 assert_eq!(observed, expected.get(token), "id {id} at step {step:?}");
7817 }
7818 assert!(records.advance(step).expect("records advance"), "{step:?}");
7819 assert!(walked.advance(step).expect("walked advance"), "{step:?}");
7820 }
7821 assert!(walked.is_accepting());
7822 assert!(records.is_accepting());
7823 }
7824
7825 #[test]
7828 #[cfg(feature = "bench-internals")]
7829 fn nested_string_body_mask_walks_only_the_uncertain_trie() {
7830 let program = program(
7831 r#"{"type":"object","properties":{"n":{"type":"string"}},"required":["n"],"additionalProperties":false}"#,
7832 );
7833 let mut tokens = FxHashMap::default();
7834 let pieces: &[&[u8]] = &[
7835 b"{", b"}", b"\"", b":", b"n", b"\"n\"", b"ab", b"cd", b"ef", b"gh", b"ij", b"kl",
7836 ];
7837 for (i, piece) in pieces.iter().enumerate() {
7838 tokens.insert(piece.to_vec(), vec![i as u32]);
7839 }
7840 let handle =
7841 VocabularyHandle::new(Arc::new(build_vocabulary(63, tokens).expect("vocabulary")))
7842 .expect("handle");
7843 let trie = TrieCache::new().bind(&handle).expect("trie");
7844 let mut matcher = program.new_matcher().expect("matcher");
7845 let mut output = vec![0u8; 64usize.div_ceil(32) * std::mem::size_of::<u32>()];
7846
7847 for step in [b"{".as_slice(), b"\"n\"", b":", b"\""] {
7848 matcher
7849 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7850 .expect("mask");
7851 assert!(matcher.advance(step).expect("advance"), "{step:?}");
7852 }
7853 matcher
7854 .write_mask_le_bytes_into(&handle, Some(&trie), &mut output)
7855 .expect("string body mask");
7856 let nodes = matcher.mask_work_metrics_for_bench().trie_nodes;
7857 let full = trie.trie().node_count() as u64;
7858 assert!(
7859 nodes < full,
7860 "string body walked {nodes} of {full} nodes; the slice never fired"
7861 );
7862 }
7863
7864 #[test]
7865 fn trie_record_and_reference_masks_agree_after_every_uniqueitems_prefix() {
7866 let schema = r#"{"type":"array","items":{"type":"integer"},"uniqueItems":true,"contains":{"const":0}}"#;
7867 let doc_ir = ir(schema);
7868 let program = StructuredProgram::compile(doc_ir.clone()).expect("program");
7869 assert_eq!(program.backend_kind(), "incremental");
7870 let mut tokens = FxHashMap::default();
7871 for (id, bytes) in [
7872 (0u32, b"[".as_slice()),
7873 (1, b"0"),
7874 (2, b","),
7875 (3, b"1"),
7876 (4, b"2"),
7877 (5, b"]"),
7878 ] {
7879 tokens.insert(bytes.to_vec(), vec![id]);
7880 }
7881 let handle = VocabularyHandle::new(Arc::new(build_vocabulary(6, tokens).expect("vocab")))
7882 .expect("handle");
7883 let trie = TrieCache::new().bind(&handle).expect("trie");
7884 let mut walked = program.new_matcher().expect("trie matcher");
7885 let mut recorded = program.new_matcher().expect("record matcher");
7886 let prefixes: &[&[u8]] = &[b"[", b"[0", b"[0,1", b"[0,1,2"];
7887 let steps: &[&[u8]] = &[b"[", b"0", b",1", b",2"];
7888 let mut committed = Vec::new();
7889 for (prefix, step) in prefixes.iter().zip(steps.iter()) {
7890 for &b in *step {
7891 assert!(walked.advance(&[b]).expect("advance"));
7892 assert!(recorded.advance(&[b]).expect("advance"));
7893 }
7894 committed.extend_from_slice(step);
7895 assert_eq!(committed.as_slice(), *prefix);
7896
7897 let record_mask = recorded
7898 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7899 .expect("record mask");
7900 let mut trie_out = vec![0u8; handle.mask_vocab_size().div_ceil(32) * 4];
7901 walked
7902 .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
7903 .expect("trie mask");
7904 for id in 0..handle.mask_vocab_size() {
7905 let token = TokenId(u32::try_from(id).expect("id"));
7906 let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
7907 let record_bit = record_mask.get(token);
7908 assert_eq!(trie_bit, record_bit, "prefix {prefix:?} id {id}");
7909 let id32 = u32::try_from(id).expect("id");
7910 let Some((bytes, _)) = handle.iter_records().find(|(_, ids)| ids.contains(&id32))
7911 else {
7912 continue;
7913 };
7914 let mut candidate = prefix.to_vec();
7915 candidate.extend_from_slice(bytes);
7916 let ref_bit = super::super::reference::accepts(&doc_ir, &candidate)
7917 || super::super::reference::can_continue(&doc_ir, &candidate);
7918 assert_eq!(trie_bit, ref_bit, "prefix {prefix:?} id {id} vs reference");
7919 }
7920 }
7921 }
7922
7923 #[test]
7924 fn unevaluated_prefix_masks_match_record_preallocated_trie_and_reference() {
7925 let cases = [
7926 (
7927 r#"{"anyOf":[{"properties":{"a":true}},{"properties":{"b":true}}],"unevaluatedProperties":false}"#,
7928 br#"{"a":1,"b":2}"#.as_slice(),
7929 ),
7930 (
7931 r#"{"prefixItems":[true],"contains":{"type":"string"},"unevaluatedItems":false}"#,
7932 br#"[1,"foo"]"#.as_slice(),
7933 ),
7934 (
7935 r#"{"properties":{"v":true},"unevaluatedProperties":true}"#,
7936 br#"{"v":[{"deep":[1,2]}]}"#.as_slice(),
7937 ),
7938 ];
7939 let mut token_map = FxHashMap::default();
7940 for byte in 0u8..=127 {
7941 token_map.insert(vec![byte], vec![u32::from(byte)]);
7942 }
7943 let handle = VocabularyHandle::new(Arc::new(
7944 build_vocabulary(128, token_map).expect("ASCII vocabulary"),
7945 ))
7946 .expect("handle");
7947 let trie = TrieCache::new().bind(&handle).expect("trie");
7948 let output_bytes = handle.mask_vocab_size().div_ceil(32) * 4;
7949 let mut bytes_by_id = vec![None; handle.mask_vocab_size()];
7950 for (bytes, ids) in handle.iter_records() {
7951 for &id in ids {
7952 bytes_by_id[usize::try_from(id).unwrap()] = Some(bytes.to_vec());
7953 }
7954 }
7955
7956 for (schema, document) in cases {
7957 let schema_ir = ir(schema);
7958 let program = StructuredProgram::compile(schema_ir.clone()).expect("program");
7959 let mut record = program.new_matcher().expect("record matcher");
7960 let mut preallocated = program.new_matcher().expect("preallocated matcher");
7961 let mut walked = program.new_matcher().expect("trie matcher");
7962 assert_eq!(record.backend_name(), "incremental", "{schema}");
7963 assert_eq!(preallocated.backend_name(), "incremental", "{schema}");
7964 assert_eq!(walked.backend_name(), "incremental", "{schema}");
7965 let mut whitespace = program.new_matcher().unwrap();
7966 assert!(
7967 whitespace.advance(b"\t").unwrap(),
7968 "leading whitespace {schema}"
7969 );
7970 let direct = record
7971 .allowed_mask_from_records(
7972 128,
7973 std::iter::once((b"\t".as_slice(), [9u32].as_slice())),
7974 )
7975 .unwrap();
7976 assert!(direct.get(TokenId(9)), "direct tab mask {schema}");
7977
7978 for prefix_len in 0..=document.len() {
7979 let prefix = &document[..prefix_len];
7980 let record_mask = record
7981 .allowed_mask_from_records(handle.mask_vocab_size(), handle.iter_records())
7982 .expect("record mask");
7983 let mut preallocated_out = vec![0xff; output_bytes];
7984 preallocated
7985 .write_record_mask_le_bytes_into(
7986 handle.mask_vocab_size(),
7987 handle.iter_records(),
7988 &mut preallocated_out,
7989 )
7990 .expect("preallocated record mask");
7991 let mut trie_out = vec![0xff; output_bytes];
7992 walked
7993 .write_mask_le_bytes_into(&handle, Some(&trie), &mut trie_out)
7994 .expect("trie mask");
7995
7996 for id in 0..handle.mask_vocab_size() {
7997 let token = TokenId(u32::try_from(id).unwrap());
7998 let record_bit = record_mask.get(token);
7999 let preallocated_bit = preallocated_out[id / 8] & (1u8 << (id % 8)) != 0;
8000 let trie_bit = trie_out[id / 8] & (1u8 << (id % 8)) != 0;
8001 assert_eq!(
8002 record_bit, preallocated_bit,
8003 "{schema} prefix={prefix:?} id={id}"
8004 );
8005 assert_eq!(record_bit, trie_bit, "{schema} prefix={prefix:?} id={id}");
8006 if let Some(bytes) = &bytes_by_id[id] {
8007 let mut candidate = prefix.to_vec();
8008 candidate.extend_from_slice(bytes);
8009 let reference_bit =
8010 super::super::reference::accepts(&schema_ir, &candidate)
8011 || super::super::reference::can_continue(&schema_ir, &candidate);
8012 assert_eq!(
8013 record_bit, reference_bit,
8014 "{schema} prefix={prefix:?} id={id} bytes={bytes:?}"
8015 );
8016 }
8017 }
8018 assert_eq!(
8019 record.eos_legal(),
8020 super::super::reference::accepts(&schema_ir, prefix)
8021 );
8022 if prefix_len < document.len() {
8023 let byte = &document[prefix_len..prefix_len + 1];
8024 assert!(record.advance(byte).unwrap(), "{schema} prefix={prefix:?}");
8025 assert!(preallocated.advance(byte).unwrap());
8026 assert!(walked.advance(byte).unwrap());
8027 }
8028 }
8029 }
8030 }
8031
8032 #[cfg(feature = "bench-internals")]
8033 #[test]
8034 fn finite_unevaluated_key_prefix_rejects_dead_end_and_matches_all_routes() {
8035 let schema = r#"{"type":"object","required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#;
8036 let structured = program(schema);
8037 assert_eq!(structured.backend_kind(), "incremental");
8038 let mut tokens = FxHashMap::default();
8039 for byte in 0u8..=255 {
8040 tokens.insert(vec![byte], vec![u32::from(byte)]);
8041 }
8042 for (id, bytes) in [
8043 (256, b"method".as_slice()),
8044 (257, b"bogus-dead-branch-one"),
8045 (258, b"bogus-dead-branch-two"),
8046 ] {
8047 tokens.insert(bytes.to_vec(), vec![id]);
8048 }
8049 let handle =
8050 VocabularyHandle::new(Arc::new(build_vocabulary(259, tokens).expect("vocabulary")))
8051 .expect("handle");
8052 let trie = TrieCache::new().bind(&handle).expect("trie");
8053 let output_len = handle.mask_vocab_size().div_ceil(32) * 4;
8054
8055 for prefix in [br#"{""#.as_slice(), br#"{"method""#, br#"{"method":"card""#] {
8056 let mut adaptive = structured.new_matcher().expect("adaptive");
8057 let mut full = structured.new_matcher().expect("full");
8058 let mut record = structured.new_matcher().expect("record");
8059 for matcher in [&mut adaptive, &mut full, &mut record] {
8060 assert!(matcher.advance(prefix).expect("valid prefix"), "{prefix:?}");
8061 }
8062 let mut adaptive_out = vec![0xa5; output_len];
8063 let mut full_out = vec![0xa5; output_len];
8064 let mut record_out = vec![0xa5; output_len];
8065 adaptive
8066 .write_mask_le_bytes_into(&handle, Some(&trie), &mut adaptive_out)
8067 .expect("adaptive mask");
8068 let adaptive_nodes = adaptive.mask_work_metrics_for_bench().trie_nodes;
8069 full.write_full_trie_mask_for_bench(&handle, &trie, &mut full_out)
8070 .expect("full mask");
8071 record
8072 .write_record_mask_le_bytes_into(
8073 handle.mask_vocab_size(),
8074 handle.iter_records(),
8075 &mut record_out,
8076 )
8077 .expect("record mask");
8078 assert_eq!(adaptive_out, full_out, "adaptive/full at {prefix:?}");
8079 assert_eq!(adaptive_out, record_out, "adaptive/record at {prefix:?}");
8080 if prefix == br#"{""# {
8081 assert!(
8082 adaptive_nodes < trie.trie().node_count() as u64,
8083 "finite key pruning did not activate: {adaptive_nodes} nodes"
8084 );
8085 }
8086
8087 for (bytes, ids) in handle.iter_records() {
8088 if !ids
8089 .iter()
8090 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
8091 {
8092 continue;
8093 }
8094 let mut equivalent = structured.new_matcher().expect("equivalent");
8095 assert!(equivalent.advance(prefix).expect("equivalent prefix"));
8096 let mut prepared = vec![0; output_len];
8097 equivalent
8098 .write_mask_le_bytes_into(&handle, Some(&trie), &mut prepared)
8099 .expect("prepare equivalent");
8100 assert_eq!(prepared, adaptive_out);
8101 assert!(
8102 equivalent.advance(bytes).expect("emitted token"),
8103 "emitted {bytes:?} at {prefix:?}"
8104 );
8105 }
8106
8107 let bit = |byte: u8| adaptive_out[byte as usize / 8] & (1 << (byte % 8)) != 0;
8108 match prefix {
8109 br#"{""# => {
8110 assert!(bit(b'm'));
8111 assert!(!bit(b'b'));
8112 }
8113 br#"{"method""# => {
8114 assert!(bit(b':'));
8115 assert!(!bit(b'}'));
8116 }
8117 br#"{"method":"card""# => {
8118 assert!(bit(b'}'));
8119 assert!(!bit(b','));
8120 }
8121 _ => unreachable!(),
8122 }
8123 }
8124
8125 for schema in [
8126 schema,
8127 r#"{"type":"object","required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"additionalProperties":false}"#,
8128 ] {
8129 let program = program(schema);
8130 let mut matcher = program.new_matcher().expect("matcher");
8131 assert!(matcher.advance(br#"{""#).expect("opening key"));
8132 assert!(!matcher.advance(b"b").expect("dead key prefix"), "{schema}");
8133 assert!(
8134 matcher.advance(b"m").expect("rollback preserved state"),
8135 "{schema}"
8136 );
8137
8138 for impossible in [br#"{"bogus""#.as_slice(), br#"{"bogus":"x""#] {
8139 let mut matcher = program.new_matcher().expect("impossible matcher");
8140 assert!(
8141 !matcher.advance(impossible).expect("impossible prefix"),
8142 "{schema} admitted {impossible:?}"
8143 );
8144 }
8145
8146 let mut no_trailing_member = program.new_matcher().expect("trailing member matcher");
8147 assert!(no_trailing_member.advance(br#"{"method":"card""#).unwrap());
8148 assert!(
8149 !no_trailing_member.advance(b",").unwrap(),
8150 "{schema} admitted a comma with no viable unseen member"
8151 );
8152 assert!(
8153 no_trailing_member.advance(b"}").unwrap(),
8154 "{schema} did not roll the rejected comma back"
8155 );
8156 }
8157 }
8158
8159 #[cfg(feature = "bench-internals")]
8160 #[test]
8161 fn finite_unevaluated_key_viability_matrix_preserves_branches_intersections_and_seen_keys() {
8162 let mut tokens = FxHashMap::default();
8163 for byte in 0u8..=255 {
8164 tokens.insert(vec![byte], vec![u32::from(byte)]);
8165 }
8166 for (id, token) in [
8167 (256, b"method\":\"card\"".as_slice()),
8168 (257, b"a\\/b\""),
8169 (258, b"\\u00e9\""),
8170 ] {
8171 tokens.insert(token.to_vec(), vec![id]);
8172 }
8173 let vocabulary =
8174 VocabularyHandle::new(Arc::new(build_vocabulary(259, tokens).unwrap())).unwrap();
8175 let trie = TrieCache::new().bind(&vocabulary).unwrap();
8176 let bit = |mask: &[u8], id: usize| mask[id / 8] & (1 << (id % 8)) != 0;
8177
8178 let conditional = program(
8179 r#"{"allOf":[
8180 {"properties":{"alpha":true}},
8181 {"if":{"properties":{"kind":{"const":"x"}},"required":["kind"]},
8182 "then":{"properties":{"beta":true}},
8183 "else":{"properties":{"gamma":true}}}
8184 ],"unevaluatedProperties":false}"#,
8185 );
8186 let (mask, nodes) = assert_three_route_mask_and_advance(
8187 &conditional,
8188 &vocabulary,
8189 &trie,
8190 br#"{""#,
8191 "conditional-union",
8192 );
8193 for allowed in [b'a', b'b', b'g', b'k'] {
8194 assert!(
8195 bit(&mask, allowed as usize),
8196 "missing branch prefix {allowed}"
8197 );
8198 }
8199 assert!(!bit(&mask, b'z' as usize));
8200 assert!(nodes < trie.trie().node_count() as u64);
8201
8202 let property_names = program(
8203 r#"{"properties":{"apple":true,"beta":true},"propertyNames":{"pattern":"^a"},"unevaluatedProperties":false}"#,
8204 );
8205 let (mask, _) = assert_three_route_mask_and_advance(
8206 &property_names,
8207 &vocabulary,
8208 &trie,
8209 br#"{""#,
8210 "propertyNames-intersection",
8211 );
8212 assert!(bit(&mask, b'a' as usize));
8213 assert!(!bit(&mask, b'b' as usize));
8214
8215 let value_certificate = program(
8216 r#"{"properties":{"impossible":false,"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8217 );
8218 let (mask, _) = assert_three_route_mask_and_advance(
8219 &value_certificate,
8220 &vocabulary,
8221 &trie,
8222 br#"{""#,
8223 "proven-empty-value",
8224 );
8225 assert!(!bit(&mask, b'i' as usize));
8226 assert!(bit(&mask, b'm' as usize));
8227
8228 let shared_prefix =
8229 program(r#"{"properties":{"a":true,"ab":true},"unevaluatedProperties":false}"#);
8230 let (mask, _) = assert_three_route_mask_and_advance(
8231 &shared_prefix,
8232 &vocabulary,
8233 &trie,
8234 br#"{"a":1,"#,
8235 "seen-terminal-unseen-descendant",
8236 );
8237 assert!(bit(&mask, b'"' as usize), "unseen `ab` still permits a key");
8238 let (mask, _) = assert_three_route_mask_and_advance(
8239 &shared_prefix,
8240 &vocabulary,
8241 &trie,
8242 br#"{"a":1,"a"#,
8243 "seen-terminal-prefix",
8244 );
8245 assert!(bit(&mask, b'b' as usize));
8246 assert!(
8247 !bit(&mask, b'"' as usize),
8248 "duplicate exact `a` must not close"
8249 );
8250 let mut exhausted = shared_prefix.new_matcher().unwrap();
8251 assert!(exhausted.advance(br#"{"a":1,"ab":2"#).unwrap());
8252 assert!(
8253 !exhausted.advance(b",").unwrap(),
8254 "all finite names are seen, so another member cannot begin"
8255 );
8256 assert!(exhausted.advance(b"}").unwrap());
8257
8258 let dependent = program(
8259 r#"{"properties":{"trigger":true},"dependentSchemas":{"trigger":{"properties":{"extra":true}}},"unevaluatedProperties":false}"#,
8260 );
8261 let (mask, _) = assert_three_route_mask_and_advance(
8262 &dependent,
8263 &vocabulary,
8264 &trie,
8265 br#"{""#,
8266 "dependent-schema-union",
8267 );
8268 assert!(bit(&mask, b'e' as usize));
8269 assert!(bit(&mask, b't' as usize));
8270 assert!(!bit(&mask, b'z' as usize));
8271
8272 let encoded_names =
8273 program(r#"{"properties":{"a/b":true,"é":true},"unevaluatedProperties":false}"#);
8274 let (mask, _) = assert_three_route_mask_and_advance(
8275 &encoded_names,
8276 &vocabulary,
8277 &trie,
8278 br#"{"a\/"#,
8279 "escaped-key",
8280 );
8281 assert!(bit(&mask, b'b' as usize));
8282 assert!(!bit(&mask, b'"' as usize));
8283 let (mask, _) = assert_three_route_mask_and_advance(
8284 &encoded_names,
8285 &vocabulary,
8286 &trie,
8287 br#"{"\u00e9"#,
8288 "unicode-escape-key",
8289 );
8290 assert!(bit(&mask, b'"' as usize));
8291 let (mask, _) = assert_three_route_mask_and_advance(
8292 &encoded_names,
8293 &vocabulary,
8294 &trie,
8295 b"{\"\xc3",
8296 "partial-utf8-key",
8297 );
8298 assert!(bit(&mask, 0xa9));
8299
8300 let method = program(
8301 r#"{"required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8302 );
8303 let (mask, _) = assert_three_route_mask_and_advance(
8304 &method,
8305 &vocabulary,
8306 &trie,
8307 br#"{""#,
8308 "quote-colon-value-crossing",
8309 );
8310 assert!(bit(&mask, 256));
8311
8312 let patterned = program(
8314 r#"{"patternProperties":{"^x$":{"type":"string"}},"additionalProperties":false,"unevaluatedProperties":false}"#,
8315 );
8316 let (mask, nodes) = assert_three_route_mask_and_advance(
8317 &patterned,
8318 &vocabulary,
8319 &trie,
8320 br#"{""#,
8321 "single-pattern-bounded-product",
8322 );
8323 assert!(bit(&mask, b'x' as usize));
8324 assert!(!bit(&mask, b'y' as usize));
8325 assert!(nodes < trie.trie().node_count() as u64);
8326 let (mask, _) = assert_three_route_mask_and_advance(
8327 &patterned,
8328 &vocabulary,
8329 &trie,
8330 br#"{"x"#,
8331 "single-pattern-complete-rejection",
8332 );
8333 assert!(bit(&mask, b'"' as usize));
8334 assert!(!bit(&mask, b'y' as usize));
8335
8336 let bounded_pattern = program(
8337 r#"{"patternProperties":{"^[a-z]{2,6}$":{"type":"string"}},"additionalProperties":false,"unevaluatedProperties":false}"#,
8338 );
8339 let (mask, _) = assert_three_route_mask_and_advance(
8340 &bounded_pattern,
8341 &vocabulary,
8342 &trie,
8343 br#"{"amount":"USD","amoun"#,
8344 "seen-pattern-key-terminal-prefix",
8345 );
8346 assert!(
8347 !bit(&mask, b't' as usize),
8348 "a duplicate maximal-length pattern key must not become reachable"
8349 );
8350 let mut duplicate = bounded_pattern.new_matcher().unwrap();
8351 assert!(duplicate.advance(br#"{"amount":"USD","amoun"#).unwrap());
8352 assert!(!duplicate.advance(b"t").unwrap());
8353 assert!(duplicate.advance(b"z").unwrap());
8354
8355 for (source, keyword) in [
8358 ("exact", "additionalProperties"),
8359 ("exact", "unevaluatedProperties"),
8360 ("pattern", "additionalProperties"),
8361 ("pattern", "unevaluatedProperties"),
8362 ] {
8363 let declaration = if source == "exact" {
8364 r#""properties":{"amount":{"type":"string"}}"#
8365 } else {
8366 r#""patternProperties":{"^[a-z]{2,6}$":{"type":"string"}}"#
8367 };
8368 let schema = format!(r#"{{"type":"object",{declaration},"{keyword}":false}}"#);
8369 let case = program(&schema);
8370 let mut direct = case.new_matcher().unwrap();
8371 assert!(
8372 !direct.advance(br#"{"amount":"x","amount"#).unwrap(),
8373 "{source}/{keyword} admitted a duplicate key prefix"
8374 );
8375 }
8376
8377 let finite_pattern = program(
8378 r#"{"patternProperties":{"^[ab]$":true},"additionalProperties":false,"unevaluatedProperties":false}"#,
8379 );
8380 let (mask, _) = assert_three_route_mask_and_advance(
8381 &finite_pattern,
8382 &vocabulary,
8383 &trie,
8384 br#"{"a":1,""#,
8385 "finite-pattern-one-unseen-key",
8386 );
8387 assert!(!bit(&mask, b'a' as usize));
8388 assert!(bit(&mask, b'b' as usize));
8389 let mut exhausted_pattern = finite_pattern.new_matcher().unwrap();
8390 assert!(exhausted_pattern.advance(br#"{"a":1,"b":2"#).unwrap());
8391 assert!(!exhausted_pattern.advance(b",").unwrap());
8392 assert!(exhausted_pattern.advance(b"}").unwrap());
8393
8394 let false_exact_intersection = program(
8395 r#"{"properties":{"x":false},"patternProperties":{"^x$":true},"additionalProperties":false}"#,
8396 );
8397 let mut false_exact = false_exact_intersection.new_matcher().unwrap();
8398 assert!(false_exact.advance(br#"{"#).unwrap());
8399 assert!(
8400 !false_exact.advance(br#""x"#).unwrap(),
8401 "a pattern must not revive an exact property whose intersected value schema is false"
8402 );
8403
8404 let escaped_pattern = program(
8405 r#"{"patternProperties":{"^cosk[a-z]$":true},"additionalProperties":false,"unevaluatedProperties":false}"#,
8406 );
8407 let (mask, _) = assert_three_route_mask_and_advance(
8408 &escaped_pattern,
8409 &vocabulary,
8410 &trie,
8411 br#"{"cosk\u"#,
8412 "partial-unicode-escape-key-language",
8413 );
8414 assert!(
8415 bit(&mask, b'0' as usize),
8416 "U+0000..U+0FFF still contains ASCII lowercase completions"
8417 );
8418 assert!(
8419 !bit(&mask, b'2' as usize),
8420 "U+2000..U+2FFF cannot complete an ASCII lowercase pattern key"
8421 );
8422 let mut escaped = escaped_pattern.new_matcher().unwrap();
8423 assert!(escaped.advance(br#"{"cosk\u"#).unwrap());
8424 assert!(!escaped.advance(b"2").unwrap());
8425 assert!(escaped.advance(b"0").unwrap());
8426 }
8427
8428 #[cfg(feature = "bench-internals")]
8429 #[test]
8430 fn admitted_bounded_pattern_key_successors_retain_a_path_to_object_close() {
8431 let schema = program(
8432 r#"{"type":"object","propertyNames":{"pattern":"^[a-z]{2,6}$"},"patternProperties":{"^[a-z]{2,6}$":{"type":"string","maxLength":6}},"additionalProperties":false,"maxProperties":2}"#,
8433 );
8434 let prefix = br#"{"amount":"USD","amoun"#;
8435 let records = (0u8..=255)
8436 .map(|byte| (vec![byte], vec![u32::from(byte)]))
8437 .collect::<Vec<_>>();
8438 let mut matcher = schema.new_matcher().unwrap();
8439 assert!(matcher.advance(prefix).unwrap());
8440 let allowed = mask(&mut matcher, 256, &records);
8441 for byte in (0x20u8..=0x7e).chain([b'\t', b'\n']) {
8442 if !allowed.get(crate::primitives::TokenId(u32::from(byte))) {
8443 continue;
8444 }
8445 let tail: &[u8] = match byte {
8446 b'"' => br#":"x"}"#,
8447 b'\\' => br#"u007a":"x"}"#,
8448 b'a'..=b'z' => br#"":"x"}"#,
8449 _ => panic!("unexpected admitted key byte {byte:#04x}"),
8450 };
8451 let mut document = prefix.to_vec();
8452 document.push(byte);
8453 document.extend_from_slice(tail);
8454 let mut completed = schema.new_matcher().unwrap();
8455 assert!(
8456 completed.advance(&document).unwrap() && completed.is_accepting(),
8457 "admitted byte {byte:#04x} left no direct path to object close: {document:?}"
8458 );
8459 }
8460 let mut search_budget = 5_000;
8461 assert_eq!(
8462 closer_first_completion_search(
8463 &schema,
8464 br#"{"amount":"USD","amounz"#,
8465 8,
8466 &mut search_budget,
8467 ),
8468 CompletionSearch::Found,
8469 "the independent closer-first search must find the short closing continuation"
8470 );
8471
8472 let escaped =
8474 program(r#"{"patternProperties":{"^cosk[a-z]$":true},"additionalProperties":false}"#);
8475 let mut dead = escaped.new_matcher().unwrap();
8476 assert!(dead.advance(br#"{"cosk\u"#).unwrap());
8477 assert!(!dead.advance(b"2").unwrap());
8478 let mut live = escaped.new_matcher().unwrap();
8479 assert!(live.advance(br#"{"cosk\u0061":1}"#).unwrap());
8480 assert!(live.is_accepting());
8481 }
8482
8483 #[cfg(feature = "bench-internals")]
8484 #[test]
8485 fn finite_unevaluated_key_session_boundary_clears_errors_and_preserves_accounting() {
8486 let program = program(
8487 r#"{"required":["method"],"properties":{"method":{"enum":["card","wallet"]}},"unevaluatedProperties":false}"#,
8488 );
8489 let records = [b"m".as_slice(), b"b", b"\"", b":", b"method", b"bogus"]
8490 .into_iter()
8491 .enumerate()
8492 .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
8493 .collect();
8494 let vocabulary =
8495 VocabularyHandle::new(Arc::new(build_vocabulary(8, records).unwrap())).unwrap();
8496 let trie = TrieCache::new().bind(&vocabulary).unwrap();
8497 let prefix = br#"{""#;
8498 let exact = minimum_successful_limit(
8499 &program,
8500 &vocabulary,
8501 &trie,
8502 prefix,
8503 program.limits.max_session_bytes,
8504 NearLimitTuning::Session,
8505 );
8506 for limit in around(exact) {
8507 assert_near_limit_three_routes(
8508 &program,
8509 &vocabulary,
8510 &trie,
8511 prefix,
8512 NearLimitTuning::Session(limit),
8513 &format!("finite-key-session-{limit}"),
8514 );
8515 }
8516 }
8517
8518 #[cfg(feature = "bench-internals")]
8519 #[test]
8520 fn contains_unevaluated_product_matrix_matches_all_routes_and_advances() {
8521 struct Case {
8522 name: &'static str,
8523 schema: &'static str,
8524 document: &'static [u8],
8525 }
8526 let cases = [
8527 Case {
8528 name: "min-contains-0",
8529 schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0}"#,
8530 document: b"[1]",
8531 },
8532 Case {
8533 name: "min-contains-1",
8534 schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok$"},"minContains":1}"#,
8535 document: br#"["ok"]"#,
8536 },
8537 Case {
8538 name: "min-contains-2-candidate-dies-then-later-satisfies",
8539 schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok"},"minContains":2,"maxItems":3}"#,
8540 document: br#"["no","okay","ok"]"#,
8541 },
8542 Case {
8543 name: "max-contains-0",
8544 schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0,"maxContains":0}"#,
8545 document: b"[1]",
8546 },
8547 Case {
8548 name: "max-contains-1",
8549 schema: r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":1}"#,
8550 document: br#"["a",1]"#,
8551 },
8552 Case {
8553 name: "max-contains-2",
8554 schema: r#"{"type":"array","items":true,"contains":{"type":"string"},"minContains":0,"maxContains":2}"#,
8555 document: br#"["a","b",1]"#,
8556 },
8557 Case {
8558 name: "prefix-items-to-tail",
8559 schema: r#"{"type":"array","prefixItems":[{"type":"integer"}],"items":{"type":"string"},"contains":{"type":"string"},"minContains":1}"#,
8560 document: br#"[1,"x"]"#,
8561 },
8562 Case {
8563 name: "contains-produces-unevaluated-annotation",
8564 schema: r#"{"prefixItems":[true],"contains":{"type":"string","pattern":"^ok$"},"unevaluatedItems":false}"#,
8565 document: br#"[1,"ok"]"#,
8566 },
8567 Case {
8568 name: "item-accepts-contains-rejects",
8569 schema: r#"{"type":"array","items":{"type":"string"},"contains":{"type":"string","pattern":"^ok$"},"minContains":0}"#,
8570 document: br#"["no"]"#,
8571 },
8572 Case {
8573 name: "contains-accepts-item-rejects-empty-array",
8574 schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"type":"string"},"minContains":0}"#,
8575 document: b"[]",
8576 },
8577 Case {
8578 name: "maximum-length-array",
8579 schema: r#"{"type":"array","items":{"type":"integer"},"contains":{"const":2},"minContains":1,"maxItems":2}"#,
8580 document: b"[1,2]",
8581 },
8582 ];
8583 let token_bytes: &[&[u8]] = &[
8584 b"[", b"]", b",", b"0", b"1", b"2", b"\"", b"a", b"b", b"k", b"n", b"o", b"x", b"y",
8585 b"okay", b"\"ok\"", b"\"no\"",
8586 ];
8587 let records = token_bytes
8588 .iter()
8589 .enumerate()
8590 .map(|(id, bytes)| (bytes.to_vec(), vec![u32::try_from(id).unwrap()]))
8591 .collect();
8592 let vocabulary =
8593 VocabularyHandle::new(Arc::new(build_vocabulary(32, records).unwrap())).unwrap();
8594 let trie = TrieCache::new().bind(&vocabulary).unwrap();
8595 let output_len = vocabulary.mask_vocab_size().div_ceil(32) * 4;
8596
8597 for case in cases {
8598 let program = program(case.schema);
8599 let mut adaptive = program.new_matcher().unwrap();
8600 let mut full = program.new_matcher().unwrap();
8601 let mut record = program.new_matcher().unwrap();
8602 for prefix_len in 0..=case.document.len() {
8603 let prefix = &case.document[..prefix_len];
8604 let mut adaptive_out = vec![0; output_len];
8605 let mut full_out = vec![0; output_len];
8606 let mut record_out = vec![0; output_len];
8607 adaptive
8608 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut adaptive_out)
8609 .unwrap_or_else(|error| panic!("{} {prefix:?}: {error}", case.name));
8610 let MatcherBackend::Incremental(inner) = &mut full.backend else {
8611 panic!("incremental full matcher")
8612 };
8613 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
8614 walk_full_trie_mask_for_test(
8615 inner,
8616 &vocabulary,
8617 &trie,
8618 &mut full_out,
8619 program.limits,
8620 )
8621 .unwrap();
8622 let MatcherBackend::Incremental(inner) = &mut record.backend else {
8623 panic!("incremental record matcher")
8624 };
8625 select_adaptive_trie(inner, &vocabulary, program.limits).unwrap();
8626 record
8627 .write_record_mask_le_bytes_into(
8628 vocabulary.mask_vocab_size(),
8629 vocabulary.iter_records(),
8630 &mut record_out,
8631 )
8632 .unwrap();
8633 assert_mask_bytes_equal(&adaptive_out, &full_out, &vocabulary, case.name);
8634 assert_mask_bytes_equal(&adaptive_out, &record_out, &vocabulary, case.name);
8635
8636 for (candidate, ids) in vocabulary.iter_records() {
8637 if !ids
8638 .iter()
8639 .any(|id| adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0)
8640 {
8641 continue;
8642 }
8643 let mut equivalent = program.new_matcher().unwrap();
8644 if !prefix.is_empty() {
8645 assert!(
8646 equivalent.advance(prefix).unwrap(),
8647 "{} {prefix:?}",
8648 case.name
8649 );
8650 }
8651 let mut prepared = vec![0; output_len];
8652 equivalent
8653 .write_mask_le_bytes_into(&vocabulary, Some(&trie), &mut prepared)
8654 .unwrap();
8655 assert_eq!(prepared, adaptive_out, "{} {prefix:?}", case.name);
8656 assert!(
8657 equivalent.advance(candidate).unwrap(),
8658 "{} emitted {candidate:?} at {prefix:?}",
8659 case.name
8660 );
8661 assert_incremental_accounting(&equivalent, case.name);
8662 }
8663 for matcher in [&adaptive, &full, &record] {
8664 assert_incremental_accounting(matcher, case.name);
8665 }
8666 if prefix_len < case.document.len() {
8667 let next = &case.document[prefix_len..prefix_len + 1];
8668 let (_, ids) = vocabulary
8669 .iter_records()
8670 .find(|(bytes, _)| *bytes == next)
8671 .expect("each document byte is a vocabulary token");
8672 assert!(ids
8673 .iter()
8674 .any(|id| { adaptive_out[*id as usize / 8] & (1 << (*id % 8)) != 0 }));
8675 assert!(adaptive.advance(next).unwrap(), "{} {prefix:?}", case.name);
8676 assert!(full.advance(next).unwrap(), "{} {prefix:?}", case.name);
8677 assert!(record.advance(next).unwrap(), "{} {prefix:?}", case.name);
8678 }
8679 }
8680 }
8681 }
8682
8683 #[test]
8684 fn unevaluated_accepts_tokens_spanning_multiple_and_nested_boundaries() {
8685 let program = StructuredProgram::compile(ir(
8686 r#"{"properties":{"known":true},"unevaluatedProperties":true}"#,
8687 ))
8688 .expect("program");
8689 let mut whole = program.new_matcher().expect("matcher");
8690 assert_eq!(whole.backend_name(), "incremental");
8691 assert!(whole
8692 .advance(br#"{"known":[1,{"deep":[2,3]}],"extra":{"x":true}}"#)
8693 .unwrap());
8694 assert!(whole.is_accepting());
8695
8696 let mut split = program.new_matcher().expect("matcher");
8697 assert!(split.advance(br#"{"known":"#).unwrap());
8698 assert!(split.advance(br#"[{"deep":[1,{"x":2}]}]"#).unwrap());
8699 assert!(split.advance(br#", "extra":false}"#).unwrap());
8700 assert!(split.is_accepting());
8701
8702 let mut rollback = program.new_matcher().expect("matcher");
8703 assert!(rollback.advance(br#"{"known":[1,2]"#).unwrap());
8704 assert!(!rollback.advance(br#",]}}"#).unwrap());
8705 assert!(rollback.advance(br#", "extra":3}"#).unwrap());
8706 assert!(rollback.is_accepting());
8707 }
8708
8709 fn boolean_vocabulary(eos: u32) -> VocabularyHandle {
8710 let mut tokens = FxHashMap::default();
8711 for b in 0u32..256 {
8712 tokens.insert(vec![b as u8], vec![b]);
8713 }
8714 VocabularyHandle::new(Arc::new(build_vocabulary(eos, tokens).unwrap())).unwrap()
8715 }
8716
8717 #[test]
8718 fn word_mask_is_bit_identical_to_the_byte_mask_including_the_eos_word() {
8719 let program = program(r#"{"not":{"type":"string"}}"#);
8720 let handle = boolean_vocabulary(256);
8721 let mut byte_matcher = program.new_matcher().unwrap();
8722 let mut word_matcher = program.new_matcher().unwrap();
8723
8724 let mut byte_out = vec![0u8; 4 * handle.mask_vocab_size().div_ceil(32)];
8725 byte_matcher
8726 .write_record_mask_le_bytes_into(
8727 handle.mask_vocab_size(),
8728 handle.iter_records(),
8729 &mut byte_out,
8730 )
8731 .unwrap();
8732 let mut word_out = vec![0u32; handle.mask_vocab_size().div_ceil(32)];
8733 word_matcher
8734 .write_record_mask_words_into(
8735 handle.mask_vocab_size(),
8736 handle.iter_records(),
8737 &mut word_out,
8738 )
8739 .unwrap();
8740
8741 let expected: Vec<u32> = byte_out
8742 .chunks_exact(4)
8743 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
8744 .collect();
8745 assert_eq!(word_out, expected);
8746 }
8747
8748 #[test]
8749 fn word_mask_sets_bit_31_correctly_and_clears_a_dirty_buffer() {
8750 let program = program(r#"{"not":{"type":"integer"}}"#);
8752 let handle = boolean_vocabulary(256);
8753 let mut matcher = program.new_matcher().unwrap();
8754 assert!(matcher.advance(b"\"").unwrap());
8755 let words = handle.mask_vocab_size().div_ceil(32);
8756
8757 let mut out = vec![0xFFFF_FFFFu32; words]; matcher
8759 .write_record_mask_words_into(handle.mask_vocab_size(), handle.iter_records(), &mut out)
8760 .unwrap();
8761
8762 assert_eq!(
8763 out[1] >> 31 & 1,
8764 1,
8765 "byte 63 ('?') must be legal and land on word 1 bit 31"
8766 );
8767 let vocab_size = handle.mask_vocab_size() as u32;
8768 for (word_index, word) in out.iter().enumerate() {
8769 for bit in 0..32 {
8770 let token_id = word_index as u32 * 32 + bit;
8771 if token_id >= vocab_size {
8772 assert_eq!(word >> bit & 1, 0, "padding bit {token_id} must stay clear");
8773 }
8774 }
8775 }
8776 }
8777
8778 #[test]
8779 fn word_mask_rejects_wrong_length_without_touching_the_buffer() {
8780 let program = program(r#"{"not":{"type":"string"}}"#);
8781 let handle = boolean_vocabulary(256);
8782 let mut matcher = program.new_matcher().unwrap();
8783
8784 let mut wrong = vec![0xFFFF_FFFFu32; 3];
8785 matcher
8786 .write_record_mask_words_into(
8787 handle.mask_vocab_size(),
8788 handle.iter_records(),
8789 &mut wrong,
8790 )
8791 .unwrap_err();
8792 assert_eq!(
8793 wrong,
8794 vec![0xFFFF_FFFFu32; 3],
8795 "a length-mismatch error must not touch the buffer"
8796 );
8797 }
8798
8799 #[test]
8800 fn word_mask_zero_allocation_hot_path_matches_records_across_many_steps() {
8801 let program = program(
8802 r#"{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"],"additionalProperties":false}"#,
8803 );
8804 let handle = boolean_vocabulary(256);
8805 let mut matcher = program.new_matcher().unwrap();
8806 let mut out = vec![0u32; handle.mask_vocab_size().div_ceil(32)];
8807
8808 for byte in b"{\"ok\":tru" {
8809 matcher
8810 .write_record_mask_words_into(
8811 handle.mask_vocab_size(),
8812 handle.iter_records(),
8813 &mut out,
8814 )
8815 .unwrap();
8816 let bit_set = |id: u32| out[(id / 32) as usize] >> (id % 32) & 1 == 1;
8817 assert!(
8818 bit_set(*byte as u32),
8819 "byte {byte} must be legal at this prefix"
8820 );
8821 assert!(matcher.advance(&[*byte]).unwrap());
8822 }
8823 }
8824}