leptris 1.9.394.0-arm-linux → 1.9.396.0-arm-linux
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/Rakefile +9 -3
- data/ext/leptris/native/native.c +30 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +5 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/leptris/xml.rb +25 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +69 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/CMakeLists.txt +12 -5
- data/vendor-src/libleptris/src/leptris/common/simd_text.c +61 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.h +13 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text_avx2.c +156 -2
- data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +113 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +19 -43
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +44 -14
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +36 -4
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +8 -0
- data/vendor-src/libleptris/test/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/test/parser/test_encoding.cpp +115 -0
- data/vendor-src/libleptris/test/parser/test_utf8_simd.cpp +130 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +184 -4
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +3 -2
|
@@ -380,3 +380,116 @@ ptrdiff_t leptris_text_scan_run_neon(const char* s, size_t len, char term,
|
|
|
380
380
|
return (p < end) ? (ptrdiff_t)(p - s) : -1;
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
|
+
|
|
384
|
+
/* ---- UTF-8 validation (leptris#1842 A5) -------------------------------
|
|
385
|
+
* Same derivation as the SSE core in simd_text_avx2.c; NEON bytes are
|
|
386
|
+
* unsigned, so ranges are direct vcge/vcle pairs. */
|
|
387
|
+
int leptris_utf8_valid_neon(const char* src, size_t len) {
|
|
388
|
+
const unsigned char* p = (const unsigned char*)src;
|
|
389
|
+
if (len < 16) return leptris_utf8_valid_scalar(src, len);
|
|
390
|
+
|
|
391
|
+
const uint8x16_t zero = vdupq_n_u8(0);
|
|
392
|
+
const uint8x16_t ff = vdupq_n_u8(0xFF);
|
|
393
|
+
const uint8x16_t b80 = vdupq_n_u8(0x80);
|
|
394
|
+
const uint8x16_t bBF = vdupq_n_u8(0xBF);
|
|
395
|
+
uint8x16_t prev = zero;
|
|
396
|
+
|
|
397
|
+
#define IN_RANGE(v, lo, hi) \
|
|
398
|
+
vandq_u8(vcgeq_u8((v), vdupq_n_u8(lo)), vcleq_u8((v), vdupq_n_u8(hi)))
|
|
399
|
+
size_t i = 0;
|
|
400
|
+
while (i + 16 <= len) {
|
|
401
|
+
uint8x16_t cur = vld1q_u8(p + i);
|
|
402
|
+
/* lane j = byte(j-1): vext(prev, cur, k) keeps the last k
|
|
403
|
+
* lanes of prev at the head -> k = 15/14/13. */
|
|
404
|
+
uint8x16_t b1 = vextq_u8(prev, cur, 15);
|
|
405
|
+
uint8x16_t b2 = vextq_u8(prev, cur, 14);
|
|
406
|
+
uint8x16_t b3 = vextq_u8(prev, cur, 13);
|
|
407
|
+
|
|
408
|
+
uint8x16_t st2c = IN_RANGE(cur, 0xC2, 0xDF);
|
|
409
|
+
uint8x16_t st3c = IN_RANGE(cur, 0xE0, 0xEF);
|
|
410
|
+
uint8x16_t st4c = IN_RANGE(cur, 0xF0, 0xF4);
|
|
411
|
+
uint8x16_t is_ascii = vcltq_u8(cur, b80);
|
|
412
|
+
uint8x16_t legal_start = vorrq_u8(st2c, vorrq_u8(st3c, st4c));
|
|
413
|
+
|
|
414
|
+
uint8x16_t st2b1 = IN_RANGE(b1, 0xC2, 0xDF);
|
|
415
|
+
uint8x16_t st3b1 = IN_RANGE(b1, 0xE0, 0xEF);
|
|
416
|
+
uint8x16_t st4b1 = IN_RANGE(b1, 0xF0, 0xF4);
|
|
417
|
+
uint8x16_t st3b2 = IN_RANGE(b2, 0xE0, 0xEF);
|
|
418
|
+
uint8x16_t st4b2 = IN_RANGE(b2, 0xF0, 0xF4);
|
|
419
|
+
uint8x16_t st4b3 = IN_RANGE(b3, 0xF0, 0xF4);
|
|
420
|
+
|
|
421
|
+
uint8x16_t must_cont =
|
|
422
|
+
vorrq_u8(vorrq_u8(st2b1, vorrq_u8(st3b1, st4b1)),
|
|
423
|
+
vorrq_u8(vorrq_u8(st3b2, st4b2), st4b3));
|
|
424
|
+
|
|
425
|
+
const uint8x16_t bA0 = vdupq_n_u8(0xA0);
|
|
426
|
+
const uint8x16_t b90 = vdupq_n_u8(0x90);
|
|
427
|
+
const uint8x16_t b9F = vdupq_n_u8(0x9F);
|
|
428
|
+
const uint8x16_t b8F = vdupq_n_u8(0x8F);
|
|
429
|
+
uint8x16_t e0 = vceqq_u8(b1, vdupq_n_u8(0xE0));
|
|
430
|
+
uint8x16_t ed = vceqq_u8(b1, vdupq_n_u8(0xED));
|
|
431
|
+
uint8x16_t f0 = vceqq_u8(b1, vdupq_n_u8(0xF0));
|
|
432
|
+
uint8x16_t f4 = vceqq_u8(b1, vdupq_n_u8(0xF4));
|
|
433
|
+
uint8x16_t lo = vbslq_u8(e0, bA0, b80);
|
|
434
|
+
lo = vbslq_u8(f0, b90, lo);
|
|
435
|
+
uint8x16_t hi = vbslq_u8(ed, b9F, bBF);
|
|
436
|
+
hi = vbslq_u8(f4, b8F, hi);
|
|
437
|
+
|
|
438
|
+
uint8x16_t cont_ok = vandq_u8(vcgeq_u8(cur, lo), vcleq_u8(cur, hi));
|
|
439
|
+
|
|
440
|
+
uint8x16_t block_error = vorrq_u8(
|
|
441
|
+
vandq_u8(must_cont, veorq_u8(cont_ok, ff)),
|
|
442
|
+
vandq_u8(veorq_u8(must_cont, ff),
|
|
443
|
+
vandq_u8(veorq_u8(is_ascii, ff),
|
|
444
|
+
veorq_u8(legal_start, ff))));
|
|
445
|
+
/* any lane set? */
|
|
446
|
+
/* any lane set? */
|
|
447
|
+
if (vmaxvq_u8(block_error) != 0) return 0;
|
|
448
|
+
prev = cur;
|
|
449
|
+
i += 16;
|
|
450
|
+
}
|
|
451
|
+
if (i >= len) {
|
|
452
|
+
/* The input ENDS at the block edge: the last sequence must
|
|
453
|
+
* be complete (the scalar DFA's "len - i <= n" rule). Walk
|
|
454
|
+
* back over at most 3 continuations; s2 lands ON the last
|
|
455
|
+
* continuation (or the final non-continuation byte). */
|
|
456
|
+
size_t s2 = i;
|
|
457
|
+
size_t conts = 0;
|
|
458
|
+
while (s2 > 0 && (p[s2 - 1] & 0xC0) == 0x80 && conts < 3) {
|
|
459
|
+
s2--;
|
|
460
|
+
conts++;
|
|
461
|
+
}
|
|
462
|
+
if (conts > 0) {
|
|
463
|
+
if (s2 == 0) return 0; /* continuations, no opener anywhere */
|
|
464
|
+
unsigned op = p[s2 - 1]; /* the opener BEFORE the run */
|
|
465
|
+
unsigned owed = (op >= 0xF0) ? 3
|
|
466
|
+
: (op >= 0xE0) ? 2
|
|
467
|
+
: (op >= 0xC2) ? 1
|
|
468
|
+
: 0;
|
|
469
|
+
if (owed > conts) return 0;
|
|
470
|
+
return 1;
|
|
471
|
+
}
|
|
472
|
+
/* the last byte is not a continuation: it must be ASCII */
|
|
473
|
+
unsigned op = p[i - 1];
|
|
474
|
+
unsigned owed = (op >= 0xF0) ? 3
|
|
475
|
+
: (op >= 0xE0) ? 2
|
|
476
|
+
: (op >= 0xC2) ? 1
|
|
477
|
+
: 0;
|
|
478
|
+
return owed == 0 ? 1 : 0;
|
|
479
|
+
}
|
|
480
|
+
/* Tail: scalar DFA anchored at a character boundary (at most 3
|
|
481
|
+
* bytes back from the SIMD end; a sequence is at most 4) — the
|
|
482
|
+
* anchored run also covers a sequence spanning the block edge.
|
|
483
|
+
* One more step back when the byte before the anchor is a start
|
|
484
|
+
* byte: an opener in the final block lane reaches into the tail
|
|
485
|
+
* (400k-case randomized cross-check vs the scalar oracle). */
|
|
486
|
+
size_t start = i;
|
|
487
|
+
while (start > 0 && (p[start] & 0xC0) == 0x80 && i - start < 3)
|
|
488
|
+
start--;
|
|
489
|
+
if (i - start < 3 && start > 0) {
|
|
490
|
+
unsigned char b = p[start - 1];
|
|
491
|
+
if (b >= 0xC2 && b <= 0xF4) start--;
|
|
492
|
+
}
|
|
493
|
+
return leptris_utf8_valid_scalar((const char*)(p + start), len - start);
|
|
494
|
+
}
|
|
495
|
+
#undef IN_RANGE
|
|
@@ -2314,6 +2314,12 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
2314
2314
|
if (*p.pos != '?') {
|
|
2315
2315
|
if (dp_nul(&p, p.pos)) goto fail;
|
|
2316
2316
|
p.pos++;
|
|
2317
|
+
/* consuming the separator may exhaust the buffer
|
|
2318
|
+
* (" <?xm" — the fuzzer shape): p.pos one past
|
|
2319
|
+
* p.end made p.end - p.pos underflow to SIZE_MAX and
|
|
2320
|
+
* the data memchr below walked off the allocation
|
|
2321
|
+
* (nightly-fuzz crash-aac3c9bb, no-LTO build) */
|
|
2322
|
+
if (p.pos >= p.end) goto fail;
|
|
2317
2323
|
data_start = p.pos;
|
|
2318
2324
|
}
|
|
2319
2325
|
/* memchr for '?' — fast-skip PI data bodies. */
|
|
@@ -3531,48 +3537,15 @@ static int dp_declares_non_utf8(const char* s, size_t len) {
|
|
|
3531
3537
|
}
|
|
3532
3538
|
|
|
3533
3539
|
static int dp_utf8_valid(const char* s, size_t len) {
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
*
|
|
3538
|
-
*
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
i += 8;
|
|
3544
|
-
}
|
|
3545
|
-
while (i < len && p[i] < 0x80) i++;
|
|
3546
|
-
while (i < len) {
|
|
3547
|
-
unsigned c = p[i];
|
|
3548
|
-
if (c < 0x80) {
|
|
3549
|
-
i++;
|
|
3550
|
-
continue;
|
|
3551
|
-
}
|
|
3552
|
-
size_t n;
|
|
3553
|
-
unsigned lo = 0x80;
|
|
3554
|
-
unsigned hi = 0xBF;
|
|
3555
|
-
if (c >= 0xC2 && c <= 0xDF) {
|
|
3556
|
-
n = 1;
|
|
3557
|
-
} else if (c >= 0xE0 && c <= 0xEF) {
|
|
3558
|
-
n = 2;
|
|
3559
|
-
if (c == 0xE0) lo = 0xA0;
|
|
3560
|
-
else if (c == 0xED) hi = 0x9F;
|
|
3561
|
-
} else if (c >= 0xF0 && c <= 0xF4) {
|
|
3562
|
-
n = 3;
|
|
3563
|
-
if (c == 0xF0) lo = 0x90;
|
|
3564
|
-
else if (c == 0xF4) hi = 0x8F;
|
|
3565
|
-
} else {
|
|
3566
|
-
return 0;
|
|
3567
|
-
}
|
|
3568
|
-
if (len - i <= n) return 0;
|
|
3569
|
-
if (p[i + 1] < lo || p[i + 1] > hi) return 0;
|
|
3570
|
-
for (size_t k = 2; k <= n; k++) {
|
|
3571
|
-
if (p[i + k] < 0x80 || p[i + k] > 0xBF) return 0;
|
|
3572
|
-
}
|
|
3573
|
-
i += n + 1;
|
|
3574
|
-
}
|
|
3575
|
-
return 1;
|
|
3540
|
+
/* #1842 A5: the per-byte DFA moved to the SIMD layer —
|
|
3541
|
+
* leptris_utf8_valid dispatches to boundary-vector cores (AVX2/
|
|
3542
|
+
* NEON) with the DFA as the fallback+oracle. Pure accept/reject
|
|
3543
|
+
* on the caller's bytes: no buffer-identity interaction (the A3
|
|
3544
|
+
* revert settled that question for xml_buffer; this face only
|
|
3545
|
+
* READS). */
|
|
3546
|
+
(void)s;
|
|
3547
|
+
(void)len;
|
|
3548
|
+
return leptris_utf8_valid(s, len);
|
|
3576
3549
|
}
|
|
3577
3550
|
|
|
3578
3551
|
static int dp_input_encoding_ok(const char* s, size_t len) {
|
|
@@ -3658,7 +3631,10 @@ struct leptris_document* direct_parse_inplace(char* buf, size_t len) {
|
|
|
3658
3631
|
if (!dp_declares_non_utf8(buf, len) && saw_high &&
|
|
3659
3632
|
!dp_utf8_valid(buf, len))
|
|
3660
3633
|
return NULL;
|
|
3661
|
-
|
|
3634
|
+
/* No sentinel write past len: the caller owns exactly `len`
|
|
3635
|
+
* writable bytes (the header contract promises NULL terminators
|
|
3636
|
+
* INSIDE the buffer; an exactly-sized caller trips ASAN, and
|
|
3637
|
+
* bindings hand read-only views). The scanner takes `len`. */
|
|
3662
3638
|
return direct_parse_internal(buf, len, 0, 0, 0, 0, 0, 0,
|
|
3663
3639
|
lt_count, dq_count, sq_count);
|
|
3664
3640
|
}
|
|
@@ -183,7 +183,9 @@ static void xsd_cm_free(XsdCm* cm) {
|
|
|
183
183
|
free(cm->name);
|
|
184
184
|
free(cm->type);
|
|
185
185
|
free(cm->ns);
|
|
186
|
-
free(cm->
|
|
186
|
+
free(cm->res_base);
|
|
187
|
+
free(cm->res_base_ns);
|
|
188
|
+
free(cm->ext_base);
|
|
187
189
|
free(cm->ext_base_ns);
|
|
188
190
|
free(cm->any_ns);
|
|
189
191
|
free(cm->not_qname);
|
|
@@ -1858,11 +1860,11 @@ static void xsd_collect_decls_doc(LeptrisElement root, const char* eff_tns,
|
|
|
1858
1860
|
xsd_collect_decls_doc(subroot, eff, sub_base, decls, n, max,
|
|
1859
1861
|
claimed, nclaimed, cmax, depth + 1);
|
|
1860
1862
|
}
|
|
1861
|
-
/* leptris#1913:
|
|
1862
|
-
* to the symbol space too — walk the
|
|
1863
|
-
* pseudo-root (it has no
|
|
1864
|
-
* own). */
|
|
1865
|
-
if (strcmp(l, "override") == 0)
|
|
1863
|
+
/* leptris#1913/#1926: override and redefine bodies'
|
|
1864
|
+
* declarations belong to the symbol space too — walk the
|
|
1865
|
+
* derivation element as a pseudo-root (it has no
|
|
1866
|
+
* include/import children of its own). */
|
|
1867
|
+
if (strcmp(l, "override") == 0 || strcmp(l, "redefine") == 0)
|
|
1866
1868
|
xsd_collect_decls_doc(c, eff_tns, base_dir, decls, n, max,
|
|
1867
1869
|
claimed, nclaimed, cmax, depth + 1);
|
|
1868
1870
|
leptris_document_free(sub);
|
|
@@ -2343,14 +2345,20 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
|
|
|
2343
2345
|
if (sub_ns) free(sub_ns);
|
|
2344
2346
|
s->target_ns = saved_ns;
|
|
2345
2347
|
}
|
|
2346
|
-
/* leptris#1913 (XSD 1.1 §I.4)
|
|
2347
|
-
*
|
|
2348
|
-
*
|
|
2349
|
-
*
|
|
2350
|
-
*
|
|
2351
|
-
* sees the
|
|
2348
|
+
/* leptris#1913 (XSD 1.1 §I.4) + #1926 (XSD
|
|
2349
|
+
* 1.0 §4.2.2): the override/redefine
|
|
2350
|
+
* element's OWN declaration children replace
|
|
2351
|
+
* same-named components of the base just
|
|
2352
|
+
* captured — capture them AFTER the base so
|
|
2353
|
+
* the lookup ladder (head-first) sees the
|
|
2354
|
+
* replacements. A redefine body that EXTENDS
|
|
2355
|
+
* the redefined name folds its extension
|
|
2356
|
+
* against the base at this moment (base
|
|
2357
|
+
* captured, ladder sees the new head), which
|
|
2358
|
+
* is exactly the redefine hook. */
|
|
2352
2359
|
if (subroot && same_ns &&
|
|
2353
|
-
strcmp(local, "override") == 0
|
|
2360
|
+
(strcmp(local, "override") == 0 ||
|
|
2361
|
+
strcmp(local, "redefine") == 0)) {
|
|
2354
2362
|
s->include_depth++;
|
|
2355
2363
|
s->capture_force = 1;
|
|
2356
2364
|
xsd_capture_root(s, (LeptrisElement)n);
|
|
@@ -2946,9 +2954,21 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
|
|
|
2946
2954
|
attrs = own_attrs;
|
|
2947
2955
|
}
|
|
2948
2956
|
} else {
|
|
2949
|
-
/* restriction: the derived model REPLACES
|
|
2957
|
+
/* restriction: the derived model REPLACES.
|
|
2958
|
+
* leptris#1923: the base still rides the type —
|
|
2959
|
+
* libxml2 admits the restriction chain's declared
|
|
2960
|
+
* attributes at validation, so record the base
|
|
2961
|
+
* (local + resolved ns) for the strict-attribute
|
|
2962
|
+
* admission walk. */
|
|
2950
2963
|
root->first_child = xsd_cm_parse(s, dq, NULL);
|
|
2951
2964
|
attrs = xsd_capture_attr_rows(s, dq, 0);
|
|
2965
|
+
if (base && strncmp(base, "xs:", 3) != 0) {
|
|
2966
|
+
free(root->res_base);
|
|
2967
|
+
root->res_base = xsd_strdup(base);
|
|
2968
|
+
free(root->res_base_ns);
|
|
2969
|
+
root->res_base_ns =
|
|
2970
|
+
buri[0] ? xsd_strdup(buri) : NULL;
|
|
2971
|
+
}
|
|
2952
2972
|
}
|
|
2953
2973
|
/* XSD 1.1: asserts nested under the derivation */
|
|
2954
2974
|
if (s->version_11)
|
|
@@ -3015,6 +3035,16 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
|
|
|
3015
3035
|
} else {
|
|
3016
3036
|
root->text_type = xsd_strdup(base);
|
|
3017
3037
|
}
|
|
3038
|
+
/* leptris#1923: a simpleContent restriction
|
|
3039
|
+
* rides its base for strict-attribute
|
|
3040
|
+
* admission (libxml2 admits the chain's
|
|
3041
|
+
* declared attributes — ctE019). */
|
|
3042
|
+
if (strcmp(dl, "restriction") == 0) {
|
|
3043
|
+
free(root->res_base);
|
|
3044
|
+
root->res_base = xsd_strdup(base);
|
|
3045
|
+
free(root->res_base_ns);
|
|
3046
|
+
root->res_base_ns = NULL;
|
|
3047
|
+
}
|
|
3018
3048
|
}
|
|
3019
3049
|
}
|
|
3020
3050
|
attrs = xsd_capture_attr_rows(s, dq, 0);
|
|
@@ -635,7 +635,8 @@ static int any_attr_matches(const char* grammar, const char* uri,
|
|
|
635
635
|
static void validate_attributes(struct xsd_validator* v,
|
|
636
636
|
struct leptris_xsd_schema* s,
|
|
637
637
|
LeptrisElement elem,
|
|
638
|
-
const XsdTypeAttrs* ta
|
|
638
|
+
const XsdTypeAttrs* ta,
|
|
639
|
+
const XsdCm* chain_ct) {
|
|
639
640
|
/* ta == NULL (no captured rows — attribute-less complexTypes)
|
|
640
641
|
* skips the walk entirely; simple/builtin element validation
|
|
641
642
|
* passes k_no_attrs to run the strict walk with zero declared
|
|
@@ -688,6 +689,37 @@ static void validate_attributes(struct xsd_validator* v,
|
|
|
688
689
|
if (strcmp(a->name, local) == 0 ||
|
|
689
690
|
strcmp(a->name, qn) == 0)
|
|
690
691
|
declared = 1;
|
|
692
|
+
/* leptris#1923 (oracle parity with libxml2): the restriction
|
|
693
|
+
* chain's declared attributes stay visible for strict
|
|
694
|
+
* admission — a RESTRICTION that narrows the base's rows
|
|
695
|
+
* still admits what the base declared (ctE019). */
|
|
696
|
+
if (!declared && chain_ct) {
|
|
697
|
+
const XsdCm* anc = chain_ct;
|
|
698
|
+
for (int hops = 0; anc && anc->res_base && hops < 8;
|
|
699
|
+
hops++) {
|
|
700
|
+
const XsdTypeAttrs* base_rows = xsd_find_type_attrs_ns(
|
|
701
|
+
s, anc->res_base,
|
|
702
|
+
anc->res_base_ns ? anc->res_base_ns
|
|
703
|
+
: xsd_target_ns(s));
|
|
704
|
+
if (!base_rows)
|
|
705
|
+
base_rows = xsd_find_type_attrs(s, anc->res_base);
|
|
706
|
+
for (XsdAttrDecl* a = base_rows ? base_rows->attrs
|
|
707
|
+
: NULL;
|
|
708
|
+
a && !declared; a = a->next)
|
|
709
|
+
if (strcmp(a->name, local) == 0 ||
|
|
710
|
+
strcmp(a->name, qn) == 0)
|
|
711
|
+
declared = 1;
|
|
712
|
+
if (declared) break;
|
|
713
|
+
const XsdCm* base_ct = anc->res_base
|
|
714
|
+
? xsd_find_complex_ns(s, anc->res_base,
|
|
715
|
+
anc->res_base_ns ? anc->res_base_ns
|
|
716
|
+
: xsd_target_ns(s))
|
|
717
|
+
: NULL;
|
|
718
|
+
if (!base_ct)
|
|
719
|
+
base_ct = xsd_find_complex(s, anc->res_base);
|
|
720
|
+
anc = base_ct;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
691
723
|
if (declared) continue;
|
|
692
724
|
/* namespace of the attribute: engine-side attributes
|
|
693
725
|
* carry their uri through the expanded form */
|
|
@@ -824,7 +856,7 @@ static void validate_local_uri(struct xsd_validator* v,
|
|
|
824
856
|
const XsdTypeAttrs* rows =
|
|
825
857
|
xsd_find_type_attrs_ns(s, type, euri);
|
|
826
858
|
if (!rows) rows = xsd_find_type_attrs(s, type);
|
|
827
|
-
validate_attributes(v, s, elem, rows);
|
|
859
|
+
validate_attributes(v, s, elem, rows, ct);
|
|
828
860
|
validate_children(v, s, elem, ct);
|
|
829
861
|
/* XSD 1.1: the type's xs:assert list — context is the
|
|
830
862
|
* element; evaluation errors are failures */
|
|
@@ -866,7 +898,7 @@ static void validate_local_uri(struct xsd_validator* v,
|
|
|
866
898
|
if (strncmp(type, "xs:", 3) == 0 ||
|
|
867
899
|
xsd_find_simple_pub(s, type)) {
|
|
868
900
|
static const XsdTypeAttrs k_no_attrs;
|
|
869
|
-
validate_attributes(v, s, elem, &k_no_attrs);
|
|
901
|
+
validate_attributes(v, s, elem, &k_no_attrs, NULL);
|
|
870
902
|
const char* text = leptris_element_text(elem);
|
|
871
903
|
if (text && *text)
|
|
872
904
|
text_valid(v, s, type, text, euri, "element", local,
|
|
@@ -886,7 +918,7 @@ static void validate_local_uri(struct xsd_validator* v,
|
|
|
886
918
|
(int)sizeof(builtin) &&
|
|
887
919
|
xsd_builtin_known(builtin)) {
|
|
888
920
|
static const XsdTypeAttrs k_no_attrs;
|
|
889
|
-
validate_attributes(v, s, elem, &k_no_attrs);
|
|
921
|
+
validate_attributes(v, s, elem, &k_no_attrs, NULL);
|
|
890
922
|
const char* text = leptris_element_text(elem);
|
|
891
923
|
if (text && *text)
|
|
892
924
|
text_valid(v, s, builtin, text, euri, "element",
|
|
@@ -120,6 +120,14 @@ typedef struct xsd_cm {
|
|
|
120
120
|
char* ext_base; /* extension whose base was not yet captured at
|
|
121
121
|
* capture time (forward ref) — folded by the
|
|
122
122
|
* post-capture resolver (leptris-ruby#440 A) */
|
|
123
|
+
char* res_base; /* restriction whose base was not yet captured at
|
|
124
|
+
* capture time — the post-capture resolver only
|
|
125
|
+
* folds EXTENSION models; the restriction's model
|
|
126
|
+
* replaces the base's, so this rides along solely
|
|
127
|
+
* for strict-attribute ADMISSION (leptris#1923:
|
|
128
|
+
* libxml2 admits the restriction chain's declared
|
|
129
|
+
* attributes) */
|
|
130
|
+
char* res_base_ns;
|
|
123
131
|
char* ext_base_ns; /* #1881: the base QName's RESOLVED namespace
|
|
124
132
|
* (prefix resolved against the extension
|
|
125
133
|
* element's in-scope declarations) — a set
|
|
@@ -118,6 +118,7 @@ target_compile_definitions(test_sch_corpus PRIVATE
|
|
|
118
118
|
leptris_add_test(test_parser parser/test_parser.cpp)
|
|
119
119
|
leptris_add_test(test_large_docs parser/test_large_documents.cpp)
|
|
120
120
|
leptris_add_test(test_encoding parser/test_encoding.cpp)
|
|
121
|
+
leptris_add_test(test_utf8_simd parser/test_utf8_simd.cpp)
|
|
121
122
|
leptris_add_test(test_dom dom/test_dom.cpp)
|
|
122
123
|
leptris_add_test(test_element_children dom/test_element_children.cpp)
|
|
123
124
|
leptris_add_test(test_entity_refs dom/test_entity_refs.cpp)
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
#include "leptris.h"
|
|
10
10
|
|
|
11
|
+
#include "../../src/leptris/common/simd_text.h"
|
|
12
|
+
|
|
11
13
|
#include <cstring>
|
|
12
14
|
#include <vector>
|
|
13
15
|
|
|
@@ -367,3 +369,116 @@ TEST(EncodingLatin1, WithEncodingConvertsToUtf8) {
|
|
|
367
369
|
EXPECT_EQ((unsigned char)text[4], 0xA9);
|
|
368
370
|
leptris_document_free(doc);
|
|
369
371
|
}
|
|
372
|
+
|
|
373
|
+
// leptris-py CI regression (2026-10-31): the binding's flow is
|
|
374
|
+
// fast-path parse (raw UTF-16 into the UTF-8 parser) then retry via
|
|
375
|
+
// the encoding-detecting entry. On x86 CI the retry of a BOM-only
|
|
376
|
+
// document whose text contains a multi-byte character failed with
|
|
377
|
+
// "malformed input". Mirror the flow stage by stage so the failing
|
|
378
|
+
// stage is visible on every architecture.
|
|
379
|
+
TEST(EncodingUtf16Le, PyBindingFlowBomWithMultibyteText) {
|
|
380
|
+
// "<?xml version='1.0'?><r><a>café</a></r>" as UTF-16LE + BOM —
|
|
381
|
+
// the exact bytes the py fixture produces.
|
|
382
|
+
static const unsigned char kDoc[] = {
|
|
383
|
+
0xFF, 0xFE,
|
|
384
|
+
'<', 0x00, '?', 0x00, 'x', 0x00, 'm', 0x00, 'l', 0x00,
|
|
385
|
+
' ', 0x00, 'v', 0x00, 'e', 0x00, 'r', 0x00, 's', 0x00,
|
|
386
|
+
'i', 0x00, 'o', 0x00, 'n', 0x00, '=', 0x00, '\'', 0x00,
|
|
387
|
+
'1', 0x00, '.', 0x00, '0', 0x00, '\'', 0x00, '?', 0x00,
|
|
388
|
+
'>', 0x00,
|
|
389
|
+
'<', 0x00, 'r', 0x00, '>', 0x00,
|
|
390
|
+
'<', 0x00, 'a', 0x00, '>', 0x00,
|
|
391
|
+
'c', 0x00, 'a', 0x00, 'f', 0x00, 0xE9, 0x00,
|
|
392
|
+
'<', 0x00, '/', 0x00, 'a', 0x00, '>', 0x00,
|
|
393
|
+
'<', 0x00, '/', 0x00, 'r', 0x00, '>', 0x00,
|
|
394
|
+
};
|
|
395
|
+
const size_t n = sizeof kDoc;
|
|
396
|
+
ASSERT_EQ(n, 80u);
|
|
397
|
+
|
|
398
|
+
// Stage 1 — the UTF-8 entry over raw UTF-16 (py fast path): the
|
|
399
|
+
// lenient contract is parse-or-fail, never a crash. The inplace
|
|
400
|
+
// entry owns the buffer it is given (NUL-masking is part of the
|
|
401
|
+
// inplace contract), so the scratch copy absorbs the mutation —
|
|
402
|
+
// the pristine kDoc stays for the retry.
|
|
403
|
+
std::vector<unsigned char> buf(kDoc, kDoc + n);
|
|
404
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
405
|
+
LeptrisDocument fast = leptris_parse_string_inplace(
|
|
406
|
+
reinterpret_cast<char*>(buf.data()), n, &st);
|
|
407
|
+
if (fast) leptris_document_free(fast);
|
|
408
|
+
ASSERT_EQ(buf.size(), n) << "inplace parse must not resize";
|
|
409
|
+
|
|
410
|
+
// Stage 2 — the encoding-detecting retry on the pristine bytes:
|
|
411
|
+
// BOM-only, multi-byte payload; must convert and parse.
|
|
412
|
+
LeptrisDocument doc = leptris_parse_string_with_encoding(
|
|
413
|
+
reinterpret_cast<const char*>(kDoc), n, &st);
|
|
414
|
+
ASSERT_NE(doc, nullptr) << "status: " << leptris_status_string(st);
|
|
415
|
+
|
|
416
|
+
LeptrisElement root = leptris_document_root(doc);
|
|
417
|
+
ASSERT_NE(root, nullptr);
|
|
418
|
+
EXPECT_STREQ(leptris_element_name(root), "r");
|
|
419
|
+
LeptrisElement a = leptris_element_first_child_any(root);
|
|
420
|
+
ASSERT_NE(a, nullptr);
|
|
421
|
+
const char* text = leptris_element_text(a);
|
|
422
|
+
ASSERT_STREQ(text, "caf\xC3\xA9");
|
|
423
|
+
|
|
424
|
+
leptris_document_free(doc);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// x86-only multibyte-parse bisection (follows the PyBindingFlow spec):
|
|
428
|
+
// the Utf8Simd matrix is green on every lane while multibyte PARSES
|
|
429
|
+
// fail on all four x86 lanes. Stage the flow so the failing surface
|
|
430
|
+
// names itself: validator verdict, per-sequence-length parses, the
|
|
431
|
+
// converted-buffer shape, and the interleaved vs default lane.
|
|
432
|
+
TEST(EncodingUtf8Strict, X86MultibyteBisection) {
|
|
433
|
+
const char two[] = "<r>caf\xC3\xA9</r>";
|
|
434
|
+
const char three[] = "<r>\xE4\xB8\xAD</r>";
|
|
435
|
+
const char four[] = "<r>\xF0\x9F\x98\x80</r>";
|
|
436
|
+
const char mix[] = "<r>caf\xC3\xA9 \xE4\xB8\xAD \xF0\x9F\x98\x80</r>";
|
|
437
|
+
EXPECT_EQ(leptris_utf8_valid(two, strlen(two)), 1) << "validator: 2-byte doc";
|
|
438
|
+
EXPECT_EQ(leptris_utf8_valid(three, strlen(three)), 1) << "validator: 3-byte doc";
|
|
439
|
+
EXPECT_EQ(leptris_utf8_valid(four, strlen(four)), 1) << "validator: 4-byte doc";
|
|
440
|
+
/* Localize the offending lane: every prefix of the mixed doc is
|
|
441
|
+
* either valid or ends mid-sequence (which the scalar oracle
|
|
442
|
+
* rejects too) — print the verdict map so the first SIMD/scalar
|
|
443
|
+
* divergence names its byte offset. */
|
|
444
|
+
{
|
|
445
|
+
std::string map;
|
|
446
|
+
for (size_t k = 0; k <= strlen(mix); k++) {
|
|
447
|
+
int sc = leptris_utf8_valid_scalar(mix, k);
|
|
448
|
+
int si = leptris_utf8_valid(mix, k);
|
|
449
|
+
if (sc != si || k == strlen(mix))
|
|
450
|
+
map += " [" + std::to_string(k) + "]:" + std::to_string(sc)
|
|
451
|
+
+ "/" + std::to_string(si);
|
|
452
|
+
}
|
|
453
|
+
EXPECT_EQ(leptris_utf8_valid(mix, strlen(mix)), 1)
|
|
454
|
+
<< "validator: mixed doc; divergences scalar/simd" << map;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
458
|
+
LeptrisDocument d2 = leptris_parse_string(two, strlen(two), &st);
|
|
459
|
+
EXPECT_NE(d2, nullptr) << "parse 2-byte: " << leptris_status_string(st);
|
|
460
|
+
if (d2) leptris_document_free(d2);
|
|
461
|
+
LeptrisDocument d3 = leptris_parse_string(three, strlen(three), &st);
|
|
462
|
+
EXPECT_NE(d3, nullptr) << "parse 3-byte: " << leptris_status_string(st);
|
|
463
|
+
if (d3) leptris_document_free(d3);
|
|
464
|
+
LeptrisDocument d4 = leptris_parse_string(four, strlen(four), &st);
|
|
465
|
+
EXPECT_NE(d4, nullptr) << "parse 4-byte: " << leptris_status_string(st);
|
|
466
|
+
if (d4) leptris_document_free(d4);
|
|
467
|
+
|
|
468
|
+
/* The converted-buffer shape from the py flow (40 bytes). */
|
|
469
|
+
const char conv[] = "<?xml version='1.0'?><r><a>caf\xC3\xA9</a></r>";
|
|
470
|
+
EXPECT_EQ(leptris_utf8_valid(conv, strlen(conv)), 1) << "validator: converted buffer";
|
|
471
|
+
LeptrisDocument dc = leptris_parse_string(conv, strlen(conv), &st);
|
|
472
|
+
EXPECT_NE(dc, nullptr) << "parse converted: " << leptris_status_string(st);
|
|
473
|
+
if (dc) leptris_document_free(dc);
|
|
474
|
+
|
|
475
|
+
/* The default (non-interleaved) lane via RECOVER. */
|
|
476
|
+
LeptrisParseOptions opts;
|
|
477
|
+
memset(&opts, 0, sizeof opts);
|
|
478
|
+
opts.flags = LEPTRIS_PARSE_RECOVER;
|
|
479
|
+
opts.strict_mode = -1;
|
|
480
|
+
LeptrisDocument dr = leptris_parse_string_ex(mix, strlen(mix),
|
|
481
|
+
&opts, &st);
|
|
482
|
+
EXPECT_NE(dr, nullptr) << "parse mixed (default lane): " << leptris_status_string(st);
|
|
483
|
+
if (dr) leptris_document_free(dr);
|
|
484
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// #1842 A5: the SIMD UTF-8 validator must agree with the scalar DFA
|
|
2
|
+
// on every input — adversarial boundary matrix plus a randomized
|
|
3
|
+
// cross-check over lengths straddling the 16-byte block edge.
|
|
4
|
+
#include "../../src/leptris/common/simd_text.h"
|
|
5
|
+
|
|
6
|
+
#include <gtest/gtest.h>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <cstdlib>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <utility>
|
|
11
|
+
#include <vector>
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
TEST(Utf8Simd, SingleByteValues) {
|
|
17
|
+
for (int b = 0; b <= 255; b++) {
|
|
18
|
+
char buf[1] = {(char)b};
|
|
19
|
+
int scalar = leptris_utf8_valid_scalar(buf, 1);
|
|
20
|
+
int simd = leptris_utf8_valid(buf, 1);
|
|
21
|
+
EXPECT_EQ(simd, scalar) << "byte 0x" << std::hex << b << std::dec;
|
|
22
|
+
bool expect = (b < 0x80); /* a lone start byte is truncated */
|
|
23
|
+
EXPECT_EQ(scalar, expect ? 1 : 0)
|
|
24
|
+
<< "byte 0x" << std::hex << b << std::dec;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
TEST(Utf8Simd, SequenceMatrixAcrossBlockEdges) {
|
|
29
|
+
// (case, expected) — corrupt rows must be rejected too.
|
|
30
|
+
const std::vector<std::pair<std::string, int>> cases = {
|
|
31
|
+
// valid
|
|
32
|
+
{{"\xC2\xA0"}, 1}, {{"\xDF\xBF"}, 1}, {{"\xE0\xA0\x80"}, 1},
|
|
33
|
+
{{"\xE1\x80\x80"}, 1}, {{"\xED\x9F\xBF"}, 1}, {{"\xEE\x80\x80"}, 1},
|
|
34
|
+
{{"\xEF\xBF\xBD"}, 1}, {{"\xF0\x90\x80\x80"}, 1},
|
|
35
|
+
{{"\xF1\xBF\xBF\xBF"}, 1}, {{"\xF3\xA0\x80\x80"}, 1},
|
|
36
|
+
{{"\xF4\x8F\xBF\xBF"}, 1},
|
|
37
|
+
// overlong / surrogates / out-of-range
|
|
38
|
+
{{"\xC0\x80"}, 0}, {{"\xC1\xBF"}, 0}, {{"\xE0\x80\x80"}, 0},
|
|
39
|
+
{{"\xE0\x9F\xBF"}, 0}, {{"\xED\xA0\x80"}, 0}, {{"\xED\xBF\xBF"}, 0},
|
|
40
|
+
{{"\xF0\x80\x80\x80"}, 0}, {{"\xF0\x8F\xBF\xBF"}, 0},
|
|
41
|
+
{{"\xF4\x90\x80\x80"}, 0}, {{"\xF5\x80\x80\x80"}, 0},
|
|
42
|
+
// truncated (each prefix)
|
|
43
|
+
{{"\xC2"}, 0}, {{"\xE0\xA0"}, 0}, {{"\xF0\x90\x80"}, 0},
|
|
44
|
+
{{"\xF4\x8F"}, 0},
|
|
45
|
+
// stray continuation
|
|
46
|
+
{{"\x80"}, 0}, {{"a\x80" "b"}, 0}, {{"\xBF"}, 0},
|
|
47
|
+
};
|
|
48
|
+
for (std::vector<std::pair<std::string, int> >::const_iterator
|
|
49
|
+
it = cases.begin();
|
|
50
|
+
it != cases.end(); ++it) {
|
|
51
|
+
const std::string& s = it->first;
|
|
52
|
+
const int expect = it->second;
|
|
53
|
+
for (int pad = 0; pad < 20; pad++) {
|
|
54
|
+
std::string buf((size_t)pad, 'a');
|
|
55
|
+
buf += s;
|
|
56
|
+
int scalar =
|
|
57
|
+
leptris_utf8_valid_scalar(buf.data(), buf.size());
|
|
58
|
+
int simd = leptris_utf8_valid(buf.data(), buf.size());
|
|
59
|
+
EXPECT_EQ(simd, scalar)
|
|
60
|
+
<< "case len=" << s.size() << " pad=" << pad;
|
|
61
|
+
EXPECT_EQ(scalar, expect)
|
|
62
|
+
<< "verdict drifted: len=" << s.size() << " pad=" << pad;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
TEST(Utf8Simd, BlockEdgeSizes) {
|
|
68
|
+
for (size_t n = 0; n <= 40; n++) {
|
|
69
|
+
std::string buf(n, 'x');
|
|
70
|
+
buf += "\xC3\xA9";
|
|
71
|
+
ASSERT_EQ(leptris_utf8_valid_scalar(buf.data(), buf.size()), 1);
|
|
72
|
+
ASSERT_EQ(leptris_utf8_valid(buf.data(), buf.size()), 1) << "n=" << n;
|
|
73
|
+
}
|
|
74
|
+
for (size_t n = 0; n <= 40; n++) {
|
|
75
|
+
std::string buf(n, 'x');
|
|
76
|
+
buf += "\xF0\x90\x80";
|
|
77
|
+
ASSERT_EQ(leptris_utf8_valid_scalar(buf.data(), buf.size()), 0);
|
|
78
|
+
ASSERT_EQ(leptris_utf8_valid(buf.data(), buf.size()), 0) << "n=" << n;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
TEST(Utf8Simd, RandomizedCrossCheck) {
|
|
83
|
+
unsigned seed = 0x1842;
|
|
84
|
+
auto rnd = [&seed]() {
|
|
85
|
+
seed = seed * 1103515245u + 12345u;
|
|
86
|
+
return (seed >> 16) & 0xFF;
|
|
87
|
+
};
|
|
88
|
+
for (int trial = 0; trial < 4000; trial++) {
|
|
89
|
+
size_t len = (size_t)(rnd() % 97);
|
|
90
|
+
std::string buf;
|
|
91
|
+
for (size_t i = 0; i < len; i++) {
|
|
92
|
+
unsigned r = rnd() % 100;
|
|
93
|
+
char c;
|
|
94
|
+
if (r < 55) c = (char)('a' + (r % 26));
|
|
95
|
+
else if (r < 75) c = (char)(0xC0 + (r % 0x40));
|
|
96
|
+
else c = (char)(0x80 + (r % 0x80));
|
|
97
|
+
buf.push_back(c);
|
|
98
|
+
}
|
|
99
|
+
/* The plain corpus rarely produces the boundary shapes that
|
|
100
|
+
* broke the cores (x86-TSAN 2026-10-31): a start byte in the
|
|
101
|
+
* final block lane with a non-continuation after it, and
|
|
102
|
+
* sequences straddling 16/32-byte edges. Append an ASCII
|
|
103
|
+
* tail after a valid-or-not multibyte run so every trial
|
|
104
|
+
* also probes the tail-anchor at a shifted offset. */
|
|
105
|
+
if (trial % 3 == 0) {
|
|
106
|
+
static const char* mids[] = {
|
|
107
|
+
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80",
|
|
108
|
+
"\xC3", "\xE0\xA0", "\xF0\x90\x80", "\xED\xA0\x80",
|
|
109
|
+
"\xF4\x8F\xBF\xBF", "\xC2\xBF", "\xEF\xBF\xBD"
|
|
110
|
+
};
|
|
111
|
+
buf += mids[trial % 10];
|
|
112
|
+
buf += "zz";
|
|
113
|
+
}
|
|
114
|
+
int scalar = leptris_utf8_valid_scalar(buf.data(), buf.size());
|
|
115
|
+
int simd = leptris_utf8_valid(buf.data(), buf.size());
|
|
116
|
+
EXPECT_EQ(simd, scalar)
|
|
117
|
+
<< "trial " << trial << " len " << buf.size();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
TEST(Utf8Simd, DenseValidDocument) {
|
|
122
|
+
std::string buf;
|
|
123
|
+
buf.reserve(64 * 1024);
|
|
124
|
+
const char* unit = "héllo wörld — 日本語テキスト 🎉 emoji §naïve";
|
|
125
|
+
for (int i = 0; i < 1024; i++) buf += unit;
|
|
126
|
+
EXPECT_EQ(leptris_utf8_valid_scalar(buf.data(), buf.size()), 1);
|
|
127
|
+
EXPECT_EQ(leptris_utf8_valid(buf.data(), buf.size()), 1);
|
|
128
|
+
buf[buf.size() / 2] = (char)0xF5;
|
|
129
|
+
EXPECT_EQ(leptris_utf8_valid(buf.data(), buf.size()), 0);
|
|
130
|
+
}
|