leptris 1.9.391.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +9 -3
  3. data/ext/leptris/native/native.c +30 -0
  4. data/lib/leptris/version.rb +1 -1
  5. data/lib/leptris/xml/ffi.rb +5 -0
  6. data/lib/leptris/xml/native.so +0 -0
  7. data/lib/leptris/xml/native_3_3.so +0 -0
  8. data/lib/leptris/xml.rb +25 -0
  9. data/lib/libleptris.so +0 -0
  10. data/vendor-src/README.md +1 -1
  11. data/vendor-src/libleptris/CHANGELOG.md +146 -0
  12. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  13. data/vendor-src/libleptris/README.adoc +37 -0
  14. data/vendor-src/libleptris/src/CMakeLists.txt +12 -5
  15. data/vendor-src/libleptris/src/include/leptris/types.h +52 -0
  16. data/vendor-src/libleptris/src/include/leptris.h +6 -0
  17. data/vendor-src/libleptris/src/leptris/common/simd_text.c +61 -0
  18. data/vendor-src/libleptris/src/leptris/common/simd_text.h +13 -0
  19. data/vendor-src/libleptris/src/leptris/common/simd_text_avx2.c +156 -2
  20. data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +113 -0
  21. data/vendor-src/libleptris/src/leptris/core.c +31 -0
  22. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +19 -43
  23. data/vendor-src/libleptris/src/leptris/xpath/parser.c +11 -29
  24. data/vendor-src/libleptris/src/leptris/xsd/compile.c +82 -8
  25. data/vendor-src/libleptris/src/leptris/xsd/validate.c +36 -4
  26. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +8 -0
  27. data/vendor-src/libleptris/test/CMakeLists.txt +18 -0
  28. data/vendor-src/libleptris/test/abi/test_capabilities.cpp +112 -0
  29. data/vendor-src/libleptris/test/dom/test_entity_refs.cpp +111 -0
  30. data/vendor-src/libleptris/test/parser/test_encoding.cpp +115 -0
  31. data/vendor-src/libleptris/test/parser/test_utf8_simd.cpp +130 -0
  32. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +299 -3
  33. data/vendor-src/libleptris/vcpkg.json +1 -1
  34. metadata +5 -2
@@ -75,6 +75,14 @@ ptrdiff_t leptris_text_find3_avx2(const char* s, size_t len,
75
75
  return -1;
76
76
  }
77
77
 
78
+ /* MSVC has no __builtin_popcount; __popcnt32 maps to the POPCNT
79
+ * instruction the /arch:AVX2 target guarantees. */
80
+ #if defined(_MSC_VER)
81
+ # define LEPTRIS_POPCOUNT32(x) __popcnt(x)
82
+ #else
83
+ # define LEPTRIS_POPCOUNT32(x) __builtin_popcount(x)
84
+ #endif
85
+
78
86
  /* Byte-population count: cmpeq → movemask → popcount per 32-byte
79
87
  * chunk, 16-byte SSE step, scalar tail. Never reads past s+len. */
