schemurai 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -4
- data/ext/schemurai_native/Cargo.lock +1 -1
- data/ext/schemurai_native/Cargo.toml +1 -1
- data/ext/schemurai_native/glue.c +155 -14
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/diagnostics.rs +395 -225
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/graph.rs +50 -0
- data/ext/schemurai_native/src/instance_graph.rs +84 -0
- data/ext/schemurai_native/src/lib.rs +947 -184
- data/ext/schemurai_native/src/memo.rs +1149 -0
- data/ext/schemurai_native/src/memo_scope.rs +240 -0
- data/ext/schemurai_native/src/number.rs +7 -8
- data/ext/schemurai_native/src/pattern.rs +157 -14
- data/ext/schemurai_native/src/scope.rs +243 -33
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +245 -90
- data/lib/schemurai/backend.rb +1 -0
- data/lib/schemurai/evaluator.rb +45 -3
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +100 -0
- data/lib/schemurai/native/evaluator.rb +32 -5
- data/lib/schemurai/schema_domain.rb +12 -7
- data/lib/schemurai/schema_graph.rb +10 -1
- data/lib/schemurai/unique_items.rb +2 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +7 -3
- data/lib/schemurai/vm/evaluator.rb +50 -3
- data/lib/schemurai/vm/limited_evaluator.rb +97 -0
- data/lib/schemurai.rb +74 -27
- data/sig/schemurai.rbs +20 -12
- metadata +16 -4
|
@@ -17,9 +17,25 @@ pub struct Report {
|
|
|
17
17
|
errors: Vec<Error>,
|
|
18
18
|
instance_path: String,
|
|
19
19
|
schema_path: String,
|
|
20
|
+
// Explicit failure signal for the current evaluation, independent of the
|
|
21
|
+
// number or rendering of diagnostic records. Child evaluations isolate it.
|
|
22
|
+
failed: bool,
|
|
23
|
+
limits: budget::Limits,
|
|
24
|
+
limit_hit: (u32, usize),
|
|
20
25
|
}
|
|
21
26
|
impl Report {
|
|
22
|
-
fn add(
|
|
27
|
+
fn add(
|
|
28
|
+
&mut self,
|
|
29
|
+
keyword: &'static str,
|
|
30
|
+
source: (i32, Value),
|
|
31
|
+
instance: Value,
|
|
32
|
+
detail: usize,
|
|
33
|
+
) -> Result<(), Verdict> {
|
|
34
|
+
if self.limits.enabled & 1 != 0 && self.errors.len() >= self.limits.max_errors {
|
|
35
|
+
self.limit_hit = (1, self.errors.len().saturating_add(1));
|
|
36
|
+
return Err(Unsupported);
|
|
37
|
+
}
|
|
38
|
+
self.failed = true;
|
|
23
39
|
let schema_path = if keyword == "falseSchema" {
|
|
24
40
|
self.schema_path.clone()
|
|
25
41
|
} else {
|
|
@@ -34,13 +50,14 @@ impl Report {
|
|
|
34
50
|
instance,
|
|
35
51
|
detail,
|
|
36
52
|
});
|
|
53
|
+
Ok(())
|
|
37
54
|
}
|
|
38
|
-
fn scoped(
|
|
55
|
+
fn scoped<T>(
|
|
39
56
|
&mut self,
|
|
40
57
|
segment: &str,
|
|
41
58
|
instance: Option<usize>,
|
|
42
|
-
visit: impl FnOnce(&mut Self) -> Result<
|
|
43
|
-
) -> Result<
|
|
59
|
+
visit: impl FnOnce(&mut Self) -> Result<T, Verdict>,
|
|
60
|
+
) -> Result<T, Verdict> {
|
|
44
61
|
use std::fmt::Write;
|
|
45
62
|
let schema_len = self.schema_path.len();
|
|
46
63
|
let instance_len = self.instance_path.len();
|
|
@@ -54,13 +71,13 @@ impl Report {
|
|
|
54
71
|
self.instance_path.truncate(instance_len);
|
|
55
72
|
result
|
|
56
73
|
}
|
|
57
|
-
fn property(
|
|
74
|
+
fn property<T>(
|
|
58
75
|
&mut self,
|
|
59
76
|
segment: &str,
|
|
60
77
|
key: Value,
|
|
61
78
|
schema_key: Option<Value>,
|
|
62
|
-
visit: impl FnOnce(&mut Self) -> Result<
|
|
63
|
-
) -> Result<
|
|
79
|
+
visit: impl FnOnce(&mut Self) -> Result<T, Verdict>,
|
|
80
|
+
) -> Result<T, Verdict> {
|
|
64
81
|
let name = pointer_name(key)?;
|
|
65
82
|
let segment = if let Some(key) = schema_key {
|
|
66
83
|
format!("{segment}/{}", pointer_name(key)?)
|
|
@@ -91,22 +108,75 @@ fn pointer_name(value: Value) -> Result<String, Verdict> {
|
|
|
91
108
|
Ok(name.replace('~', "~0").replace('/', "~1"))
|
|
92
109
|
}
|
|
93
110
|
|
|
94
|
-
impl
|
|
95
|
-
fn collect(
|
|
111
|
+
impl Execution<'_> {
|
|
112
|
+
fn collect<const ANNOTATIONS: bool>(
|
|
96
113
|
&self,
|
|
97
114
|
report: &mut Report,
|
|
98
115
|
node: usize,
|
|
99
116
|
value: Value,
|
|
100
117
|
depth: usize,
|
|
101
118
|
active: scope::Context<'_>,
|
|
102
|
-
) -> Result<
|
|
119
|
+
) -> Result<Tracked, Verdict> {
|
|
103
120
|
if depth > MAX_EVALUATION_DEPTH {
|
|
104
121
|
return Err(Unsupported);
|
|
105
122
|
}
|
|
123
|
+
if !self.budget.checkpoint() {
|
|
124
|
+
return Err(Unsupported);
|
|
125
|
+
}
|
|
106
126
|
let kind = unsafe { sn_kind(value) };
|
|
107
127
|
if kind == TYPE_UNSUPPORTED {
|
|
108
128
|
return Err(Unsupported);
|
|
109
129
|
}
|
|
130
|
+
self.collect_kind::<ANNOTATIONS>(report, node, value, depth, active, kind)
|
|
131
|
+
}
|
|
132
|
+
fn collect_kind<const ANNOTATIONS: bool>(
|
|
133
|
+
&self,
|
|
134
|
+
report: &mut Report,
|
|
135
|
+
node: usize,
|
|
136
|
+
value: Value,
|
|
137
|
+
depth: usize,
|
|
138
|
+
active: scope::Context<'_>,
|
|
139
|
+
kind: i32,
|
|
140
|
+
) -> Result<Tracked, Verdict> {
|
|
141
|
+
// A diagnostic success is established by complete traversal, never by
|
|
142
|
+
// a short-circuit validity result. Keep exported annotations and the
|
|
143
|
+
// collection policy separate; failures retain their path-specific errors.
|
|
144
|
+
if !self.budget.checkpoint() {
|
|
145
|
+
return Err(Unsupported);
|
|
146
|
+
}
|
|
147
|
+
let key = self
|
|
148
|
+
.memo
|
|
149
|
+
.key(self, node, value, depth, active, &self.budget);
|
|
150
|
+
if self.budget.hit.get().0 != 0 {
|
|
151
|
+
return Err(Unsupported);
|
|
152
|
+
}
|
|
153
|
+
if let Some(key) = key
|
|
154
|
+
&& let Some(result) = self.memo.diagnostic_success(key, ANNOTATIONS)
|
|
155
|
+
{
|
|
156
|
+
return Ok(result);
|
|
157
|
+
}
|
|
158
|
+
let result = self.collect_uncached::<ANNOTATIONS>(report, node, value, depth, active, kind);
|
|
159
|
+
if let Some(key) = key
|
|
160
|
+
&& let Ok(coverage) = &result
|
|
161
|
+
&& coverage.status == Valid
|
|
162
|
+
{
|
|
163
|
+
self.memo
|
|
164
|
+
.record_diagnostic_success(key, ANNOTATIONS, coverage);
|
|
165
|
+
}
|
|
166
|
+
result
|
|
167
|
+
}
|
|
168
|
+
fn collect_uncached<const ANNOTATIONS: bool>(
|
|
169
|
+
&self,
|
|
170
|
+
report: &mut Report,
|
|
171
|
+
node: usize,
|
|
172
|
+
value: Value,
|
|
173
|
+
depth: usize,
|
|
174
|
+
active: scope::Context<'_>,
|
|
175
|
+
kind: i32,
|
|
176
|
+
) -> Result<Tracked, Verdict> {
|
|
177
|
+
if depth > MAX_EVALUATION_DEPTH {
|
|
178
|
+
return Err(Unsupported);
|
|
179
|
+
}
|
|
110
180
|
let frame = scope::ScopeFrame {
|
|
111
181
|
resource: self.metadata[node].resource,
|
|
112
182
|
parent: active.scope,
|
|
@@ -116,19 +186,23 @@ impl Engine {
|
|
|
116
186
|
.or(self.metadata[node].primary_binding),
|
|
117
187
|
};
|
|
118
188
|
let active = self.enter_scope(node, active, &frame);
|
|
119
|
-
let tracked = self.tracks[node];
|
|
189
|
+
let tracked = ANNOTATIONS || self.tracks[node];
|
|
190
|
+
let parent_failed = std::mem::replace(&mut report.failed, false);
|
|
120
191
|
let mut prior = Tracked::new(Valid);
|
|
121
192
|
for (op, &source) in self.nodes[node].iter().zip(&self.error_operands[node]) {
|
|
193
|
+
if !self.budget.checkpoint() {
|
|
194
|
+
return Err(Unsupported);
|
|
195
|
+
}
|
|
122
196
|
match op {
|
|
123
197
|
Op::Unsupported => return Err(Unsupported),
|
|
124
198
|
Op::Boolean(false) => {
|
|
125
|
-
report.add("falseSchema", source, value, 0)
|
|
199
|
+
report.add("falseSchema", source, value, 0)?;
|
|
126
200
|
prior = Tracked::new(Invalid);
|
|
127
201
|
}
|
|
128
202
|
Op::Boolean(true) => {}
|
|
129
203
|
Op::Type(mask) => {
|
|
130
204
|
if kind & mask == 0 {
|
|
131
|
-
report.add("type", source, value, 0)
|
|
205
|
+
report.add("type", source, value, 0)?;
|
|
132
206
|
}
|
|
133
207
|
}
|
|
134
208
|
Op::Const(..) | Op::Enum(..) => match self.leaf(op, kind, value, depth) {
|
|
@@ -142,12 +216,12 @@ impl Engine {
|
|
|
142
216
|
source,
|
|
143
217
|
value,
|
|
144
218
|
0,
|
|
145
|
-
)
|
|
219
|
+
)?,
|
|
146
220
|
Valid => {}
|
|
147
221
|
},
|
|
148
222
|
Op::Number(required, rules) => {
|
|
149
223
|
if *required != 0 && kind & required == 0 {
|
|
150
|
-
report.add("type", source, value, 0)
|
|
224
|
+
report.add("type", source, value, 0)?;
|
|
151
225
|
}
|
|
152
226
|
if kind & TYPE_NUMBER != 0 {
|
|
153
227
|
let bits = rules.failures(value)?;
|
|
@@ -158,15 +232,18 @@ impl Engine {
|
|
|
158
232
|
(EXCLUSIVE_MINIMUM, "exclusiveMinimum"),
|
|
159
233
|
(MULTIPLE_OF, "multipleOf"),
|
|
160
234
|
] {
|
|
235
|
+
if !self.budget.checkpoint() {
|
|
236
|
+
return Err(Unsupported);
|
|
237
|
+
}
|
|
161
238
|
if bits & bit != 0 {
|
|
162
|
-
report.add(keyword, source, value, 0)
|
|
239
|
+
report.add(keyword, source, value, 0)?;
|
|
163
240
|
}
|
|
164
241
|
}
|
|
165
242
|
}
|
|
166
243
|
}
|
|
167
244
|
Op::String(required, rules) => {
|
|
168
245
|
if *required != 0 && kind != TYPE_STRING {
|
|
169
|
-
report.add("type", source, value, 0)
|
|
246
|
+
report.add("type", source, value, 0)?;
|
|
170
247
|
}
|
|
171
248
|
if kind == TYPE_STRING {
|
|
172
249
|
let len = if rules.min.is_some() || rules.max.is_some() {
|
|
@@ -175,22 +252,22 @@ impl Engine {
|
|
|
175
252
|
0
|
|
176
253
|
};
|
|
177
254
|
if rules.max.is_some_and(|max| len > max) {
|
|
178
|
-
report.add("maxLength", source, value, len)
|
|
255
|
+
report.add("maxLength", source, value, len)?;
|
|
179
256
|
}
|
|
180
257
|
if rules.min.is_some_and(|min| len < min) {
|
|
181
|
-
report.add("minLength", source, value, len)
|
|
258
|
+
report.add("minLength", source, value, len)?;
|
|
182
259
|
}
|
|
183
260
|
if let Some(pattern) = &rules.pattern {
|
|
184
|
-
match pattern.eval(value) {
|
|
261
|
+
match pattern.eval(self.caches, value) {
|
|
185
262
|
Unsupported => return Err(Unsupported),
|
|
186
|
-
Invalid => report.add("pattern", source, value, 0)
|
|
263
|
+
Invalid => report.add("pattern", source, value, 0)?,
|
|
187
264
|
Valid => {}
|
|
188
265
|
}
|
|
189
266
|
}
|
|
190
267
|
if let Some(format) = &rules.format {
|
|
191
268
|
match format.eval(value) {
|
|
192
269
|
Unsupported => return Err(Unsupported),
|
|
193
|
-
Invalid => report.add("format", source, value, 0)
|
|
270
|
+
Invalid => report.add("format", source, value, 0)?,
|
|
194
271
|
Valid => {}
|
|
195
272
|
}
|
|
196
273
|
}
|
|
@@ -206,15 +283,18 @@ impl Engine {
|
|
|
206
283
|
source,
|
|
207
284
|
value,
|
|
208
285
|
0,
|
|
209
|
-
)
|
|
286
|
+
)?,
|
|
210
287
|
Valid => {}
|
|
211
288
|
}
|
|
212
289
|
}
|
|
213
290
|
}
|
|
214
291
|
}
|
|
215
292
|
Op::Array(required, rules) => {
|
|
293
|
+
if kind == TYPE_ARRAY && !self.budget.checkpoint() {
|
|
294
|
+
return Err(Unsupported);
|
|
295
|
+
}
|
|
216
296
|
if *required != 0 && kind != TYPE_ARRAY {
|
|
217
|
-
report.add("type", source, value, 0)
|
|
297
|
+
report.add("type", source, value, 0)?;
|
|
218
298
|
}
|
|
219
299
|
if kind != TYPE_ARRAY {
|
|
220
300
|
continue;
|
|
@@ -222,24 +302,27 @@ impl Engine {
|
|
|
222
302
|
let mut coverage = Tracked::new(Valid);
|
|
223
303
|
let len = unsafe { sn_len(value) };
|
|
224
304
|
if rules.max.is_some_and(|max| len > max) {
|
|
225
|
-
report.add("maxItems", source, value, len)
|
|
305
|
+
report.add("maxItems", source, value, len)?;
|
|
226
306
|
}
|
|
227
307
|
if rules.min.is_some_and(|min| len < min) {
|
|
228
|
-
report.add("minItems", source, value, len)
|
|
308
|
+
report.add("minItems", source, value, len)?;
|
|
229
309
|
}
|
|
230
310
|
if rules.unique {
|
|
231
|
-
match unique(value, depth, self.fixnum_flag) {
|
|
311
|
+
match unique(value, depth, self.fixnum_flag, &self.budget) {
|
|
232
312
|
Unsupported => return Err(Unsupported),
|
|
233
|
-
Invalid => report.add("uniqueItems", source, value, 0)
|
|
313
|
+
Invalid => report.add("uniqueItems", source, value, 0)?,
|
|
234
314
|
Valid => {}
|
|
235
315
|
}
|
|
236
316
|
}
|
|
237
317
|
for (index, &child) in rules.prefix.iter().take(len).enumerate() {
|
|
318
|
+
if !self.budget.checkpoint() {
|
|
319
|
+
return Err(Unsupported);
|
|
320
|
+
}
|
|
238
321
|
if tracked {
|
|
239
322
|
coverage.item(index);
|
|
240
323
|
}
|
|
241
324
|
report.scoped(&format!("prefixItems/{index}"), Some(index), |r| {
|
|
242
|
-
self.collect(
|
|
325
|
+
self.collect::<false>(
|
|
243
326
|
r,
|
|
244
327
|
child,
|
|
245
328
|
unsafe { sn_entry(value, index) },
|
|
@@ -250,11 +333,14 @@ impl Engine {
|
|
|
250
333
|
}
|
|
251
334
|
if rules.list {
|
|
252
335
|
for (index, &child) in rules.items.iter().take(len).enumerate() {
|
|
336
|
+
if !self.budget.checkpoint() {
|
|
337
|
+
return Err(Unsupported);
|
|
338
|
+
}
|
|
253
339
|
if tracked {
|
|
254
340
|
coverage.item(index);
|
|
255
341
|
}
|
|
256
342
|
report.scoped(&format!("items/{index}"), Some(index), |r| {
|
|
257
|
-
self.collect(
|
|
343
|
+
self.collect::<false>(
|
|
258
344
|
r,
|
|
259
345
|
child,
|
|
260
346
|
unsafe { sn_entry(value, index) },
|
|
@@ -265,11 +351,14 @@ impl Engine {
|
|
|
265
351
|
}
|
|
266
352
|
if let Some(child) = rules.additional {
|
|
267
353
|
for index in rules.items.len()..len {
|
|
354
|
+
if !self.budget.checkpoint() {
|
|
355
|
+
return Err(Unsupported);
|
|
356
|
+
}
|
|
268
357
|
if tracked {
|
|
269
358
|
coverage.item(index);
|
|
270
359
|
}
|
|
271
360
|
report.scoped("additionalItems", Some(index), |r| {
|
|
272
|
-
self.collect(
|
|
361
|
+
self.collect::<false>(
|
|
273
362
|
r,
|
|
274
363
|
child,
|
|
275
364
|
unsafe { sn_entry(value, index) },
|
|
@@ -300,6 +389,7 @@ impl Engine {
|
|
|
300
389
|
}
|
|
301
390
|
_ => None,
|
|
302
391
|
};
|
|
392
|
+
let pattern = pattern.filter(|_| !self.budget.timed());
|
|
303
393
|
let number = match self.nodes[child].as_slice() {
|
|
304
394
|
[Op::Number(required, number)] if depth < MAX_EVALUATION_DEPTH => {
|
|
305
395
|
Some((*required, number))
|
|
@@ -312,81 +402,58 @@ impl Engine {
|
|
|
312
402
|
}
|
|
313
403
|
_ => None,
|
|
314
404
|
};
|
|
405
|
+
let format = format.filter(|_| !self.budget.timed());
|
|
315
406
|
let content = match self.nodes[child].as_slice() {
|
|
316
407
|
[Op::String(required, string)] if depth < MAX_EVALUATION_DEPTH => {
|
|
317
408
|
string.content_only().map(|content| (*required, content))
|
|
318
409
|
}
|
|
319
410
|
_ => None,
|
|
320
411
|
};
|
|
321
|
-
|
|
412
|
+
let content = content.filter(|_| !self.budget.timed());
|
|
413
|
+
if pattern.is_some() || format.is_some() || content.is_some() {
|
|
322
414
|
let start = rules.prefix.len().min(len);
|
|
323
415
|
if tracked {
|
|
324
416
|
for index in start..len {
|
|
417
|
+
if !self.budget.checkpoint() {
|
|
418
|
+
return Err(Unsupported);
|
|
419
|
+
}
|
|
325
420
|
coverage.item(index);
|
|
326
421
|
}
|
|
327
422
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
coverage.item(index);
|
|
423
|
+
// The invalid callback is shared, but each scanner retains
|
|
424
|
+
// its statically selected parser and guarded byte views.
|
|
425
|
+
let invalid = |index, item| match report.scoped(
|
|
426
|
+
"items",
|
|
427
|
+
Some(start + index),
|
|
428
|
+
|r| self.collect::<false>(r, child, item, depth + 1, active),
|
|
429
|
+
) {
|
|
430
|
+
Ok(_) => Valid,
|
|
431
|
+
Err(status) => status,
|
|
432
|
+
};
|
|
433
|
+
let status = match (pattern, format, content) {
|
|
434
|
+
(Some((required, pattern)), _, _) => pattern.visit_invalid_items(
|
|
435
|
+
self.caches,
|
|
436
|
+
required,
|
|
437
|
+
&values[start..],
|
|
438
|
+
invalid,
|
|
439
|
+
),
|
|
440
|
+
(_, Some((required, format)), _) => {
|
|
441
|
+
format.visit_invalid_items(required, &values[start..], invalid)
|
|
348
442
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
required,
|
|
352
|
-
&values[start..],
|
|
353
|
-
|index, item| match report.scoped(
|
|
354
|
-
"items",
|
|
355
|
-
Some(start + index),
|
|
356
|
-
|r| self.collect(r, child, item, depth + 1, active),
|
|
357
|
-
) {
|
|
358
|
-
Ok(()) => Valid,
|
|
359
|
-
Err(status) => status,
|
|
360
|
-
},
|
|
361
|
-
);
|
|
362
|
-
if status != Valid {
|
|
363
|
-
return Err(status);
|
|
364
|
-
}
|
|
365
|
-
} else if let Some((required, content)) = content {
|
|
366
|
-
let start = rules.prefix.len().min(len);
|
|
367
|
-
if tracked {
|
|
368
|
-
for index in start..len {
|
|
369
|
-
coverage.item(index);
|
|
443
|
+
(_, _, Some((required, content))) => {
|
|
444
|
+
content.visit_invalid_items(required, &values[start..], invalid)
|
|
370
445
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
required,
|
|
374
|
-
&values[start..],
|
|
375
|
-
|index, item| match report.scoped(
|
|
376
|
-
"items",
|
|
377
|
-
Some(start + index),
|
|
378
|
-
|r| self.collect(r, child, item, depth + 1, active),
|
|
379
|
-
) {
|
|
380
|
-
Ok(()) => Valid,
|
|
381
|
-
Err(status) => status,
|
|
382
|
-
},
|
|
383
|
-
);
|
|
446
|
+
_ => unreachable!("a scalar item scanner was selected"),
|
|
447
|
+
};
|
|
384
448
|
if status != Valid {
|
|
385
449
|
return Err(status);
|
|
386
450
|
}
|
|
387
451
|
} else {
|
|
388
452
|
for (index, &item) in values.iter().enumerate().skip(rules.prefix.len())
|
|
389
453
|
{
|
|
454
|
+
if !self.budget.checkpoint() {
|
|
455
|
+
return Err(Unsupported);
|
|
456
|
+
}
|
|
390
457
|
if tracked {
|
|
391
458
|
coverage.item(index);
|
|
392
459
|
}
|
|
@@ -417,29 +484,37 @@ impl Engine {
|
|
|
417
484
|
continue;
|
|
418
485
|
}
|
|
419
486
|
report.scoped("items", Some(index), |r| {
|
|
420
|
-
self.collect(r, child, item, depth + 1, active)
|
|
487
|
+
self.collect::<false>(r, child, item, depth + 1, active)
|
|
421
488
|
})?;
|
|
422
489
|
}
|
|
423
490
|
}
|
|
424
491
|
}
|
|
425
492
|
if let Some(child) = rules.contains {
|
|
426
|
-
let mut matches = 0;
|
|
427
493
|
let values = if len == 0 {
|
|
428
494
|
&[]
|
|
429
495
|
} else {
|
|
430
496
|
unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
|
|
431
497
|
};
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
498
|
+
let matches = evaluation::scan_contains(
|
|
499
|
+
len,
|
|
500
|
+
|index| match self.needs_diagnostics(
|
|
501
|
+
child,
|
|
502
|
+
values[index],
|
|
503
|
+
depth + 1,
|
|
504
|
+
active,
|
|
505
|
+
) {
|
|
506
|
+
Ok(needs) => verdict(!needs),
|
|
507
|
+
Err(status) => status,
|
|
508
|
+
},
|
|
509
|
+
|index| {
|
|
435
510
|
if tracked {
|
|
436
511
|
coverage.item(index);
|
|
437
512
|
}
|
|
438
|
-
}
|
|
439
|
-
|
|
513
|
+
},
|
|
514
|
+
)?;
|
|
440
515
|
if bounds(matches, Some(rules.min_contains), rules.max_contains) == Invalid
|
|
441
516
|
{
|
|
442
|
-
report.add("contains", source, value, matches)
|
|
517
|
+
report.add("contains", source, value, matches)?;
|
|
443
518
|
}
|
|
444
519
|
}
|
|
445
520
|
if let Some(child) = rules
|
|
@@ -447,11 +522,14 @@ impl Engine {
|
|
|
447
522
|
.filter(|_| prior.item_count() < len && coverage.item_count() < len)
|
|
448
523
|
{
|
|
449
524
|
for index in 0..len {
|
|
525
|
+
if !self.budget.checkpoint() {
|
|
526
|
+
return Err(Unsupported);
|
|
527
|
+
}
|
|
450
528
|
if prior.contains_item(index) || coverage.contains_item(index) {
|
|
451
529
|
continue;
|
|
452
530
|
}
|
|
453
531
|
report.scoped("unevaluatedItems", Some(index), |r| {
|
|
454
|
-
self.collect(
|
|
532
|
+
self.collect::<false>(
|
|
455
533
|
r,
|
|
456
534
|
child,
|
|
457
535
|
unsafe { sn_entry(value, index) },
|
|
@@ -467,8 +545,11 @@ impl Engine {
|
|
|
467
545
|
}
|
|
468
546
|
}
|
|
469
547
|
Op::Object(required, rules) => {
|
|
548
|
+
if kind == TYPE_OBJECT && !self.budget.checkpoint() {
|
|
549
|
+
return Err(Unsupported);
|
|
550
|
+
}
|
|
470
551
|
if *required != 0 && kind != TYPE_OBJECT {
|
|
471
|
-
report.add("type", source, value, 0)
|
|
552
|
+
report.add("type", source, value, 0)?;
|
|
472
553
|
}
|
|
473
554
|
if kind != TYPE_OBJECT {
|
|
474
555
|
continue;
|
|
@@ -476,14 +557,17 @@ impl Engine {
|
|
|
476
557
|
let mut coverage = Tracked::new(Valid);
|
|
477
558
|
let len = unsafe { sn_len(value) };
|
|
478
559
|
if rules.max.is_some_and(|max| len > max) {
|
|
479
|
-
report.add("maxProperties", source, value, len)
|
|
560
|
+
report.add("maxProperties", source, value, len)?;
|
|
480
561
|
}
|
|
481
562
|
if rules.min.is_some_and(|min| len < min) {
|
|
482
|
-
report.add("minProperties", source, value, len)
|
|
563
|
+
report.add("minProperties", source, value, len)?;
|
|
483
564
|
}
|
|
484
565
|
for &name in &rules.required_order {
|
|
566
|
+
if !self.budget.checkpoint() {
|
|
567
|
+
return Err(Unsupported);
|
|
568
|
+
}
|
|
485
569
|
if missing(value, name, self.undefined) {
|
|
486
|
-
report.add("required", source, name, 0)
|
|
570
|
+
report.add("required", source, name, 0)?;
|
|
487
571
|
}
|
|
488
572
|
}
|
|
489
573
|
let mut context = ObjectDiagnostics {
|
|
@@ -525,14 +609,20 @@ impl Engine {
|
|
|
525
609
|
}
|
|
526
610
|
}
|
|
527
611
|
for dependency in &rules.diagnostic_dependencies {
|
|
612
|
+
if !self.budget.checkpoint() {
|
|
613
|
+
return Err(Unsupported);
|
|
614
|
+
}
|
|
528
615
|
match dependency {
|
|
529
616
|
DiagnosticDependency::Required(name, names, keyword) => {
|
|
530
617
|
if missing(value, *name, self.undefined) {
|
|
531
618
|
continue;
|
|
532
619
|
}
|
|
533
620
|
for &required in names {
|
|
621
|
+
if !self.budget.checkpoint() {
|
|
622
|
+
return Err(Unsupported);
|
|
623
|
+
}
|
|
534
624
|
if missing(value, required, self.undefined) {
|
|
535
|
-
report.add(keyword, (source.0, *name), required, 0)
|
|
625
|
+
report.add(keyword, (source.0, *name), required, 0)?;
|
|
536
626
|
}
|
|
537
627
|
}
|
|
538
628
|
}
|
|
@@ -540,27 +630,42 @@ impl Engine {
|
|
|
540
630
|
if missing(value, *name, self.undefined) {
|
|
541
631
|
continue;
|
|
542
632
|
}
|
|
543
|
-
if
|
|
544
|
-
report.scoped(
|
|
633
|
+
if tracked {
|
|
634
|
+
let result = report.scoped(
|
|
545
635
|
&format!("{keyword}/{}", pointer_name(*name)?),
|
|
546
636
|
None,
|
|
547
|
-
|r|
|
|
637
|
+
|r| {
|
|
638
|
+
self.collect::<true>(
|
|
639
|
+
r,
|
|
640
|
+
*child,
|
|
641
|
+
value,
|
|
642
|
+
depth + 1,
|
|
643
|
+
active,
|
|
644
|
+
)
|
|
645
|
+
},
|
|
548
646
|
)?;
|
|
549
|
-
}
|
|
550
|
-
if tracked {
|
|
551
|
-
let result = self.eval_tracked_kind(
|
|
552
|
-
*child,
|
|
553
|
-
value,
|
|
554
|
-
depth + 1,
|
|
555
|
-
active,
|
|
556
|
-
kind,
|
|
557
|
-
);
|
|
558
|
-
if result.status == Unsupported {
|
|
559
|
-
return Err(Unsupported);
|
|
560
|
-
}
|
|
561
647
|
if result.status == Valid {
|
|
562
648
|
coverage.merge(result);
|
|
563
649
|
}
|
|
650
|
+
} else if self.needs_diagnostics(
|
|
651
|
+
*child,
|
|
652
|
+
value,
|
|
653
|
+
depth + 1,
|
|
654
|
+
active,
|
|
655
|
+
)? {
|
|
656
|
+
report.scoped(
|
|
657
|
+
&format!("{keyword}/{}", pointer_name(*name)?),
|
|
658
|
+
None,
|
|
659
|
+
|r| {
|
|
660
|
+
self.collect::<false>(
|
|
661
|
+
r,
|
|
662
|
+
*child,
|
|
663
|
+
value,
|
|
664
|
+
depth + 1,
|
|
665
|
+
active,
|
|
666
|
+
)
|
|
667
|
+
},
|
|
668
|
+
)?;
|
|
564
669
|
}
|
|
565
670
|
}
|
|
566
671
|
}
|
|
@@ -599,17 +704,18 @@ impl Engine {
|
|
|
599
704
|
Op::All(nodes) => {
|
|
600
705
|
let mut coverage = Tracked::new(Valid);
|
|
601
706
|
for (index, &child) in nodes.iter().enumerate() {
|
|
602
|
-
|
|
603
|
-
|
|
707
|
+
if !self.budget.checkpoint() {
|
|
708
|
+
return Err(Unsupported);
|
|
709
|
+
}
|
|
710
|
+
let result = report.scoped(&format!("allOf/{index}"), None, |r| {
|
|
711
|
+
if tracked {
|
|
712
|
+
self.collect_kind::<true>(r, child, value, depth + 1, active, kind)
|
|
713
|
+
} else {
|
|
714
|
+
self.collect_kind::<false>(r, child, value, depth + 1, active, kind)
|
|
715
|
+
}
|
|
604
716
|
})?;
|
|
605
717
|
if tracked {
|
|
606
|
-
coverage.merge_diagnostics(
|
|
607
|
-
child,
|
|
608
|
-
value,
|
|
609
|
-
depth + 1,
|
|
610
|
-
active,
|
|
611
|
-
kind,
|
|
612
|
-
))?;
|
|
718
|
+
coverage.merge_diagnostics(result)?;
|
|
613
719
|
}
|
|
614
720
|
}
|
|
615
721
|
if tracked {
|
|
@@ -617,52 +723,56 @@ impl Engine {
|
|
|
617
723
|
}
|
|
618
724
|
}
|
|
619
725
|
Op::Any(nodes) | Op::One(nodes) => {
|
|
620
|
-
let
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (matches!(op, Op::Any(_)) && matches == 0)
|
|
640
|
-
|| (matches!(op, Op::One(_)) && matches != 1)
|
|
641
|
-
{
|
|
642
|
-
report.add(
|
|
643
|
-
if matches!(op, Op::Any(_)) {
|
|
644
|
-
"anyOf"
|
|
645
|
-
} else {
|
|
646
|
-
"oneOf"
|
|
726
|
+
let branch_kind = if matches!(op, Op::Any(_)) {
|
|
727
|
+
evaluation::BranchKind::Any
|
|
728
|
+
} else {
|
|
729
|
+
evaluation::BranchKind::One
|
|
730
|
+
};
|
|
731
|
+
// Complete scanning preserves exact oneOf counts and VM
|
|
732
|
+
// exceptions from branches after an anyOf success.
|
|
733
|
+
let result = if tracked {
|
|
734
|
+
evaluation::scan_branches(
|
|
735
|
+
nodes,
|
|
736
|
+
evaluation::BranchPolicy::Complete,
|
|
737
|
+
|child| self.eval_tracked_kind(child, value, depth + 1, active, kind),
|
|
738
|
+
)?
|
|
739
|
+
} else {
|
|
740
|
+
let result = evaluation::scan_branches(
|
|
741
|
+
nodes,
|
|
742
|
+
evaluation::BranchPolicy::Complete,
|
|
743
|
+
|child| {
|
|
744
|
+
self.eval_with_kind(child, value, depth + 1, active, Some(kind))
|
|
647
745
|
},
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
matches,
|
|
651
|
-
|
|
746
|
+
)?;
|
|
747
|
+
evaluation::BranchMatches {
|
|
748
|
+
matches: result.matches,
|
|
749
|
+
coverage: Tracked::new(Valid),
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
if !branch_kind.accepts(result.matches) {
|
|
753
|
+
report.add(branch_kind.keyword(), source, value, result.matches)?;
|
|
652
754
|
} else if tracked {
|
|
653
|
-
prior.merge_diagnostics(coverage)?;
|
|
755
|
+
prior.merge_diagnostics(result.coverage)?;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
Op::Not(child) => {
|
|
759
|
+
match self.eval_with_kind(*child, value, depth + 1, active, Some(kind)) {
|
|
760
|
+
Unsupported => return Err(Unsupported),
|
|
761
|
+
Valid => report.add("not", source, value, 0)?,
|
|
762
|
+
Invalid => {}
|
|
654
763
|
}
|
|
655
764
|
}
|
|
656
|
-
Op::Not(child) => match self.eval(*child, value, depth + 1, active) {
|
|
657
|
-
Unsupported => return Err(Unsupported),
|
|
658
|
-
Valid => report.add("not", source, value, 0),
|
|
659
|
-
Invalid => {}
|
|
660
|
-
},
|
|
661
765
|
Op::Conditional(condition, yes, no) => {
|
|
662
766
|
let condition = if tracked {
|
|
663
767
|
self.eval_tracked_kind(*condition, value, depth + 1, active, kind)
|
|
664
768
|
} else {
|
|
665
|
-
Tracked::new(self.
|
|
769
|
+
Tracked::new(self.eval_with_kind(
|
|
770
|
+
*condition,
|
|
771
|
+
value,
|
|
772
|
+
depth + 1,
|
|
773
|
+
active,
|
|
774
|
+
Some(kind),
|
|
775
|
+
))
|
|
666
776
|
};
|
|
667
777
|
let mut coverage = Tracked::new(Valid);
|
|
668
778
|
let (child, segment) = match condition.status {
|
|
@@ -676,17 +786,22 @@ impl Engine {
|
|
|
676
786
|
Invalid => (no, "else"),
|
|
677
787
|
};
|
|
678
788
|
if let Some(child) = child {
|
|
679
|
-
report.scoped(segment, None, |r| {
|
|
680
|
-
|
|
789
|
+
let result = report.scoped(segment, None, |r| {
|
|
790
|
+
if tracked {
|
|
791
|
+
self.collect_kind::<true>(r, *child, value, depth + 1, active, kind)
|
|
792
|
+
} else {
|
|
793
|
+
self.collect_kind::<false>(
|
|
794
|
+
r,
|
|
795
|
+
*child,
|
|
796
|
+
value,
|
|
797
|
+
depth + 1,
|
|
798
|
+
active,
|
|
799
|
+
kind,
|
|
800
|
+
)
|
|
801
|
+
}
|
|
681
802
|
})?;
|
|
682
803
|
if tracked {
|
|
683
|
-
coverage.merge_diagnostics(
|
|
684
|
-
*child,
|
|
685
|
-
value,
|
|
686
|
-
depth + 1,
|
|
687
|
-
active,
|
|
688
|
-
kind,
|
|
689
|
-
))?;
|
|
804
|
+
coverage.merge_diagnostics(result)?;
|
|
690
805
|
}
|
|
691
806
|
}
|
|
692
807
|
if tracked {
|
|
@@ -707,34 +822,29 @@ impl Engine {
|
|
|
707
822
|
OP_DYNAMIC_REF => "$dynamicRef",
|
|
708
823
|
_ => "$ref",
|
|
709
824
|
};
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
825
|
+
let target = self.reference_target(*target, active);
|
|
826
|
+
let active = scope::Context {
|
|
827
|
+
active: Some(&frame),
|
|
828
|
+
..active
|
|
829
|
+
};
|
|
830
|
+
let result = report.scoped(keyword, None, |r| {
|
|
831
|
+
if tracked {
|
|
832
|
+
self.collect_kind::<true>(r, target, value, depth + 1, active, kind)
|
|
833
|
+
} else {
|
|
834
|
+
self.collect_kind::<false>(r, target, value, depth + 1, active, kind)
|
|
835
|
+
}
|
|
721
836
|
})?;
|
|
722
837
|
if tracked {
|
|
723
|
-
prior.merge_diagnostics(
|
|
724
|
-
self.reference_target(*target, active),
|
|
725
|
-
value,
|
|
726
|
-
depth + 1,
|
|
727
|
-
scope::Context {
|
|
728
|
-
active: Some(&frame),
|
|
729
|
-
..active
|
|
730
|
-
},
|
|
731
|
-
kind,
|
|
732
|
-
))?;
|
|
838
|
+
prior.merge_diagnostics(result)?;
|
|
733
839
|
}
|
|
734
840
|
}
|
|
735
841
|
}
|
|
736
842
|
}
|
|
737
|
-
|
|
843
|
+
let failed = report.failed;
|
|
844
|
+
report.failed |= parent_failed;
|
|
845
|
+
// Diagnostic coverage keeps local attempted locations for subsequent
|
|
846
|
+
// unevaluated checks; only a successful evaluation exports annotations.
|
|
847
|
+
Ok(if failed { Tracked::new(Invalid) } else { prior })
|
|
738
848
|
}
|
|
739
849
|
|
|
740
850
|
fn needs_diagnostics(
|
|
@@ -744,6 +854,9 @@ impl Engine {
|
|
|
744
854
|
depth: usize,
|
|
745
855
|
active: scope::Context<'_>,
|
|
746
856
|
) -> Result<bool, Verdict> {
|
|
857
|
+
if !self.budget.checkpoint() {
|
|
858
|
+
return Err(Unsupported);
|
|
859
|
+
}
|
|
747
860
|
let status = match self.nodes[node].as_slice() {
|
|
748
861
|
[Op::Number(required, rules)] if depth <= MAX_EVALUATION_DEPTH => {
|
|
749
862
|
rules.eval_guarded(*required, value)
|
|
@@ -773,7 +886,7 @@ impl Engine {
|
|
|
773
886
|
}
|
|
774
887
|
|
|
775
888
|
struct ObjectDiagnostics<'a, 'b, 'c> {
|
|
776
|
-
engine: &'a
|
|
889
|
+
engine: &'a Execution<'a>,
|
|
777
890
|
report: &'b mut Report,
|
|
778
891
|
rules: &'a Object,
|
|
779
892
|
depth: usize,
|
|
@@ -787,10 +900,13 @@ struct ObjectDiagnostics<'a, 'b, 'c> {
|
|
|
787
900
|
unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value: Value) -> i32 {
|
|
788
901
|
catch_unwind(AssertUnwindSafe(|| {
|
|
789
902
|
let c = unsafe { &mut *context.cast::<ObjectDiagnostics<'_, '_, '_>>() };
|
|
903
|
+
if !c.engine.budget.checkpoint() {
|
|
904
|
+
return Unsupported as i32;
|
|
905
|
+
}
|
|
790
906
|
let visit = |report: &mut Report, child: usize, item: Value, segment: &str, schema_key| {
|
|
791
907
|
if c.engine.needs_diagnostics(child, item, c.depth, c.active)? {
|
|
792
908
|
report.property(segment, key, schema_key, |r| {
|
|
793
|
-
c.engine.collect(r, child, item, c.depth, c.active)
|
|
909
|
+
c.engine.collect::<false>(r, child, item, c.depth, c.active)
|
|
794
910
|
})?;
|
|
795
911
|
}
|
|
796
912
|
Ok::<_, Verdict>(())
|
|
@@ -818,28 +934,14 @@ unsafe extern "C" fn object_diagnostic(context: *mut c_void, key: Value, value:
|
|
|
818
934
|
c.coverage.property(c.engine, key);
|
|
819
935
|
return Ok(());
|
|
820
936
|
}
|
|
821
|
-
let
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
for ((pattern, child), &name) in c.rules.patterns.iter().zip(&c.rules.pattern_names) {
|
|
830
|
-
match pattern.eval(key) {
|
|
831
|
-
Unsupported => return Err(Unsupported),
|
|
832
|
-
Valid => {
|
|
833
|
-
matched = true;
|
|
834
|
-
visit(c.report, *child, value, "patternProperties", Some(name))?;
|
|
835
|
-
}
|
|
836
|
-
Invalid => {}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if !matched && let Some(child) = c.rules.additional {
|
|
840
|
-
visit(c.report, child, value, "additionalProperties", None)?;
|
|
841
|
-
}
|
|
842
|
-
if c.tracked && (matched || c.rules.additional.is_some()) {
|
|
937
|
+
let evaluated = c.rules.visit_value_rules::<true>(
|
|
938
|
+
c.engine.caches,
|
|
939
|
+
&c.engine.budget,
|
|
940
|
+
key,
|
|
941
|
+
None,
|
|
942
|
+
|segment, name, child| visit(c.report, child, value, segment, name),
|
|
943
|
+
)?;
|
|
944
|
+
if c.tracked && evaluated {
|
|
843
945
|
c.coverage.property(c.engine, key);
|
|
844
946
|
}
|
|
845
947
|
Ok(())
|
|
@@ -866,23 +968,28 @@ pub struct Record {
|
|
|
866
968
|
detail: usize,
|
|
867
969
|
}
|
|
868
970
|
/// # Safety
|
|
869
|
-
/// Engine
|
|
971
|
+
/// Engine/status must be live under the GVL; root must be in bounds and
|
|
972
|
+
/// instance live. Caches must be null or live, validator-owned and not in use.
|
|
870
973
|
#[unsafe(no_mangle)]
|
|
871
974
|
pub unsafe extern "C" fn sn_diagnose(
|
|
872
975
|
engine: *const Engine,
|
|
976
|
+
caches: *const pattern::Caches,
|
|
977
|
+
root: usize,
|
|
873
978
|
instance: Value,
|
|
874
979
|
status: *mut i32,
|
|
875
980
|
) -> *mut Report {
|
|
876
981
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
877
982
|
let engine = unsafe { &*engine };
|
|
983
|
+
let local = pattern::Caches::default();
|
|
984
|
+
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
985
|
+
let execution = Execution {
|
|
986
|
+
engine,
|
|
987
|
+
caches,
|
|
988
|
+
memo: memo::Memo::default(),
|
|
989
|
+
budget: budget::Budget::default(),
|
|
990
|
+
};
|
|
878
991
|
let mut report = Report::default();
|
|
879
|
-
|
|
880
|
-
&mut report,
|
|
881
|
-
engine.root,
|
|
882
|
-
instance,
|
|
883
|
-
0,
|
|
884
|
-
scope::Context::default(),
|
|
885
|
-
)?;
|
|
992
|
+
execution.collect::<false>(&mut report, root, instance, 0, scope::Context::default())?;
|
|
886
993
|
Ok::<_, Verdict>(Box::into_raw(Box::new(report)))
|
|
887
994
|
}));
|
|
888
995
|
match result {
|
|
@@ -903,6 +1010,69 @@ pub unsafe extern "C" fn sn_diagnose(
|
|
|
903
1010
|
}
|
|
904
1011
|
}
|
|
905
1012
|
/// # Safety
|
|
1013
|
+
/// Same requirements as sn_diagnose; all limit/output pointers must be live.
|
|
1014
|
+
#[unsafe(no_mangle)]
|
|
1015
|
+
pub unsafe extern "C" fn sn_diagnose_with_limits(
|
|
1016
|
+
engine: *const Engine,
|
|
1017
|
+
caches: *const pattern::Caches,
|
|
1018
|
+
root: usize,
|
|
1019
|
+
instance: Value,
|
|
1020
|
+
status: *mut i32,
|
|
1021
|
+
limits: *const budget::Limits,
|
|
1022
|
+
outcome: *mut budget::Outcome,
|
|
1023
|
+
) -> *mut Report {
|
|
1024
|
+
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
1025
|
+
let local = pattern::Caches::default();
|
|
1026
|
+
let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
|
|
1027
|
+
let limits = unsafe { *limits };
|
|
1028
|
+
let execution = Execution {
|
|
1029
|
+
engine: unsafe { &*engine },
|
|
1030
|
+
caches,
|
|
1031
|
+
memo: memo::Memo::default(),
|
|
1032
|
+
budget: budget::Budget::new(limits),
|
|
1033
|
+
};
|
|
1034
|
+
let mut report = Report {
|
|
1035
|
+
limits,
|
|
1036
|
+
..Report::default()
|
|
1037
|
+
};
|
|
1038
|
+
let result = if execution.budget.check_deadline() {
|
|
1039
|
+
execution.collect::<false>(&mut report, root, instance, 0, scope::Context::default())
|
|
1040
|
+
} else {
|
|
1041
|
+
Err(Unsupported)
|
|
1042
|
+
};
|
|
1043
|
+
let result = if execution.budget.check_deadline() {
|
|
1044
|
+
result
|
|
1045
|
+
} else {
|
|
1046
|
+
Err(Unsupported)
|
|
1047
|
+
};
|
|
1048
|
+
let hit = if report.limit_hit.0 != 0 {
|
|
1049
|
+
report.limit_hit
|
|
1050
|
+
} else {
|
|
1051
|
+
execution.budget.hit.get()
|
|
1052
|
+
};
|
|
1053
|
+
unsafe {
|
|
1054
|
+
((*outcome).kind, (*outcome).attempted) = hit;
|
|
1055
|
+
}
|
|
1056
|
+
result.map(|_| Box::into_raw(Box::new(report)))
|
|
1057
|
+
}));
|
|
1058
|
+
match result {
|
|
1059
|
+
Ok(Ok(report)) => {
|
|
1060
|
+
unsafe { *status = VERDICT_VALID };
|
|
1061
|
+
report
|
|
1062
|
+
}
|
|
1063
|
+
other => {
|
|
1064
|
+
unsafe {
|
|
1065
|
+
*status = if other.is_err() {
|
|
1066
|
+
STATUS_PANIC
|
|
1067
|
+
} else {
|
|
1068
|
+
VERDICT_UNSUPPORTED
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
std::ptr::null_mut()
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
/// # Safety
|
|
906
1076
|
/// Report must be live under the GVL.
|
|
907
1077
|
#[unsafe(no_mangle)]
|
|
908
1078
|
pub unsafe extern "C" fn sn_report_len(report: *const Report) -> usize {
|