leptris 1.9.226.0-ppc64le-linux → 1.9.229.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 +27 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +89 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/dom/cdata.c +1 -2
- data/vendor-src/libleptris/src/leptris/dom/comment.c +0 -1
- data/vendor-src/libleptris/src/leptris/dom/compact.c +19 -1
- data/vendor-src/libleptris/src/leptris/dom/compact.h +22 -1
- data/vendor-src/libleptris/src/leptris/dom/element.h +6 -7
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +19 -13
- data/vendor-src/libleptris/src/leptris/dom/entity_ref.c +0 -1
- data/vendor-src/libleptris/src/leptris/dom/node.h +3 -5
- data/vendor-src/libleptris/src/leptris/dom/node_public.c +76 -5
- data/vendor-src/libleptris/src/leptris/dom/pi.c +0 -1
- data/vendor-src/libleptris/src/leptris/dom/text.c +17 -10
- data/vendor-src/libleptris/src/leptris/dom/text.h +89 -12
- data/vendor-src/libleptris/src/leptris/dtd/validator.c +3 -1
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +29 -8
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +11 -11
- data/vendor-src/libleptris/src/leptris/leptris.c +5 -0
- data/vendor-src/libleptris/src/leptris/leptris_internal.h +9 -0
- data/vendor-src/libleptris/src/leptris/serialize/serialize.c +5 -5
- data/vendor-src/libleptris/src/leptris/xinclude/xinclude.c +15 -10
- data/vendor-src/libleptris/src/leptris/xpath/evaluator_types.c +6 -2
- data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +13 -6
- data/vendor-src/libleptris/test/abi/test_header_hygiene.cpp +7 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +45 -0
- data/vendor-src/libleptris/test/dom/test_text_borrowed.cpp +37 -33
- data/vendor-src/libleptris/test/parser/test_interleaved.cpp +74 -8
- 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: 4d963f56fd6859c13be718d1497ff6afd7c630d5c78e2e1415e148185dc987e8
|
|
4
|
+
data.tar.gz: db0c57e15ecb59015976b3971e5d9fa71889639a5fec94674ddfea739db13a25
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 670bf571cbd82801ce4a3a7ea96d5e33e81640ec5bbf5cfbcbaa29359c6eeb01ac7a2f9e0cffd9e5e3e8e56182ec41f96ff5cd889f874c02db7c4c15cdbe3954
|
|
7
|
+
data.tar.gz: 444dddccea09269b3a010d434f15e2fee30983844d68c8a8330b7d0530fdde57cabcef4f456a5f446005db8bc964eb3332b7593b74bf33b74dbc2f8f40610c23
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.229.0] - 2026-09-23
|
|
9
|
+
|
|
10
|
+
### Performance — engine sync (1.9.229)
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.229: the text node struct shrinks
|
|
13
|
+
40 → 32 bytes on LP64 and ILP32 alike (#1285 slice 4b) — the
|
|
14
|
+
content pointer became an int32 self-relative offset with
|
|
15
|
+
doc-tagged overflow stamps (0 = NULL, shared sentinel for the
|
|
16
|
+
static empty string, byte offset otherwise; >2 GB deltas spill
|
|
17
|
+
to the overflow table tagged EXPLICIT). No binding-facing
|
|
18
|
+
surface changes; audit 351/351, suite 781/0.
|
|
19
|
+
|
|
20
|
+
## [1.9.228.0] - 2026-09-23
|
|
21
|
+
|
|
22
|
+
### Fixed — Text#content= on created-but-unattached text nodes (#318; engine 1.9.227–228)
|
|
23
|
+
|
|
24
|
+
- **The #318 regression is fixed at the engine root**: engine
|
|
25
|
+
1.9.228 (upstream leptris#1320) gives detached text nodes the
|
|
26
|
+
same #519 owner treatment PI/comment/CDATA already had — an
|
|
27
|
+
owner backref stamped by `leptris_text_node_create` (compact
|
|
28
|
+
int32 offset, LP64 layout preserved) — so
|
|
29
|
+
`leptris_text_node_set_content` works before attach again.
|
|
30
|
+
The create → fill → attach builder sequence moxml's
|
|
31
|
+
`Text#content=` depends on is unblocked; verified against the
|
|
32
|
+
vendored 1.9.228 (the #318 repro passes; suite 778 → 781/0).
|
|
33
|
+
- 1.9.227 rides along (header-hygiene spec zeroing).
|
|
34
|
+
|
|
8
35
|
## [1.9.226.0] - 2026-09-23
|
|
9
36
|
|
|
10
37
|
### Performance — engine sync (1.9.226)
|
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.229"
|
|
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/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.229] - 2026-09-23
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- **dom: text node 40 → 32 bytes — content pointer became an int32
|
|
8
|
+
self-relative offset, with doc-tagged overflow stamps (#1285
|
|
9
|
+
slice 4b).** The struct is base + five int32s on both LP64 and
|
|
10
|
+
ILP32 (content off, uint32 len, int32 next, int32 parent, int32
|
|
11
|
+
owner doc). Encoding: 0 = NULL, a shared sentinel = the static
|
|
12
|
+
empty string (xsl:strip-space writes land there instead of one
|
|
13
|
+
overflow-table entry per stripped node), any other value a byte
|
|
14
|
+
offset from the node. **Thread safety:** the rare >2 GB delta
|
|
15
|
+
spills to the overflow table tagged with the EXPLICIT owning
|
|
16
|
+
document — the direct-parse lane is overflow-table-free by design
|
|
17
|
+
(it never sets the TLS current-doc), so a TLS-tagged spill leaked
|
|
18
|
+
the entry past document teardown, which Linux CI ASAN/LSan
|
|
19
|
+
surfaced as aborting the two concurrency tests. Every content
|
|
20
|
+
stamp now carries its document; the untagged setter is deleted
|
|
21
|
+
from the codebase. Local gates: 1683/1683 both parse lanes; the
|
|
22
|
+
CI ASAN leg that caught the leak is green.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## [1.9.228] - 2026-09-23
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **dom: `leptris_text_node_set_content` works again on
|
|
31
|
+
created-but-unattached text nodes (#1320).** The 1.9.223–1.9.226
|
|
32
|
+
window returned `LEPTRIS_ERROR_INVALID_ARG` for exactly the
|
|
33
|
+
create → fill → attach builder sequence bindings use (moxml's
|
|
34
|
+
`Text#content=` failed 5 shared examples). Root cause: issue #519
|
|
35
|
+
gave detached PI/comment/CDATA nodes an `owner_doc` field but
|
|
36
|
+
skipped text — at the time text nodes carried their own pool
|
|
37
|
+
backref, which #1285 slice 4 then deleted, leaving detached text
|
|
38
|
+
with no document to allocate from. Text now gets the same #519
|
|
39
|
+
treatment: an owner backref stamped by the public creator only,
|
|
40
|
+
stored as a compact int32 offset (the LP64 struct keeps its 40
|
|
41
|
+
bytes — the field lands in existing padding; ILP32 32), with
|
|
42
|
+
overflow-table fallback tagged with the EXPLICIT document (the
|
|
43
|
+
parse-context TLS slot holds the wrong doc or NULL outside parse).
|
|
44
|
+
Every parse-carve path zero-initializes the field; the
|
|
45
|
+
attached/parsed parent-walk fast path is untouched. TDD'd
|
|
46
|
+
red-first against the reported minimal repro;
|
|
47
|
+
`DetachedNodes.TextSetContentWorksBeforeAttach` pins the
|
|
48
|
+
unattached, attach-then-set, and parsed paths.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## [1.9.227] - 2026-09-23
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **test:** the header-hygiene compact-pointer edge specs zero their
|
|
57
|
+
fake stack-allocated nodes before use. The `last_child` and
|
|
58
|
+
`last_attribute` getters *walk* the sibling/attribute list, so the
|
|
59
|
+
uninitialized fake node's next-edge was read as stack garbage —
|
|
60
|
+
zero on arm64, nonzero on x86_64, where the slice-3b layout shift
|
|
61
|
+
surfaced the latent UB as a real CI failure. It was always UB.
|
|
62
|
+
|
|
63
|
+
### Performance
|
|
64
|
+
|
|
65
|
+
- **dom: binding_wrapper out of the node base — element 48B, text
|
|
66
|
+
40B, every node type −8B (#1285 slice 3b).** `LeptrisNode` itself
|
|
67
|
+
drops 24 → 12 bytes; the element struct reaches the lane's stated
|
|
68
|
+
goal of 48 bytes. The `binding_wrapper` field (#262) was never
|
|
69
|
+
adopted — zero callers in leptris-ruby and leptris-py (both cache
|
|
70
|
+
wrappers on their own side), making it pure dead weight at 8 bytes
|
|
71
|
+
per node per document, and it carried the #421 stale-pointer bug
|
|
72
|
+
class, which the removal structurally eliminates. The public
|
|
73
|
+
`leptris_node_get/set_binding_wrapper` API is unchanged and now
|
|
74
|
+
serves from a lazily-allocated open-addressed map owned by the
|
|
75
|
+
document: documents that never wrap (all of them today) allocate
|
|
76
|
+
nothing, and entries cannot outlive their nodes because the map
|
|
77
|
+
dies with the document.
|
|
78
|
+
- **parse: interleaved lane size floor — small docs defer to classic
|
|
79
|
+
(#1258).** The two-phase scan+replay lane pays a fixed setup cost
|
|
80
|
+
that small documents cannot amortize: canon's gate measured ~25%
|
|
81
|
+
slower on a 24KB readonly shape while large documents run 0.69–0.74×
|
|
82
|
+
(256KB–3.4MB). `dp_il_try` now defers to the classic parser below a
|
|
83
|
+
64KB floor (the midpoint between the measured loss and the wins);
|
|
84
|
+
`LEPTRIS_IL_MIN` overrides it in bytes, with explicit `0` disabling
|
|
85
|
+
the floor. Coverage is preserved, not narrowed: the parity suite
|
|
86
|
+
pins `LEPTRIS_IL_MIN=1` in every lane toggle, the lane-forced CI leg
|
|
87
|
+
sets it to `0` so the full suite still exercises the lane, and a new
|
|
88
|
+
spec pins the deferral itself.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
3
92
|
## [1.9.226] - 2026-09-22
|
|
4
93
|
|
|
5
94
|
### Performance
|
|
@@ -23,8 +23,7 @@ LeptrisCDATANode* leptris_cdata_create(const char* content,
|
|
|
23
23
|
node->base.type = LEPTRIS_NODE_TYPE_CDATA;
|
|
24
24
|
node->base.frozen = 0;
|
|
25
25
|
node->base.version = 0;
|
|
26
|
-
node->
|
|
27
|
-
node->parent_off = 0;
|
|
26
|
+
node->parent_off = 0;
|
|
28
27
|
node->next_sibling_off = 0;
|
|
29
28
|
|
|
30
29
|
if (content && content_len > 0) {
|
|
@@ -286,7 +286,9 @@ int32_t leptris_compact_int32_encode(void* base, void* target,
|
|
|
286
286
|
const int32_t* field_addr) {
|
|
287
287
|
if (!target) return 0;
|
|
288
288
|
ptrdiff_t d = (char*)target - (char*)base;
|
|
289
|
-
|
|
289
|
+
/* INT32_MAX is the EMPTY sentinel (compact.h) — spill exact
|
|
290
|
+
* deltas there too so raw offsets never collide with it. */
|
|
291
|
+
if (d < INT32_MIN || d >= INT32_MAX) {
|
|
290
292
|
LeptrisCompactOverflowTable* table = get_overflow_table();
|
|
291
293
|
struct leptris_document* doc = get_current_document();
|
|
292
294
|
if (table && leptris_compact_overflow_set(table, field_addr,
|
|
@@ -298,6 +300,22 @@ int32_t leptris_compact_int32_encode(void* base, void* target,
|
|
|
298
300
|
return (int32_t)d;
|
|
299
301
|
}
|
|
300
302
|
|
|
303
|
+
int32_t leptris_compact_int32_encode_doc(void* base, void* target,
|
|
304
|
+
const int32_t* field_addr,
|
|
305
|
+
struct leptris_document* doc) {
|
|
306
|
+
if (!target) return 0;
|
|
307
|
+
ptrdiff_t d = (char*)target - (char*)base;
|
|
308
|
+
if (d < INT32_MIN || d > INT32_MAX) {
|
|
309
|
+
LeptrisCompactOverflowTable* table = get_overflow_table();
|
|
310
|
+
if (table && leptris_compact_overflow_set(table, field_addr,
|
|
311
|
+
target, doc) == 0) {
|
|
312
|
+
return LEPTRIS_INT32_OVERFLOW_SENTINEL;
|
|
313
|
+
}
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
return (int32_t)d;
|
|
317
|
+
}
|
|
318
|
+
|
|
301
319
|
void* leptris_compact_int32_decode(void* base, int32_t off,
|
|
302
320
|
const int32_t* field_addr) {
|
|
303
321
|
if (off == 0) return NULL;
|
|
@@ -109,6 +109,15 @@ int32_t leptris_compact_int32_encode(void* base, void* target,
|
|
|
109
109
|
const int32_t* field_addr);
|
|
110
110
|
void* leptris_compact_int32_decode(void* base, int32_t off,
|
|
111
111
|
const int32_t* field_addr);
|
|
112
|
+
/* #1320: same encoding, but the overflow-table entry is tagged with
|
|
113
|
+
* the caller's explicit document instead of the thread-local
|
|
114
|
+
* "current document" — for stamps made OUTSIDE a parse context
|
|
115
|
+
* (public node creators), where the TLS slot holds the wrong doc or
|
|
116
|
+
* NULL and a mis-tagged entry would outlive its document. */
|
|
117
|
+
struct leptris_document;
|
|
118
|
+
int32_t leptris_compact_int32_encode_doc(void* base, void* target,
|
|
119
|
+
const int32_t* field_addr,
|
|
120
|
+
struct leptris_document* doc);
|
|
112
121
|
#ifdef __cplusplus
|
|
113
122
|
}
|
|
114
123
|
#endif
|
|
@@ -118,6 +127,15 @@ void* leptris_compact_int32_decode(void* base, int32_t off,
|
|
|
118
127
|
* value is INT32_MIN). */
|
|
119
128
|
#define LEPTRIS_INT32_OVERFLOW_SENTINEL_H ((int32_t)(-2147483647 - 1))
|
|
120
129
|
|
|
130
|
+
/* int32 sentinel for a non-NULL static "" pointer (#1285 slice 4b).
|
|
131
|
+
* Text content can be set to the empty string from far-away
|
|
132
|
+
* read-only storage (xsl:strip-space field writes); encoding that
|
|
133
|
+
* address would register one overflow-table entry per stripped
|
|
134
|
+
* node. Decoding returns a shared "" instead. Collision-free: the
|
|
135
|
+
* encode fast path below spills exact-INT32_MAX deltas to the
|
|
136
|
+
* overflow table, so no raw offset ever equals this value. */
|
|
137
|
+
#define LEPTRIS_COMPACT_INT32_EMPTY ((int32_t)2147483647)
|
|
138
|
+
|
|
121
139
|
/* Inline fast paths (lane 18): the shared encode/decode live in
|
|
122
140
|
* compact.c, so every tree-edge store or sibling/parent read paid an
|
|
123
141
|
* out-of-line call — two per node in the parse loop and one per edge
|
|
@@ -131,7 +149,10 @@ static inline int32_t leptris_compact_int32_encode_inline(
|
|
|
131
149
|
void* base, void* target, const int32_t* field_addr) {
|
|
132
150
|
if (!target) return 0;
|
|
133
151
|
ptrdiff_t d = (char*)target - (char*)base;
|
|
134
|
-
|
|
152
|
+
/* INT32_MAX is the EMPTY sentinel — a real delta of exactly
|
|
153
|
+
* that size spills to the overflow table (correct, just not
|
|
154
|
+
* inline). */
|
|
155
|
+
if (d < INT32_MIN || d >= INT32_MAX)
|
|
135
156
|
return leptris_compact_int32_encode(base, target, field_addr);
|
|
136
157
|
return (int32_t)d;
|
|
137
158
|
}
|
|
@@ -683,17 +683,16 @@ static inline struct leptris_namespace** leptris_elem_namespaces_ptr(
|
|
|
683
683
|
* pinned structs hold only pointers and int-sized scalars, so i686
|
|
684
684
|
* and armv7 produce identical layouts. */
|
|
685
685
|
#if SIZE_MAX == UINT64_MAX
|
|
686
|
-
LEPTRIS_STATIC_ASSERT(sizeof(struct leptris_element) ==
|
|
687
|
-
"#1285 slice
|
|
686
|
+
LEPTRIS_STATIC_ASSERT(sizeof(struct leptris_element) == 48,
|
|
687
|
+
"#1285 slice 3b: binding_wrapper out of the base -> 48 bytes");
|
|
688
688
|
|
|
689
689
|
LEPTRIS_STATIC_ASSERT(sizeof(struct leptris_attribute) == 40,
|
|
690
690
|
"round 19 attr layout: 16+16+4+2+2 = 40");
|
|
691
691
|
#else
|
|
692
|
-
/* ILP32: LeptrisNode
|
|
693
|
-
*
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
"#1285 slice 3a ILP32: 48 bytes");
|
|
692
|
+
/* ILP32: LeptrisNode 16->12 (#1285 slice 3b); attribute
|
|
693
|
+
* name_view 16->8 (value union stays 16 via inline_value[16]). */
|
|
694
|
+
LEPTRIS_STATIC_ASSERT(sizeof(struct leptris_element) == 44,
|
|
695
|
+
"#1285 slice 3b ILP32: base 12 + 8 + 6*4 + ptr 4 = 44 bytes");
|
|
697
696
|
|
|
698
697
|
LEPTRIS_STATIC_ASSERT(sizeof(struct leptris_attribute) == 32,
|
|
699
698
|
"leptris_attribute ILP32 layout: 8+16+4+2+2 = 32");
|
|
@@ -677,7 +677,8 @@ LeptrisStatus leptris_element_set_text(LeptrisElement elem, const char* text) {
|
|
|
677
677
|
if (text) {
|
|
678
678
|
/* Create new text node (even if empty to match pugixml behavior) */
|
|
679
679
|
LeptrisMemoryPool* pool = leptris_element_get_pool(elem);
|
|
680
|
-
LeptrisTextNode* text_node = leptris_text_create(text, strlen(text), pool
|
|
680
|
+
LeptrisTextNode* text_node = leptris_text_create(text, strlen(text), pool,
|
|
681
|
+
leptris_element_get_document(elem));
|
|
681
682
|
if (!text_node) {
|
|
682
683
|
return LEPTRIS_ERROR_MEMORY;
|
|
683
684
|
}
|
|
@@ -1341,9 +1342,10 @@ LeptrisElement leptris_element_append_copy(LeptrisElement parent, LeptrisElement
|
|
|
1341
1342
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
1342
1343
|
/* SAFETY: Verify text pointer is valid before accessing content */
|
|
1343
1344
|
if ((uintptr_t)text > 0x1000) {
|
|
1344
|
-
LeptrisTextNode* text_copy = leptris_text_create(text
|
|
1345
|
+
LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
|
|
1345
1346
|
text->content_len,
|
|
1346
|
-
leptris_element_get_pool(copy)
|
|
1347
|
+
leptris_element_get_pool(copy),
|
|
1348
|
+
leptris_element_get_document(copy));
|
|
1347
1349
|
if (text_copy) {
|
|
1348
1350
|
leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
|
|
1349
1351
|
}
|
|
@@ -1507,9 +1509,10 @@ LeptrisElement leptris_element_prepend_copy(LeptrisElement parent, LeptrisElemen
|
|
|
1507
1509
|
leptris_element_append_copy(copy, child);
|
|
1508
1510
|
} else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
|
|
1509
1511
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
1510
|
-
LeptrisTextNode* text_copy = leptris_text_create(text
|
|
1512
|
+
LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
|
|
1511
1513
|
text->content_len,
|
|
1512
|
-
leptris_element_get_pool(copy)
|
|
1514
|
+
leptris_element_get_pool(copy),
|
|
1515
|
+
leptris_element_get_document(copy));
|
|
1513
1516
|
if (text_copy) {
|
|
1514
1517
|
leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
|
|
1515
1518
|
}
|
|
@@ -1622,9 +1625,10 @@ LeptrisElement leptris_element_insert_copy_after(LeptrisElement sibling, Leptris
|
|
|
1622
1625
|
leptris_element_append_copy(copy, child);
|
|
1623
1626
|
} else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
|
|
1624
1627
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
1625
|
-
LeptrisTextNode* text_copy = leptris_text_create(text
|
|
1628
|
+
LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
|
|
1626
1629
|
text->content_len,
|
|
1627
|
-
leptris_element_get_pool(copy)
|
|
1630
|
+
leptris_element_get_pool(copy),
|
|
1631
|
+
leptris_element_get_document(copy));
|
|
1628
1632
|
if (text_copy) {
|
|
1629
1633
|
leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
|
|
1630
1634
|
}
|
|
@@ -1737,9 +1741,10 @@ LeptrisElement leptris_element_insert_copy_before(LeptrisElement sibling, Leptri
|
|
|
1737
1741
|
leptris_element_append_copy(copy, child);
|
|
1738
1742
|
} else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
|
|
1739
1743
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
1740
|
-
LeptrisTextNode* text_copy = leptris_text_create(text
|
|
1744
|
+
LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
|
|
1741
1745
|
text->content_len,
|
|
1742
|
-
leptris_element_get_pool(copy)
|
|
1746
|
+
leptris_element_get_pool(copy),
|
|
1747
|
+
leptris_element_get_document(copy));
|
|
1743
1748
|
if (text_copy) {
|
|
1744
1749
|
leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
|
|
1745
1750
|
}
|
|
@@ -1925,9 +1930,10 @@ static LeptrisElement leptris_element_copy_subtree_bulk_internal(
|
|
|
1925
1930
|
} else if (child_node->type == LEPTRIS_NODE_TYPE_TEXT) {
|
|
1926
1931
|
/* Copy text node (not bulk-allocated) */
|
|
1927
1932
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
1928
|
-
LeptrisTextNode* text_copy = leptris_text_create(text
|
|
1933
|
+
LeptrisTextNode* text_copy = leptris_text_create(leptris_textnode_content(text),
|
|
1929
1934
|
text->content_len,
|
|
1930
|
-
leptris_element_get_pool(copy)
|
|
1935
|
+
leptris_element_get_pool(copy),
|
|
1936
|
+
leptris_element_get_document(copy));
|
|
1931
1937
|
if (text_copy) {
|
|
1932
1938
|
/* Link as child using internal function */
|
|
1933
1939
|
leptris_element_append_child_internal(copy, (LeptrisNode*)text_copy);
|
|
@@ -2066,8 +2072,8 @@ static LeptrisElement copy_subtree_detached(LeptrisElement source,
|
|
|
2066
2072
|
copy, pool, doc);
|
|
2067
2073
|
} else if (ty == LEPTRIS_NODE_TYPE_TEXT) {
|
|
2068
2074
|
LeptrisTextNode* t = (LeptrisTextNode*)c;
|
|
2069
|
-
cc = (LeptrisNodeRef)leptris_text_create(t
|
|
2070
|
-
t->content_len, pool);
|
|
2075
|
+
cc = (LeptrisNodeRef)leptris_text_create(leptris_textnode_content(t),
|
|
2076
|
+
t->content_len, pool, doc);
|
|
2071
2077
|
if (cc) leptris_textnode_set_parent((LeptrisTextNode*)cc, copy);
|
|
2072
2078
|
} else if (ty == LEPTRIS_NODE_TYPE_CDATA) {
|
|
2073
2079
|
LeptrisCDATANode* cd = (LeptrisCDATANode*)c;
|
|
@@ -24,7 +24,6 @@ LeptrisEntityRefNode* leptris_entity_ref_create(const char* name,
|
|
|
24
24
|
node->base.type = LEPTRIS_NODE_TYPE_ENTITY_REF;
|
|
25
25
|
node->base.frozen = 0;
|
|
26
26
|
node->base.version = 0;
|
|
27
|
-
node->base.binding_wrapper = NULL;
|
|
28
27
|
node->parent_off = 0;
|
|
29
28
|
node->next_sibling_off = 0;
|
|
30
29
|
node->owner_doc = NULL;
|
|
@@ -57,11 +57,9 @@ typedef struct leptris_node {
|
|
|
57
57
|
* parse-time bulk strides hold. */
|
|
58
58
|
unsigned int version : 30; /* COW 2.2: Node version for tracking modifications */
|
|
59
59
|
uint32_t line; /* Source line (1-based, 0 = unknown). Issue #223 */
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* the cached wrapper without FFI call overhead. */
|
|
64
|
-
/* NOTE: Parent/sibling pointers stored in compressed form in specific node types */
|
|
60
|
+
/* NOTE: Parent/sibling pointers stored in compressed form in specific node types.
|
|
61
|
+
* #1285 slice 3b: binding_wrapper (#262) moved to the document-
|
|
62
|
+
* owned wrapper map — see leptris_internal.h; base is 12 bytes. */
|
|
65
63
|
} LeptrisNode;
|
|
66
64
|
|
|
67
65
|
/* ============================================================================
|
|
@@ -296,8 +296,13 @@ static LeptrisDocument node_public_document(LeptrisNodeRef node) {
|
|
|
296
296
|
LeptrisElement parent = leptris_node_parent(node);
|
|
297
297
|
if (parent) return leptris_element_get_document(parent);
|
|
298
298
|
/* Issue #519: detached non-element nodes carry their owning
|
|
299
|
-
* document — mutations must work before any attach.
|
|
299
|
+
* document — mutations must work before any attach. Text was the
|
|
300
|
+
* one kind #519 skipped (its pool backref covered it until #1285
|
|
301
|
+
* slice 4 removed it — the #1320 regression); its stamp is an
|
|
302
|
+
* int32 offset instead of a field. */
|
|
300
303
|
switch (node->type) {
|
|
304
|
+
case LEPTRIS_NODE_TYPE_TEXT:
|
|
305
|
+
return leptris_textnode_owner_doc((const LeptrisTextNode*)node);
|
|
301
306
|
case LEPTRIS_NODE_TYPE_PI:
|
|
302
307
|
return ((LeptrisPINode*)node)->owner_doc;
|
|
303
308
|
case LEPTRIS_NODE_TYPE_COMMENT:
|
|
@@ -328,7 +333,14 @@ LEPTRIS_API LeptrisNodeRef leptris_text_node_create(LeptrisDocument doc,
|
|
|
328
333
|
if (!doc->pool) return NULL;
|
|
329
334
|
if (!content) content = "";
|
|
330
335
|
size_t len = strlen(content);
|
|
331
|
-
LeptrisTextNode* n = leptris_text_create(content, len, doc->pool);
|
|
336
|
+
LeptrisTextNode* n = leptris_text_create(content, len, doc->pool, doc);
|
|
337
|
+
if (n) {
|
|
338
|
+
/* #1320: stamp the owner so detached mutations can reach the
|
|
339
|
+
* doc's pool (PI/comment/CDATA do this via owner_doc fields;
|
|
340
|
+
* explicit-doc encoder — no parse context here). */
|
|
341
|
+
n->owner_doc_off = leptris_compact_int32_encode_doc(
|
|
342
|
+
n, doc, &n->owner_doc_off, doc);
|
|
343
|
+
}
|
|
332
344
|
return n ? (LeptrisNodeRef)n : NULL;
|
|
333
345
|
}
|
|
334
346
|
|
|
@@ -404,7 +416,7 @@ LEPTRIS_API LeptrisStatus leptris_text_node_set_content(LeptrisNodeRef node,
|
|
|
404
416
|
if (!doc || !doc->pool) return LEPTRIS_ERROR_INVALID_ARG;
|
|
405
417
|
char* copy = node_public_pool_strdup(doc->pool, content, len);
|
|
406
418
|
if (!copy) return LEPTRIS_ERROR_MEMORY;
|
|
407
|
-
t
|
|
419
|
+
leptris_textnode_set_content_ptr_doc(t, copy, doc);
|
|
408
420
|
t->content_len = (uint32_t)len;
|
|
409
421
|
return LEPTRIS_OK;
|
|
410
422
|
}
|
|
@@ -688,12 +700,71 @@ LEPTRIS_API void leptris_node_source_position(LeptrisNodeRef node,
|
|
|
688
700
|
}
|
|
689
701
|
}
|
|
690
702
|
|
|
703
|
+
/* #1285 slice 3b: binding_wrapper storage moved out of LeptrisNode
|
|
704
|
+
* (both bindings have zero callers — they cache wrappers on their
|
|
705
|
+
* own side — and the in-node field carried the #421 stale-pointer
|
|
706
|
+
* bug class on recycled arena pages). Open-addressed map keyed by
|
|
707
|
+
* node pointer, lazily allocated per document, freed with it: map
|
|
708
|
+
* entries can never outlive their nodes, and documents that never
|
|
709
|
+
* wrap (all of them today) pay nothing. NULL before any set — the
|
|
710
|
+
* parse-NULL contract holds by construction. */
|
|
711
|
+
struct leptris_wrapper_entry {
|
|
712
|
+
LeptrisNodeRef node; /* 0 = empty slot */
|
|
713
|
+
void* wrapper;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
static size_t wrapper_hash(LeptrisNodeRef n, size_t cap) {
|
|
717
|
+
uintptr_t k = (uintptr_t)n;
|
|
718
|
+
return ((size_t)((k * 0x9E3779B97F4A7C15ULL) >> 32)) & (cap - 1);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
static int wrapper_grow(struct leptris_document* doc) {
|
|
722
|
+
size_t ncap = doc->wrapper_cap ? doc->wrapper_cap * 2 : 16;
|
|
723
|
+
struct leptris_wrapper_entry* nt = (struct leptris_wrapper_entry*)
|
|
724
|
+
calloc(ncap, sizeof(*nt));
|
|
725
|
+
if (!nt) return -1;
|
|
726
|
+
for (size_t i = 0; i < doc->wrapper_cap; i++) {
|
|
727
|
+
struct leptris_wrapper_entry* e = &doc->wrapper_map[i];
|
|
728
|
+
if (e->node) {
|
|
729
|
+
size_t j = wrapper_hash(e->node, ncap);
|
|
730
|
+
while (nt[j].node) j = (j + 1) & (ncap - 1);
|
|
731
|
+
nt[j] = *e;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
free(doc->wrapper_map);
|
|
735
|
+
doc->wrapper_map = nt;
|
|
736
|
+
doc->wrapper_cap = ncap;
|
|
737
|
+
return 0;
|
|
738
|
+
}
|
|
739
|
+
|
|
691
740
|
LEPTRIS_API void* leptris_node_get_binding_wrapper(LeptrisNodeRef node) {
|
|
692
|
-
|
|
741
|
+
if (!node) return NULL;
|
|
742
|
+
struct leptris_document* doc = node_public_document(node);
|
|
743
|
+
if (!doc || !doc->wrapper_map) return NULL;
|
|
744
|
+
size_t i = wrapper_hash(node, doc->wrapper_cap);
|
|
745
|
+
while (doc->wrapper_map[i].node) {
|
|
746
|
+
if (doc->wrapper_map[i].node == node)
|
|
747
|
+
return doc->wrapper_map[i].wrapper;
|
|
748
|
+
i = (i + 1) & (doc->wrapper_cap - 1);
|
|
749
|
+
}
|
|
750
|
+
return NULL;
|
|
693
751
|
}
|
|
694
752
|
|
|
695
753
|
LEPTRIS_API void leptris_node_set_binding_wrapper(LeptrisNodeRef node, void* wrapper) {
|
|
696
|
-
if (node)
|
|
754
|
+
if (!node) return;
|
|
755
|
+
struct leptris_document* doc = node_public_document(node);
|
|
756
|
+
if (!doc) return; /* detached, unregistered node: no carrier */
|
|
757
|
+
if (doc->wrapper_count * 2 + 2 >= doc->wrapper_cap) {
|
|
758
|
+
if (wrapper_grow(doc) != 0) return;
|
|
759
|
+
}
|
|
760
|
+
size_t i = wrapper_hash(node, doc->wrapper_cap);
|
|
761
|
+
while (doc->wrapper_map[i].node && doc->wrapper_map[i].node != node)
|
|
762
|
+
i = (i + 1) & (doc->wrapper_cap - 1);
|
|
763
|
+
if (!doc->wrapper_map[i].node) {
|
|
764
|
+
doc->wrapper_map[i].node = node;
|
|
765
|
+
doc->wrapper_count++;
|
|
766
|
+
}
|
|
767
|
+
doc->wrapper_map[i].wrapper = wrapper;
|
|
697
768
|
}
|
|
698
769
|
|
|
699
770
|
LEPTRIS_API int leptris_node_compare(LeptrisNodeRef a, LeptrisNodeRef b) {
|
|
@@ -46,7 +46,6 @@ LeptrisPINode* leptris_pi_create(const char* target,
|
|
|
46
46
|
node->base.type = LEPTRIS_NODE_TYPE_PI;
|
|
47
47
|
node->base.frozen = 0;
|
|
48
48
|
node->base.version = 0;
|
|
49
|
-
node->base.binding_wrapper = NULL;
|
|
50
49
|
node->parent_off = 0;
|
|
51
50
|
node->next_sibling_off = 0;
|
|
52
51
|
node->target = target_storage;
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* CRITICAL: Content is NEVER trimmed — preserved exactly as given. */
|
|
19
19
|
LeptrisTextNode* leptris_text_create(const char* content,
|
|
20
20
|
size_t content_len,
|
|
21
|
-
LeptrisMemoryPool* pool
|
|
21
|
+
LeptrisMemoryPool* pool,
|
|
22
|
+
struct leptris_document* doc) {
|
|
22
23
|
if (!pool) return NULL;
|
|
23
24
|
|
|
24
25
|
char* content_storage;
|
|
@@ -30,18 +31,18 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
30
31
|
node->base.frozen = 0;
|
|
31
32
|
node->base.version = 0;
|
|
32
33
|
node->base.raw = 0; /* uninitialized: see create_borrowed */
|
|
33
|
-
/* Explicit: pool memory is not zeroed and the retained arena
|
|
34
|
-
* recycles dirty pages — a stale binding_wrapper would hand
|
|
35
|
-
* bindings a dangling pointer. */
|
|
36
|
-
node->base.binding_wrapper = NULL;
|
|
37
34
|
node->parent_off = 0;
|
|
38
35
|
node->next_sibling_off = 0;
|
|
36
|
+
node->owner_doc_off = 0; /* #1320: only the public creator stamps */
|
|
39
37
|
|
|
40
38
|
if (content && content_len > 0) {
|
|
41
39
|
memcpy(content_storage, content, content_len);
|
|
42
40
|
}
|
|
43
41
|
content_storage[content_len] = '\0';
|
|
44
|
-
node
|
|
42
|
+
/* Contiguous with the node in the same pool alloc — the offset
|
|
43
|
+
* is a small positive delta; doc tags the oversized-content
|
|
44
|
+
* fallback should it ever leave the inline window. */
|
|
45
|
+
leptris_textnode_set_content_ptr_doc(node, content_storage, doc);
|
|
45
46
|
node->content_len = (uint32_t)content_len;
|
|
46
47
|
|
|
47
48
|
return node;
|
|
@@ -57,7 +58,8 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
57
58
|
* the parser's pool. */
|
|
58
59
|
LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
59
60
|
size_t content_len,
|
|
60
|
-
LeptrisMemoryPool* pool
|
|
61
|
+
LeptrisMemoryPool* pool,
|
|
62
|
+
struct leptris_document* doc) {
|
|
61
63
|
if (!pool) return NULL;
|
|
62
64
|
|
|
63
65
|
LeptrisTextNode* node = (LeptrisTextNode*)leptris_pool_alloc(pool, sizeof(LeptrisTextNode));
|
|
@@ -70,11 +72,15 @@ LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
|
70
72
|
* uninitialized raw bit silently flips DOE/cdata behavior by
|
|
71
73
|
* build configuration. */
|
|
72
74
|
node->base.raw = 0;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
/* Non-owning view into the doc-owned buffer; caller guarantees
|
|
76
|
+
* lifetime + termination (#1285 slice 4). The buffer-to-node
|
|
77
|
+
* distance crosses allocations — the doc-tagged stamp is
|
|
78
|
+
* MANDATORY here (TLS is unset on parse worker threads). */
|
|
79
|
+
leptris_textnode_set_content_ptr_doc(node, content, doc);
|
|
75
80
|
node->content_len = (uint32_t)content_len;
|
|
76
81
|
node->parent_off = 0;
|
|
77
82
|
node->next_sibling_off = 0;
|
|
83
|
+
node->owner_doc_off = 0; /* #1320: only the public creator stamps */
|
|
78
84
|
|
|
79
85
|
return node;
|
|
80
86
|
}
|
|
@@ -98,5 +104,6 @@ void leptris_text_free(LeptrisTextNode* text) {
|
|
|
98
104
|
* here. */
|
|
99
105
|
const char* leptris_text_get_content(LeptrisTextNode* text) {
|
|
100
106
|
if (!text) return NULL;
|
|
101
|
-
|
|
107
|
+
const char* c = leptris_textnode_content(text);
|
|
108
|
+
return c ? c : "";
|
|
102
109
|
}
|