80
88
  size_t leptris_text_count_char_avx2(const char* s, size_t len, char c) {
@@ -86,13 +94,13 @@ size_t leptris_text_count_char_avx2(const char* s, size_t len, char c) {
86
94
  while (end - p >= 32) {
87
95
  __m256i v = _mm256_loadu_si256((const __m256i*)p);
88
96
  __m256i eq = _mm256_cmpeq_epi8(v, needle);
89
- n += (size_t)__builtin_popcount((unsigned)_mm256_movemask_epi8(eq));
97
+ n += (size_t)LEPTRIS_POPCOUNT32((unsigned)_mm256_movemask_epi8(eq));
90
98
  p += 32;
91
99
  }
92
100
  if (end - p >= 16) {
93
101
  __m128i v = _mm_loadu_si128((const __m128i*)p);
94
102
  __m128i eq = _mm_cmpeq_epi8(v, _mm256_castsi256_si128(needle));
95
- n += (size_t)__builtin_popcount((unsigned)_mm_movemask_epi8(eq));
103
+ n += (size_t)LEPTRIS_POPCOUNT32((unsigned)_mm_movemask_epi8(eq));
96
104
  p += 16;
97
105
  }
98
106
  while (p < end) {
@@ -153,3 +161,149 @@ ptrdiff_t leptris_text_scan_run_avx2(const char* s, size_t len, char term,
153
161
  return (p < end) ? (ptrdiff_t)(p - s) : -1;
154
162
  }
155
163
  }
164
+
165
+ /* ---- UTF-8 validation (leptris#1842 A5) -------------------------------
166
+ *
167
+ * Per-16-byte block, per-byte required ranges derived from the three
168
+ * preceding bytes (carried across blocks via palignr):
169
+ * must_cont(j) = start2(j-1) | start3(j-1) | start4(j-1)
170
+ * | start3(j-2) | start4(j-2) | start4(j-3)
171
+ * where startN(v) = v in [C2..DF | E0..EF | F0..F4] by length class.
172
+ * A must-cont byte must land in its opener's continuation range
173
+ * (E0 -> A0..BF, ED -> 80..9F, F0 -> 90..BF, F4 -> 80..8F, else
174
+ * 80..BF); a non-cont byte must be ASCII or a legal start. The tail
175
+ * (<16 bytes) runs the scalar DFA anchored at a character boundary
176
+ * (walk back at most 3 bytes). Verdicts identical to
177
+ * leptris_utf8_valid_scalar — pinned by the encoding suite.
178
+ */
179
+ int leptris_utf8_valid_avx2(const char* src, size_t len) {
180
+ const unsigned char* p = (const unsigned char*)src;
181
+ if (len < 16) return leptris_utf8_valid_scalar(src, len);
182
+
183
+ const __m128i zero = _mm_setzero_si128();
184
+ const __m128i b80 = _mm_set1_epi8((char)0x80);
185
+ const __m128i bBF = _mm_set1_epi8((char)0xBF);
186
+ __m128i prev = zero; /* the previous block's 16 bytes */
187
+
188
+ size_t i = 0;
189
+ while (i + 16 <= len) {
190
+ __m128i cur = _mm_loadu_si128((const __m128i*)(p + i));
191
+ /* result[j] = byte(j-1): concat(a:b) shifted right leaves the
192
+ * PREVIOUS block's last lanes at the head — imm 15/14/13. */
193
+ __m128i b1 = _mm_alignr_epi8(cur, prev, 15);
194
+ __m128i b2 = _mm_alignr_epi8(cur, prev, 14);
195
+ __m128i b3 = _mm_alignr_epi8(cur, prev, 13);
196
+
197
+ /* start classes — the saturating idiom's operand order is the
198
+ * whole game: subs(v, lo)==0 tests v<=lo (saturate on
199
+ * underflow AND equality), so the LOWER bound must be
200
+ * subs(lo, v)==0 (0 iff lo<=v). subs(v, hi)==0 ⇔ v<=hi is
201
+ * already the right direction for the upper bound. */
202
+ #define IN_RANGE(v, lo, hi) \
203
+ (_mm_and_si128( \
204
+ _mm_cmpeq_epi8(_mm_subs_epu8(_mm_set1_epi8(lo), (v)), zero), \
205
+ _mm_cmpeq_epi8(_mm_subs_epu8((v), _mm_set1_epi8(hi)), zero)))
206
+ __m128i st2c = IN_RANGE(cur, (char)0xC2, (char)0xDF);
207
+ __m128i st3c = IN_RANGE(cur, (char)0xE0, (char)0xEF);
208
+ __m128i st4c = IN_RANGE(cur, (char)0xF0, (char)0xF4);
209
+ __m128i is_ascii =
210
+ _mm_cmpeq_epi8(_mm_and_si128(cur, b80), zero);
211
+ __m128i legal_start = _mm_or_si128(
212
+ st2c, _mm_or_si128(st3c, st4c));
213
+
214
+ __m128i st2b1 = IN_RANGE(b1, (char)0xC2, (char)0xDF);
215
+ __m128i st3b1 = IN_RANGE(b1, (char)0xE0, (char)0xEF);
216
+ __m128i st4b1 = IN_RANGE(b1, (char)0xF0, (char)0xF4);
217
+ __m128i st3b2 = IN_RANGE(b2, (char)0xE0, (char)0xEF);
218
+ __m128i st4b2 = IN_RANGE(b2, (char)0xF0, (char)0xF4);
219
+ __m128i st4b3 = IN_RANGE(b3, (char)0xF0, (char)0xF4);
220
+ #undef IN_RANGE
221
+
222
+ __m128i must_cont = _mm_or_si128(
223
+ _mm_or_si128(st2b1, _mm_or_si128(st3b1, st4b1)),
224
+ _mm_or_si128(_mm_or_si128(st3b2, st4b2), st4b3));
225
+
226
+ /* continuation range refinement from the opener (b1) */
227
+ const __m128i bA0 = _mm_set1_epi8((char)0xA0);
228
+ const __m128i b90 = _mm_set1_epi8((char)0x90);
229
+ const __m128i b9F = _mm_set1_epi8((char)0x9F);
230
+ const __m128i b8F = _mm_set1_epi8((char)0x8F);
231
+ const __m128i e0 = _mm_cmpeq_epi8(b1, _mm_set1_epi8((char)0xE0));
232
+ const __m128i ed = _mm_cmpeq_epi8(b1, _mm_set1_epi8((char)0xED));
233
+ const __m128i f0 = _mm_cmpeq_epi8(b1, _mm_set1_epi8((char)0xF0));
234
+ const __m128i f4 = _mm_cmpeq_epi8(b1, _mm_set1_epi8((char)0xF4));
235
+ __m128i lo = _mm_blendv_epi8(b80, bA0, e0);
236
+ lo = _mm_blendv_epi8(lo, b90, f0);
237
+ __m128i hi = _mm_blendv_epi8(bBF, b9F, ed);
238
+ hi = _mm_blendv_epi8(hi, b8F, f4);
239
+
240
+ /* cont-in-range — NEON's vcge/vcle pair translated to the
241
+ * saturating-subtraction idiom: subs(lo, cur) saturates to 0
242
+ * iff lo <= cur, subs(cur, hi) iff cur <= hi (the operand
243
+ * order is the whole game: subs(cur, lo) tests cur <= lo). */
244
+ __m128i cge = _mm_subs_epu8(lo, cur);
245
+ __m128i cle = _mm_subs_epu8(cur, hi);
246
+ __m128i cont_ok = _mm_and_si128(
247
+ _mm_cmpeq_epi8(cge, zero), _mm_cmpeq_epi8(cle, zero));
248
+
249
+ const __m128i ff = _mm_set1_epi8((char)0xFF);
250
+ __m128i not_must = _mm_xor_si128(must_cont, ff);
251
+ __m128i not_ascii = _mm_xor_si128(is_ascii, ff);
252
+ __m128i not_start = _mm_xor_si128(legal_start, ff);
253
+ /* error = must_cont & !cont_ok | !must_cont & !ascii &
254
+ * !legal_start (0x80..C1 and F5..FF fall out of the latter). */
255
+ __m128i block_error = _mm_or_si128(
256
+ _mm_and_si128(must_cont,
257
+ _mm_xor_si128(cont_ok, ff)),
258
+ _mm_and_si128(not_must,
259
+ _mm_and_si128(not_ascii, not_start)));
260
+ if (_mm_movemask_epi8(
261
+ _mm_cmpeq_epi8(_mm_and_si128(block_error, b80), b80)) != 0)
262
+ return 0;
263
+ prev = cur;
264
+ i += 16;
265
+ }
266
+ if (i >= len) {
267
+ /* The input ENDS at the block edge: the last sequence must
268
+ * be complete (the scalar DFA's "len - i <= n" rule). Walk
269
+ * back over at most 3 continuations; s2 lands ON the last
270
+ * continuation (or the final non-continuation byte). */
271
+ size_t s2 = i;
272
+ size_t conts = 0;
273
+ while (s2 > 0 && (p[s2 - 1] & 0xC0) == 0x80 && conts < 3) {
274
+ s2--;
275
+ conts++;
276
+ }
277
+ if (conts > 0) {
278
+ if (s2 == 0) return 0; /* continuations, no opener anywhere */
279
+ unsigned op = p[s2 - 1]; /* the opener BEFORE the run */
280
+ unsigned owed = (op >= 0xF0) ? 3
281
+ : (op >= 0xE0) ? 2
282
+ : (op >= 0xC2) ? 1
283
+ : 0;
284
+ if (owed > conts) return 0;
285
+ return 1;
286
+ }
287
+ /* the last byte is not a continuation: it must be ASCII */
288
+ unsigned op = p[i - 1];
289
+ unsigned owed = (op >= 0xF0) ? 3
290
+ : (op >= 0xE0) ? 2
291
+ : (op >= 0xC2) ? 1
292
+ : 0;
293
+ return owed == 0 ? 1 : 0;
294
+ }
295
+ /* Tail: scalar DFA anchored at a character boundary (at most 3
296
+ * bytes back from the SIMD end; a sequence is at most 4) — the
297
+ * anchored run also covers a sequence spanning the block edge.
298
+ * One more step back when the byte before the anchor is a start
299
+ * byte: an opener in the final block lane reaches into the tail
300
+ * (400k-case randomized cross-check vs the scalar oracle). */
301
+ size_t start = i;
302
+ while (start > 0 && (p[start] & 0xC0) == 0x80 && i - start < 3)
303
+ start--;
304
+ if (i - start < 3 && start > 0) {
305
+ unsigned char b = p[start - 1];
306
+ if (b >= 0xC2 && b <= 0xF4) start--;
307
+ }
308
+ return leptris_utf8_valid_scalar((const char*)(p + start), len - start);
309
+ }
@@ -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
@@ -72,6 +72,37 @@ LEPTRIS_API void leptris_version_components(int* major, int* minor, int* patch)
72
72
  if (patch) *patch = LEPTRIS_VERSION_PATCH;
73
73
  }
74
74
 
75
+ /* leptris#1905: the capability descriptor. Unconditional faces are
76
+ * always compiled into this library; the build-conditional bits
77
+ * follow the same macros the build system defines for the sources
78
+ * — the descriptor cannot drift from the binary it ships in. */
79
+ LEPTRIS_API LeptrisCapabilities leptris_capabilities(void) {
80
+ LeptrisCapabilities c;
81
+ c.caps = LEPTRIS_CAP_PARSE | LEPTRIS_CAP_DOM | LEPTRIS_CAP_XPATH |
82
+ LEPTRIS_CAP_XQUERY | LEPTRIS_CAP_DTD | LEPTRIS_CAP_SAX |
83
+ LEPTRIS_CAP_HTML | LEPTRIS_CAP_XINCLUDE | LEPTRIS_CAP_DIFF |
84
+ LEPTRIS_CAP_DIGEST | LEPTRIS_CAP_XSD | LEPTRIS_CAP_XSD_SET |
85
+ LEPTRIS_CAP_RELAXNG | LEPTRIS_CAP_SCHEMATRON |
86
+ LEPTRIS_CAP_XSLT | LEPTRIS_CAP_DESCRIPTOR |
87
+ LEPTRIS_CAP_NS_SNAPSHOT_BULK | LEPTRIS_CAP_CHILDREN_BATCH |
88
+ LEPTRIS_CAP_NAME_COMPARE | LEPTRIS_CAP_VERSION |
89
+ LEPTRIS_CAP_ENTITY_REF_NODES;
90
+ #if defined(LEPTRIS_HAS_DUCET)
91
+ c.caps |= LEPTRIS_CAP_COLLATION_DUCET;
92
+ #endif
93
+ #if defined(LEPTRIS_HAS_UTF8PROC)
94
+ c.caps |= LEPTRIS_CAP_UNICODE_UTF8PROC;
95
+ #endif
96
+ #if defined(LEPTRIS_HAS_ICONV)
97
+ c.caps |= LEPTRIS_CAP_ENCODING_ICONV;
98
+ #endif
99
+ #if defined(LEPTRIS_HAS_AVX2_BUILD)
100
+ c.caps |= LEPTRIS_CAP_SIMD_AVX2;
101
+ #endif
102
+ c.descriptor_version = LEPTRIS_CAPS_DESCRIPTOR_VERSION;
103
+ return c;
104
+ }
105
+
75
106
  LEPTRIS_API void leptris_thread_cleanup(void) {
76
107
  leptris_xpath_drain_thread_caches();
77
108
  leptris_root_doc_drain_thread_caches();
@@ -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
- const unsigned char* p = (const unsigned char*)s;
3535
- size_t i = 0;
3536
- /* ASCII fast path: 8 bytes per iteration. The DFA below runs
3537
- * only when a high byte is present; ordinary documents pay one
3538
- * memory-bandwidth pass. */
3539
- while (i + 8 <= len) {
3540
- uint64_t w;
3541
- memcpy(&w, p + i, 8);
3542
- if (w & UINT64_C(0x8080808080808080)) break;
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
- buf[len] = '\0'; /* Ensure NUL termination */
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
  }
@@ -643,44 +643,26 @@ static XPathASTNode* parse_string_concat_expr(XPathParser* parser) {
643
643
  }
644
644
 
645
645
  /* True when the token AFTER a bare value-comparison keyword may
646
- * START an operand. The base list covers scalars/parens; an NCName
647
- * qualifies ONLY when it opens a function call — a bare NCName
648
- * follower stays a path step (XPath 1.0 name-test precedence). */
646
+ * START an operand. Scalars, parens, variables, and (leptris#1910)
647
+ * every path start: a bare NCName/QName is an element name test in
648
+ * operand position, and adjacent name tokens are never a valid
649
+ * path continuation, so the keyword reading is unambiguous
650
+ * (`w le h` in xs:assert/xs:alternative tests). */
649
651
  static int kw_operand_boundary(XPathParser* parser, XPathToken* nx) {
652
+ (void)parser;
650
653
  if (!nx) return 1;
651
654
  switch (nx->type) {
652
655
  case TOK_EOF: case TOK_RPAREN: case TOK_COMMA:
653
656
  case TOK_RBRACE: case TOK_LBRACE: case TOK_NUMBER:
654
657
  case TOK_STRING: case TOK_DOLLAR: case TOK_LPAREN:
655
658
  case TOK_MINUS: case TOK_PLUS:
659
+ case TOK_SLASH: case TOK_DOUBLE_SLASH: case TOK_AT:
660
+ case TOK_DOT: case TOK_DOUBLE_DOT: case TOK_STAR:
656
661
  return 1;
657
662
  case TOK_QNAME:
658
- /* prefixed call: QName '(' — a lone QName stays a path
659
- * step (name-test precedence, like NCName). */
660
- case TOK_NCNAME: {
661
- /* unprefixed call: name '(' ... prefixed call:
662
- * name ':' name '(' (the QT3 corpus writes fn:year-...). */
663
- XPathToken* nnx =
664
- parser->token_pos + 2 < parser->token_count
665
- ? &parser->tokens[parser->token_pos + 2]
666
- : NULL;
667
- if (nnx && nnx->type == TOK_LPAREN) return 1;
668
- XPathToken* t3 =
669
- parser->token_pos + 3 < parser->token_count
670
- ? &parser->tokens[parser->token_pos + 3]
671
- : NULL;
672
- XPathToken* t4 =
673
- parser->token_pos + 4 < parser->token_count
674
- ? &parser->tokens[parser->token_pos + 4]
675
- : NULL;
676
- XPathToken* t5 =
677
- parser->token_pos + 5 < parser->token_count
678
- ? &parser->tokens[parser->token_pos + 5]
679
- : NULL;
680
- return nnx && nnx->type == TOK_COLON && t3 &&
681
- t3->type == TOK_NCNAME && t4 &&
682
- t4->type == TOK_LPAREN && t5;
683
- }
663
+ case TOK_NCNAME:
664
+ /* name test, function call, or prefixed call */
665
+ return 1;
684
666
  default:
685
667
  return 0;
686
668
  }
@@ -51,7 +51,11 @@ struct leptris_xsd_schema {
51
51
  char* target_ns;
52
52
  int version_11; /* xs:schema @version = "1.1" */
53
53
  char* base_dir; /* schemaLocation resolution (file compile) */
54
- int include_depth; /* include/import recursion guard */
54
+ int include_depth;
55
+ /* xs:override body capture replaces same-named components: the
56
+ * attributeGroup pre-pass must re-register defs the base
57
+ * already captured (leptris#1913). */
58
+ int capture_force; /* include/import recursion guard */
55
59
  /* leptris#1644: already-captured schema units (canonical
56
60
  * location per entry) — the include/import DAG is dense and
57
61
  * cyclic; without this set every DAG path re-parses and
@@ -179,7 +183,9 @@ static void xsd_cm_free(XsdCm* cm) {
179
183
  free(cm->name);
180
184
  free(cm->type);
181
185
  free(cm->ns);
182
- free(cm->ext_base);
186
+ free(cm->res_base);
187
+ free(cm->res_base_ns);
188
+ free(cm->ext_base);
183
189
  free(cm->ext_base_ns);
184
190
  free(cm->any_ns);
185
191
  free(cm->not_qname);
@@ -679,7 +685,7 @@ static int xsd_is_declaration(const char* local, const char* uri) {
679
685
  "element", "attribute", "simpleType", "complexType",
680
686
  "group", "attributeGroup", "notation", "include",
681
687
  "defaultOpenContent",
682
- "import", "redefine", NULL
688
+ "import", "redefine", "override", NULL
683
689
  };
684
690
  for (int i = 0; names[i]; i++)
685
691
  if (strcmp(local, names[i]) == 0) return 1;
@@ -1810,7 +1816,8 @@ static void xsd_collect_decls_doc(LeptrisElement root, const char* eff_tns,
1810
1816
  if (!l || !u || strcmp(u, XSD_NS) != 0)
1811
1817
  continue;
1812
1818
  int is_inc = (strcmp(l, "include") == 0 ||
1813
- strcmp(l, "redefine") == 0);
1819
+ strcmp(l, "redefine") == 0 ||
1820
+ strcmp(l, "override") == 0);
1814
1821
  int is_imp = (strcmp(l, "import") == 0);
1815
1822
  if (!is_inc && !is_imp) continue;
1816
1823
  const char* loc = leptris_element_attribute(c, "schemaLocation");
@@ -1853,6 +1860,13 @@ static void xsd_collect_decls_doc(LeptrisElement root, const char* eff_tns,
1853
1860
  xsd_collect_decls_doc(subroot, eff, sub_base, decls, n, max,
1854
1861
  claimed, nclaimed, cmax, depth + 1);
1855
1862
  }
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)
1868
+ xsd_collect_decls_doc(c, eff_tns, base_dir, decls, n, max,
1869
+ claimed, nclaimed, cmax, depth + 1);
1856
1870
  leptris_document_free(sub);
1857
1871
  }
1858
1872
  }
@@ -1960,8 +1974,24 @@ static LeptrisXsdSchema xsd_compile_impl(const char* xsd_text,
1960
1974
  s->target_ns = xsd_strdup(
1961
1975
  leptris_element_attribute(root, "targetNamespace"));
1962
1976
  {
1977
+ /* XSD 1.1 §4.3 schema versioning: the W3C suite and the
1978
+ * binding ecosystem mark 1.1 with vc:minVersion (the
1979
+ * versioning namespace); the plain version attribute is
1980
+ * the other legal spelling. vc:maxVersion below 1.1 pins
1981
+ * a 1.0 processor target regardless. leptris#1910/#1911:
1982
+ * reading only @version silently compiled asserts and
1983
+ * alternatives away for every vc-marked schema. */
1963
1984
  const char* ver = leptris_element_attribute(root, "version");
1964
- s->version_11 = (ver && strcmp(ver, "1.1") == 0);
1985
+ const char* vmin = leptris_element_attribute_ns(
1986
+ root, "http://www.w3.org/2001/XMLSchema-versioning",
1987
+ "minVersion");
1988
+ const char* vmax = leptris_element_attribute_ns(
1989
+ root, "http://www.w3.org/2001/XMLSchema-versioning",
1990
+ "maxVersion");
1991
+ int is11 = (ver && strcmp(ver, "1.1") == 0) ||
1992
+ (vmin && strcmp(vmin, "1.1") == 0);
1993
+ if (vmax && strcmp(vmax, "1.0") == 0) is11 = 0;
1994
+ s->version_11 = is11;
1965
1995
  }
1966
1996
 
1967
1997
  const char* local = NULL;
@@ -2247,7 +2277,8 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
2247
2277
 
2248
2278
  if (strcmp(local, "include") == 0 ||
2249
2279
  strcmp(local, "import") == 0 ||
2250
- strcmp(local, "redefine") == 0) {
2280
+ strcmp(local, "redefine") == 0 ||
2281
+ strcmp(local, "override") == 0) {
2251
2282
  const char* loc = leptris_element_attribute(
2252
2283
  (LeptrisElement)n, "schemaLocation");
2253
2284
  if (loc && s->base_dir && s->include_depth < 16) {
@@ -2314,6 +2345,26 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
2314
2345
  if (sub_ns) free(sub_ns);
2315
2346
  s->target_ns = saved_ns;
2316
2347
  }
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. */
2359
+ if (subroot && same_ns &&
2360
+ (strcmp(local, "override") == 0 ||
2361
+ strcmp(local, "redefine") == 0)) {
2362
+ s->include_depth++;
2363
+ s->capture_force = 1;
2364
+ xsd_capture_root(s, (LeptrisElement)n);
2365
+ s->capture_force = 0;
2366
+ s->include_depth--;
2367
+ }
2317
2368
  leptris_document_free(sub);
2318
2369
  }
2319
2370
  free(text);
@@ -2448,7 +2499,8 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
2448
2499
  XsdAttrGroupDef* seen = s->attr_groups;
2449
2500
  for (; seen; seen = seen->next)
2450
2501
  if (strcmp(seen->name, nm) == 0) break;
2451
- if (!seen) xsd_capture_attr_group_def(s, (LeptrisElement)n, nm);
2502
+ if (!seen || s->capture_force)
2503
+ xsd_capture_attr_group_def(s, (LeptrisElement)n, nm);
2452
2504
  }
2453
2505
  } else if (strcmp(local, "group") == 0 && uri && !strcmp(uri, XSD_NS)) {
2454
2506
  const char* nm =
@@ -2902,9 +2954,21 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
2902
2954
  attrs = own_attrs;
2903
2955
  }
2904
2956
  } else {
2905
- /* 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. */
2906
2963
  root->first_child = xsd_cm_parse(s, dq, NULL);
2907
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
+ }
2908
2972
  }
2909
2973
  /* XSD 1.1: asserts nested under the derivation */
2910
2974
  if (s->version_11)
@@ -2971,6 +3035,16 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
2971
3035
  } else {
2972
3036
  root->text_type = xsd_strdup(base);
2973
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
+ }
2974
3048
  }
2975
3049
  }
2976
3050
  attrs = xsd_capture_attr_rows(s, dq, 0);