leptris 1.9.334.0-ppc64le-linux → 1.9.335.0-ppc64le-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c687c6b5c09c8f7ec7bd48a26c197318f6f2a396680075a39f2cbfe445eb9c8
4
- data.tar.gz: 458aac071ab017d4bc2192bb9e1ac2c2c003edf631a058a16eb5446d1051e4f1
3
+ metadata.gz: a28806b919f8a4a8c3afff49f972713477e6c48aba6290ce988ec494a5564907
4
+ data.tar.gz: b3c61e5b3304608da316db83dfc3e2e911fc5914bf8fca78ed4ad8a1889a47ba
5
5
  SHA512:
6
- metadata.gz: f3cacf66947185632eda797cc645d036978912e5acb10726a83c615db019ced9ffe6146411321ee47ffdb521f64d25f7f9789af7dd1938c2d7809bbf260a522d
7
- data.tar.gz: 4efeebf09318e7c65ef475fc3fc05c44fa7597226fd8f087f26acac28d50b3a79c76fc574ff2937aa2436d2c16ac30aa5f9769813358e005f3a757701afd9c46
6
+ metadata.gz: c8275ee8cc2269f43a68aa94eb19722de3ed04aa0c8309d263c97bda37d05cea06b96092b01bc389d646a89f84f330e7630a1b4712082798a8e3af5806318297
7
+ data.tar.gz: 491b13abf8b1e1d60b2e9aeca96ba41e847eaf308b3ba367488c215bd1d071d9bf74136915a50759ece0c6a068f670e9aea04563265041efae5d6c378951f57f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## [1.9.335.0] - 2026-10-11
2
+
3
+ ### Fixed — engine sync (libleptris 1.9.335)
4
+
5
+ - **The #414 wave (#1626, engine PR #1627)**: cross-namespace
6
+ `xs:import` declarations merge under a compiling schema's own
7
+ targetNamespace (the wml-2010 chain shape — previously invisible),
8
+ prefixed QName type references (`type="w:CT_Border"`) resolve
9
+ with `xs:`/`xsd:` builtin spellings canonized (closing a silent
10
+ gap where `xsd:`-spelled types were untyped), and content-model
11
+ failures NAME the offending child once (`(at 'right')`) — a
12
+ misordered child no longer cascades anonymously down the
13
+ ancestors. All three pinned with minimal cases.
14
+
15
+ ## [1.9.334.1] - 2026-10-10
16
+
17
+ ### Performance
18
+
19
+ - **The attach paths stop minting FFI::Pointer wrappers per node
20
+ (#408 Ask 1)**: every native fast path read the canonical
21
+ Integer address through `c_ptr.address` — lazily materializing
22
+ a Pointer per node handed to one C call — instead of the
23
+ `c_address` twin. The FFI-fallback append paths route through
24
+ `leptris_element_add_child_addr` (both sides as Integers; same
25
+ status contract). Measured on the object-mode build(500):
26
+ FFI::Pointer allocations 1002 → 2 (the document/root wrappers
27
+ that genuinely need one), ~4% CPU off the build row under
28
+ load. Ask 2's face (`Descriptor#build_result`) shipped in
29
+ 1.9.334.0.
30
+
1
31
  ## [1.9.334.0] - 2026-10-10
2
32
 
3
33
  ### Added — engine sync (libleptris 1.9.332-1.9.334)
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.334"
11
+ LIBLEPTRIS_VERSION = "1.9.335"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.334.0"
4
+ VERSION = "1.9.335.0"
5
5
  end
@@ -386,7 +386,7 @@ class Leptris::XML::Element < Leptris::XML::Node
386
386
  # build appends.
387
387
  return true unless node.is_a?(Leptris::XML::Element)
388
388
  return false unless defined?(Leptris::XML::NATIVE_FAST)
389
- !Leptris::XML::Native.ns_lift_needed?(node.c_ptr.address)
389
+ !Leptris::XML::Native.ns_lift_needed?(node.c_address)
390
390
  end
391
391
 
392
392
  # A node is foreign to +document+ when it belongs to another one
@@ -445,7 +445,7 @@ class Leptris::XML::Element < Leptris::XML::Node
445
445
  end
446
446
  if native_fast_children?
447
447
  unless Leptris::XML::Native.insert_binding_child(
448
- @document, @c_address, node.c_ptr.address, 1).nil?
448
+ @document, @c_address, node.c_address, 1).nil?
449
449
  return node
450
450
  end
451
451
  end
@@ -478,7 +478,7 @@ class Leptris::XML::Element < Leptris::XML::Node
478
478
  end
479
479
  if native_fast_children?
480
480
  unless Leptris::XML::Native.insert_binding_child(
481
- @document, @c_address, node.c_ptr.address, 2).nil?
481
+ @document, @c_address, node.c_address, 2).nil?
482
482
  return node
483
483
  end
484
484
  end
@@ -511,7 +511,7 @@ class Leptris::XML::Element < Leptris::XML::Node
511
511
  end
512
512
  if native_fast_children?
513
513
  unless Leptris::XML::Native.insert_binding_child(
514
- @document, @c_address, node.c_ptr.address, 3).nil?
514
+ @document, @c_address, node.c_address, 3).nil?
515
515
  return node
516
516
  end
517
517
  end
@@ -556,7 +556,7 @@ class Leptris::XML::Element < Leptris::XML::Node
556
556
  end
557
557
  cross = nodes.any? { |n| Leptris::XML::Element.foreign_to?(n, @document) }
558
558
  if native_fast_children?
559
- addrs = nodes.map { |n| n.c_ptr.address }
559
+ addrs = nodes.map { |n| n.c_address }
560
560
  done = Leptris::XML::Native.append_binding_children(
561
561
  @document, @c_address, addrs)
