leptris 1.9.309.0-ppc64le-linux → 1.9.311.1-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 +4 -4
- data/CHANGELOG.md +49 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +48 -2
- data/lib/leptris/xml/element.rb +33 -21
- data/lib/leptris/xml/ffi.rb +8 -0
- data/lib/leptris/xml/node.rb +3 -2
- 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/include/leptris.h +25 -0
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +8 -0
- data/vendor-src/libleptris/src/leptris/leptris.c +31 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +13 -1
- data/vendor-src/libleptris/src/leptris/xpath/evaluator.c +6 -3
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +184 -28
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +1 -1
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +231 -78
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +21 -0
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +1 -1
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +60 -2
- data/vendor-src/libleptris/test/abi/test_dom_mutation_bugs.cpp +151 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +30 -55
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +57 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4652fa888a962a651464d12ed97e9a17cc8c19056f55f4694eb97ecd7f8447b
|
|
4
|
+
data.tar.gz: 3614fc667bddf01d0be1f014b0076988eb0a53f52ed3d9e1a70a14fb270fe85a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0711c12380bb334b39f888aef74f8045940f4d726a9932e7f69eb93c9a80af6c32d3b4e7359675e0206329d855bad9905e06ffc05e623226b3dc6eedb3770543
|
|
7
|
+
data.tar.gz: 964a54d1fb7ab8976a52d11fb12b6e799afd21a7e865fa0fa8f382dcf298906214260e567b0459e3f52ca81f1abdbffbf4836a6db034f4205c56bc51dbb46c50
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
## [1.9.311.1] - 2026-10-06
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **Docless children now route through the adoption contract
|
|
6
|
+
(#376)**: moxml's created nodes arrive as Node wrappers whose
|
|
7
|
+
#document is nil but whose C node is live in a foreign pool —
|
|
8
|
+
the splice sites' foreign check required a document, so those
|
|
9
|
+
children bypassed the namespace lift and, worse, the returned
|
|
10
|
+
wrapper addressed the ABANDONED source instead of the installed
|
|
11
|
+
in-tree copy (moxml's flows then mutated a node the tree never
|
|
12
|
+
saw). `foreign_child?` now treats document-nil as foreign at
|
|
13
|
+
add_child, prepend_child, add_next_sibling,
|
|
14
|
+
add_previous_sibling, and append_children, so every splice
|
|
15
|
+
returns the installed wrapper. Document#root= routes docless
|
|
16
|
+
roots through the copying ex-face too — the plain set_root of a
|
|
17
|
+
foreign-pool node linked the pointer WITHOUT adopting it into
|
|
18
|
+
the destination pool, so destination-side writes landed in the
|
|
19
|
+
source arena and reads through the root memo missed them.
|
|
20
|
+
Node-level mutations on docless
|
|
21
|
+
wrappers no longer crash the version gate (they memoize
|
|
22
|
+
nothing, so there is no version to advance).
|
|
23
|
+
|
|
24
|
+
## [1.9.311.0] - 2026-10-06
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Document#absorb — move semantics for doomed splice sources
|
|
29
|
+
(libleptris 1.9.311, engine #1548)**: one call transfers a
|
|
30
|
+
scratch document's pool ownership into the destination, so
|
|
31
|
+
subsequent cross-document splices of its nodes move BY REFERENCE
|
|
32
|
+
— O(1), no deep copy, no live-set doubling (the sectioned-
|
|
33
|
+
cleanup flow's 7GB cg3 overrun; completes without the copy
|
|
34
|
+
blow-up). The absorbed document's #free is handle-only (the
|
|
35
|
+
destination's free performs the real release); splices into any
|
|
36
|
+
THIRD document still deep-copy; chaining rejects. The splice
|
|
37
|
+
seams skip source-side removal for absorbed sources (the move
|
|
38
|
+
already transferred the node — nothing to remove).
|
|
39
|
+
leptris_document_absorb attached (audit 370/369).
|
|
40
|
+
|
|
41
|
+
### Fixed — engine sync (1.9.309-1.9.311)
|
|
42
|
+
|
|
43
|
+
- 1.9.309: XPath signed exponent literals (`1E+1` truncated at
|
|
44
|
+
the E; the sign is part of the literal now).
|
|
45
|
+
- 1.9.310: a typed cast stored the operator frame's stack buffer
|
|
46
|
+
in the result's borrowed atomic_type slot (ASAN stack-use-after-
|
|
47
|
+
return); recursive closures sharing parameter names with an
|
|
48
|
+
outer frame no longer drop the outer bindings.
|
|
49
|
+
|
|
1
50
|
## [1.9.308.0] - 2026-10-05
|
|
2
51
|
|
|
3
52
|
### Engine sync (libleptris 1.9.308)
|
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.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.311"
|
|
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
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/document.rb
CHANGED
|
@@ -388,7 +388,12 @@ class Leptris::XML::Document
|
|
|
388
388
|
# the foreign subtree into this document's pool and reports the
|
|
389
389
|
# installed element — a fresh handle distinct from the source
|
|
390
390
|
# pointer. Same-document installs keep the native fast path.
|
|
391
|
-
|
|
391
|
+
# Docless roots count as foreign (#376): a plain set_root of a
|
|
392
|
+
# foreign-pool node links the pointer WITHOUT adopting it into
|
|
393
|
+
# this pool — writes through this document land in the source
|
|
394
|
+
# arena and reads through the root memo miss them. The ex-face
|
|
395
|
+
# copies properly.
|
|
396
|
+
cross = Leptris::XML::Element.foreign_to?(element, self)
|
|
392
397
|
installed_ptr = nil
|
|
393
398
|
if cross
|
|
394
399
|
out = ::FFI::MemoryPointer.new(:pointer)
|
|
@@ -561,10 +566,51 @@ class Leptris::XML::Document
|
|
|
561
566
|
end
|
|
562
567
|
alias_method :c14n, :canonicalize
|
|
563
568
|
|
|
569
|
+
# Move semantics for doomed splice sources (1.9.311, engine
|
|
570
|
+
# #1548): transfer +other+'s pool ownership into this document
|
|
571
|
+
# in one call. Every subsequent cross-document splice of other's
|
|
572
|
+
# nodes into THIS document moves by reference — O(1), no deep
|
|
573
|
+
# copy, no live-set doubling (the sectioned-cleanup flow's 7GB
|
|
574
|
+
# cg3 overrun). other's handle becomes handle-only: its #free
|
|
575
|
+
# releases just the wrapper; this document's free performs the
|
|
576
|
+
# real release. Chaining rejects (an absorbed document cannot
|
|
577
|
+
# absorb or be absorbed again). Returns self.
|
|
578
|
+
def absorb(other)
|
|
579
|
+
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
580
|
+
unless other.is_a?(Leptris::XML::Document)
|
|
581
|
+
raise ArgumentError, "absorb expects a Leptris::XML::Document"
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
Leptris::XML::FFI.check_status(
|
|
585
|
+
Leptris::XML::FFI.leptris_document_absorb(c_ptr, other.c_ptr))
|
|
586
|
+
other.mark_absorbed_into(self)
|
|
587
|
+
self
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# Absorption bookkeeping (private seam — never set ivars across
|
|
591
|
+
# objects by hand): the absorbed document's #free skips the
|
|
592
|
+
# engine release, and splice seams skip source-side removals
|
|
593
|
+
# because absorbed splices MOVE the node (nothing to remove).
|
|
594
|
+
def mark_absorbed_into(destination)
|
|
595
|
+
@absorbed_into = destination
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def absorbed_into?(destination)
|
|
599
|
+
@absorbed_into&.equal?(destination)
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def absorbed?
|
|
603
|
+
!@absorbed_into.nil?
|
|
604
|
+
end
|
|
605
|
+
private :absorbed?
|
|
564
606
|
def free
|
|
565
607
|
return if @freed.state == :freed
|
|
566
608
|
@freed.state = :freed
|
|
567
|
-
|
|
609
|
+
# An absorbed source's pool is owned by its destination — this
|
|
610
|
+
# free is handle-only; the destination's free releases memory.
|
|
611
|
+
unless absorbed? || c_ptr.nil?
|
|
612
|
+
Leptris::XML::FFI.leptris_document_free(c_ptr)
|
|
613
|
+
end
|
|
568
614
|
@c_ptr = nil
|
|
569
615
|
@c_address = nil
|
|
570
616
|
@wrapper_cache&.clear
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -389,6 +389,15 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
389
389
|
!Leptris::XML::Native.ns_lift_needed?(node.c_ptr.address)
|
|
390
390
|
end
|
|
391
391
|
|
|
392
|
+
# A node is foreign to +document+ when it belongs to another one
|
|
393
|
+
# — or to NONE (#376): moxml's created nodes are live C nodes
|
|
394
|
+
# wrapped without a document, and they splice by copy exactly
|
|
395
|
+
# like attached-foreign ones. One definition of "foreign" for
|
|
396
|
+
# the element splices and Document#root=.
|
|
397
|
+
def self.foreign_to?(node, document)
|
|
398
|
+
node.document.nil? || !node.document.equal?(document)
|
|
399
|
+
end
|
|
400
|
+
|
|
392
401
|
def self.lift_namespaces_for_adoption(node, target_scope)
|
|
393
402
|
return unless node.is_a?(Leptris::XML::Element)
|
|
394
403
|
# Declarations the node already carries (its own definitions —
|
|
@@ -419,11 +428,11 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
419
428
|
# C-bound insert (TODO.perf/14): gates + predicate + version
|
|
420
429
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
421
430
|
# the namespace lift — the full path below handles it.
|
|
422
|
-
# Cross-document nodes skip the fast path entirely
|
|
423
|
-
# #1528): the engine deep-copies them at the
|
|
424
|
-
# full path's lift must run FIRST and the source
|
|
425
|
-
# removed after (move semantics).
|
|
426
|
-
if
|
|
431
|
+
# Cross-document and docless nodes skip the fast path entirely
|
|
432
|
+
# (1.9.304, #1528; #376): the engine deep-copies them at the
|
|
433
|
+
# splice, so the full path's lift must run FIRST and the source
|
|
434
|
+
# original is removed after (move semantics).
|
|
435
|
+
if Leptris::XML::Element.foreign_to?(node, @document)
|
|
427
436
|
ensure_writable!
|
|
428
437
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
429
438
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
@@ -455,10 +464,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
455
464
|
# C-bound insert (TODO.perf/14): gates + predicate + version
|
|
456
465
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
457
466
|
# the namespace lift — the full path below handles it.
|
|
458
|
-
# Cross-document: full path first (lift +
|
|
459
|
-
# the source original is removed; returns
|
|
460
|
-
# (self's new next sibling).
|
|
461
|
-
if
|
|
467
|
+
# Cross-document and docless (#376): full path first (lift +
|
|
468
|
+
# engine copy), then the source original is removed; returns
|
|
469
|
+
# the installed copy (self's new next sibling).
|
|
470
|
+
if Leptris::XML::Element.foreign_to?(node, @document)
|
|
462
471
|
ensure_writable!
|
|
463
472
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
464
473
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
@@ -490,10 +499,10 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
490
499
|
# C-bound insert (TODO.perf/14): gates + predicate + version
|
|
491
500
|
# bump + engine insert in one dispatch; Qnil = the child needs
|
|
492
501
|
# the namespace lift — the full path below handles it.
|
|
493
|
-
# Cross-document: full path first (lift +
|
|
494
|
-
# the source original is removed; returns
|
|
495
|
-
# (self's new previous sibling).
|
|
496
|
-
if
|
|
502
|
+
# Cross-document and docless (#376): full path first (lift +
|
|
503
|
+
# engine copy), then the source original is removed; returns
|
|
504
|
+
# the installed copy (self's new previous sibling).
|
|
505
|
+
if Leptris::XML::Element.foreign_to?(node, @document)
|
|
497
506
|
ensure_writable!
|
|
498
507
|
unless Leptris::XML::Element.skip_adoption_lift?(node)
|
|
499
508
|
Leptris::XML::Element.lift_namespaces_for_adoption(node, namespaces)
|
|
@@ -551,9 +560,7 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
551
560
|
nodes.each { |n| add_child(n) }
|
|
552
561
|
return
|
|
553
562
|
end
|
|
554
|
-
cross = nodes.any?
|
|
555
|
-
n.document && !n.document.equal?(@document)
|
|
556
|
-
end
|
|
563
|
+
cross = nodes.any? { |n| Leptris::XML::Element.foreign_to?(n, @document) }
|
|
557
564
|
if native_fast_children?
|
|
558
565
|
addrs = nodes.map { |n| n.c_ptr.address }
|
|
559
566
|
done = Leptris::XML::Native.append_binding_children(
|
|
@@ -668,13 +675,13 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
668
675
|
ensure_writable!
|
|
669
676
|
case node_or_markup
|
|
670
677
|
when Leptris::XML::Node
|
|
671
|
-
#
|
|
678
|
+
# Foreign-child adoption (1.9.304, #1528): the engine
|
|
672
679
|
# deep-copies foreign nodes at the splice — the returned
|
|
673
680
|
# wrapper must be the INSTALLED copy, not the detached
|
|
674
|
-
# source.
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
return append_installed(node_or_markup) if
|
|
681
|
+
# source. Docless wrappers count as foreign (#376): moxml's
|
|
682
|
+
# created nodes carry live C pointers with no owning
|
|
683
|
+
# document, and their callers expect the in-tree handle.
|
|
684
|
+
return append_installed(node_or_markup) if Leptris::XML::Element.foreign_to?(node_or_markup, @document)
|
|
678
685
|
# C-bound append (TODO.perf/08-09): one dispatch runs the
|
|
679
686
|
# readonly/liveness gates, the provable no-op lift
|
|
680
687
|
# predicate, the version bump, and the engine append. Qnil
|
|
@@ -752,6 +759,11 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
752
759
|
# source parent and remove through a wrapped handle.
|
|
753
760
|
def remove_from_source_original(node)
|
|
754
761
|
return unless node.document && !node.document.equal?(@document)
|
|
762
|
+
# An ABSORBED source's splices move by reference (engine
|
|
763
|
+
# #1548): the node is already owned by this document — there is
|
|
764
|
+
# no source-side original to remove, and the engine parent is
|
|
765
|
+
# this tree's own parent.
|
|
766
|
+
return if node.document.absorbed_into?(@document)
|
|
755
767
|
|
|
756
768
|
srcp = Leptris::XML::FFI.leptris_node_parent(node.c_ptr)
|
|
757
769
|
return if srcp.nil? || srcp.null?
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -490,6 +490,14 @@ attach_function :leptris_parse_string,
|
|
|
490
490
|
[:string, :pointer], :pointer
|
|
491
491
|
attach_function :leptris_document_free,
|
|
492
492
|
[:leptris_document], :void
|
|
493
|
+
# 1.9.311 (#1548): move semantics for doomed splice sources —
|
|
494
|
+
# one call transfers a source document's pool ownership into
|
|
495
|
+
# the destination, so subsequent cross-document splices move
|
|
496
|
+
# nodes by reference (O(1), no deep copy, no live-set
|
|
497
|
+
# doubling). The caller's src handle becomes handle-only;
|
|
498
|
+
# dst's free performs the real release. Chaining rejects.
|
|
499
|
+
attach_function :leptris_document_absorb,
|
|
500
|
+
[:leptris_document, :leptris_document], :leptris_status
|
|
493
501
|
attach_function :leptris_document_create,
|
|
494
502
|
[], :leptris_document
|
|
495
503
|
attach_function :leptris_document_set_root,
|
data/lib/leptris/xml/node.rb
CHANGED
|
@@ -281,14 +281,15 @@ class Leptris::XML::Node
|
|
|
281
281
|
# passes through this gate, so it is where the document's mutation
|
|
282
282
|
# version advances — the invalidation behind writable-document
|
|
283
283
|
# memoization. Bumping before the C call is conservative: a failed
|
|
284
|
-
# mutation merely discards memos.
|
|
284
|
+
# mutation merely discards memos. Docless wrappers (#376) mutate
|
|
285
|
+
# fine — they memoize nothing, so there is no version to advance.
|
|
285
286
|
def ensure_writable!
|
|
286
287
|
ensure_alive!
|
|
287
288
|
if readonly_document?
|
|
288
289
|
raise Leptris::XML::ReadOnlyError,
|
|
289
290
|
"document is readonly — mutation attempted on #{inspect}"
|
|
290
291
|
end
|
|
291
|
-
@document
|
|
292
|
+
@document&.advance_version
|
|
292
293
|
nil
|
|
293
294
|
end
|
|
294
295
|
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.311] - 2026-10-06
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`leptris_document_absorb(dst, src)` — move semantics for doomed
|
|
8
|
+
splice sources (#1548).** The #1528 cross-document adoption gate
|
|
9
|
+
deep-copies every foreign splice, doubling the live set; the
|
|
10
|
+
sectioned-semantic cleanup flow (scratch-document splices) pushed
|
|
11
|
+
the cg3 corpus past a 7GB budget (fatal alloc ~396s; completes at
|
|
12
|
+
16GB). One call before the splices transfers a doomed source
|
|
13
|
+
document's lifetime into the destination (riding the existing
|
|
14
|
+
xi:include ownership chain): the adoption gate then moves nodes by
|
|
15
|
+
reference — O(1) per seam, no copy, no live-set doubling, and
|
|
16
|
+
existing splice code is unchanged. The caller's later
|
|
17
|
+
`leptris_document_free(src)` releases only the handle; the
|
|
18
|
+
destination's free performs the real release. Splicing an absorbed
|
|
19
|
+
document's nodes into any OTHER document still deep-copies, and
|
|
20
|
+
the no-absorb copy contract is unchanged. No chaining: an
|
|
21
|
+
absorbed document cannot absorb or be absorbed again.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## [1.9.310] - 2026-10-06
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **Memory safety: a typed cast stored the operator frame's stack
|
|
30
|
+
buffer in the result's borrowed `atomic_type` slot — read after
|
|
31
|
+
return by the sequence fold (ASAN stack-use-after-return, caught
|
|
32
|
+
by Linux CI on this very batch).** Cast targets now assign
|
|
33
|
+
lifetime-stable family-table labels.
|
|
34
|
+
- **Recursive closures: a nested call sharing parameter names with
|
|
35
|
+
an outer frame removed the outer frame's bindings on exit —
|
|
36
|
+
`$a` vanished mid-recursion.** Both dynamic-call sites
|
|
37
|
+
detach/save the prior nodeset and restore it (fold-left/
|
|
38
|
+
fold-right-101..104: the spec'd recursive folds over `tail()`).
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **XQuery closure-call carriers, end-to-end — 19 more QT3
|
|
43
|
+
adoptions (for-each-010, for-each-pair-024/026/027, fold-left
|
|
44
|
+
-019/020/021/101..104, fold-right-013/019/020/101..104,
|
|
45
|
+
K2-SeqReverseFunc-1, cbcl-distinct-values-002).**
|
|
46
|
+
- `declare function` parameter types skip depth-aware — a
|
|
47
|
+
function-test type's own commas
|
|
48
|
+
(`$f as function(item()*, item()) as item()*`) broke the
|
|
49
|
+
parameter list; inline function literals accept `as TYPE`
|
|
50
|
+
annotations on params and the return.
|
|
51
|
+
- Callback arguments and accumulators keep their shape across
|
|
52
|
+
every hop: sequences ride the member carrier, empty sequences
|
|
53
|
+
bind zero nodes (`empty($acc)` on a `()` zero is true — the
|
|
54
|
+
average fold yields 8.625, min folds return 1), booleans stay
|
|
55
|
+
falsy through variable round-trips (and-folds return false).
|
|
56
|
+
- `xs:untypedAtomic` rides its own family mark: value-eq casts
|
|
57
|
+
it to the counterpart (cbcl-002 matches Saxon byte-for-byte)
|
|
58
|
+
while a plain `xs:string` never merges with typed values.
|
|
59
|
+
- QName tail: `namespace-uri-from-QName` resolves prefixed
|
|
60
|
+
lexical QNames in the static namespaces (`xs:int#1` → the
|
|
61
|
+
XMLSchema namespace); `castable/cast as xs:QName` accept
|
|
62
|
+
NCName:NCName lexicals; QName eq compares full lexicals.
|
|
63
|
+
- Range sequences and nodeset growth admit 1,000,000 items —
|
|
64
|
+
`fold-left(1 to 1000000, 0, ...)` computes the full million
|
|
65
|
+
(previously truncated at exactly 524288 items).
|
|
66
|
+
- Remaining QT3 pins are down to three, each documented in the
|
|
67
|
+
harness: fold-left-009/016 (node-typed accumulators must be
|
|
68
|
+
real nodes for path/identity shapes) and cbcl-002b (XQ10 vs
|
|
69
|
+
XQ30 value-eq contradiction).
|
|
70
|
+
|
|
71
|
+
|
|
3
72
|
## [1.9.309] - 2026-10-05
|
|
4
73
|
|
|
5
74
|
### Fixed
|
|
@@ -1068,6 +1068,31 @@ LEPTRIS_API void leptris_document_free(LeptrisDocument doc);
|
|
|
1068
1068
|
LEPTRIS_API void leptris_document_adopt_child(LeptrisDocument parent,
|
|
1069
1069
|
LeptrisDocument child);
|
|
1070
1070
|
|
|
1071
|
+
/**
|
|
1072
|
+
* Absorb a DOOMED source document into the destination: the source's
|
|
1073
|
+
* node pool is released with the destination, so cross-document
|
|
1074
|
+
* splices from source to destination move nodes by reference instead
|
|
1075
|
+
* of deep-copying (#1548 — the #1528 adoption copy doubled the live
|
|
1076
|
+
* set on scratch-document splice flows).
|
|
1077
|
+
*
|
|
1078
|
+
* After a successful absorb the caller must treat `src` as consumed:
|
|
1079
|
+
* its tree may be spliced into `dst` (zero-copy), and the caller's
|
|
1080
|
+
* leptris_document_free(src) releases only the handle — the memory
|
|
1081
|
+
* dies with `dst`. Splicing an absorbed document's nodes into any
|
|
1082
|
+
* OTHER document still deep-copies (#1528 semantics).
|
|
1083
|
+
*
|
|
1084
|
+
* @param dst Destination document (survives; owns src's pool)
|
|
1085
|
+
* @param src Doomed source document (handle consumed)
|
|
1086
|
+
* @return LEPTRIS_OK; LEPTRIS_ERROR_NULL_ARG on NULL arguments;
|
|
1087
|
+
* LEPTRIS_ERROR_INVALID_ARG when src == dst or src is
|
|
1088
|
+
* already absorbed (chaining is not supported).
|
|
1089
|
+
*
|
|
1090
|
+
* Memory: src's pool ownership transfers to dst. The caller's src
|
|
1091
|
+
* handle becomes handle-only.
|
|
1092
|
+
*/
|
|
1093
|
+
LEPTRIS_API LeptrisStatus leptris_document_absorb(LeptrisDocument dst,
|
|
1094
|
+
LeptrisDocument src);
|
|
1095
|
+
|
|
1071
1096
|
/**
|
|
1072
1097
|
* Get root element of document
|
|
1073
1098
|
*
|
|
@@ -416,6 +416,10 @@ static LeptrisNode* adopt_for_splice(LeptrisNode* node,
|
|
|
416
416
|
src = leaf_owner_doc(top);
|
|
417
417
|
}
|
|
418
418
|
if (!src || src == target) return node;
|
|
419
|
+
/* #1548: the source is a doomed scratch doc absorbed into
|
|
420
|
+
* the target — its pool dies with the target, so the node
|
|
421
|
+
* moves by reference. */
|
|
422
|
+
if (src->absorbed_by == target) return node;
|
|
419
423
|
LeptrisNode* copy = copy_leaf_chain(node, target);
|
|
420
424
|
return copy;
|
|
421
425
|
}
|
|
@@ -430,6 +434,10 @@ static LeptrisNode* adopt_for_splice(LeptrisNode* node,
|
|
|
430
434
|
struct leptris_document* src =
|
|
431
435
|
leptris_element_get_document((LeptrisElement)top);
|
|
432
436
|
if (!src || src == target) return node;
|
|
437
|
+
/* #1548: the source is a doomed scratch doc absorbed into
|
|
438
|
+
* the target — its pool dies with the target, so the subtree
|
|
439
|
+
* moves by reference (O(1), no live-set doubling). */
|
|
440
|
+
if (src->absorbed_by == target) return node;
|
|
433
441
|
LeptrisElement copy =
|
|
434
442
|
leptris_element_copy((LeptrisElement)node, target);
|
|
435
443
|
return copy ? (LeptrisNode*)copy : NULL;
|
|
@@ -884,6 +884,24 @@ LEPTRIS_API LeptrisDocument leptris_parse_file(const char* filepath, LeptrisStat
|
|
|
884
884
|
/**
|
|
885
885
|
* Free document and all its contents
|
|
886
886
|
*/
|
|
887
|
+
LEPTRIS_API LeptrisStatus leptris_document_absorb(LeptrisDocument dst,
|
|
888
|
+
LeptrisDocument src) {
|
|
889
|
+
if (!dst || !src) return LEPTRIS_ERROR_NULL_ARG;
|
|
890
|
+
if (dst == src) return LEPTRIS_ERROR_INVALID_ARG;
|
|
891
|
+
/* No chains: an absorbed document's lifetime already belongs to
|
|
892
|
+
* its absorber; re-absorbing would give one pool two owners. */
|
|
893
|
+
if (src->absorbed_by || dst->absorbed_by)
|
|
894
|
+
return LEPTRIS_ERROR_INVALID_ARG;
|
|
895
|
+
|
|
896
|
+
/* Ride the xi:include child_docs chain (TODO 117): document_free
|
|
897
|
+
* releases adopted children with the parent, AFTER its own tree
|
|
898
|
+
* references are gone — exactly the #1548 lifetime. */
|
|
899
|
+
leptris_document_adopt_child(dst, src);
|
|
900
|
+
src->absorbed_by = dst;
|
|
901
|
+
src->absorbed_handle = 1;
|
|
902
|
+
return LEPTRIS_OK;
|
|
903
|
+
}
|
|
904
|
+
|
|
887
905
|
LEPTRIS_API void leptris_document_adopt_child(LeptrisDocument parent,
|
|
888
906
|
LeptrisDocument child) {
|
|
889
907
|
if (!parent || !child) return;
|
|
@@ -913,6 +931,14 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
|
|
|
913
931
|
}
|
|
914
932
|
if (!doc) return;
|
|
915
933
|
|
|
934
|
+
/* #1548: an absorbed document's MEMORY belongs to its absorber
|
|
935
|
+
* (it rides the absorber's child_docs chain). A caller free
|
|
936
|
+
* releases only the outstanding handle. */
|
|
937
|
+
if (doc->absorbed_by) {
|
|
938
|
+
doc->absorbed_handle = 0;
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
916
942
|
/* Decrement reference count */
|
|
917
943
|
if (doc->ref_count > 0) {
|
|
918
944
|
doc->ref_count--;
|
|
@@ -995,6 +1021,11 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
|
|
|
995
1021
|
* doesn't try to walk the
|
|
996
1022
|
* adopted subtree. */
|
|
997
1023
|
ch->root = NULL;
|
|
1024
|
+
/* #1548: the anchor's free is the REAL one — clear the
|
|
1025
|
+
* absorbed state so the early handle-return above does
|
|
1026
|
+
* not swallow it. */
|
|
1027
|
+
ch->absorbed_by = NULL;
|
|
1028
|
+
ch->absorbed_handle = 0;
|
|
998
1029
|
leptris_document_free(ch);
|
|
999
1030
|
ch = next;
|
|
1000
1031
|
}
|
|
@@ -179,10 +179,22 @@ struct leptris_document {
|
|
|
179
179
|
* copies / analyze-string result trees, released with THIS
|
|
180
180
|
* document so borrowed nodes live exactly as long as the
|
|
181
181
|
* source tree (the ctx owned_docs chain dies at public-eval
|
|
182
|
-
* return and cannot serve plain leptris_xpath_eval results).
|
|
182
|
+
* return and cannot serve plain leptris_xpath_eval results).
|
|
183
|
+
* Absorbed documents (#1548) ride the SAME chain: a doomed
|
|
184
|
+
* scratch doc's pool is released with the absorbing document,
|
|
185
|
+
* letting the splice adoption gate move nodes by reference. */
|
|
183
186
|
struct leptris_document** anchored_docs;
|
|
184
187
|
size_t n_anchored_docs;
|
|
185
188
|
size_t cap_anchored_docs;
|
|
189
|
+
/* #1548 absorb-adoption: set by leptris_document_absorb on the
|
|
190
|
+
* SOURCE. absorbed_by owns this document's lifetime (it is in
|
|
191
|
+
* absorbed_by->anchored_docs); cross-document splice adoption
|
|
192
|
+
* into absorbed_by moves nodes by reference instead of
|
|
193
|
+
* copying. absorbed_handle counts outstanding CALLER handles:
|
|
194
|
+
* the caller's leptris_document_free releases only the handle
|
|
195
|
+
* (the anchor frees the memory later). */
|
|
196
|
+
struct leptris_document* absorbed_by;
|
|
197
|
+
int absorbed_handle;
|
|
186
198
|
/* XML Declaration support */
|
|
187
199
|
char* xml_version; /* "1.0", "1.1", etc. or NULL if not present */
|
|
188
200
|
int standalone; /* -1=not set, 0=no, 1=yes */
|
|
@@ -650,7 +650,7 @@ void xpath_nodeset_add(XPathNodeSet* nodeset, void* node) {
|
|
|
650
650
|
|
|
651
651
|
/* SAFETY: Validate nodeset structure before reallocation
|
|
652
652
|
* Corruption in count/capacity can cause heap corruption during realloc */
|
|
653
|
-
if (nodeset->count > nodeset->capacity || nodeset->capacity >
|
|
653
|
+
if (nodeset->count > nodeset->capacity || nodeset->capacity > 4000000) {
|
|
654
654
|
/* Corrupted structure - skip this addition */
|
|
655
655
|
return;
|
|
656
656
|
}
|
|
@@ -659,8 +659,11 @@ void xpath_nodeset_add(XPathNodeSet* nodeset, void* node) {
|
|
|
659
659
|
if (nodeset->count >= nodeset->capacity) {
|
|
660
660
|
size_t new_capacity = nodeset->capacity == 0 ? 4 : nodeset->capacity * 2;
|
|
661
661
|
|
|
662
|
-
/* SAFETY: Check for overflow
|
|
663
|
-
|
|
662
|
+
/* SAFETY: Check for overflow. 4M admits the corpus's
|
|
663
|
+
* million-item range folds (`1 to 1000000` needs a
|
|
664
|
+
* power-of-two capacity ABOVE 1M — the old 1M bound
|
|
665
|
+
* truncated the sequence at exactly 524288). */
|
|
666
|
+
if (new_capacity > 4000000) {
|
|
664
667
|
return;
|
|
665
668
|
}
|
|
666
669
|
|