leptris 1.9.226.0-x86_64-linux → 1.9.228.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 +15 -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 +66 -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 +16 -0
- data/vendor-src/libleptris/src/leptris/dom/compact.h +9 -0
- data/vendor-src/libleptris/src/leptris/dom/element.h +6 -7
- 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 +74 -3
- data/vendor-src/libleptris/src/leptris/dom/pi.c +0 -1
- data/vendor-src/libleptris/src/leptris/dom/text.c +2 -5
- data/vendor-src/libleptris/src/leptris/dom/text.h +23 -1
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +21 -1
- 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/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 +13 -10
- 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: de81e97aa3ab8a52850dbb21c9b9fbfdd47707458ebc1132817cffd36bd7dcdf
|
|
4
|
+
data.tar.gz: b11e49c626fd6995a0b6c60fe0730dbec6543c88dac1951ea87345c0054de15f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c6d7f0bff8c2b63decad14b04c787e7243521163ff505daa7e1e760b4d4cbf3a6c2d13e5f8b6306eaec2dd721f3508efca5f515bc333d6181232fefd0f2a7527
|
|
7
|
+
data.tar.gz: cc50b867785d0a8ee19b1185ac681fffc8e46677d8d3bc0e4897f60888741a155d56cde58d43ea7a72d7fca20fafd6bdf4230e23942052588489bcf6dfdbce65
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.228.0] - 2026-09-23
|
|
9
|
+
|
|
10
|
+
### Fixed — Text#content= on created-but-unattached text nodes (#318; engine 1.9.227–228)
|
|
11
|
+
|
|
12
|
+
- **The #318 regression is fixed at the engine root**: engine
|
|
13
|
+
1.9.228 (upstream leptris#1320) gives detached text nodes the
|
|
14
|
+
same #519 owner treatment PI/comment/CDATA already had — an
|
|
15
|
+
owner backref stamped by `leptris_text_node_create` (compact
|
|
16
|
+
int32 offset, LP64 layout preserved) — so
|
|
17
|
+
`leptris_text_node_set_content` works before attach again.
|
|
18
|
+
The create → fill → attach builder sequence moxml's
|
|
19
|
+
`Text#content=` depends on is unblocked; verified against the
|
|
20
|
+
vendored 1.9.228 (the #318 repro passes; suite 778 → 781/0).
|
|
21
|
+
- 1.9.227 rides along (header-hygiene spec zeroing).
|
|
22
|
+
|
|
8
23
|
## [1.9.226.0] - 2026-09-23
|
|
9
24
|
|
|
10
25
|
### 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.228"
|
|
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,71 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.228] - 2026-09-23
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **dom: `leptris_text_node_set_content` works again on
|
|
8
|
+
created-but-unattached text nodes (#1320).** The 1.9.223–1.9.226
|
|
9
|
+
window returned `LEPTRIS_ERROR_INVALID_ARG` for exactly the
|
|
10
|
+
create → fill → attach builder sequence bindings use (moxml's
|
|
11
|
+
`Text#content=` failed 5 shared examples). Root cause: issue #519
|
|
12
|
+
gave detached PI/comment/CDATA nodes an `owner_doc` field but
|
|
13
|
+
skipped text — at the time text nodes carried their own pool
|
|
14
|
+
backref, which #1285 slice 4 then deleted, leaving detached text
|
|
15
|
+
with no document to allocate from. Text now gets the same #519
|
|
16
|
+
treatment: an owner backref stamped by the public creator only,
|
|
17
|
+
stored as a compact int32 offset (the LP64 struct keeps its 40
|
|
18
|
+
bytes — the field lands in existing padding; ILP32 32), with
|
|
19
|
+
overflow-table fallback tagged with the EXPLICIT document (the
|
|
20
|
+
parse-context TLS slot holds the wrong doc or NULL outside parse).
|
|
21
|
+
Every parse-carve path zero-initializes the field; the
|
|
22
|
+
attached/parsed parent-walk fast path is untouched. TDD'd
|
|
23
|
+
red-first against the reported minimal repro;
|
|
24
|
+
`DetachedNodes.TextSetContentWorksBeforeAttach` pins the
|
|
25
|
+
unattached, attach-then-set, and parsed paths.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## [1.9.227] - 2026-09-23
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **test:** the header-hygiene compact-pointer edge specs zero their
|
|
34
|
+
fake stack-allocated nodes before use. The `last_child` and
|
|
35
|
+
`last_attribute` getters *walk* the sibling/attribute list, so the
|
|
36
|
+
uninitialized fake node's next-edge was read as stack garbage —
|
|
37
|
+
zero on arm64, nonzero on x86_64, where the slice-3b layout shift
|
|
38
|
+
surfaced the latent UB as a real CI failure. It was always UB.
|
|
39
|
+
|
|
40
|
+
### Performance
|
|
41
|
+
|
|
42
|
+
- **dom: binding_wrapper out of the node base — element 48B, text
|
|
43
|
+
40B, every node type −8B (#1285 slice 3b).** `LeptrisNode` itself
|
|
44
|
+
drops 24 → 12 bytes; the element struct reaches the lane's stated
|
|
45
|
+
goal of 48 bytes. The `binding_wrapper` field (#262) was never
|
|
46
|
+
adopted — zero callers in leptris-ruby and leptris-py (both cache
|
|
47
|
+
wrappers on their own side), making it pure dead weight at 8 bytes
|
|
48
|
+
per node per document, and it carried the #421 stale-pointer bug
|
|
49
|
+
class, which the removal structurally eliminates. The public
|
|
50
|
+
`leptris_node_get/set_binding_wrapper` API is unchanged and now
|
|
51
|
+
serves from a lazily-allocated open-addressed map owned by the
|
|
52
|
+
document: documents that never wrap (all of them today) allocate
|
|
53
|
+
nothing, and entries cannot outlive their nodes because the map
|
|
54
|
+
dies with the document.
|
|
55
|
+
- **parse: interleaved lane size floor — small docs defer to classic
|
|
56
|
+
(#1258).** The two-phase scan+replay lane pays a fixed setup cost
|
|
57
|
+
that small documents cannot amortize: canon's gate measured ~25%
|
|
58
|
+
slower on a 24KB readonly shape while large documents run 0.69–0.74×
|
|
59
|
+
(256KB–3.4MB). `dp_il_try` now defers to the classic parser below a
|
|
60
|
+
64KB floor (the midpoint between the measured loss and the wins);
|
|
61
|
+
`LEPTRIS_IL_MIN` overrides it in bytes, with explicit `0` disabling
|
|
62
|
+
the floor. Coverage is preserved, not narrowed: the parity suite
|
|
63
|
+
pins `LEPTRIS_IL_MIN=1` in every lane toggle, the lane-forced CI leg
|
|
64
|
+
sets it to `0` so the full suite still exercises the lane, and a new
|
|
65
|
+
spec pins the deferral itself.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
3
69
|
## [1.9.226] - 2026-09-22
|
|
4
70
|
|
|
5
71
|
### 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) {
|
|
@@ -298,6 +298,22 @@ int32_t leptris_compact_int32_encode(void* base, void* target,
|
|
|
298
298
|
return (int32_t)d;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
int32_t leptris_compact_int32_encode_doc(void* base, void* target,
|
|
302
|
+
const int32_t* field_addr,
|
|
303
|
+
struct leptris_document* doc) {
|
|
304
|
+
if (!target) return 0;
|
|
305
|
+
ptrdiff_t d = (char*)target - (char*)base;
|
|
306
|
+
if (d < INT32_MIN || d > INT32_MAX) {
|
|
307
|
+
LeptrisCompactOverflowTable* table = get_overflow_table();
|
|
308
|
+
if (table && leptris_compact_overflow_set(table, field_addr,
|
|
309
|
+
target, doc) == 0) {
|
|
310
|
+
return LEPTRIS_INT32_OVERFLOW_SENTINEL;
|
|
311
|
+
}
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
return (int32_t)d;
|
|
315
|
+
}
|
|
316
|
+
|
|
301
317
|
void* leptris_compact_int32_decode(void* base, int32_t off,
|
|
302
318
|
const int32_t* field_addr) {
|
|
303
319
|
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
|
|
@@ -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");
|
|
@@ -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:
|
|
@@ -329,6 +334,13 @@ LEPTRIS_API LeptrisNodeRef leptris_text_node_create(LeptrisDocument doc,
|
|
|
329
334
|
if (!content) content = "";
|
|
330
335
|
size_t len = strlen(content);
|
|
331
336
|
LeptrisTextNode* n = leptris_text_create(content, len, doc->pool);
|
|
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
|
|
|
@@ -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;
|
|
@@ -30,12 +30,9 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
30
30
|
node->base.frozen = 0;
|
|
31
31
|
node->base.version = 0;
|
|
32
32
|
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
33
|
node->parent_off = 0;
|
|
38
34
|
node->next_sibling_off = 0;
|
|
35
|
+
node->owner_doc_off = 0; /* #1320: only the public creator stamps */
|
|
39
36
|
|
|
40
37
|
if (content && content_len > 0) {
|
|
41
38
|
memcpy(content_storage, content, content_len);
|
|
@@ -70,11 +67,11 @@ LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
|
70
67
|
* uninitialized raw bit silently flips DOE/cdata behavior by
|
|
71
68
|
* build configuration. */
|
|
72
69
|
node->base.raw = 0;
|
|
73
|
-
node->base.binding_wrapper = NULL;
|
|
74
70
|
node->content = (char*)content; /* Non-owning; caller guarantees lifetime + termination. */
|
|
75
71
|
node->content_len = (uint32_t)content_len;
|
|
76
72
|
node->parent_off = 0;
|
|
77
73
|
node->next_sibling_off = 0;
|
|
74
|
+
node->owner_doc_off = 0; /* #1320: only the public creator stamps */
|
|
78
75
|
|
|
79
76
|
return node;
|
|
80
77
|
}
|
|
@@ -26,7 +26,15 @@
|
|
|
26
26
|
* are outside every supported target.
|
|
27
27
|
*
|
|
28
28
|
* Issue #168: parent_off mirrors next_sibling_off so the parent of a
|
|
29
|
-
* text node can be queried in O(1).
|
|
29
|
+
* text node can be queried in O(1).
|
|
30
|
+
*
|
|
31
|
+
* Issue #1320: owner_doc_off gives DETACHED text nodes their owning
|
|
32
|
+
* document — #519 added an owner_doc field to PI/comment/CDATA but
|
|
33
|
+
* skipped text (whose pool backref covered it at the time); #1285
|
|
34
|
+
* slice 4 deleted that backref and detached set_content started
|
|
35
|
+
* returning INVALID_ARG. Stamped by the public creator only —
|
|
36
|
+
* parse-carved nodes are attached by construction. int32, so the
|
|
37
|
+
* LP64 struct keeps its 40 bytes (padding absorbs it). */
|
|
30
38
|
typedef struct leptris_text_node {
|
|
31
39
|
LeptrisNode base; /* MUST be first */
|
|
32
40
|
char* content; /* Text content - NEVER trim! NUL-terminated at [content_len] */
|
|
@@ -40,6 +48,7 @@ typedef struct leptris_text_node {
|
|
|
40
48
|
* 0 = NULL. */
|
|
41
49
|
int32_t next_sibling_off;
|
|
42
50
|
int32_t parent_off; /* Byte offset to parent element (0=NULL) */
|
|
51
|
+
int32_t owner_doc_off; /* Byte offset to owning document (0=unstamped; #1320) */
|
|
43
52
|
} LeptrisTextNode;
|
|
44
53
|
|
|
45
54
|
/* Text node creation.
|
|
@@ -110,4 +119,17 @@ static inline void leptris_textnode_set_parent(LeptrisTextNode* t, LeptrisElemen
|
|
|
110
119
|
t->parent_off = leptris_compact_int32_encode_inline(t, parent, &t->parent_off);
|
|
111
120
|
}
|
|
112
121
|
|
|
122
|
+
/* Owning-document access (issue #1320). The stamp is written by
|
|
123
|
+
* leptris_text_node_create with the explicit-doc encoder (an
|
|
124
|
+
* overflow-table entry made outside a parse context must carry the
|
|
125
|
+
* REAL document tag, not the TLS current-doc). Decode is the
|
|
126
|
+
* standard edge decode. */
|
|
127
|
+
struct leptris_document;
|
|
128
|
+
static inline struct leptris_document* leptris_textnode_owner_doc(const LeptrisTextNode* t) {
|
|
129
|
+
return (t && t->owner_doc_off != 0)
|
|
130
|
+
? (struct leptris_document*)leptris_compact_int32_decode_inline(
|
|
131
|
+
(void*)t, t->owner_doc_off, &t->owner_doc_off)
|
|
132
|
+
: NULL;
|
|
133
|
+
}
|
|
134
|
+
|
|
113
135
|
#endif /* LEPTRIS_DOM_TEXT_H */
|
|
@@ -1005,7 +1005,6 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
1005
1005
|
* serialize_text_internal then emitted text VERBATIM (raw < in
|
|
1006
1006
|
* the output, invalid XML). */
|
|
1007
1007
|
tn->base.raw = 0;
|
|
1008
|
-
tn->base.binding_wrapper = NULL;
|
|
1009
1008
|
tn->base.line = 0; /* caller stamps the offset */
|
|
1010
1009
|
/* #1285 slice 4: the run is NUL-terminated in place BEFORE this
|
|
1011
1010
|
* carve (dispatch-entry dp_nul / walker '&' NUL / EOF sentinel)
|
|
@@ -1015,6 +1014,7 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
1015
1014
|
tn->content_len = (uint32_t)content_len;
|
|
1016
1015
|
tn->parent_off = 0;
|
|
1017
1016
|
tn->next_sibling_off = 0;
|
|
1017
|
+
tn->owner_doc_off = 0; /* #1320: parse-carved nodes attach */
|
|
1018
1018
|
return tn;
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
@@ -2598,6 +2598,7 @@ static struct leptris_document* dp_il_build(
|
|
|
2598
2598
|
* run is already NUL-terminated at carve) */
|
|
2599
2599
|
tn->parent_off = 0;
|
|
2600
2600
|
tn->next_sibling_off = 0;
|
|
2601
|
+
tn->owner_doc_off = 0; /* #1320: parse-carved nodes attach */
|
|
2601
2602
|
smap[i] = (LeptrisNode*)tn;
|
|
2602
2603
|
pp.depth = (int)rdepth[r->parent] + 1;
|
|
2603
2604
|
pp.last_child_stack[pp.depth - 1] = lc[r->parent];
|
|
@@ -2686,6 +2687,25 @@ static struct leptris_document* dp_il_try(const char* xml, size_t len,
|
|
|
2686
2687
|
if (!gate || !*gate) return NULL;
|
|
2687
2688
|
if (keep_ent || dtd_attrs) return NULL;
|
|
2688
2689
|
if (len == 0 || len >= 0x7FFFFFFFu) return NULL;
|
|
2690
|
+
/* #1258: small documents do not amortize the record pass.
|
|
2691
|
+
* Canon's moxml shape (24KB / 902 elements) measured ~25%
|
|
2692
|
+
* SLOWER on the lane while allocations stayed identical - the
|
|
2693
|
+
* two-phase scan+replay pays fixed setup that a small doc
|
|
2694
|
+
* cannot win back; large docs take 0.69-0.74x. Below the
|
|
2695
|
+
* minimum the lane defers to the classic parser. LEPTRIS_IL_MIN
|
|
2696
|
+
* overrides (bytes; 0 = no threshold) for hosts and for the
|
|
2697
|
+
* parity gates, which force it to 0 so the lane stays covered
|
|
2698
|
+
* by the suite at fixture sizes. */
|
|
2699
|
+
{
|
|
2700
|
+
const char* mins = getenv("LEPTRIS_IL_MIN");
|
|
2701
|
+
size_t min_len = 64 * 1024;
|
|
2702
|
+
if (mins && *mins) {
|
|
2703
|
+
long mv = strtol(mins, NULL, 10);
|
|
2704
|
+
if (mv > 0) min_len = (size_t)mv;
|
|
2705
|
+
else min_len = 0; /* explicit 0 disables */
|
|
2706
|
+
}
|
|
2707
|
+
if (len < min_len) return NULL;
|
|
2708
|
+
}
|
|
2689
2709
|
|
|
2690
2710
|
/* Scratch is built UP FRONT (slice 2): il_scan reads it with
|
|
2691
2711
|
* the NUL-sentinel discipline, and dp_il_build reuses the same
|
|
@@ -967,6 +967,11 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
|
|
|
967
967
|
free(doc->elem_pos);
|
|
968
968
|
doc->elem_pos = NULL;
|
|
969
969
|
doc->elem_pos_count = doc->elem_pos_cap = 0;
|
|
970
|
+
/* #1285 slice 3b: binding-wrapper map — entries die with the
|
|
971
|
+
* document, so they can never outlive their nodes. */
|
|
972
|
+
free(doc->wrapper_map);
|
|
973
|
+
doc->wrapper_map = NULL;
|
|
974
|
+
doc->wrapper_count = doc->wrapper_cap = 0;
|
|
970
975
|
if (doc->xml_buffer && doc->xml_buffer_needs_free) {
|
|
971
976
|
/* Release through the retained-buffer free list (see
|
|
972
977
|
* memory/arena.c): large inputs would otherwise be munmapped
|
|
@@ -226,6 +226,15 @@ struct leptris_document {
|
|
|
226
226
|
* xml_buffer) and resolves queries via binary search, caching
|
|
227
227
|
* the result in the node with the high bit set. NULL = not yet
|
|
228
228
|
* built. Freed with the document. */
|
|
229
|
+
/* #1285 slice 3b: binding_wrapper left the node base (#262's
|
|
230
|
+
* FFI cache was never adopted — zero callers in leptris-ruby/py
|
|
231
|
+
* — and its stale-pointer bug class, #421, is structural). The
|
|
232
|
+
* public get/set API now serves from this lazily-allocated
|
|
233
|
+
* open-addressed map, keyed by node pointer; it dies with the
|
|
234
|
+
* document so entries can never outlive their nodes. NULL until
|
|
235
|
+
* the first set — documents that never wrap pay nothing. */
|
|
236
|
+
struct leptris_wrapper_entry* wrapper_map;
|
|
237
|
+
size_t wrapper_count, wrapper_cap;
|
|
229
238
|
int html_mode; /* #1309: HTML-parsed / create_html documents
|
|
230
239
|
serialize with the §16.2 HTML method by
|
|
231
240
|
default (libxml2/Nokogiri behavior). */
|
|
@@ -103,6 +103,12 @@ TEST(HeaderHygiene, ElementTreeEdgeRoundTrip) {
|
|
|
103
103
|
alignas(kAlign) char buf[2 * sizeof(struct leptris_element)];
|
|
104
104
|
struct leptris_element* a = (struct leptris_element*)buf;
|
|
105
105
|
struct leptris_element* b = (struct leptris_element*)(buf + sizeof(*a));
|
|
106
|
+
/* The last_child getter WALKS the child list, so b's sibling
|
|
107
|
+
* edge is read — zero the fake nodes or the walk follows
|
|
108
|
+
* uninitialized stack garbage. (#1285 slice 3b moved the field
|
|
109
|
+
* onto a stack slot whose garbage is nonzero on x86_64, turning
|
|
110
|
+
* the latent UB into a real failure; it was always UB.) */
|
|
111
|
+
memset(buf, 0, sizeof(buf));
|
|
106
112
|
|
|
107
113
|
/* parent round-trip */
|
|
108
114
|
leptris_elem_set_parent(a, b);
|
|
@@ -132,6 +138,7 @@ TEST(HeaderHygiene, ElementAttributeEdgeRoundTrip) {
|
|
|
132
138
|
struct leptris_element* e = (struct leptris_element*)buf;
|
|
133
139
|
struct leptris_attribute* attr =
|
|
134
140
|
(struct leptris_attribute*)(buf + sizeof(*e));
|
|
141
|
+
memset(buf, 0, sizeof(buf));
|
|
135
142
|
leptris_attr_set_next(attr, NULL);
|
|
136
143
|
|
|
137
144
|
leptris_elem_set_first_attribute(e, attr);
|
|
@@ -2014,6 +2014,51 @@ TEST(DetachedNodes, MutationWorksBeforeAttach) {
|
|
|
2014
2014
|
leptris_document_free(doc);
|
|
2015
2015
|
}
|
|
2016
2016
|
|
|
2017
|
+
/* Issue #1320: TEXT was the one node kind #519's detached-mutation
|
|
2018
|
+
* fix skipped — no owner_doc, so after #1285 slice 4 removed the
|
|
2019
|
+
* node-side pool backref, set_content on a created-but-unattached
|
|
2020
|
+
* text node resolved no document and returned INVALID_ARG. moxml's
|
|
2021
|
+
* create → fill → attach builder sequence hit it on every leg. */
|
|
2022
|
+
TEST(DetachedNodes, TextSetContentWorksBeforeAttach) {
|
|
2023
|
+
/* Unattached: the discriminator that regressed (1.9.223–1.9.226
|
|
2024
|
+
* returned EINVAL here; content stayed at the create-time value). */
|
|
2025
|
+
LeptrisDocument doc = leptris_document_create();
|
|
2026
|
+
ASSERT_NE(doc, nullptr);
|
|
2027
|
+
LeptrisNodeRef t = leptris_text_node_create(doc, "abc");
|
|
2028
|
+
ASSERT_NE(t, nullptr);
|
|
2029
|
+
EXPECT_EQ(leptris_text_node_set_content(t, "world"), LEPTRIS_OK);
|
|
2030
|
+
EXPECT_STREQ(leptris_text_node_get_content(t), "world");
|
|
2031
|
+
|
|
2032
|
+
/* The mutation survives attach + serialize. */
|
|
2033
|
+
LeptrisElement root = leptris_element_create(doc, "r");
|
|
2034
|
+
ASSERT_EQ(leptris_document_set_root(doc, root), LEPTRIS_OK);
|
|
2035
|
+
ASSERT_EQ(leptris_element_append_child(root, (LeptrisElement)t), LEPTRIS_OK);
|
|
2036
|
+
char* x = leptris_document_serialize(doc, nullptr);
|
|
2037
|
+
ASSERT_NE(x, nullptr);
|
|
2038
|
+
EXPECT_NE(std::strstr(x, "<r>world</r>"), nullptr);
|
|
2039
|
+
leptris_free_string(x);
|
|
2040
|
+
|
|
2041
|
+
/* Attach-then-set and set-after-parse still work (never
|
|
2042
|
+
* regressed — pin them so the owner path can't shadow the
|
|
2043
|
+
* parent-walk path). */
|
|
2044
|
+
LeptrisNodeRef t2 = leptris_text_node_create(doc, "one");
|
|
2045
|
+
ASSERT_EQ(leptris_element_append_child(root, (LeptrisElement)t2), LEPTRIS_OK);
|
|
2046
|
+
EXPECT_EQ(leptris_text_node_set_content(t2, "two"), LEPTRIS_OK);
|
|
2047
|
+
EXPECT_STREQ(leptris_text_node_get_content(t2), "two");
|
|
2048
|
+
|
|
2049
|
+
leptris_document_free(doc);
|
|
2050
|
+
|
|
2051
|
+
const char xml[] = "<r>parsed</r>";
|
|
2052
|
+
LeptrisDocument doc2 = leptris_parse_string(xml, std::strlen(xml), nullptr);
|
|
2053
|
+
ASSERT_NE(doc2, nullptr);
|
|
2054
|
+
LeptrisElement root2 = leptris_document_root(doc2);
|
|
2055
|
+
LeptrisNodeRef pt = leptris_node_first_child(leptris_element_as_node(root2));
|
|
2056
|
+
ASSERT_NE(pt, nullptr);
|
|
2057
|
+
EXPECT_EQ(leptris_text_node_set_content(pt, "replaced"), LEPTRIS_OK);
|
|
2058
|
+
EXPECT_STREQ(leptris_text_node_get_content(pt), "replaced");
|
|
2059
|
+
leptris_document_free(doc2);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2017
2062
|
/* Issue #526: document-level PI enumeration + creation. */
|
|
2018
2063
|
TEST(DocumentPIs, EnumerateAndAdd) {
|
|
2019
2064
|
const char xml[] = "<?xml version='1.0'?><?docpi d1?><r/>";
|
|
@@ -50,25 +50,28 @@ TEST(TextBorrowed, ParsedTextNodeIsTerminatedInPlace) {
|
|
|
50
50
|
TEST(TextBorrowed, NodeStructIsBasePointerAndThreeInt32) {
|
|
51
51
|
/* #1285 slice 4: pool + borrowed removed, content_len is uint32.
|
|
52
52
|
* The width-independent invariant: base + ONE content pointer +
|
|
53
|
-
*
|
|
54
|
-
* — nothing else.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* FOUR int32s (len, next, parent, owner doc — #1320), rounded to
|
|
54
|
+
* pointer alignment — nothing else. 40 on LP64 (the owner field
|
|
55
|
+
* lands in padding; sizeof unchanged); 32 on ILP32. Going lower
|
|
56
|
+
* needs content as an int32 pool offset (slice 4b). The
|
|
57
57
|
* parse-time bulk strides (dp text block, il lane table) hardcode
|
|
58
58
|
* this layout economics — a silent field growth would erode the
|
|
59
59
|
* #1222 parse win this slice exists for. */
|
|
60
|
-
/* C++11: no constexpr lambda — inline the round-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
/* C++11: no constexpr lambda — inline the round-ups. The
|
|
61
|
+
* content pointer needs pointer alignment INSIDE the struct:
|
|
62
|
+
* base rounds up to the pointer's alignment first. */
|
|
63
63
|
constexpr size_t kA = alignof(void*);
|
|
64
|
+
constexpr size_t kBaseA =
|
|
65
|
+
(sizeof(LeptrisNode) + kA - 1) / kA * kA;
|
|
66
|
+
constexpr size_t kRaw = kBaseA + sizeof(void*) + 4 * sizeof(int32_t);
|
|
64
67
|
constexpr size_t kAligned = (kRaw + kA - 1) / kA * kA;
|
|
65
68
|
static_assert(sizeof(LeptrisTextNode) == kAligned,
|
|
66
69
|
"LeptrisTextNode must be base + one content pointer + "
|
|
67
|
-
"uint32 len + int32 next + int32 parent
|
|
68
|
-
"no pool/borrowed fields (#1285 slice 4)");
|
|
70
|
+
"uint32 len + int32 next + int32 parent + int32 owner "
|
|
71
|
+
"(aligned) — no pool/borrowed fields (#1285 slice 4, #1320)");
|
|
69
72
|
EXPECT_EQ(sizeof(LeptrisTextNode), kAligned);
|
|
70
73
|
#if defined(__LP64__) || defined(_WIN64)
|
|
71
|
-
EXPECT_EQ(sizeof(LeptrisTextNode), (size_t)
|
|
74
|
+
EXPECT_EQ(sizeof(LeptrisTextNode), (size_t)40);
|
|
72
75
|
#else
|
|
73
76
|
EXPECT_EQ(sizeof(LeptrisTextNode), (size_t)32);
|
|
74
77
|
#endif
|
|
@@ -55,8 +55,16 @@ const char* kBail[] = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
std::string parse_str(const char* xml, bool lane) {
|
|
58
|
-
if (lane)
|
|
59
|
-
|
|
58
|
+
if (lane) {
|
|
59
|
+
set_env("LEPTRIS_INTERLEAVED", "1");
|
|
60
|
+
/* #1258: fixtures are far below the 64KB default minimum —
|
|
61
|
+
* force the lane's floor to 1 byte so parity specs exercise
|
|
62
|
+
* the lane, never a silent classic fallback. */
|
|
63
|
+
set_env("LEPTRIS_IL_MIN", "1");
|
|
64
|
+
} else {
|
|
65
|
+
unset_env("LEPTRIS_INTERLEAVED");
|
|
66
|
+
unset_env("LEPTRIS_IL_MIN");
|
|
67
|
+
}
|
|
60
68
|
LeptrisStatus st = LEPTRIS_OK;
|
|
61
69
|
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
62
70
|
std::string out = d ? "" : "(null)";
|
|
@@ -128,6 +136,7 @@ TEST(InterleavedParity, BailFixturesByteIdentical) {
|
|
|
128
136
|
TEST(InterleavedParity, AttrAndNameAccessIdentical) {
|
|
129
137
|
const char* xml = "<r k='v' n='m'><child z='9'>tx</child></r>";
|
|
130
138
|
set_env("LEPTRIS_INTERLEAVED", "1");
|
|
139
|
+
set_env("LEPTRIS_IL_MIN", "1");
|
|
131
140
|
LeptrisStatus st = LEPTRIS_OK;
|
|
132
141
|
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
133
142
|
ASSERT_NE(d, nullptr);
|
|
@@ -186,6 +195,7 @@ TEST(InterleavedParity, EngagementCanaryFiresOnlyWhenLaneRuns) {
|
|
|
186
195
|
};
|
|
187
196
|
|
|
188
197
|
set_env("LEPTRIS_INTERLEAVED", "1");
|
|
198
|
+
set_env("LEPTRIS_IL_MIN", "1");
|
|
189
199
|
set_env("LEPTRIS_IL_STATS", "1");
|
|
190
200
|
char lane[512] = {0};
|
|
191
201
|
ssize_t n = capture(lane, sizeof(lane));
|
|
@@ -195,6 +205,7 @@ TEST(InterleavedParity, EngagementCanaryFiresOnlyWhenLaneRuns) {
|
|
|
195
205
|
EXPECT_NE(strstr(lane, "attrs=1"), nullptr) << lane;
|
|
196
206
|
|
|
197
207
|
unset_env("LEPTRIS_IL_STATS");
|
|
208
|
+
unset_env("LEPTRIS_IL_MIN");
|
|
198
209
|
char classic[512] = {0};
|
|
199
210
|
n = capture(classic, sizeof(classic));
|
|
200
211
|
ASSERT_GE(n, 0);
|
|
@@ -216,8 +227,10 @@ TEST(InterleavedParity, XmlAttrNamespaceParity) {
|
|
|
216
227
|
const char* xml = "<root><code xml:space='preserve'>t</code></root>";
|
|
217
228
|
for (int lane = 0; lane <= 1; lane++) {
|
|
218
229
|
SCOPED_TRACE(lane ? "interleaved" : "classic");
|
|
219
|
-
if (lane) set_env("LEPTRIS_INTERLEAVED", "1");
|
|
220
|
-
|
|
230
|
+
if (lane) { set_env("LEPTRIS_INTERLEAVED", "1");
|
|
231
|
+
set_env("LEPTRIS_IL_MIN", "1"); }
|
|
232
|
+
else { unset_env("LEPTRIS_INTERLEAVED");
|
|
233
|
+
unset_env("LEPTRIS_IL_MIN"); }
|
|
221
234
|
LeptrisStatus st = LEPTRIS_OK;
|
|
222
235
|
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
223
236
|
ASSERT_NE(d, nullptr);
|
|
@@ -247,8 +260,10 @@ TEST(InterleavedParity, DeclaredPrefixNamespaceParity) {
|
|
|
247
260
|
const char* xml = "<d xmlns:q='http://example/q' q:k='v'/>";
|
|
248
261
|
for (int lane = 0; lane <= 1; lane++) {
|
|
249
262
|
SCOPED_TRACE(lane ? "interleaved" : "classic");
|
|
250
|
-
if (lane) set_env("LEPTRIS_INTERLEAVED", "1");
|
|
251
|
-
|
|
263
|
+
if (lane) { set_env("LEPTRIS_INTERLEAVED", "1");
|
|
264
|
+
set_env("LEPTRIS_IL_MIN", "1"); }
|
|
265
|
+
else { unset_env("LEPTRIS_INTERLEAVED");
|
|
266
|
+
unset_env("LEPTRIS_IL_MIN"); }
|
|
252
267
|
LeptrisStatus st = LEPTRIS_OK;
|
|
253
268
|
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
254
269
|
ASSERT_NE(d, nullptr);
|
|
@@ -266,8 +281,10 @@ TEST(InterleavedParity, DeclaredPrefixNamespaceParity) {
|
|
|
266
281
|
TEST(InterleavedParity, DupAttrDiagnosticParity) {
|
|
267
282
|
const char* xml = "<r a='1' a='2'/>";
|
|
268
283
|
for (int lane = 0; lane <= 1; lane++) {
|
|
269
|
-
if (lane) set_env("LEPTRIS_INTERLEAVED", "1");
|
|
270
|
-
|
|
284
|
+
if (lane) { set_env("LEPTRIS_INTERLEAVED", "1");
|
|
285
|
+
set_env("LEPTRIS_IL_MIN", "1"); }
|
|
286
|
+
else { unset_env("LEPTRIS_INTERLEAVED");
|
|
287
|
+
unset_env("LEPTRIS_IL_MIN"); }
|
|
271
288
|
LeptrisStatus st = LEPTRIS_OK;
|
|
272
289
|
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
273
290
|
ASSERT_NE(d, nullptr);
|
|
@@ -280,3 +297,52 @@ TEST(InterleavedParity, DupAttrDiagnosticParity) {
|
|
|
280
297
|
unset_env("LEPTRIS_INTERLEAVED");
|
|
281
298
|
}
|
|
282
299
|
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
#ifndef _WIN32
|
|
303
|
+
/* #1258: the size floor. Below LEPTRIS_IL_MIN (default 64KB) the
|
|
304
|
+
* lane defers to the classic parser even when the gate is on —
|
|
305
|
+
* small documents cannot amortize the record pass (canon measured
|
|
306
|
+
* ~25% slower at 24KB). Forced floor (IL_MIN=1) restores engagement. */
|
|
307
|
+
TEST(InterleavedParity, SizeFloorDefersSmallDocsToClassic) {
|
|
308
|
+
const char* xml = "<r><a x='1'>t</a></r>";
|
|
309
|
+
set_env("LEPTRIS_INTERLEAVED", "1");
|
|
310
|
+
set_env("LEPTRIS_IL_STATS", "1");
|
|
311
|
+
unset_env("LEPTRIS_IL_MIN"); /* default floor: 64KB */
|
|
312
|
+
|
|
313
|
+
/* stderr capture, same pipe discipline as the canary spec. */
|
|
314
|
+
auto capture_one = [&](char* buf, size_t bufsz) -> void {
|
|
315
|
+
int fds[2];
|
|
316
|
+
ASSERT_EQ(pipe(fds), 0);
|
|
317
|
+
int saved = dup(STDERR_FILENO);
|
|
318
|
+
fflush(stderr);
|
|
319
|
+
dup2(fds[1], STDERR_FILENO);
|
|
320
|
+
close(fds[1]);
|
|
321
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
322
|
+
LeptrisDocument d = leptris_parse_string(xml, strlen(xml), &st);
|
|
323
|
+
if (d) leptris_document_free(d);
|
|
324
|
+
fflush(stderr);
|
|
325
|
+
dup2(saved, STDERR_FILENO);
|
|
326
|
+
close(saved);
|
|
327
|
+
ssize_t n = read(fds[0], buf, bufsz - 1);
|
|
328
|
+
if (n >= 0) buf[n] = '\0';
|
|
329
|
+
close(fds[0]);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
char small[512] = {0};
|
|
333
|
+
capture_one(small, sizeof(small));
|
|
334
|
+
EXPECT_EQ(strstr(small, "il: records="), nullptr)
|
|
335
|
+
<< "small doc must NOT engage the lane under the default floor: "
|
|
336
|
+
<< small;
|
|
337
|
+
|
|
338
|
+
set_env("LEPTRIS_IL_MIN", "1");
|
|
339
|
+
char forced[512] = {0};
|
|
340
|
+
capture_one(forced, sizeof(forced));
|
|
341
|
+
EXPECT_NE(strstr(forced, "il: records="), nullptr)
|
|
342
|
+
<< "forced floor must restore engagement";
|
|
343
|
+
|
|
344
|
+
unset_env("LEPTRIS_IL_STATS");
|
|
345
|
+
unset_env("LEPTRIS_IL_MIN");
|
|
346
|
+
unset_env("LEPTRIS_INTERLEAVED");
|
|
347
|
+
}
|
|
348
|
+
#endif /* !_WIN32: pipe/dup2 capture */
|
|
@@ -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.228",
|
|
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",
|