562
562
  done = 0 unless done.is_a?(Integer)
@@ -698,7 +698,7 @@ class Leptris::XML::Element < Leptris::XML::Node
698
698
  # The face raises on failure (TODO.perf/36); Qnil = the
699
699
  # child needs the lift — the full path handles it.
700
700
  unless Leptris::XML::Native.append_binding_child(
701
- @document, @c_address, node_or_markup.c_ptr.address).nil?
701
+ @document, @c_address, node_or_markup.c_address).nil?
702
702
  return node_or_markup
703
703
  end
704
704
  end
@@ -706,8 +706,11 @@ class Leptris::XML::Element < Leptris::XML::Node
706
706
  unless Leptris::XML::Element.skip_adoption_lift?(node_or_markup)
707
707
  Leptris::XML::Element.lift_namespaces_for_adoption(node_or_markup, namespaces)
708
708
  end
709
+ # Address-based attach (#408): same status contract, no
710
+ # FFI::Pointer mint for either side.
709
711
  Leptris::XML::FFI.check_status(
710
- Leptris::XML::FFI.leptris_element_append_child(c_ptr, node_or_markup.c_ptr))
712
+ Leptris::XML::FFI.leptris_element_add_child_addr(
713
+ @c_address, node_or_markup.c_address))
711
714
  node_or_markup
712
715
  when String
713
716
  # TODO.perf/34: one dispatch parses the markup and appends
@@ -732,7 +735,8 @@ class Leptris::XML::Element < Leptris::XML::Node
732
735
  added = []
733
736
  frag.children.each do |n|
734
737
  Leptris::XML::FFI.check_status(
735
- Leptris::XML::FFI.leptris_element_append_child(c_ptr, n.c_ptr))
738
+ Leptris::XML::FFI.leptris_element_add_child_addr(
739
+ @c_address, n.c_address))
736
740
  added << n
737
741
  end
738
742
  Leptris::XML::NodeSet.new(@document, added)
@@ -753,7 +757,8 @@ class Leptris::XML::Element < Leptris::XML::Node
753
757
  Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
754
758
  end
755
759
  Leptris::XML::FFI.check_status(
756
- Leptris::XML::FFI.leptris_element_append_child(c_ptr, node.c_ptr))
760
+ Leptris::XML::FFI.leptris_element_add_child_addr(
761
+ @c_address, node.c_address))
757
762
  Leptris::XML::Node.invalidate_cross_document!(node, @document)
758
763
  remove_from_source_original(node)
759
764
  last_installed
@@ -27,6 +27,12 @@ begin
27
27
  if Gem.win_platform?
28
28
  require "leptris/xml/native_#{RUBY_VERSION[/\A\d+\.\d+/].tr('.', '_')}"
29
29
  else
30
+ # #1623: the bundle beside this file must carry a valid
31
+ # signature before dyld maps it — pre-verify and re-stamp.
32
+ Leptris.resign_bundle_if_needed(
33
+ File.expand_path(
34
+ "native#{RUBY_PLATFORM.include?('darwin') ? '.bundle' : '.so'}",
35
+ __dir__))
30
36
  require "leptris/xml/native"
31
37
  end
32
38
  rescue LoadError => e
data/lib/leptris.rb CHANGED
@@ -3,6 +3,23 @@
3
3
  require "leptris/version"
4
4
 
5
5
  module Leptris
6
+ # leptris#1623: macOS 14.x dyld intermittently SIGKILLs native
7
+ # bundles whose adhoc signature went stale (post-build byte
8
+ # rewrites) or is absent (x86_64 links carry none by default).
9
+ # A kill cannot be rescued — pre-verify and re-stamp BEFORE dyld
10
+ # ever sees the binary. Adhoc is deterministic and needs no
11
+ # identity; Developer ID rides owner secrets when wired.
12
+ def self.resign_bundle_if_needed(path)
13
+ return false unless path && File.exist?(path)
14
+ return false unless RUBY_PLATFORM.include?("darwin")
15
+ return true if system("codesign", "--verify", path.to_s,
16
+ out: File::NULL, err: File::NULL)
17
+ system("codesign", "--force", "--sign", "-", path.to_s,
18
+ out: File::NULL, err: File::NULL) &&
19
+ system("codesign", "--verify", path.to_s,
20
+ out: File::NULL, err: File::NULL)
21
+ end
22
+
6
23
  autoload :XML, "leptris/xml"
7
24
  # One file, three entry constants (HTML/HTML4/HTML5) — each
8
25
  # autoload maps so any reference loads the facade.
