leptris 1.9.308.0-ppc64le-linux → 1.9.311.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 +4 -4
- data/CHANGELOG.md +43 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/document.rb +42 -1
- data/lib/leptris/xml/element.rb +5 -0
- data/lib/leptris/xml/ffi.rb +8 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +104 -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 +10 -3
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_operators.c +234 -29
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +1 -1
- data/vendor-src/libleptris/src/leptris/xpath/functions.c +38 -0
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +389 -88
- data/vendor-src/libleptris/src/leptris/xpath/lexer.c +9 -0
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +31 -0
- data/vendor-src/libleptris/src/leptris/xpath/xpath_public.c +1 -1
- data/vendor-src/libleptris/src/leptris/xquery/xquery.c +76 -3
- data/vendor-src/libleptris/test/abi/test_dom_mutation_bugs.cpp +151 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +52 -71
- data/vendor-src/libleptris/test/xquery/test_xquery.cpp +62 -1
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 253cc4928e73c6151db3426cec6564a0c1e9b7e6d735921210de3498d07c9f80
|
|
4
|
+
data.tar.gz: 944c12f01b50a20fb5c909532467488a83dd94554465b1b55126f48f608c2a63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a459b9b1dd995cd2cb553c0941f1741a308aa585351eaa2cde3034ed4fcdda7f5eb3c5100d58603cc1d8046995892b5d7b7ea723e198277ae03423386aac6146
|
|
7
|
+
data.tar.gz: 2ecc975d6e07f68bc76d458ba62e3ba4fffe97c577bb2c7f24e41896729a9049a3bb7cccb0d26d23274dc3a237e40da2ea64ee70a15bf3d082e93a1f5646caf4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
## [1.9.311.0] - 2026-10-06
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **Document#absorb — move semantics for doomed splice sources
|
|
6
|
+
(libleptris 1.9.311, engine #1548)**: one call transfers a
|
|
7
|
+
scratch document's pool ownership into the destination, so
|
|
8
|
+
subsequent cross-document splices of its nodes move BY REFERENCE
|
|
9
|
+
— O(1), no deep copy, no live-set doubling (the sectioned-
|
|
10
|
+
cleanup flow's 7GB cg3 overrun; completes without the copy
|
|
11
|
+
blow-up). The absorbed document's #free is handle-only (the
|
|
12
|
+
destination's free performs the real release); splices into any
|
|
13
|
+
THIRD document still deep-copy; chaining rejects. The splice
|
|
14
|
+
seams skip source-side removal for absorbed sources (the move
|
|
15
|
+
already transferred the node — nothing to remove).
|
|
16
|
+
leptris_document_absorb attached (audit 370/369).
|
|
17
|
+
|
|
18
|
+
### Fixed — engine sync (1.9.309-1.9.311)
|
|
19
|
+
|
|
20
|
+
- 1.9.309: XPath signed exponent literals (`1E+1` truncated at
|
|
21
|
+
the E; the sign is part of the literal now).
|
|
22
|
+
- 1.9.310: a typed cast stored the operator frame's stack buffer
|
|
23
|
+
in the result's borrowed atomic_type slot (ASAN stack-use-after-
|
|
24
|
+
return); recursive closures sharing parameter names with an
|
|
25
|
+
outer frame no longer drop the outer bindings.
|
|
26
|
+
|
|
27
|
+
## [1.9.308.0] - 2026-10-05
|
|
28
|
+
|
|
29
|
+
### Engine sync (libleptris 1.9.308)
|
|
30
|
+
|
|
31
|
+
- **QT3 #1182 tail (leptris#1540)** — the 13 lever cases:
|
|
32
|
+
fn:avg's numeric promotion ladder with int64-exact decimal
|
|
33
|
+
division, the O(xs:double) carrier mark, typeswitch case `$v as
|
|
34
|
+
TYPE` binding arms, fn:apply function conversion (zero-arity,
|
|
35
|
+
atomization, sequence members), and fn:sum's per-type canonical
|
|
36
|
+
duration zero. Full QT3 61/61.
|
|
37
|
+
- **Per-engine crates, step 1 (leptris#1541)** — the shared
|
|
38
|
+
leptris-descriptor ABI crate in a new Cargo workspace, with the
|
|
39
|
+
runtime layout gate (Rust size_of vs engine introspection +
|
|
40
|
+
build/walk round trip) and the build.rs stale-link cache fix.
|
|
41
|
+
Ruby-side impact: none (Rust-side surface; zero header deltas,
|
|
42
|
+
audit unchanged 369/368, suite 866/0).
|
|
43
|
+
|
|
1
44
|
## [1.9.307.0] - 2026-10-05
|
|
2
45
|
|
|
3
46
|
### Fixed
|
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
|
@@ -561,10 +561,51 @@ class Leptris::XML::Document
|
|
|
561
561
|
end
|
|
562
562
|
alias_method :c14n, :canonicalize
|
|
563
563
|
|
|
564
|
+
# Move semantics for doomed splice sources (1.9.311, engine
|
|
565
|
+
# #1548): transfer +other+'s pool ownership into this document
|
|
566
|
+
# in one call. Every subsequent cross-document splice of other's
|
|
567
|
+
# nodes into THIS document moves by reference — O(1), no deep
|
|
568
|
+
# copy, no live-set doubling (the sectioned-cleanup flow's 7GB
|
|
569
|
+
# cg3 overrun). other's handle becomes handle-only: its #free
|
|
570
|
+
# releases just the wrapper; this document's free performs the
|
|
571
|
+
# real release. Chaining rejects (an absorbed document cannot
|
|
572
|
+
# absorb or be absorbed again). Returns self.
|
|
573
|
+
def absorb(other)
|
|
574
|
+
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
|
|
575
|
+
unless other.is_a?(Leptris::XML::Document)
|
|
576
|
+
raise ArgumentError, "absorb expects a Leptris::XML::Document"
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
Leptris::XML::FFI.check_status(
|
|
580
|
+
Leptris::XML::FFI.leptris_document_absorb(c_ptr, other.c_ptr))
|
|
581
|
+
other.mark_absorbed_into(self)
|
|
582
|
+
self
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# Absorption bookkeeping (private seam — never set ivars across
|
|
586
|
+
# objects by hand): the absorbed document's #free skips the
|
|
587
|
+
# engine release, and splice seams skip source-side removals
|
|
588
|
+
# because absorbed splices MOVE the node (nothing to remove).
|
|
589
|
+
def mark_absorbed_into(destination)
|
|
590
|
+
@absorbed_into = destination
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def absorbed_into?(destination)
|
|
594
|
+
@absorbed_into&.equal?(destination)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def absorbed?
|
|
598
|
+
!@absorbed_into.nil?
|
|
599
|
+
end
|
|
600
|
+
private :absorbed?
|
|
564
601
|
def free
|
|
565
602
|
return if @freed.state == :freed
|
|
566
603
|
@freed.state = :freed
|
|
567
|
-
|
|
604
|
+
# An absorbed source's pool is owned by its destination — this
|
|
605
|
+
# free is handle-only; the destination's free releases memory.
|
|
606
|
+
unless absorbed? || c_ptr.nil?
|
|
607
|
+
Leptris::XML::FFI.leptris_document_free(c_ptr)
|
|
608
|
+
end
|
|
568
609
|
@c_ptr = nil
|
|
569
610
|
@c_address = nil
|
|
570
611
|
@wrapper_cache&.clear
|
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -752,6 +752,11 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
752
752
|
# source parent and remove through a wrapped handle.
|
|
753
753
|
def remove_from_source_original(node)
|
|
754
754
|
return unless node.document && !node.document.equal?(@document)
|
|
755
|
+
# An ABSORBED source's splices move by reference (engine
|
|
756
|
+
# #1548): the node is already owned by this document — there is
|
|
757
|
+
# no source-side original to remove, and the engine parent is
|
|
758
|
+
# this tree's own parent.
|
|
759
|
+
return if node.document.absorbed_into?(@document)
|
|
755
760
|
|
|
756
761
|
srcp = Leptris::XML::FFI.leptris_node_parent(node.c_ptr)
|
|
757
762
|
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/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
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
|
+
|
|
72
|
+
## [1.9.309] - 2026-10-05
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **XPath lexer: signed exponent literals truncated at the `E` —
|
|
77
|
+
`1E+1` evaluated to `2` and `3.567812E+3 eq 3567.812` was false.**
|
|
78
|
+
The number scanner consumed the exponent digits but not its sign,
|
|
79
|
+
so `1E+1` tokenized as `1E` followed by `+ 1`. The sign is now part
|
|
80
|
+
of the literal (also `5.2E-3` and friends).
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- **XQuery numeric literal typing (XQuery 3.1 §3.13.1) with
|
|
85
|
+
end-to-end type discrimination — 17 more QT3 cases adopted
|
|
86
|
+
(abs +5, ceiling +3, floor +3, round-half-to-even +6), 32 with the
|
|
87
|
+
same-batch string-to-codepoints and min/max gates.**
|
|
88
|
+
- Exponent literals are `xs:double`, decimals with a point are
|
|
89
|
+
`xs:decimal`, plain integers `xs:integer`; `1.1 instance of
|
|
90
|
+
xs:double` is now `false` per spec.
|
|
91
|
+
- `instance of` / `typeswitch` match labeled numeric results by
|
|
92
|
+
XDM derivation (`xs:integer` <: `xs:decimal`); `case $v as TYPE`
|
|
93
|
+
arms bind their variable inside the arm. Unlabeled numerics keep
|
|
94
|
+
the historical any-numeric match — no XPath 1.0 surface change.
|
|
95
|
+
- `fn:abs` preserves its argument's numeric type (integer/decimal/
|
|
96
|
+
float/double, float through float32); `fn:ceiling`/`fn:floor`
|
|
97
|
+
label float results; `fn:round-half-to-even` computes its scale
|
|
98
|
+
in double (no `int` truncation UB at 2^32) and guards huge
|
|
99
|
+
scales; `fn:min`/`fn:max` label results by promoted input type;
|
|
100
|
+
FLWOR bindings preserve float/decimal/double marks.
|
|
101
|
+
- Vendor corpus: `fn/string-to-codepoints.xml` (+28), `fn/min`
|
|
102
|
+
and `fn/max` +1 each.
|
|
103
|
+
- Remaining pinned QT3 reds are documented by lever in the test
|
|
104
|
+
harness (HOF node-carrier, map iteration order, typed grouping).
|
|
105
|
+
|
|
106
|
+
|
|
3
107
|
## [1.9.308] - 2026-10-05
|
|
4
108
|
|
|
5
109
|
### Added
|
|
@@ -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
|
|
|
@@ -973,6 +976,10 @@ static struct leptris_xpath_result* evaluate_literal(XPathContext* ctx,
|
|
|
973
976
|
struct leptris_xpath_result* result = xpath_result_new(XPATH_RESULT_NUMBER);
|
|
974
977
|
if (!result) return NULL;
|
|
975
978
|
result->value.number_value = ast->number_value;
|
|
979
|
+
/* XQuery literal typing from the lexical (the parser
|
|
980
|
+
* stashed "xs:decimal"/"xs:double" in value; integers
|
|
981
|
+
* stay unlabeled). */
|
|
982
|
+
result->atomic_type = ast->value;
|
|
976
983
|
return result;
|
|
977
984
|
}
|
|
978
985
|
|