leptris 1.9.228.0-x86_64-linux → 1.9.229.0-x86_64-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 +12 -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 +23 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/leptris/dom/compact.c +3 -1
- data/vendor-src/libleptris/src/leptris/dom/compact.h +13 -1
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +19 -13
- data/vendor-src/libleptris/src/leptris/dom/node_public.c +2 -2
- data/vendor-src/libleptris/src/leptris/dom/text.c +15 -5
- data/vendor-src/libleptris/src/leptris/dom/text.h +66 -11
- data/vendor-src/libleptris/src/leptris/dtd/validator.c +3 -1
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +8 -7
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +11 -11
- 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/dom/test_text_borrowed.cpp +36 -35
- 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: 94655f5964d22d85d415d8e8f8b48c7a8fc0772c6dec1bde5a3e539a1d569820
|
|
4
|
+
data.tar.gz: 8771d75bff1b08340b07b0db2c8996d4dcc9b65097d8766b62aeada7fd1e93a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 205f207da94cc4e8378554692b658bb3bf4e99e5b21755f5933a59691c044304dbb28a92af2307a728409e9fce18b0a7658f0d8cec438168a72a962ccc9f77ba
|
|
7
|
+
data.tar.gz: 31528505abde42209dc834e1385b1f7c4693000f5f048a91ee8cc1f9c62ec47fd38e6b1b9781787afec368566909722f2b44e5735ee6adac14432e4e641488cf
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ 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
|
+
|
|
8
20
|
## [1.9.228.0] - 2026-09-23
|
|
9
21
|
|
|
10
22
|
### Fixed — Text#content= on created-but-unattached text nodes (#318; engine 1.9.227–228)
|
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,28 @@
|
|
|
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
|
+
|
|
3
26
|
## [1.9.228] - 2026-09-23
|
|
4
27
|
|
|
5
28
|
### Fixed
|
|
@@ -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,
|
|
@@ -127,6 +127,15 @@ int32_t leptris_compact_int32_encode_doc(void* base, void* target,
|
|
|
127
127
|
* value is INT32_MIN). */
|
|
128
128
|
#define LEPTRIS_INT32_OVERFLOW_SENTINEL_H ((int32_t)(-2147483647 - 1))
|
|
129
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
|
+
|
|
130
139
|
/* Inline fast paths (lane 18): the shared encode/decode live in
|
|
131
140
|
* compact.c, so every tree-edge store or sibling/parent read paid an
|
|
132
141
|
* out-of-line call — two per node in the parse loop and one per edge
|
|
@@ -140,7 +149,10 @@ static inline int32_t leptris_compact_int32_encode_inline(
|
|
|
140
149
|
void* base, void* target, const int32_t* field_addr) {
|
|
141
150
|
if (!target) return 0;
|
|
142
151
|
ptrdiff_t d = (char*)target - (char*)base;
|
|
143
|
-
|
|
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)
|
|
144
156
|
return leptris_compact_int32_encode(base, target, field_addr);
|
|
145
157
|
return (int32_t)d;
|
|
146
158
|
}
|
|
@@ -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;
|
|
@@ -333,7 +333,7 @@ LEPTRIS_API LeptrisNodeRef leptris_text_node_create(LeptrisDocument doc,
|
|
|
333
333
|
if (!doc->pool) return NULL;
|
|
334
334
|
if (!content) content = "";
|
|
335
335
|
size_t len = strlen(content);
|
|
336
|
-
LeptrisTextNode* n = leptris_text_create(content, len, doc->pool);
|
|
336
|
+
LeptrisTextNode* n = leptris_text_create(content, len, doc->pool, doc);
|
|
337
337
|
if (n) {
|
|
338
338
|
/* #1320: stamp the owner so detached mutations can reach the
|
|
339
339
|
* doc's pool (PI/comment/CDATA do this via owner_doc fields;
|
|
@@ -416,7 +416,7 @@ LEPTRIS_API LeptrisStatus leptris_text_node_set_content(LeptrisNodeRef node,
|
|
|
416
416
|
if (!doc || !doc->pool) return LEPTRIS_ERROR_INVALID_ARG;
|
|
417
417
|
char* copy = node_public_pool_strdup(doc->pool, content, len);
|
|
418
418
|
if (!copy) return LEPTRIS_ERROR_MEMORY;
|
|
419
|
-
t
|
|
419
|
+
leptris_textnode_set_content_ptr_doc(t, copy, doc);
|
|
420
420
|
t->content_len = (uint32_t)len;
|
|
421
421
|
return LEPTRIS_OK;
|
|
422
422
|
}
|
|
@@ -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;
|
|
@@ -38,7 +39,10 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
38
39
|
memcpy(content_storage, content, content_len);
|
|
39
40
|
}
|
|
40
41
|
content_storage[content_len] = '\0';
|
|
41
|
-
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);
|
|
42
46
|
node->content_len = (uint32_t)content_len;
|
|
43
47
|
|
|
44
48
|
return node;
|
|
@@ -54,7 +58,8 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
54
58
|
* the parser's pool. */
|
|
55
59
|
LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
56
60
|
size_t content_len,
|
|
57
|
-
LeptrisMemoryPool* pool
|
|
61
|
+
LeptrisMemoryPool* pool,
|
|
62
|
+
struct leptris_document* doc) {
|
|
58
63
|
if (!pool) return NULL;
|
|
59
64
|
|
|
60
65
|
LeptrisTextNode* node = (LeptrisTextNode*)leptris_pool_alloc(pool, sizeof(LeptrisTextNode));
|
|
@@ -67,7 +72,11 @@ LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
|
67
72
|
* uninitialized raw bit silently flips DOE/cdata behavior by
|
|
68
73
|
* build configuration. */
|
|
69
74
|
node->base.raw = 0;
|
|
70
|
-
|
|
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);
|
|
71
80
|
node->content_len = (uint32_t)content_len;
|
|
72
81
|
node->parent_off = 0;
|
|
73
82
|
node->next_sibling_off = 0;
|
|
@@ -95,5 +104,6 @@ void leptris_text_free(LeptrisTextNode* text) {
|
|
|
95
104
|
* here. */
|
|
96
105
|
const char* leptris_text_get_content(LeptrisTextNode* text) {
|
|
97
106
|
if (!text) return NULL;
|
|
98
|
-
|
|
107
|
+
const char* c = leptris_textnode_content(text);
|
|
108
|
+
return c ? c : "";
|
|
99
109
|
}
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
#include "node.h"
|
|
12
12
|
#include "compact.h" /* compact-pointer helpers (TODO 121, TODO 178) */
|
|
13
13
|
|
|
14
|
-
/* Text node - inherits from LeptrisNode.
|
|
15
|
-
* was
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
/* Text node - inherits from LeptrisNode. 32 bytes (#1285 slice 4b +
|
|
15
|
+
* #1320, was 40 — the content pointer became an int32 self-relative
|
|
16
|
+
* byte offset and the detached-owner backref joined it; base 12 +
|
|
17
|
+
* five int32s on both LP64 and ILP32).
|
|
18
18
|
*
|
|
19
|
-
* #1285 slice 4 invariants:
|
|
19
|
+
* #1285 slice 4 invariants (unchanged):
|
|
20
20
|
* - `content` is NUL-terminated at content[content_len] in ALL
|
|
21
21
|
* cases — pooled copies (leptris_text_create) and in-place runs
|
|
22
22
|
* into the doc-owned input buffer alike (every parse lane
|
|
@@ -25,6 +25,17 @@
|
|
|
25
25
|
* (dp_text_create rejects beyond that); parse buffers that size
|
|
26
26
|
* are outside every supported target.
|
|
27
27
|
*
|
|
28
|
+
* #1285 slice 4b content_off encoding:
|
|
29
|
+
* - 0 = NULL (zero-init pool memory reads as no content).
|
|
30
|
+
* - LEPTRIS_COMPACT_INT32_EMPTY = the static "" (xsl:strip-space
|
|
31
|
+
* field writes point at a read-only literal far from any node;
|
|
32
|
+
* the shared sentinel avoids one overflow-table entry per
|
|
33
|
+
* stripped node).
|
|
34
|
+
* - anything else = byte offset from the node itself; deltas beyond
|
|
35
|
+
* int32 range spill to the overflow table (compact.h machinery,
|
|
36
|
+
* same as parent/sibling edges — ASLR can place the pool and the
|
|
37
|
+
* doc-owned input buffer > 2 GB apart).
|
|
38
|
+
*
|
|
28
39
|
* Issue #168: parent_off mirrors next_sibling_off so the parent of a
|
|
29
40
|
* text node can be queried in O(1).
|
|
30
41
|
*
|
|
@@ -37,7 +48,7 @@
|
|
|
37
48
|
* LP64 struct keeps its 40 bytes (padding absorbs it). */
|
|
38
49
|
typedef struct leptris_text_node {
|
|
39
50
|
LeptrisNode base; /* MUST be first */
|
|
40
|
-
|
|
51
|
+
int32_t content_off; /* Self-relative offset to NUL-terminated content (0=NULL, EMPTY=static "") */
|
|
41
52
|
uint32_t content_len; /* Byte length (excl. NUL); 4 GB cap */
|
|
42
53
|
/* (#450) int32 sibling edge — was cp16 (±256 KB). Text nodes
|
|
43
54
|
* link to ELEMENT siblings across the parse-time element↔text
|
|
@@ -61,28 +72,37 @@ typedef struct leptris_text_node {
|
|
|
61
72
|
* have a length-bounded view). Passing 0 with non-NULL content is
|
|
62
73
|
* treated as an empty string.
|
|
63
74
|
*
|
|
75
|
+
* `doc` (#1285 slice 4b thread safety): the content offset is
|
|
76
|
+
* node-relative int32; the rare >2GB delta (oversized-content pool
|
|
77
|
+
* path under far-apart allocators) spills to the overflow table and
|
|
78
|
+
* MUST be tagged with the owning document — a NULL/TLS tag leaks the
|
|
79
|
+
* entry past doc teardown (Linux-ASAN/LSan catches it). Every caller
|
|
80
|
+
* has the doc.
|
|
81
|
+
*
|
|
64
82
|
* Ownership: pool-allocated; leptris_document_free releases via pool. */
|
|
65
83
|
LeptrisTextNode* leptris_text_create(const char* content,
|
|
66
84
|
size_t content_len,
|
|
67
|
-
LeptrisMemoryPool* pool
|
|
85
|
+
LeptrisMemoryPool* pool,
|
|
86
|
+
struct leptris_document* doc);
|
|
68
87
|
|
|
69
88
|
/* Create a text node borrowing the caller's bytes (parse hot path).
|
|
70
89
|
*
|
|
71
90
|
* The node struct is pool-allocated; `content` is stored as a
|
|
72
|
-
* non-owning
|
|
91
|
+
* non-owning view into the caller's buffer — no memcpy. CALLER
|
|
73
92
|
* CONTRACT (#1285 slice 4): the run MUST already be NUL-terminated
|
|
74
93
|
* at content[content_len] and must outlive the document (parse
|
|
75
94
|
* lanes terminate the run in the doc-owned input copy before
|
|
76
|
-
* carving the node).
|
|
95
|
+
* carving the node). `doc` tags the overflow entry when the
|
|
96
|
+
* buffer-to-node distance exceeds int32 range (see leptris_text_create). */
|
|
77
97
|
LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
78
98
|
size_t content_len,
|
|
79
|
-
LeptrisMemoryPool* pool
|
|
99
|
+
LeptrisMemoryPool* pool,
|
|
100
|
+
struct leptris_document* doc);
|
|
80
101
|
|
|
81
102
|
void leptris_text_free(LeptrisTextNode* text);
|
|
82
103
|
|
|
83
104
|
/* Content access */
|
|
84
105
|
const char* leptris_text_get_content(LeptrisTextNode* text);
|
|
85
|
-
void leptris_text_set_content(LeptrisTextNode* text, const char* content);
|
|
86
106
|
|
|
87
107
|
/* Casting helpers */
|
|
88
108
|
#define LEPTRIS_NODE_AS_TEXT(node) \
|
|
@@ -119,6 +139,41 @@ static inline void leptris_textnode_set_parent(LeptrisTextNode* t, LeptrisElemen
|
|
|
119
139
|
t->parent_off = leptris_compact_int32_encode_inline(t, parent, &t->parent_off);
|
|
120
140
|
}
|
|
121
141
|
|
|
142
|
+
/* Compact content accessors (#1285 slice 4b). The content pointer
|
|
143
|
+
* is a self-relative int32 byte offset — same encoding as the edges
|
|
144
|
+
* above. Writers that store an empty string land on the shared
|
|
145
|
+
* EMPTY sentinel; pooled and in-place runs are a small positive
|
|
146
|
+
* offset from the node (the create paths allocate struct + content
|
|
147
|
+
* contiguously); far targets (static literals, cross-block buffer
|
|
148
|
+
* runs) spill to the overflow table. */
|
|
149
|
+
static inline const char* leptris_textnode_content(const LeptrisTextNode* t) {
|
|
150
|
+
if (!t || t->content_off == 0) return NULL;
|
|
151
|
+
if (t->content_off == LEPTRIS_COMPACT_INT32_EMPTY) return "";
|
|
152
|
+
return (const char*)leptris_compact_int32_decode_inline(
|
|
153
|
+
(void*)t, t->content_off, &t->content_off);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Doc-tagged stamp (#1285 slice 4b thread safety). Identical to
|
|
157
|
+
* set_content_ptr except the >2GB spill registers in the overflow
|
|
158
|
+
* table tagged with the EXPLICIT document (the TLS current-doc slot
|
|
159
|
+
* is unset on parse worker threads — dp is overflow-table-free by
|
|
160
|
+
* design — so a TLS-tagged entry leaks past doc teardown and Linux
|
|
161
|
+
* CI ASAN/LSan aborts the run). Parse-lane and builder sites MUST
|
|
162
|
+
* use this form; the pooled-create contiguous path never spills but
|
|
163
|
+
* takes doc anyway so the oversized-content fallback is covered. */
|
|
164
|
+
static inline void leptris_textnode_set_content_ptr_doc(
|
|
165
|
+
LeptrisTextNode* t, const char* p, struct leptris_document* doc) {
|
|
166
|
+
if (!t) return;
|
|
167
|
+
if (!p) { t->content_off = 0; return; }
|
|
168
|
+
if (*p == '\0') { t->content_off = LEPTRIS_COMPACT_INT32_EMPTY; return; }
|
|
169
|
+
ptrdiff_t d = (char*)p - (char*)t;
|
|
170
|
+
if (d < INT32_MIN || d >= INT32_MAX)
|
|
171
|
+
t->content_off = leptris_compact_int32_encode_doc(
|
|
172
|
+
t, (void*)p, &t->content_off, doc);
|
|
173
|
+
else
|
|
174
|
+
t->content_off = (int32_t)d;
|
|
175
|
+
}
|
|
176
|
+
|
|
122
177
|
/* Owning-document access (issue #1320). The stamp is written by
|
|
123
178
|
* leptris_text_node_create with the explicit-doc encoder (an
|
|
124
179
|
* overflow-table entry made outside a parse context must carry the
|
|
@@ -173,8 +173,10 @@ static int element_has_char_content(LeptrisElement elem) {
|
|
|
173
173
|
unsigned t = leptris_node_get_type(child);
|
|
174
174
|
if (t == LEPTRIS_NODE_TYPE_TEXT) {
|
|
175
175
|
const LeptrisTextNode* tn = (const LeptrisTextNode*)child;
|
|
176
|
+
const char* tc = leptris_textnode_content(tn);
|
|
177
|
+
if (!tc) continue;
|
|
176
178
|
for (size_t i = 0; i < tn->content_len; i++) {
|
|
177
|
-
if (!isspace((unsigned char)
|
|
179
|
+
if (!isspace((unsigned char)tc[i])) return 1;
|
|
178
180
|
}
|
|
179
181
|
} else if (t == LEPTRIS_NODE_TYPE_CDATA) {
|
|
180
182
|
const LeptrisCDATANode* cn = (const LeptrisCDATANode*)child;
|
|
@@ -991,7 +991,7 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
991
991
|
p->pool, 128 * sizeof(LeptrisTextNode));
|
|
992
992
|
if (!tn) {
|
|
993
993
|
return leptris_text_create_borrowed(content, content_len,
|
|
994
|
-
p->pool);
|
|
994
|
+
p->pool, p->doc);
|
|
995
995
|
}
|
|
996
996
|
p->text_end = tn + 128;
|
|
997
997
|
/* cursor == tn: carve below */
|
|
@@ -1010,7 +1010,7 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
1010
1010
|
* carve (dispatch-entry dp_nul / walker '&' NUL / EOF sentinel)
|
|
1011
1011
|
* — the terminator write lives with the byte's last reader, not
|
|
1012
1012
|
* here. */
|
|
1013
|
-
tn
|
|
1013
|
+
leptris_textnode_set_content_ptr_doc(tn, content, p->doc);
|
|
1014
1014
|
tn->content_len = (uint32_t)content_len;
|
|
1015
1015
|
tn->parent_off = 0;
|
|
1016
1016
|
tn->next_sibling_off = 0;
|
|
@@ -1443,7 +1443,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1443
1443
|
leptris_decode_entities_view(&ssv, pool);
|
|
1444
1444
|
if (sexp) {
|
|
1445
1445
|
rt = leptris_text_create(
|
|
1446
|
-
sexp, strlen(sexp), pool);
|
|
1446
|
+
sexp, strlen(sexp), pool, p.doc);
|
|
1447
1447
|
}
|
|
1448
1448
|
}
|
|
1449
1449
|
if (!rt) {
|
|
@@ -1482,7 +1482,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1482
1482
|
LeptrisStringView ssv = leptris_sv_from_ptr(seg, slen);
|
|
1483
1483
|
char* sexp = leptris_decode_entities_view(&ssv, pool);
|
|
1484
1484
|
if (sexp) {
|
|
1485
|
-
rt = leptris_text_create(sexp, strlen(sexp), pool);
|
|
1485
|
+
rt = leptris_text_create(sexp, strlen(sexp), pool, p.doc);
|
|
1486
1486
|
}
|
|
1487
1487
|
}
|
|
1488
1488
|
if (!rt) {
|
|
@@ -1512,7 +1512,7 @@ static struct leptris_document* direct_parse_internal(char* buf, size_t len,
|
|
|
1512
1512
|
&sv, p.dtd, pool)
|
|
1513
1513
|
: leptris_decode_entities_view(&sv, pool);
|
|
1514
1514
|
if (expanded) {
|
|
1515
|
-
tn = leptris_text_create(expanded, strlen(expanded), pool);
|
|
1515
|
+
tn = leptris_text_create(expanded, strlen(expanded), pool, p.doc);
|
|
1516
1516
|
} else {
|
|
1517
1517
|
tn = dp_text_create(&p, text_start, tlen);
|
|
1518
1518
|
}
|
|
@@ -2592,7 +2592,7 @@ static struct leptris_document* dp_il_build(
|
|
|
2592
2592
|
tn->base.type = LEPTRIS_NODE_TYPE_TEXT;
|
|
2593
2593
|
tn->base.frozen = 1;
|
|
2594
2594
|
tn->base.line = r->line;
|
|
2595
|
-
tn
|
|
2595
|
+
leptris_textnode_set_content_ptr_doc(tn, content, doc);
|
|
2596
2596
|
tn->content_len = r->len;
|
|
2597
2597
|
/* (slice 4: pool/borrowed fields gone; the il scratch
|
|
2598
2598
|
* run is already NUL-terminated at carve) */
|
|
@@ -2639,7 +2639,8 @@ oom_early:
|
|
|
2639
2639
|
int leptris_il_run(const char* xml, size_t len, int drop_ws,
|
|
2640
2640
|
IlCtx* c, char** scratch_out, uint32_t* root_out) {
|
|
2641
2641
|
*scratch_out = NULL;
|
|
2642
|
-
c->recs =
|
|
2642
|
+
c->recs = NULL;
|
|
2643
|
+
c->attrs = NULL;
|
|
2643
2644
|
c->nrec = c->crec = c->nattr = c->cattr = 0;
|
|
2644
2645
|
c->depth = 0;
|
|
2645
2646
|
if (!xml || len == 0 || len >= 0x7FFFFFFFu) return 0;
|
|
@@ -3908,7 +3908,7 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
3908
3908
|
w9++;
|
|
3909
3909
|
if (t9 && w9 && t9[w9]) {
|
|
3910
3910
|
LeptrisTextNode* tn9 = (LeptrisTextNode*)n;
|
|
3911
|
-
tn9
|
|
3911
|
+
leptris_textnode_set_content_ptr_doc(tn9, t9 + w9, b->doc);
|
|
3912
3912
|
tn9->content_len -= w9;
|
|
3913
3913
|
} else if (t9 && w9 && !t9[w9]) {
|
|
3914
3914
|
return; /* pure before-head ws: dropped (tests7:7) */
|
|
@@ -3986,7 +3986,7 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
3986
3986
|
if (ct && ct[cw] && cw > 0) {
|
|
3987
3987
|
LeptrisTextNode* tail =
|
|
3988
3988
|
leptris_text_create(ct + cw, strlen(ct + cw),
|
|
3989
|
-
b->pool);
|
|
3989
|
+
b->pool, b->doc);
|
|
3990
3990
|
((char*)ct)[cw] = '\0';
|
|
3991
3991
|
((LeptrisTextNode*)n)->content_len = cw;
|
|
3992
3992
|
leptris_element_append_child_internal_doc(
|
|
@@ -4916,7 +4916,7 @@ head_end =
|
|
|
4916
4916
|
if (w2 > tx && *w2) {
|
|
4917
4917
|
LeptrisTextNode* tail =
|
|
4918
4918
|
leptris_text_create(
|
|
4919
|
-
w2, strlen(w2), b->pool);
|
|
4919
|
+
w2, strlen(w2), b->pool, b->doc);
|
|
4920
4920
|
if (tail) {
|
|
4921
4921
|
leptris_node_set_next_sibling(
|
|
4922
4922
|
(LeptrisNodeRef)tail,
|
|
@@ -5438,12 +5438,12 @@ static LeptrisTextNode* h_text_node(HBuilder* b, const char* s,
|
|
|
5438
5438
|
}
|
|
5439
5439
|
}
|
|
5440
5440
|
*(char*)e = '\0';
|
|
5441
|
-
return leptris_text_create_borrowed(s, len, b->pool);
|
|
5441
|
+
return leptris_text_create_borrowed(s, len, b->pool, b->doc);
|
|
5442
5442
|
}
|
|
5443
5443
|
size_t dlen = 0;
|
|
5444
5444
|
char* dec = h_decode_text(b, s, e, &dlen);
|
|
5445
5445
|
if (!dec || !*dec) return NULL;
|
|
5446
|
-
return leptris_text_create(dec, dlen, b->pool);
|
|
5446
|
+
return leptris_text_create(dec, dlen, b->pool, b->doc);
|
|
5447
5447
|
}
|
|
5448
5448
|
|
|
5449
5449
|
static LeptrisDocument html_parse_shared(
|
|
@@ -5783,13 +5783,13 @@ static LeptrisDocument html_parse_shared(
|
|
|
5783
5783
|
}
|
|
5784
5784
|
}
|
|
5785
5785
|
LeptrisTextNode* t =
|
|
5786
|
-
leptris_text_create(buf, o, b.pool);
|
|
5786
|
+
leptris_text_create(buf, o, b.pool, b.doc);
|
|
5787
5787
|
if (t)
|
|
5788
5788
|
h_append(&b, (LeptrisNodeRef)t);
|
|
5789
5789
|
}
|
|
5790
5790
|
} else {
|
|
5791
5791
|
LeptrisTextNode* t = leptris_text_create(
|
|
5792
|
-
cs, clen, b.pool);
|
|
5792
|
+
cs, clen, b.pool, b.doc);
|
|
5793
5793
|
if (t) h_append(&b, (LeptrisNodeRef)t);
|
|
5794
5794
|
}
|
|
5795
5795
|
}
|
|
@@ -5838,7 +5838,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
5838
5838
|
if (t) h_append(&b, (LeptrisNodeRef)t);
|
|
5839
5839
|
}
|
|
5840
5840
|
LeptrisTextNode* t =
|
|
5841
|
-
leptris_text_create("</", 2, b.pool);
|
|
5841
|
+
leptris_text_create("</", 2, b.pool, b.doc);
|
|
5842
5842
|
if (t) h_append(&b, (LeptrisNodeRef)t);
|
|
5843
5843
|
p = end;
|
|
5844
5844
|
text = p;
|
|
@@ -7907,7 +7907,7 @@ static LeptrisDocument html_parse_shared(
|
|
|
7907
7907
|
b.whatwg, &dlen);
|
|
7908
7908
|
if (dec && dlen) {
|
|
7909
7909
|
LeptrisTextNode* t = leptris_text_create(
|
|
7910
|
-
dec, dlen, b.pool);
|
|
7910
|
+
dec, dlen, b.pool, b.doc);
|
|
7911
7911
|
if (t)
|
|
7912
7912
|
leptris_element_append_child_internal_doc(
|
|
7913
7913
|
e, (LeptrisNodeRef)t, b.doc);
|
|
@@ -7917,14 +7917,14 @@ static LeptrisDocument html_parse_shared(
|
|
|
7917
7917
|
h_nul_fffd_copy(b.pool, cs, clen, eof_fffd);
|
|
7918
7918
|
if (mapped && mapped[0]) {
|
|
7919
7919
|
LeptrisTextNode* t = leptris_text_create(
|
|
7920
|
-
mapped, strlen(mapped), b.pool);
|
|
7920
|
+
mapped, strlen(mapped), b.pool, b.doc);
|
|
7921
7921
|
if (t)
|
|
7922
7922
|
leptris_element_append_child_internal_doc(
|
|
7923
7923
|
et, (LeptrisNodeRef)t, b.doc);
|
|
7924
7924
|
}
|
|
7925
7925
|
} else {
|
|
7926
7926
|
LeptrisTextNode* t = leptris_text_create(
|
|
7927
|
-
cs, clen, b.pool);
|
|
7927
|
+
cs, clen, b.pool, b.doc);
|
|
7928
7928
|
if (t)
|
|
7929
7929
|
leptris_element_append_child_internal_doc(
|
|
7930
7930
|
et, (LeptrisNodeRef)t, b.doc);
|
|
@@ -937,7 +937,7 @@ static int ser_children_have_text(LeptrisNode* fc) {
|
|
|
937
937
|
* entity-decoded at parse time — length-bounded read,
|
|
938
938
|
* no serve-mode branch. */
|
|
939
939
|
LeptrisTextNode* tn = (LeptrisTextNode*)c;
|
|
940
|
-
const char* tc = tn
|
|
940
|
+
const char* tc = leptris_textnode_content(tn);
|
|
941
941
|
size_t tl = tc ? tn->content_len : 0;
|
|
942
942
|
for (size_t i = 0; i < tl; i++) {
|
|
943
943
|
if (tc[i] != ' ' && tc[i] != '\t' &&
|
|
@@ -1016,7 +1016,7 @@ void serialize_element_internal(LeptrisElement root_elem, SerializeBuffer* buf,
|
|
|
1016
1016
|
cur->type == LEPTRIS_NODE_TYPE_TEXT &&
|
|
1017
1017
|
!((sp > 0) && st[sp - 1].mixed)) {
|
|
1018
1018
|
LeptrisTextNode* wsn = (LeptrisTextNode*)cur;
|
|
1019
|
-
const char* wsc = wsn
|
|
1019
|
+
const char* wsc = leptris_textnode_content(wsn);
|
|
1020
1020
|
size_t wsl = wsc ? wsn->content_len : 0;
|
|
1021
1021
|
int ws_only = 1;
|
|
1022
1022
|
for (size_t i = 0; i < wsl; i++) {
|
|
@@ -1033,7 +1033,7 @@ void serialize_element_internal(LeptrisElement root_elem, SerializeBuffer* buf,
|
|
|
1033
1033
|
if (buf->indent_text && buf->indent_spaces > 0 &&
|
|
1034
1034
|
cur->type == LEPTRIS_NODE_TYPE_TEXT) {
|
|
1035
1035
|
LeptrisTextNode* itn = (LeptrisTextNode*)cur;
|
|
1036
|
-
const char* isc = itn
|
|
1036
|
+
const char* isc = leptris_textnode_content(itn);
|
|
1037
1037
|
size_t isl = isc ? itn->content_len : 0;
|
|
1038
1038
|
int ws_only = 1;
|
|
1039
1039
|
for (size_t k = 0; k < isl; k++) {
|
|
@@ -1174,7 +1174,7 @@ void serialize_element_internal(LeptrisElement root_elem, SerializeBuffer* buf,
|
|
|
1174
1174
|
* NUL-terminate, which the escaper doesn't need
|
|
1175
1175
|
* (it's length-bounded). Entity-bearing text still
|
|
1176
1176
|
* materializes for correct expansion. */
|
|
1177
|
-
const char* tc0 = tn0
|
|
1177
|
+
const char* tc0 = leptris_textnode_content(tn0);
|
|
1178
1178
|
size_t tl0 = tc0 ? tn0->content_len : 0;
|
|
1179
1179
|
/* Pretty mode fuses newline + indent + open + text +
|
|
1180
1180
|
* close into the same single reservation — leaves
|
|
@@ -1545,7 +1545,7 @@ void serialize_element_internal(LeptrisElement root_elem, SerializeBuffer* buf,
|
|
|
1545
1545
|
}
|
|
1546
1546
|
if (buf->indent_spaces == 0) {
|
|
1547
1547
|
LeptrisTextNode* tn = (LeptrisTextNode*)fc;
|
|
1548
|
-
const char* tc = tn
|
|
1548
|
+
const char* tc = leptris_textnode_content(tn);
|
|
1549
1549
|
size_t tlen = tc ? tn->content_len : 0;
|
|
1550
1550
|
buffer_ensure_capacity(buf, 2 + epl + nl + 6 * tlen + 2 + 1);
|
|
1551
1551
|
char* te = buf->data + buf->size;
|
|
@@ -121,10 +121,12 @@ static char* load_file_content(const char* path, size_t* out_len) {
|
|
|
121
121
|
* Returns NULL on allocation failure (partial copies are abandoned
|
|
122
122
|
* along with the pool, which is freed document-scoped). */
|
|
123
123
|
static LeptrisNode* deep_copy_node(const LeptrisNode* src,
|
|
124
|
-
LeptrisMemoryPool* target_pool
|
|
124
|
+
LeptrisMemoryPool* target_pool,
|
|
125
|
+
struct leptris_document* target_doc);
|
|
125
126
|
|
|
126
127
|
static LeptrisElement deep_copy_element(const LeptrisElement src,
|
|
127
|
-
LeptrisMemoryPool* target_pool
|
|
128
|
+
LeptrisMemoryPool* target_pool,
|
|
129
|
+
struct leptris_document* target_doc) {
|
|
128
130
|
const char* name = leptris_element_get_name(src);
|
|
129
131
|
LeptrisElement dst = leptris_element_create_pooled(name, target_pool);
|
|
130
132
|
if (!dst) return NULL;
|
|
@@ -144,7 +146,7 @@ static LeptrisElement deep_copy_element(const LeptrisElement src,
|
|
|
144
146
|
child;
|
|
145
147
|
child = leptris_node_get_next_sibling(child)) {
|
|
146
148
|
|
|
147
|
-
LeptrisNode* child_copy = deep_copy_node(child, target_pool);
|
|
149
|
+
LeptrisNode* child_copy = deep_copy_node(child, target_pool, target_doc);
|
|
148
150
|
if (!child_copy) return NULL;
|
|
149
151
|
|
|
150
152
|
if (prev_copy) {
|
|
@@ -161,18 +163,21 @@ static LeptrisElement deep_copy_element(const LeptrisElement src,
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
static LeptrisNode* deep_copy_node(const LeptrisNode* src,
|
|
164
|
-
LeptrisMemoryPool* target_pool
|
|
166
|
+
LeptrisMemoryPool* target_pool,
|
|
167
|
+
struct leptris_document* target_doc) {
|
|
165
168
|
if (!src) return NULL;
|
|
166
169
|
|
|
167
170
|
switch (src->type) {
|
|
168
171
|
case LEPTRIS_NODE_TYPE_ELEMENT:
|
|
169
|
-
return (LeptrisNode*)deep_copy_element((LeptrisElement)src, target_pool);
|
|
172
|
+
return (LeptrisNode*)deep_copy_element((LeptrisElement)src, target_pool, target_doc);
|
|
170
173
|
|
|
171
174
|
case LEPTRIS_NODE_TYPE_TEXT: {
|
|
172
175
|
const LeptrisTextNode* t = (const LeptrisTextNode*)src;
|
|
173
|
-
/*
|
|
174
|
-
* is authoritative. TODO 115 Phase B. */
|
|
175
|
-
return (LeptrisNode*)leptris_text_create(t
|
|
176
|
+
/* #1285 slice 4: runs are always NUL-terminated;
|
|
177
|
+
* content_len is authoritative. TODO 115 Phase B. */
|
|
178
|
+
return (LeptrisNode*)leptris_text_create(leptris_textnode_content(t),
|
|
179
|
+
t->content_len, target_pool,
|
|
180
|
+
target_doc);
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
case LEPTRIS_NODE_TYPE_CDATA: {
|
|
@@ -725,7 +730,7 @@ static int process_element_xinclude(LeptrisElement elem,
|
|
|
725
730
|
content_len = conv_len;
|
|
726
731
|
#endif
|
|
727
732
|
}
|
|
728
|
-
substitute = (LeptrisNode*)leptris_text_create(content, content_len, doc->pool);
|
|
733
|
+
substitute = (LeptrisNode*)leptris_text_create(content, content_len, doc->pool, doc);
|
|
729
734
|
/* parse="text" doesn't recurse into another file's body
|
|
730
735
|
* (no risk of A -> B -> A via text), so no push/pop here. */
|
|
731
736
|
}
|
|
@@ -744,7 +749,7 @@ fallback:
|
|
|
744
749
|
if (fb) {
|
|
745
750
|
char* fb_text = leptris_element_get_text_content(fb);
|
|
746
751
|
if (fb_text) {
|
|
747
|
-
substitute = (LeptrisNode*)leptris_text_create(fb_text, strlen(fb_text), doc->pool);
|
|
752
|
+
substitute = (LeptrisNode*)leptris_text_create(fb_text, strlen(fb_text), doc->pool, doc);
|
|
748
753
|
leptris_free(fb_text);
|
|
749
754
|
}
|
|
750
755
|
}
|
|
@@ -175,8 +175,12 @@ char* get_node_text(void* node) {
|
|
|
175
175
|
int ty = leptris_node_get_type(c);
|
|
176
176
|
if (ty == LEPTRIS_NODE_TYPE_TEXT ||
|
|
177
177
|
ty == LEPTRIS_NODE_TYPE_CDATA) {
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
/* #1285 slice 4b: text and CDATA structs no
|
|
179
|
+
* longer share `content`'s offset — use the
|
|
180
|
+
* matching accessor. */
|
|
181
|
+
const char* t = (ty == LEPTRIS_NODE_TYPE_CDATA)
|
|
182
|
+
? leptris_cdata_get_content((LeptrisCDATANode*)c)
|
|
183
|
+
: leptris_text_get_content((LeptrisTextNode*)c);
|
|
180
184
|
if (t && *t) {
|
|
181
185
|
any_text = 1;
|
|
182
186
|
size_t tl = strlen(t);
|
|
@@ -460,7 +460,9 @@ static char* string_value_deep(LeptrisElement e) {
|
|
|
460
460
|
LeptrisNodeRef n = stack[--sp];
|
|
461
461
|
int ty = leptris_node_get_type(n);
|
|
462
462
|
if (ty == LEPTRIS_NODE_TYPE_TEXT || ty == LEPTRIS_NODE_TYPE_CDATA) {
|
|
463
|
-
const char* t =
|
|
463
|
+
const char* t = (ty == LEPTRIS_NODE_TYPE_CDATA)
|
|
464
|
+
? leptris_cdata_get_content((LeptrisCDATANode*)n)
|
|
465
|
+
: leptris_text_get_content((LeptrisTextNode*)n);
|
|
464
466
|
if (t) {
|
|
465
467
|
size_t tl = strlen(t);
|
|
466
468
|
if (len + tl + 1 > cap) {
|
|
@@ -2734,8 +2736,12 @@ static int copy_node_deep(XsltExec* ex, LeptrisElement node,
|
|
|
2734
2736
|
if (ty == LEPTRIS_NODE_TYPE_ELEMENT) {
|
|
2735
2737
|
copy_node_deep(ex, (LeptrisElement)c, e);
|
|
2736
2738
|
} else if (ty == LEPTRIS_NODE_TYPE_CDATA) {
|
|
2739
|
+
/* #1285 slice 4b: text and CDATA structs no longer
|
|
2740
|
+
* share `content`'s offset — use the CDATA accessor on
|
|
2741
|
+
* CDATA nodes; the cross-cast `text_get_content((TextNode*)c)`
|
|
2742
|
+
* reads garbage here. */
|
|
2737
2743
|
const char* t =
|
|
2738
|
-
|
|
2744
|
+
leptris_cdata_get_content((LeptrisCDATANode*)c);
|
|
2739
2745
|
LeptrisNodeRef cc = (LeptrisNodeRef)leptris_cdata_create(
|
|
2740
2746
|
t, t ? strlen(t) : 0,
|
|
2741
2747
|
((struct leptris_document*)ex->result)->pool);
|
|
@@ -3035,11 +3041,12 @@ static void strip_source_whitespace(XsltExec* ex) {
|
|
|
3035
3041
|
if (!ws_only(t)) continue;
|
|
3036
3042
|
/* Empty in place — never unlink (compact-parse text
|
|
3037
3043
|
* nodes link via int32 offsets that unlinking can
|
|
3038
|
-
* orphan for following siblings).
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3044
|
+
* orphan for following siblings). Store the shared
|
|
3045
|
+
* EMPTY sentinel: the node stays readable and the
|
|
3046
|
+
* content decodes to a NUL-terminated "" (#1285 slice
|
|
3047
|
+
* 4b — no overflow-table entry per stripped node). */
|
|
3041
3048
|
LeptrisTextNode* tn = (LeptrisTextNode*)c;
|
|
3042
|
-
tn
|
|
3049
|
+
leptris_textnode_set_content_ptr_doc(tn, "", ex->result);
|
|
3043
3050
|
tn->content_len = 0;
|
|
3044
3051
|
}
|
|
3045
3052
|
}
|
|
@@ -36,45 +36,47 @@ TEST(TextBorrowed, ParsedTextNodeIsTerminatedInPlace) {
|
|
|
36
36
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
37
37
|
EXPECT_EQ(text->content_len, std::strlen("hello world"));
|
|
38
38
|
/* #1285 slice 4: the run is NUL-terminated IN PLACE in the
|
|
39
|
-
* doc-owned input copy — no pool copy, no serve mode.
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
* doc-owned input copy — no pool copy, no serve mode. #1285
|
|
40
|
+
* slice 4b: content is an int32 self-relative offset; decode
|
|
41
|
+
* twice — the pointer must be stable across calls. */
|
|
42
|
+
const char* c1 = leptris_textnode_content(text);
|
|
43
|
+
const char* c2 = leptris_textnode_content(text);
|
|
44
|
+
ASSERT_NE(c1, nullptr);
|
|
45
|
+
EXPECT_EQ(c1, c2) << "decode is deterministic";
|
|
46
|
+
EXPECT_EQ(c1[text->content_len], '\0')
|
|
42
47
|
<< "run must be terminated at [content_len]";
|
|
43
|
-
|
|
44
|
-
EXPECT_EQ(leptris_text_get_content(text), text->content);
|
|
48
|
+
EXPECT_EQ(leptris_text_get_content(text), c1);
|
|
45
49
|
EXPECT_STREQ(leptris_text_get_content(text), "hello world");
|
|
46
50
|
|
|
47
51
|
leptris_document_free(doc);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
TEST(TextBorrowed,
|
|
51
|
-
/* #1285 slice
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
constexpr size_t
|
|
54
|
+
TEST(TextBorrowed, NodeStructIsBaseAndFiveInt32) {
|
|
55
|
+
/* #1285 slice 4b + #1320: the content pointer became an int32
|
|
56
|
+
* self-relative byte offset, and the detached-owner backref
|
|
57
|
+
* (#1320) joined it as a fifth int32. Width-independent
|
|
58
|
+
* invariant: base + FIVE int32s (content off, uint32 len,
|
|
59
|
+
* int32 next, int32 parent, int32 owner doc), int32-aligned —
|
|
60
|
+
* nothing else. 40 -> 32 on LP64; 32 on ILP32. Encoding:
|
|
61
|
+
* content_off 0 = NULL, LEPTRIS_COMPACT_INT32_EMPTY = static ""
|
|
62
|
+
* (ws-strip sentinel, no overflow-table entry per stripped
|
|
63
|
+
* node), any other value = self-relative offset with the
|
|
64
|
+
* overflow-table fallback (same machinery as the tree edges);
|
|
65
|
+
* owner_doc_off 0 = unstamped (parse-carved nodes attach). The
|
|
66
|
+
* parse-time bulk strides (dp text block, il lane table)
|
|
67
|
+
* hardcode this layout economics — a silent field growth would
|
|
68
|
+
* erode the #1222 parse win. */
|
|
69
|
+
/* C++11: no constexpr lambda — inline the round-ups. */
|
|
70
|
+
constexpr size_t kA = alignof(int32_t);
|
|
71
|
+
constexpr size_t kRaw = sizeof(LeptrisNode) + 5 * sizeof(int32_t);
|
|
67
72
|
constexpr size_t kAligned = (kRaw + kA - 1) / kA * kA;
|
|
68
73
|
static_assert(sizeof(LeptrisTextNode) == kAligned,
|
|
69
|
-
"LeptrisTextNode must be base +
|
|
74
|
+
"LeptrisTextNode must be base + int32 content off + "
|
|
70
75
|
"uint32 len + int32 next + int32 parent + int32 owner "
|
|
71
|
-
"(aligned) — no
|
|
76
|
+
"(int32-aligned) — no content pointer "
|
|
77
|
+
"(#1285 slice 4b, #1320)");
|
|
72
78
|
EXPECT_EQ(sizeof(LeptrisTextNode), kAligned);
|
|
73
|
-
#if defined(__LP64__) || defined(_WIN64)
|
|
74
|
-
EXPECT_EQ(sizeof(LeptrisTextNode), (size_t)40);
|
|
75
|
-
#else
|
|
76
79
|
EXPECT_EQ(sizeof(LeptrisTextNode), (size_t)32);
|
|
77
|
-
#endif
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
TEST(TextBorrowed, PublicAccessorsReturnCorrectContent) {
|
|
@@ -143,8 +145,9 @@ TEST(TextBorrowed, MaterializationIsStableAcrossCalls) {
|
|
|
143
145
|
|
|
144
146
|
LeptrisNodeRef child = leptris_node_first_child(leptris_element_as_node(root));
|
|
145
147
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
146
|
-
EXPECT_EQ(leptris_text_get_content(text),
|
|
147
|
-
|
|
148
|
+
EXPECT_EQ(leptris_text_get_content(text),
|
|
149
|
+
leptris_textnode_content(text))
|
|
150
|
+
<< "get_content decodes the same offset (#1285 slice 4b)";
|
|
148
151
|
|
|
149
152
|
const char* second = leptris_element_text(root);
|
|
150
153
|
EXPECT_EQ(second, first) << "repeated calls return the same pointer";
|
|
@@ -183,14 +186,12 @@ TEST(TextBorrowed, ServeContractHoldsInBothParseLanes) {
|
|
|
183
186
|
LeptrisTextNode* text = (LeptrisTextNode*)child;
|
|
184
187
|
EXPECT_EQ(text->content_len, std::strlen("hello world"));
|
|
185
188
|
/* terminated in place in BOTH lanes (#1285 slice 4) */
|
|
186
|
-
ASSERT_NE(text->content, nullptr);
|
|
187
|
-
EXPECT_EQ(text->content[text->content_len], '\0');
|
|
188
|
-
|
|
189
189
|
const char* a = leptris_text_get_content(text);
|
|
190
190
|
ASSERT_NE(a, nullptr);
|
|
191
|
+
EXPECT_EQ(a[text->content_len], '\0');
|
|
191
192
|
EXPECT_STREQ(a, "hello world");
|
|
192
|
-
EXPECT_EQ(a, text
|
|
193
|
-
<< "get_content returns the in-place
|
|
193
|
+
EXPECT_EQ(a, leptris_textnode_content(text))
|
|
194
|
+
<< "get_content returns the in-place view in both lanes";
|
|
194
195
|
EXPECT_STREQ(leptris_element_text(root), "hello world");
|
|
195
196
|
|
|
196
197
|
leptris_document_free(doc);
|
|
@@ -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.
|
|
4
|
+
"version": "1.9.229",
|
|
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",
|