data/lib/libleptris.so CHANGED
Binary file
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.334
4
+ - libleptris v1.9.335
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,32 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.335] - 2026-10-10
4
+
5
+ ### Fixed
6
+
7
+ - **XSD: cross-namespace imports, prefixed type refs, named
8
+ content errors (#1626, PR #1627).** `xs:import` with a foreign
9
+ targetNamespace merges its declarations — the previous gate
10
+ skipped the normal multi-namespace shape (wml-2010 imports were
11
+ invisible when the compiling schema carried its own
12
+ targetNamespace). Prefixed QName references
13
+ (`type="w:CT_Border"`) resolve by local name at every capture
14
+ site, and the `xs:`/`xsd:` builtin spellings canonize —
15
+ closing a pre-existing gap where `xsd:`-spelled types were
16
+ silently untyped. Content-model failures name the offending
17
+ child once (`children do not match the content model
18
+ (at 'insideH')`) via a greedy expected-next match with
19
+ `minOccurs="0"` optionals skippable — one misordered child no
20
+ longer cascades anonymously down the ancestors (the reported
21
+ ~175x amplification is a bounded single report).
22
+ - **interleaved lane: compute record depths before sizing the
23
+ replay stacks (leptris#1577, PR #1624).** The sizing scan read
24
+ the still-zero-filled depth array, bound the replay to the
25
+ in-struct 256-slot buffers, and every document deeper than
26
+ DP_MAX_DEPTH overflowed them under a raised cap.
27
+
28
+
29
+
3
30
  ## [1.9.334] - 2026-10-10
4
31
 
5
32
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.334
6
+ VERSION 1.9.335
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -64,15 +64,26 @@ typedef struct {
64
64
  char* end;
65
65
  LeptrisMemoryPool* pool;
66
66
  struct leptris_document* doc;
67
- LeptrisElement open_stack[DP_MAX_DEPTH];
67
+ LeptrisElement open_stack_buf[DP_MAX_DEPTH];
68
68
  /* Round 10: journal slot of each open element's elem_pos entry —
69
69
  * the close path updates by slot (O(1)) instead of hashing. */
70
- int pos_slot_stack[DP_MAX_DEPTH];
70
+ int pos_slot_stack_buf[DP_MAX_DEPTH];
71
71
  /* TODO 155 Phase C: per-depth last-child cache. Replaces the
72
72
  * last_child_off field on element. last_child_stack[i] holds
73
73
  * the most recently wired child of open_stack[i], or NULL when
74
74
  * no child has been wired yet at that depth. */
75
- LeptrisNode* last_child_stack[DP_MAX_DEPTH];
75
+ LeptrisNode* last_child_stack_buf[DP_MAX_DEPTH];
76
+ /* Depth-stack indirection (leptris#1577): the in-struct buffers
77
+ * serve the default DP_MAX_DEPTH cap; a configured max depth
78
+ * above that (public leptris_set_max_depth / parse options)
79
+ * switches the pointers to one heap block sized for the cap.
80
+ * The open-tag guard honors the configured depth — with the
81
+ * arrays fixed at 256, any deeper document wrote past them. */
82
+ LeptrisElement* open_stack;
83
+ int* pos_slot_stack;
84
+ LeptrisNode** last_child_stack;
85
+ size_t stack_cap;
86
+ void* stack_heap;
76
87
  int depth;
77
88
  LeptrisElement root;
78
89
  /* Document children (issue #580): [prolog..., root, epilog...]
@@ -195,6 +206,36 @@ typedef struct {
195
206
  LeptrisDTD* dtd;
196
207
  } DParser;
197
208
 
209
+ /* Depth stacks (leptris#1577): the default cap stays on the in-struct
210
+ * buffers (zero allocation); a larger configured cap gets one
211
+ * aligned heap block. Returns 0 on OOM (pointers left NULL). */
212
+ static int dp_stacks_init(DParser* p, size_t cap) {
213
+ if (cap <= DP_MAX_DEPTH) {
214
+ p->open_stack = p->open_stack_buf;
215
+ p->pos_slot_stack = p->pos_slot_stack_buf;
216
+ p->last_child_stack = p->last_child_stack_buf;
217
+ p->stack_cap = DP_MAX_DEPTH;
218
+ p->stack_heap = NULL;
219
+ return 1;
220
+ }
221
+ size_t a = (cap * sizeof(LeptrisElement) + 15u) & ~(size_t)15u;
222
+ size_t b = (cap * sizeof(int) + 15u) & ~(size_t)15u;
223
+ size_t c = cap * sizeof(LeptrisNode*);
224
+ unsigned char* blk = (unsigned char*)malloc(a + b + c);
225
+ if (!blk) return 0;
226
+ p->open_stack = (LeptrisElement*)blk;
227
+ p->pos_slot_stack = (int*)(blk + a);
228
+ p->last_child_stack = (LeptrisNode**)(blk + a + b);
229
+ p->stack_cap = cap;
230
+ p->stack_heap = blk;
231
+ return 1;
232
+ }
233
+
234
+ static void dp_stacks_release(DParser* p) {
235
+ free(p->stack_heap);
236
+ p->stack_heap = NULL;
237
+ }
238
+
198
239
  /* ---- #1125: immutable-buffer logs --------------------------------
199
240
  *
200
241
  * The zero-copy parse NUL-terminates names/values in place. To keep
@@ -1551,6 +1592,12 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
1551
1592
  p.end = buf + len;
1552
1593
  p.pool = pool;
1553
1594
  p.doc = doc;
1595
+ {
1596
+ extern LEPTRIS_THREAD_LOCAL int g_leptris_max_depth;
1597
+ size_t dcap = g_leptris_max_depth > 0
1598
+ ? (size_t)g_leptris_max_depth : (size_t)DP_MAX_DEPTH;
1599
+ if (!dp_stacks_init(&p, dcap)) goto fail;
1600
+ }
1554
1601
  p.skip_flags = skip_flags;
1555
1602
  p.depth = 0;
1556
1603
  p.root = NULL;
@@ -2311,6 +2358,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
2311
2358
  * ends the parse byte-identical to the input. (Runs after the
2312
2359
  * encoding/version strdups above, which read the buffer.) */
2313
2360
 
2361
+ dp_stacks_release(&p);
2314
2362
  return doc;
2315
2363
 
2316
2364
  fail:
@@ -2372,6 +2420,7 @@ fail:
2372
2420
  * pool_destroy above. Don't LEPTRIS_FREE(doc) (TODO 154). */
2373
2421
  /* Round 8: scratch/pristine are arena-carved — pool_destroy
2374
2422
  * above already reclaimed them. */
2423
+ dp_stacks_release(&p);
2375
2424
  return NULL;
2376
2425
  }
2377
2426
 
@@ -2446,7 +2495,35 @@ static LEPTRIS_ALWAYS_INLINE size_t il_name_end(const char* s,
2446
2495
  return (size_t)(p - s);
2447
2496
  }
2448
2497
 
2498
+ /* leptris#1577: the IL scan's depth stacks sized to the configured
2499
+ * max depth (fixed 256 before — same overflow class as the classic
2500
+ * lane). The wrapper owns the block so every body exit is covered. */
2501
+ static int il_stacks_init(IlCtx* c, size_t cap) {
2502
+ if (cap <= LEPTRIS_IL_MAX_DEPTH) {
2503
+ c->open = c->open_buf;
2504
+ c->last_child = c->last_child_buf;
2505
+ c->stack_heap = NULL;
2506
+ return 1;
2507
+ }
2508
+ unsigned char* blk = (unsigned char*)malloc(
2509
+ 2u * cap * sizeof(uint32_t));
2510
+ if (!blk) return 0;
2511
+ c->open = (uint32_t*)blk;
2512
+ c->last_child = (uint32_t*)(blk + cap * sizeof(uint32_t));
2513
+ c->stack_heap = blk;
2514
+ return 1;
2515
+ }
2516
+
2517
+ static void il_stacks_release(IlCtx* c) {
2518
+ free(c->stack_heap);
2519
+ c->stack_heap = NULL;
2520
+ }
2521
+
2449
2522
  int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2523
+ uint32_t* root_out, size_t* trail_off,
2524
+ size_t* trail_len);
2525
+
2526
+ static int il_scan_body(char* s, size_t len, int drop_ws, IlCtx* c,
2450
2527
  uint32_t* root_out, size_t* trail_off,
2451
2528
  size_t* trail_len) {
2452
2529
  const char* send = s + len;
@@ -2682,6 +2759,19 @@ int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2682
2759
  }
2683
2760
  }
2684
2761
 
2762
+ int leptris_il_scan(char* s, size_t len, int drop_ws, IlCtx* c,
2763
+ uint32_t* root_out, size_t* trail_off,
2764
+ size_t* trail_len) {
2765
+ extern LEPTRIS_THREAD_LOCAL int g_leptris_max_depth;
2766
+ size_t dcap = g_leptris_max_depth > 0
2767
+ ? (size_t)g_leptris_max_depth : (size_t)LEPTRIS_IL_MAX_DEPTH;
2768
+ if (!il_stacks_init(c, dcap)) return 0;
2769
+ int ok = il_scan_body(s, len, drop_ws, c, root_out, trail_off,
2770
+ trail_len);
2771
+ il_stacks_release(c);
2772
+ return ok;
2773
+ }
2774
+
2685
2775
 
2686
2776
  /* Post-pass: build the public tree from the streams, replaying the
2687
2777
  * classic lane's exact stores. Returns NULL on OOM (caller frees). */
@@ -2791,11 +2881,28 @@ static struct leptris_document* dp_il_build(
2791
2881
  if (!smap || !rdepth || !lc) {
2792
2882
  free(smap); free(rdepth); free(lc); goto oom_pool;
2793
2883
  }
2884
+ /* Depth-stack indirection (leptris#1577): record depths are not
2885
+ * bounded by DP_MAX_DEPTH here — the replay indexes
2886
+ * last_child_stack by them. Size the stacks to the document. */
2887
+ /* Depth computation FIRST: the sizing scan below reads
2888
+ * rdepth — computed here, before the max is taken (the sizing
2889
+ * pass originally ran on the zero-filled array and sized the
2890
+ * stacks to 1, so every deep replay overflowed the in-struct
2891
+ * buffers — the exact class this PR fixes). */
2892
+ for (size_t i = 0; i < c->nrec; i++) {
2893
+ rdepth[i] = (c->recs[i].parent == IL_NONE)
2894
+ ? 0 : rdepth[c->recs[i].parent] + 1;
2895
+ }
2896
+ uint32_t il_max_d = 0;
2897
+ for (size_t i = 0; i < c->nrec; i++) {
2898
+ if (rdepth[i] > il_max_d) il_max_d = rdepth[i];
2899
+ }
2900
+ if (!dp_stacks_init(&pp, (size_t)il_max_d + 1)) {
2901
+ free(smap); free(rdepth); free(lc); goto oom_pool;
2902
+ }
2794
2903
 
2795
2904
  for (size_t i = 0; i < c->nrec; i++) {
2796
2905
  IlRec* r = &c->recs[i];
2797
- rdepth[i] = (r->parent == IL_NONE)
2798
- ? 0 : rdepth[r->parent] + 1;
2799
2906
  if (r->kind == 0) {
2800
2907
  LeptrisElement e = &eblk[nelem++];
2801
2908
  e->base.type = LEPTRIS_NODE_TYPE_ELEMENT;
@@ -3015,10 +3122,14 @@ static struct leptris_document* dp_il_build(
3015
3122
  }
3016
3123
 
3017
3124
  leptris_root_doc_register(doc->root, doc);
3125
+ dp_stacks_release(&pp);
3018
3126
  free(smap); free(rdepth); free(lc);
3019
3127
  return doc;
3020
3128
 
3021
3129
  oom_pool:
3130
+ /* Reached only at/after the replay's DParser init — the init
3131
+ * failure path leaves stack_heap NULL, so the release is safe. */
3132
+ dp_stacks_release(&pp);
3022
3133
  leptris_pool_destroy(pool);
3023
3134
  free(scratch);
3024
3135
  free(pristine);
@@ -43,8 +43,16 @@ typedef struct IlAttr {
43
43
  typedef struct IlCtx {
44
44
  IlRec* recs; size_t nrec, crec;
45
45
  IlAttr* attrs; size_t nattr, cattr;
46
- uint32_t open[LEPTRIS_IL_MAX_DEPTH];
47
- uint32_t last_child[LEPTRIS_IL_MAX_DEPTH];
46
+ /* Depth stacks (leptris#1577): the fixed buffers serve the
47
+ * default 256 cap; a configured max depth above that switches
48
+ * the pointers to one heap block. Owned by leptris_il_scan —
49
+ * initialized at entry, released at every exit; callers never
50
+ * touch these directly. */
51
+ uint32_t open_buf[LEPTRIS_IL_MAX_DEPTH];
52
+ uint32_t last_child_buf[LEPTRIS_IL_MAX_DEPTH];
53
+ uint32_t* open;
54
+ uint32_t* last_child;
55
+ void* stack_heap;
48
56
  int depth;
49
57
  } IlCtx;
50
58
 
@@ -88,6 +88,37 @@ static XsdAssert* xsd_capture_assert(struct leptris_xsd_schema* s,
88
88
  LeptrisElement e);
89
89
  static XsdCm* xsd_capture_any(LeptrisElement any_child);
90
90
  static char* xsd_capture_any_attr_ns(LeptrisElement container);
91
+
92
+ /* #1626: prefixed QName refs (type="w:CT_Border") resolve by local
93
+ * name — the engine's tables key on local names; the prefix maps
94
+ * to a namespace the name-based resolution folds away. Returns the
95
+ * local part after the colon when present, else the argument. */
96
+ static char xsd_qname_buf[8][512];
97
+ static int xsd_qname_slot;
98
+
99
+ static const char* xsd_local_name(const char* qname) {
100
+ if (!qname) return NULL;
101
+ const char* colon = strchr(qname, ':');
102
+ if (!colon) return qname;
103
+ /* xs:/xsd: are builtin references — canonize to "xs:local" so
104
+ * text_valid's dispatch matches both spellings (xsd:-spelled
105
+ * types were silently untyped before #1626). */
106
+ size_t pl = (size_t)(colon - qname);
107
+ if ((pl == 2 && strncmp(qname, "xs", 2) == 0) ||
108
+ (pl == 3 && strncmp(qname, "xsd", 3) == 0)) {
109
+ if (pl == 2) return qname;
110
+ char* dst = xsd_qname_buf[xsd_qname_slot++ & 7];
111
+ dst[0] = 'x';
112
+ dst[1] = 's';
113
+ dst[2] = ':';
114
+ snprintf(dst + 3, sizeof(xsd_qname_buf[0]) - 3, "%s",
115
+ colon + 1);
116
+ return dst;
117
+ }
118
+ /* a foreign prefix maps to a namespace; the name-based tables
119
+ * resolve by local name */
120
+ return colon + 1;
121
+ }
91
122
  static void xsd_capture_alternatives(struct leptris_xsd_schema* s,
92
123
  LeptrisElement decl_elem,
93
124
  XsdElementDecl* d);
@@ -193,12 +224,14 @@ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
193
224
  if (cm) {
194
225
  const char* nm =
195
226
  leptris_element_attribute(e, "name");
196
- if (!nm) nm = leptris_element_attribute(e, "ref");
227
+ if (!nm)
228
+ nm = xsd_local_name(
229
+ leptris_element_attribute(e, "ref"));
197
230
  cm->name = xsd_strdup(nm);
198
231
  const char* pty =
199
232
  leptris_element_attribute(e, "type");
200
233
  if (pty) {
201
- cm->type = xsd_strdup(pty);
234
+ cm->type = xsd_strdup(xsd_local_name(pty));
202
235
  } else if (s && nm) {
203
236
  /* #1592: inline anonymous type on the particle */
204
237
  char synth[512];
@@ -212,8 +245,8 @@ static XsdCm* xsd_cm_parse(struct leptris_xsd_schema* s,
212
245
  * forward references need no capture order */
213
246
  cm = xsd_cm_new(XSD_CM_GROUP_REF);
214
247
  if (cm)
215
- cm->name = xsd_strdup(
216
- leptris_element_attribute(e, "ref"));
248
+ cm->name = xsd_strdup(xsd_local_name(
249
+ leptris_element_attribute(e, "ref")));
217
250
  } else if (strcmp(local, "any") == 0) {
218
251
  cm = xsd_cm_new(XSD_CM_ANY);
219
252
  if (cm) {
@@ -273,7 +306,8 @@ static XsdSimple* xsd_capture_simple(LeptrisElement st) {
273
306
  if (!local) continue;
274
307
  if (strcmp(local, "list") == 0) {
275
308
  simple->deriv = XSD_SIMPLE_LIST;
276
- const char* it = leptris_element_attribute(e, "itemType");
309
+ const char* it = xsd_local_name(
310
+ leptris_element_attribute(e, "itemType"));
277
311
  if (it && *it) {
278
312
  simple->item_type = xsd_strdup(it);
279
313
  } else {
@@ -346,8 +380,8 @@ static XsdSimple* xsd_capture_simple(LeptrisElement st) {
346
380
  simple->member_defs[simple->member_def_count++] = def;
347
381
  }
348
382
  } else if (strcmp(local, "restriction") == 0) {
349
- simple->base =
350
- xsd_strdup(leptris_element_attribute(e, "base"));
383
+ simple->base = xsd_strdup(xsd_local_name(
384
+ leptris_element_attribute(e, "base")));
351
385
  for (LeptrisNodeRef f = leptris_node_first_child(
352
386
  leptris_element_as_node(e));
353
387
  f; f = leptris_node_next_sibling(f)) {
@@ -694,9 +728,16 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
694
728
  /* same-namespace merges and no-namespace
695
729
  * chameleon includes; foreign-namespace
696
730
  * imports stay lax at this tier */
731
+ /* xs:include: same-namespace (or
732
+ * chameleon — the no-tns form adopts ours).
733
+ * xs:import: foreign namespaces are the
734
+ * point — merge regardless (#1626: wml-2010
735
+ * shape imports were skipped when the
736
+ * compiler had its own targetNamespace). */
737
+ int same_ns = subroot && (!tns || !s->target_ns ||
738
+ strcmp(tns, s->target_ns) == 0);
697
739
  if (subroot &&
698
- (!tns || !s->target_ns ||
699
- strcmp(tns, s->target_ns) == 0)) {
740
+ (strcmp(local, "import") == 0 || same_ns)) {
700
741
  s->include_depth++;
701
742
  xsd_capture_root(s, subroot);
702
743
  s->include_depth--;
@@ -719,7 +760,7 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
719
760
  (XsdElementDecl*)calloc(1, sizeof(*d));
720
761
  if (d) {
721
762
  d->name = xsd_strdup(nm);
722
- d->type = xsd_strdup(ty);
763
+ d->type = xsd_strdup(xsd_local_name(ty));
723
764
  xsd_capture_alternatives(s, (LeptrisElement)n, d);
724
765
  d->fixed = xsd_strdup(
725
766
  leptris_element_attribute((LeptrisElement)n,
@@ -789,9 +830,9 @@ static void xsd_capture_root(struct leptris_xsd_schema* s,
789
830
  XsdAttrDecl* d = (XsdAttrDecl*)calloc(1, sizeof(*d));
790
831
  if (d) {
791
832
  d->name = xsd_strdup(nm);
792
- d->type = xsd_strdup(
833
+ d->type = xsd_strdup(xsd_local_name(
793
834
  leptris_element_attribute((LeptrisElement)n,
794
- "type"));
835
+ "type")));
795
836
  d->fixed = xsd_strdup(
796
837
  leptris_element_attribute((LeptrisElement)n,
797
838
  "fixed"));
@@ -1141,8 +1182,9 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
1141
1182
  if (!dl || (strcmp(dl, "extension") != 0 &&
1142
1183
  strcmp(dl, "restriction") != 0))
1143
1184
  continue;
1144
- const char* base =
1185
+ const char* base_raw =
1145
1186
  leptris_element_attribute(dq, "base");
1187
+ const char* base = xsd_local_name(base_raw);
1146
1188
  XsdCm* base_root =
1147
1189
  (base && strncmp(base, "xs:", 3) != 0)
1148
1190
  ? xsd_find_complex(s, base)
@@ -1199,8 +1241,8 @@ static XsdCm* xsd_capture_complex_model(struct leptris_xsd_schema* s,
1199
1241
  if (!dl || (strcmp(dl, "extension") != 0 &&
1200
1242
  strcmp(dl, "restriction") != 0))
1201
1243
  continue;
1202
- const char* base =
1203
- leptris_element_attribute(dq, "base");
1244
+ const char* base = xsd_local_name(
1245
+ leptris_element_attribute(dq, "base"));
1204
1246
  if (base) {
1205
1247
  if (strncmp(base, "xs:", 3) == 0) {
1206
1248
  root->text_type = xsd_strdup(base);
@@ -1434,8 +1476,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1434
1476
  if (!d) continue;
1435
1477
  if (an) {
1436
1478
  d->name = xsd_strdup(an);
1437
- d->type = xsd_strdup(
1438
- leptris_element_attribute(ae, "type"));
1479
+ d->type = xsd_strdup(xsd_local_name(
1480
+ leptris_element_attribute(ae, "type")));
1439
1481
  if (!d->type)
1440
1482
  d->type = xsd_strdup(
1441
1483
  xsd_inline_attr_type(s, ae, an));
@@ -1444,8 +1486,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1444
1486
  d->required = (use && strcmp(use, "required") == 0);
1445
1487
  } else {
1446
1488
  /* ref to a top-level attribute declaration */
1447
- const char* ref =
1448
- leptris_element_attribute(ae, "ref");
1489
+ const char* ref = xsd_local_name(
1490
+ leptris_element_attribute(ae, "ref"));
1449
1491
  const XsdAttrDecl* src =
1450
1492
  ref ? xsd_find_top_attr(s, ref) : NULL;
1451
1493
  if (!src) {
@@ -1465,8 +1507,8 @@ static XsdAttrDecl* xsd_capture_attr_rows(struct leptris_xsd_schema* s,
1465
1507
  head = d;
1466
1508
  tail = d;
1467
1509
  } else if (strcmp(al, "attributeGroup") == 0) {
1468
- const char* ref =
1469
- leptris_element_attribute(ae, "ref");
1510
+ const char* ref = xsd_local_name(
1511
+ leptris_element_attribute(ae, "ref"));
1470
1512
  if (!ref) continue;
1471
1513
  XsdAttrGroupDef* g = s->attr_groups;
1472
1514
  for (; g; g = g->next)
@@ -208,12 +208,56 @@ static void validate_children(struct xsd_validator* v,
208
208
  }
209
209
  int ok = xsd_content_valid(s, model, xsd_target_ns(s), names, uris,
210
210
  n);
211
- free(names);
212
- free(uris);
213
- if (!ok) {
211
+ if (ok) {
212
+ free(names);
213
+ free(uris);
214
+ } else {
215
+ /* Name the culprit: libxml2 names the offending child once
216
+ * and recovers; the unnamed form cascaded ambiguously
217
+ * (#1626 — 353 errors for one misordered pair). Greedy
218
+ * linear match against the sequence's particle list;
219
+ * non-sequence models keep the generic message. */
220
+ const char* culprit = NULL;
221
+ if (model->kind == XSD_CM_SEQ) {
222
+ XsdCm* parts[256];
223
+ size_t j = 0;
224
+ for (XsdCm* q = model->first_child; q && j < 256;
225
+ q = q->next)
226
+ parts[j++] = q;
227
+ size_t pj = 0;
228
+ for (size_t k2 = 0; k2 < n && !culprit; k2++) {
229
+ const char* uri = uris ? uris[k2] : NULL;
230
+ int matched = 0;
231
+ while (pj < j) {
232
+ XsdCm* q = parts[pj];
233
+ if (q->name && strcmp(q->name, names[k2]) == 0 &&
234
+ (!q->ns ||
235
+ (uri && strcmp(uri, q->ns) == 0))) {
236
+ matched = 1;
237
+ pj++;
238
+ break;
239
+ }
240
+ if (q->min == 0) {
241
+ pj++; /* skippable optional: not expected here */
242
+ continue;
243
+ }
244
+ culprit = names[k2]; /* expected q, found this */
245
+ break;
246
+ }
247
+ if (!matched && !culprit) culprit = names[k2];
248
+ }
249
+ }
214
250
  const char* local = NULL;
215
251
  leptris_element_expanded_name(elem, &local, NULL, NULL);
216
- verrf(v, "element", local, "children do not match the content model");
252
+ char msg[256];
253
+ snprintf(msg, sizeof(msg),
254
+ culprit
255
+ ? "children do not match the content model (at '%s')"
256
+ : "children do not match the content model",
257
+ culprit ? culprit : "");
258
+ verrf(v, "element", local, msg);
259
+ free(names);
260
+ free(uris);
217
261
  }
218
262
  /* recurse: a global declaration governs the child when one
219
263
  * exists; otherwise the parent's particle declaration does
@@ -233,6 +233,36 @@ TEST(ParserConfigurableDepth, LowerCapRejectsShallowerNesting) {
233
233
  leptris_set_max_depth(0);
234
234
  }
235
235
 
236
+ /* leptris#1577: the open-tag guard honors the configured max depth
237
+ * while the parser's depth stacks were fixed at DP_MAX_DEPTH (256) —
238
+ * any document deeper than 256 under a raised cap wrote past the
239
+ * stacks (layout-dependent state corruption). The stacks now size to
240
+ * the cap; a 600-deep document must parse and walk cleanly. */
241
+ TEST(ParserConfigurableDepth, DeepDocumentUnderRaisedCap) {
242
+ leptris_set_max_depth(1024);
243
+ std::string xml;
244
+ for (int i = 0; i < 600; i++) xml += "<d>";
245
+ xml += 'x';
246
+ for (int i = 0; i < 600; i++) xml += "</d>";
247
+
248
+ LeptrisStatus st;
249
+ LeptrisDocument doc = leptris_parse_string(xml.data(), xml.size(), &st);
250
+ ASSERT_NE(doc, nullptr);
251
+ LeptrisElement root = leptris_document_root(doc);
252
+ ASSERT_NE(root, nullptr);
253
+
254
+ /* Walk to the spine bottom: 600 opens must survive intact. */
255
+ int depth_seen = 0;
256
+ LeptrisElement e = root;
257
+ while (e) {
258
+ depth_seen++;
259
+ e = leptris_element_first_child_any(e);
260
+ }
261
+ EXPECT_EQ(depth_seen, 600); /* the spine: 600 nested <d> */
262
+ leptris_document_free(doc);
263
+ leptris_set_max_depth(0);
264
+ }
265
+
236
266
  TEST(ParserConfigurableDepth, GetReturnsEffectiveValue) {
237
267
  leptris_set_max_depth(0);
238
268
  EXPECT_EQ(leptris_get_max_depth(), 256);
@@ -1499,3 +1499,128 @@ TEST(XsdValidate, UndeclaredAttributeIsStrictUnlessAnyAttribute) {
1499
1499
  }
1500
1500
 
1501
1501
  } // namespace
1502
+
1503
+ // ---- #1626: cross-ns imports, prefixed refs, error cascade -----
1504
+ namespace {
1505
+
1506
+ /* 1. cross-namespace xs:import registers its declarations when the
1507
+ * compiling schema carries its own targetNamespace (the wml-2010
1508
+ * shape). Chameleon/same-ns worked; foreign imports were skipped. */
1509
+ TEST(XsdImport1626, CrossNamespaceImportRegisters) {
1510
+ std::string dir = ::testing::TempDir();
1511
+ FILE* f1 = fopen((dir + "x6shared.xsd").c_str(), "wb");
1512
+ ASSERT_NE(f1, nullptr);
1513
+ const char* shared =
1514
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1515
+ " xmlns:w=\"urn:shared\""
1516
+ " targetNamespace=\"urn:shared\">"
1517
+ "<xs:complexType name=\"CT_Border\">"
1518
+ "<xs:attribute name=\"val\" type=\"xs:token\"/>"
1519
+ "</xs:complexType>"
1520
+ "</xs:schema>";
1521
+ fwrite(shared, 1, strlen(shared), f1);
1522
+ fclose(f1);
1523
+ FILE* f2 = fopen((dir + "x6main.xsd").c_str(), "wb");
1524
+ ASSERT_NE(f2, nullptr);
1525
+ /* the importing schema has ITS OWN target namespace and
1526
+ * references the imported type through the shared prefix */
1527
+ const char* mainx =
1528
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1529
+ " xmlns:w=\"urn:shared\""
1530
+ " targetNamespace=\"urn:main\">"
1531
+ "<xs:import namespace=\"urn:shared\""
1532
+ " schemaLocation=\"x6shared.xsd\"/>"
1533
+ "<xs:element name=\"b\">"
1534
+ "<xs:complexType>"
1535
+ "<xs:attribute name=\"id\" type=\"xs:integer\"/>"
1536
+ "<xs:attribute name=\"bd\" type=\"w:CT_Border\"/>"
1537
+ "</xs:complexType>"
1538
+ "</xs:element>"
1539
+ "</xs:schema>";
1540
+ fwrite(mainx, 1, strlen(mainx), f2);
1541
+ fclose(f2);
1542
+
1543
+ LeptrisStatus st = LEPTRIS_OK;
1544
+ LeptrisXsdSchema s =
1545
+ leptris_xsd_compile_file((dir + "x6main.xsd").c_str(), &st);
1546
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
1547
+ /* the imported type resolves: the attribute types */
1548
+ LeptrisDocument d = leptris_parse_string(
1549
+ "<b id=\"1\" bd=\"x\"/>", strlen("<b id=\"1\" bd=\"x\"/>"), &st);
1550
+ ASSERT_NE(d, nullptr);
1551
+ EXPECT_EQ(leptris_xsd_validate(s, d), 1);
1552
+ leptris_document_free(d);
1553
+ leptris_xsd_free(s);
1554
+ remove((dir + "x6shared.xsd").c_str());
1555
+ remove((dir + "x6main.xsd").c_str());
1556
+ }
1557
+
1558
+ /* 3. prefixed type refs resolve by local name (uniword's
1559
+ * default-xmlns spelling always worked; the w: spelling did not) */
1560
+ TEST(XsdImport1626, PrefixedTypeRefsResolve) {
1561
+ const char* x =
1562
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
1563
+ " xmlns:w=\"urn:x\" targetNamespace=\"urn:x\">"
1564
+ "<xs:complexType name=\"CT_Border\">"
1565
+ "<xs:attribute name=\"v\" type=\"xs:integer\"/>"
1566
+ "</xs:complexType>"
1567
+ "<xs:complexType name=\"CT_Wrap\">"
1568
+ "<xs:sequence>"
1569
+ "<xs:element name=\"b\" type=\"w:CT_Border\"/>"
1570
+ "</xs:sequence>"
1571
+ "</xs:complexType>"
1572
+ "<xs:element name=\"r\" type=\"w:CT_Wrap\"/>"
1573
+ "</xs:schema>";
1574
+ EXPECT_EQ(validate_doc(x, "<r><b v=\"1\"/></r>"), 1);
1575
+ EXPECT_EQ(validate_doc(x, "<r><b v=\"z\"/></r>"), 0);
1576
+ }
1577
+
1578
+ /* 2. error amplification: one misordered child reports ONCE, with
1579
+ * the offending child named — not once per ancestor (~175x) */
1580
+ TEST(XsdImport1626, MisorderReportsOnceNamed) {
1581
+ const char* x =
1582
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
1583
+ "<xs:complexType name=\"CT_TblBorders\">"
1584
+ "<xs:sequence>"
1585
+ "<xs:element name=\"top\" type=\"xs:token\"/>"
1586
+ "<xs:element name=\"right\" type=\"xs:token\"/>"
1587
+ "<xs:element name=\"insideH\" type=\"xs:token\"/>"
1588
+ "</xs:sequence>"
1589
+ "</xs:complexType>"
1590
+ "<xs:complexType name=\"CT_TblPr\">"
1591
+ "<xs:sequence>"
1592
+ "<xs:element name=\"borders\" type=\"CT_TblBorders\"/>"
1593
+ "<xs:element name=\"width\" type=\"xs:integer\"/>"
1594
+ "</xs:sequence>"
1595
+ "</xs:complexType>"
1596
+ "<xs:complexType name=\"CT_Tbl\">"
1597
+ "<xs:sequence>"
1598
+ "<xs:element name=\"tblPr\" type=\"CT_TblPr\"/>"
1599
+ "<xs:element name=\"row\" type=\"xs:token\"/>"
1600
+ "</xs:sequence>"
1601
+ "</xs:complexType>"
1602
+ "<xs:element name=\"tbl\" type=\"CT_Tbl\"/>"
1603
+ "</xs:schema>";
1604
+ /* insideH before right: the classic wml minimal case */
1605
+ LeptrisStatus st = LEPTRIS_OK;
1606
+ LeptrisXsdSchema s = leptris_xsd_compile(x, strlen(x), &st);
1607
+ ASSERT_NE(s, (LeptrisXsdSchema)0);
1608
+ const char* bad =
1609
+ "<tbl><tblPr><borders><top/><insideH/><right/>"
1610
+ "</borders><width>5</width></tblPr><row/></tbl>";
1611
+ LeptrisDocument d = leptris_parse_string(bad, strlen(bad), &st);
1612
+ ASSERT_NE(d, nullptr);
1613
+ EXPECT_EQ(leptris_xsd_validate(s, d), 0);
1614
+ size_t n = leptris_xsd_error_count(s);
1615
+ EXPECT_LE(n, 3u) << "amplified: " << n << " errors";
1616
+ if (n > 0) {
1617
+ const char* e = leptris_xsd_error_at(s, 0);
1618
+ EXPECT_NE(e, nullptr);
1619
+ EXPECT_NE(strstr(e, "insideH"), nullptr)
1620
+ << "first error names the offending child: " << e;
1621
+ }
1622
+ leptris_document_free(d);
1623
+ leptris_xsd_free(s);
1624
+ }
1625
+
1626
+ } // namespace
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
3
3
  "name": "leptris",
4
- "version": "1.9.334",
4
+ "version": "1.9.335",
5
5
  "description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
6
6
  "homepage": "https://github.com/leptris/leptris",
7
7
  "license": "MIT",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.334.0
4
+ version: 1.9.335.0
5
5
  platform: ppc64le-linux
6
6
  authors:
7
7
  - Ribose Inc.