leptris 1.9.207.0 → 1.9.208.0
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/Rakefile +1 -1
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +25 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/TODO.max-perf/00-INDEX.md +3 -3
- data/vendor-src/libleptris/TODO.max-perf/2-3-oneshot-tokenizer-design.md +7 -1
- data/vendor-src/libleptris/src/leptris/dom/element_query.c +43 -1
- data/vendor-src/libleptris/src/leptris/dom/root_doc_map.c +31 -9
- data/vendor-src/libleptris/src/leptris/dom/root_doc_map.h +2 -0
- data/vendor-src/libleptris/src/leptris/leptris.c +13 -3
- data/vendor-src/libleptris/test/dom/test_dom.cpp +96 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c88c4d67f3b29d8e39348f96c9dfd6580563de8c1adcbb76a35a29a932a45dc
|
|
4
|
+
data.tar.gz: '030917e922c2ae16f63d588378d7d5552e52d03cfc2b5e33043aaea39c067c78'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09ba6dc5293eb1d93becec0eef194a97466ae800017ba5e2d44169dc3912b9620398f090300147ed62a8ebcacce5b83a7165b133f9bb84771cbdefc0e4c8d303'
|
|
7
|
+
data.tar.gz: 8b88a8828ce0bed5e3b7b8140bbb3f31238fc5e484737e6dcdc3a3f4171f3c36e768dd8245c3c7097657a7715add8afcf1c2f0a22d42c0f6fb5631b7d1771650
|
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.208"
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.208] - 2026-09-19
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Per-machine process-global corruption (#1242): the TLS (root, doc)
|
|
8
|
+
last-hit memo in `leptris_element_get_document` trusted the root
|
|
9
|
+
ADDRESS and was cleared only by document identity — an element-level
|
|
10
|
+
unregistration left `(freed_address, doc)` behind, and a
|
|
11
|
+
recycled address resolved the WRONG document (poisoning attribute
|
|
12
|
+
ownership: whole-run all-NULL `leptris_element_attribute` reads;
|
|
13
|
+
and tripping set_root's cross-document check). The memo is now
|
|
14
|
+
cleared whenever the outgoing root address matches, in both the
|
|
15
|
+
element-level unregister and the document sweep — closing the
|
|
16
|
+
same recycled-address class #1038 fixed for the root-doc map.
|
|
17
|
+
- `leptris_document_set_root` now sets a fresh thread-local error on
|
|
18
|
+
every rejection (NULL_ARG + both EINVAL paths) — previously the
|
|
19
|
+
previous operation's message leaked through (the reported "EINVAL
|
|
20
|
+
with a stale XPath parse error").
|
|
21
|
+
- New diagnostic: `LEPTRIS_DEBUG_ATTR_MISS=1` dumps attribute-chain
|
|
22
|
+
state on lookup misses (chain length, per-attr name bytes/hash15)
|
|
23
|
+
— the discriminator for any residual binding-side corruption
|
|
24
|
+
report.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
3
28
|
## [1.9.207] - 2026-09-19
|
|
4
29
|
|
|
5
30
|
### Fixed
|
|
@@ -5,12 +5,12 @@ RED bench first where perf claims are made.
|
|
|
5
5
|
|
|
6
6
|
| # | Lever | Type | Effort | Status |
|
|
7
7
|
|---|-------|------|--------|--------|
|
|
8
|
-
| 3 | iterparse fixed-cost (6.9µs) + error channel (#563) | perf | bench landed #
|
|
8
|
+
| 3 | iterparse fixed-cost (6.9µs) + error channel (#563) | perf | bench #1195; zero-copy emission landed #1236 (295->52us full-doc); remaining = iterparse per-event DOM construction (0.45->0.15us/event stretch) | mostly done |
|
|
9
9
|
| 1 | SIMD/memchr HTML tokenizer scans | perf | memchr ×2 (#1176/#1190) + LUT (#1199, +30%/+51%) | DONE v1.9.199 |
|
|
10
10
|
| 7 | leptris validate CLI + bindings | feature | engine half merged #1198 (--dtd + DTD engine fixes); bindings remain | half done |
|
|
11
|
-
| 8 | native XML diff consumer surface | feature |
|
|
11
|
+
| 8 | native XML diff consumer surface | feature | CLI diff text/JSON/--summary + engine API shipped | DONE (#1184 closed) |
|
|
12
12
|
| 4 | compact-node micro-opts (non-layout) | perf | audited: TODOs 172/195/Lane18-P3 already in; residual = node-creation cost (node-layout fork, owner call) | parked on fork |
|
|
13
|
-
| 2 | compiled-dispatch tokenizer | perf |
|
|
13
|
+
| 2 | compiled-dispatch tokenizer | perf | slices 1+3 landed #1236; slice 2's class LUT + dense switch pre-existed; residual = iterparse consumer (row 3) | mostly done |
|
|
14
14
|
| 5 | XSLT 2x per-bench profile rounds (#682) | perf | medium, ongoing | in progress |
|
|
15
15
|
| 6 | language tails (QT3/dates/fn-items/lane 15) | correctness | medium-large | queued |
|
|
16
16
|
| 9 | prebuilt-gem 12-platform matrix | release | small, BLOCKED on owner go | blocked |
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Levers 2+3 — compiled-dispatch + zero-copy one-shot emission (design)
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: LANDED (2026-09-19, PR #1236). Slices 1+3 implemented;
|
|
4
|
+
slice 2's class LUT (`sxs_chartype`) + dense state switch already
|
|
5
|
+
existed. Measured: bench_iterparse full-doc 295->52-63us wall,
|
|
6
|
+
15.9->74-91 MB/s, 2.54->0.45-0.55us/event. Residual to the 0.15
|
|
7
|
+
stretch = iterparse per-event DOM construction (element create +
|
|
8
|
+
namespace resolution through the public element API), a consumer
|
|
9
|
+
lever, not string copies.
|
|
4
10
|
Owners: engine repo. Bench: `benchmarks/sax/bench_iterparse.c` (#1195).
|
|
5
11
|
|
|
6
12
|
## Problem
|
|
@@ -178,7 +178,49 @@ LEPTRIS_API const char* leptris_element_attribute(LeptrisElement elem, const cha
|
|
|
178
178
|
if (!uri) return NULL; /* (4) */
|
|
179
179
|
attr = find_attr_expanded(elem, uri, colon + 1); /* (2) */
|
|
180
180
|
}
|
|
181
|
-
if (!attr)
|
|
181
|
+
if (!attr) {
|
|
182
|
+
/* #1242 diagnostic: LEPTRIS_DEBUG_ATTR_MISS=1 dumps the
|
|
183
|
+
* element's attribute-chain state for the first 5 misses in
|
|
184
|
+
* the process — distinguishes construction loss
|
|
185
|
+
* (chain_len=0) from storage corruption (garbage name
|
|
186
|
+
* bytes) from hash-discipline drift (correct bytes,
|
|
187
|
+
* mismatching hash15). Off by default; fprintf only on
|
|
188
|
+
* miss. */
|
|
189
|
+
static int dumped = 0;
|
|
190
|
+
if (dumped < 5 && getenv("LEPTRIS_DEBUG_ATTR_MISS")) {
|
|
191
|
+
dumped++;
|
|
192
|
+
size_t chain = 0;
|
|
193
|
+
for (struct leptris_attribute* a =
|
|
194
|
+
leptris_element_get_first_attribute(elem);
|
|
195
|
+
a; a = leptris_attr_next(a))
|
|
196
|
+
chain++;
|
|
197
|
+
fprintf(stderr,
|
|
198
|
+
"[leptris-attr-miss] elem=%p needle='%s' "
|
|
199
|
+
"chain_len=%zu\n",
|
|
200
|
+
(void*)elem, name, chain);
|
|
201
|
+
size_t i = 0;
|
|
202
|
+
for (struct leptris_attribute* a =
|
|
203
|
+
leptris_element_get_first_attribute(elem);
|
|
204
|
+
a && i < 8; a = leptris_attr_next(a), i++) {
|
|
205
|
+
char nb[17];
|
|
206
|
+
size_t nl = a->name_view.length < 16
|
|
207
|
+
? a->name_view.length : 16;
|
|
208
|
+
for (size_t k = 0; k < nl; k++) {
|
|
209
|
+
unsigned char c = (unsigned char)a->name_view.data[k];
|
|
210
|
+
nb[k] = (c >= 32 && c < 127) ? (char)c : '.';
|
|
211
|
+
}
|
|
212
|
+
nb[nl] = '\0';
|
|
213
|
+
fprintf(stderr,
|
|
214
|
+
" attr[%zu] addr=%p name_ptr=%p len=%zu "
|
|
215
|
+
"bytes='%s' hash15=%u entities=%d\n",
|
|
216
|
+
i, (void*)a, (void*)a->name_view.data,
|
|
217
|
+
a->name_view.length, nb,
|
|
218
|
+
(unsigned)(a->name_hash & 0x7FFFu),
|
|
219
|
+
(int)((a->name_hash & 0x8000u) != 0));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return NULL;
|
|
223
|
+
}
|
|
182
224
|
|
|
183
225
|
/* Single representation (round 4): entity values expand lazily
|
|
184
226
|
* INTO the view (owned copy); no-entity views are already
|
|
@@ -102,6 +102,15 @@ static size_t bucket_index(LeptrisElement root) {
|
|
|
102
102
|
return (size_t)(v & (ROOT_DOC_BUCKETS - 1));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/* #682 2x lever (sound): last-hit (root, doc) memo. The climb to
|
|
106
|
+
* the root is unavoidable without a per-element field, but the TLS
|
|
107
|
+
* bucket-array walk is not — consecutive queries in a transform
|
|
108
|
+
* hit the same root, so one pointer compare replaces the hash
|
|
109
|
+
* bucket chain. One TLS access total (the memo pair), and the
|
|
110
|
+
* 1M-loop sentinel becomes a plain NULL check. */
|
|
111
|
+
static LEPTRIS_THREAD_LOCAL LeptrisElement g_memo_root;
|
|
112
|
+
static LEPTRIS_THREAD_LOCAL struct leptris_document* g_memo_doc;
|
|
113
|
+
|
|
105
114
|
void leptris_root_doc_register(LeptrisElement root, struct leptris_document* doc) {
|
|
106
115
|
if (!root || !doc) return;
|
|
107
116
|
size_t idx = bucket_index(root);
|
|
@@ -160,6 +169,17 @@ void leptris_root_doc_drain_thread_caches(void) {
|
|
|
160
169
|
void leptris_root_doc_unregister(LeptrisElement root) {
|
|
161
170
|
if (!root) return;
|
|
162
171
|
if (!rootmap_marked(root)) return; /* never registered: O(1) out */
|
|
172
|
+
/* #1242: the (root, doc) memo trusts the root ADDRESS. An
|
|
173
|
+
* element-level unregister frees that address while the doc
|
|
174
|
+
* lives — the doc-keyed invalidation never fires, and the
|
|
175
|
+
* allocator recycling the address into another element's climb
|
|
176
|
+
* target made get_document return the WRONG document. Same
|
|
177
|
+
* exposure class #1038 closed for the map; the memo needs it
|
|
178
|
+
* at element granularity. */
|
|
179
|
+
if (g_memo_root == root) {
|
|
180
|
+
g_memo_root = NULL;
|
|
181
|
+
g_memo_doc = NULL;
|
|
182
|
+
}
|
|
163
183
|
size_t idx = bucket_index(root);
|
|
164
184
|
RootDocEntry** pp = &g_root_doc_buckets[idx];
|
|
165
185
|
while (*pp) {
|
|
@@ -202,6 +222,13 @@ size_t leptris_root_doc_unregister_doc(struct leptris_document* doc) {
|
|
|
202
222
|
doc->map_entries = NULL;
|
|
203
223
|
while (e) {
|
|
204
224
|
RootDocEntry* next = e->doc_next;
|
|
225
|
+
/* #1242: a swept root address must not survive in the memo
|
|
226
|
+
* even when the memo's doc differs (a previously poisoned
|
|
227
|
+
* pair). */
|
|
228
|
+
if (g_memo_root == e->root) {
|
|
229
|
+
g_memo_root = NULL;
|
|
230
|
+
g_memo_doc = NULL;
|
|
231
|
+
}
|
|
205
232
|
size_t idx = bucket_index(e->root);
|
|
206
233
|
RootDocEntry** pp = &g_root_doc_buckets[idx];
|
|
207
234
|
while (*pp) {
|
|
@@ -234,15 +261,6 @@ struct leptris_document* leptris_root_doc_lookup(LeptrisElement root) {
|
|
|
234
261
|
* are skipped entirely (get_document was 3.5% of the light bench).
|
|
235
262
|
* Hint is thread-local-scoped by construction (one transform per
|
|
236
263
|
* thread), validated by the root-walk fallback when it misses. */
|
|
237
|
-
/* #682 2x lever (sound): last-hit (root, doc) memo. The climb to
|
|
238
|
-
* the root is unavoidable without a per-element field, but the TLS
|
|
239
|
-
* bucket-array walk is not — consecutive queries in a transform
|
|
240
|
-
* hit the same root, so one pointer compare replaces the hash
|
|
241
|
-
* bucket chain. One TLS access total (the memo pair), and the
|
|
242
|
-
* 1M-loop sentinel becomes a plain NULL check. */
|
|
243
|
-
static LEPTRIS_THREAD_LOCAL LeptrisElement g_memo_root;
|
|
244
|
-
static LEPTRIS_THREAD_LOCAL struct leptris_document* g_memo_doc;
|
|
245
|
-
|
|
246
264
|
/* #904: prime the TLS last-root memo from a driver that already
|
|
247
265
|
* knows the (root, doc) pair — the iterparse yield path hands out
|
|
248
266
|
* a subtree that is released before the next, so the memo misses
|
|
@@ -263,6 +281,10 @@ void leptris_root_doc_memo_invalidate(const struct leptris_document* doc) {
|
|
|
263
281
|
}
|
|
264
282
|
}
|
|
265
283
|
|
|
284
|
+
LeptrisElement leptris_root_doc_memo_root_for_tests(void) {
|
|
285
|
+
return g_memo_root;
|
|
286
|
+
}
|
|
287
|
+
|
|
266
288
|
struct leptris_document* leptris_element_get_document(LeptrisElement elem) {
|
|
267
289
|
if (!elem) return NULL;
|
|
268
290
|
LeptrisElement cur = elem;
|
|
@@ -15,6 +15,8 @@ void leptris_root_doc_register(LeptrisElement root, struct leptris_document* doc
|
|
|
15
15
|
void leptris_root_doc_unregister(LeptrisElement root);
|
|
16
16
|
struct leptris_document* leptris_root_doc_lookup(LeptrisElement root);
|
|
17
17
|
void leptris_root_doc_memo_invalidate(const struct leptris_document* doc);
|
|
18
|
+
/* Test hook (#1242): the memo's current root, or NULL. */
|
|
19
|
+
LeptrisElement leptris_root_doc_memo_root_for_tests(void);
|
|
18
20
|
/* #904: prime the TLS (root, doc) memo from a driver that knows
|
|
19
21
|
* the pair (iterparse yields). Root must be registered. */
|
|
20
22
|
void leptris_root_doc_memo_prime(LeptrisElement root,
|
|
@@ -253,12 +253,19 @@ struct leptris_document* leptris_document_create_on_arena(
|
|
|
253
253
|
*/
|
|
254
254
|
LEPTRIS_API LeptrisStatus leptris_document_set_root(LeptrisDocument doc,
|
|
255
255
|
LeptrisElement root) {
|
|
256
|
-
if (!doc || !root)
|
|
256
|
+
if (!doc || !root) {
|
|
257
|
+
leptris_set_error(LEPTRIS_ERROR_NULL_ARG,
|
|
258
|
+
"set_root: NULL document or element");
|
|
259
|
+
return LEPTRIS_ERROR_NULL_ARG;
|
|
260
|
+
}
|
|
257
261
|
|
|
258
262
|
/* Already attached under a parent? (parent_off == 0 encodes NULL;
|
|
259
263
|
* leptris_elem_parent is the static inline accessor from dom/element.h) */
|
|
260
|
-
if (leptris_elem_parent(root))
|
|
264
|
+
if (leptris_elem_parent(root)) {
|
|
265
|
+
leptris_set_error(LEPTRIS_ERROR_INVALID_ARG,
|
|
266
|
+
"set_root: element is attached to a parent");
|
|
261
267
|
return LEPTRIS_ERROR_INVALID_ARG;
|
|
268
|
+
}
|
|
262
269
|
|
|
263
270
|
/* Cross-document attach would dangle the source pool on free.
|
|
264
271
|
* Lane 18 P1: resolve via get_document (map + namebp fallback)
|
|
@@ -266,8 +273,11 @@ LEPTRIS_API LeptrisStatus leptris_document_set_root(LeptrisDocument doc,
|
|
|
266
273
|
* lookup rejected every one of them. */
|
|
267
274
|
extern struct leptris_document* leptris_element_get_document(
|
|
268
275
|
LeptrisElement elem);
|
|
269
|
-
if (leptris_element_get_document(root) != doc)
|
|
276
|
+
if (leptris_element_get_document(root) != doc) {
|
|
277
|
+
leptris_set_error(LEPTRIS_ERROR_INVALID_ARG,
|
|
278
|
+
"set_root: element belongs to a different document");
|
|
270
279
|
return LEPTRIS_ERROR_INVALID_ARG;
|
|
280
|
+
}
|
|
271
281
|
|
|
272
282
|
doc->root = root;
|
|
273
283
|
doc->new_dom_root = root;
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
#include <gtest/gtest.h>
|
|
4
4
|
|
|
5
5
|
#include "leptris.h"
|
|
6
|
+
#include "leptris/error.h"
|
|
6
7
|
/* #1125 pristine-buffer gate: reach doc->xml_buffer directly. */
|
|
7
8
|
#include "../leptris/leptris_internal.h"
|
|
9
|
+
/* Internal root-doc-map surface (#1242 spec); C linkage. */
|
|
10
|
+
extern "C" {
|
|
11
|
+
LeptrisElement leptris_root_doc_memo_root_for_tests(void);
|
|
12
|
+
void leptris_root_doc_register(LeptrisElement root,
|
|
13
|
+
struct leptris_document* doc);
|
|
14
|
+
void leptris_root_doc_unregister(LeptrisElement root);
|
|
15
|
+
struct leptris_document* leptris_element_get_document(LeptrisElement elem);
|
|
16
|
+
}
|
|
8
17
|
#include "../leptris/memory/pool.h"
|
|
9
18
|
|
|
10
19
|
#include <cstring>
|
|
@@ -92,6 +101,30 @@ TEST(DomBasics, DocumentSetRootRejectsNullInputs) {
|
|
|
92
101
|
leptris_document_free(doc);
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
// #1242 symptom 2: a failed set_root must set a FRESH thread-local
|
|
105
|
+
// error — the binding's exception message was stitched from whatever
|
|
106
|
+
// the previous operation left behind (a stale XPath parse error).
|
|
107
|
+
TEST(DomBasics, DocumentSetRootFailureSetsFreshError) {
|
|
108
|
+
LeptrisDocument doc_a = leptris_document_create();
|
|
109
|
+
LeptrisDocument doc_b = leptris_document_create();
|
|
110
|
+
ASSERT_NE(doc_a, nullptr);
|
|
111
|
+
ASSERT_NE(doc_b, nullptr);
|
|
112
|
+
/* Poison the thread-local slot with an unrelated message. */
|
|
113
|
+
ASSERT_EQ(leptris_parse_string("<a><b></a>", 10, nullptr), nullptr);
|
|
114
|
+
|
|
115
|
+
LeptrisElement foreign = leptris_element_create(doc_b, "b");
|
|
116
|
+
ASSERT_NE(foreign, nullptr);
|
|
117
|
+
EXPECT_EQ(leptris_document_set_root(doc_a, foreign),
|
|
118
|
+
LEPTRIS_ERROR_INVALID_ARG);
|
|
119
|
+
const char* msg = leptris_last_error();
|
|
120
|
+
ASSERT_NE(msg, nullptr);
|
|
121
|
+
EXPECT_EQ(strstr(msg, "set_root"), msg)
|
|
122
|
+
<< "expected a fresh set_root message, got: " << msg;
|
|
123
|
+
|
|
124
|
+
leptris_document_free(doc_a);
|
|
125
|
+
leptris_document_free(doc_b);
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
TEST(DomBasics, DocumentSetRootRejectsForeignElement) {
|
|
96
129
|
LeptrisDocument doc_a = leptris_document_create();
|
|
97
130
|
LeptrisDocument doc_b = leptris_document_create();
|
|
@@ -117,6 +150,69 @@ TEST(DomBasics, DocumentSetRootRejectsParentedElement) {
|
|
|
117
150
|
leptris_document_free(doc);
|
|
118
151
|
}
|
|
119
152
|
|
|
153
|
+
/* #1242: the TLS (root, doc) memo trusts the root address. An
|
|
154
|
+
* element-level unregister frees that address while the document
|
|
155
|
+
* lives; a recycled address later made get_document resolve the
|
|
156
|
+
* WRONG document (per-machine corruption signature). Unregistering
|
|
157
|
+
* a root must drop the memo. */
|
|
158
|
+
TEST(DomBasics, RootDocMemoClearedWhenRootUnregisters) {
|
|
159
|
+
LeptrisDocument doc = leptris_document_create();
|
|
160
|
+
ASSERT_NE(doc, nullptr);
|
|
161
|
+
LeptrisElement e = leptris_element_create(doc, "r");
|
|
162
|
+
ASSERT_NE(e, nullptr);
|
|
163
|
+
leptris_root_doc_register(e, doc);
|
|
164
|
+
/* Prime the memo through a resolution. */
|
|
165
|
+
ASSERT_EQ(leptris_element_get_document(e), doc);
|
|
166
|
+
ASSERT_EQ(leptris_root_doc_memo_root_for_tests(), e);
|
|
167
|
+
/* Element-level lifecycle: the root goes away, the doc stays. */
|
|
168
|
+
leptris_root_doc_unregister(e);
|
|
169
|
+
EXPECT_EQ(leptris_root_doc_memo_root_for_tests(), nullptr)
|
|
170
|
+
<< "stale (root, doc) memo survived root unregistration";
|
|
171
|
+
leptris_document_free(doc);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* #1242: every status-returning rejection must refresh the
|
|
175
|
+
* thread-local error channel — a stale message from an earlier
|
|
176
|
+
* operation leaking through an EINVAL is the reported signature. */
|
|
177
|
+
TEST(DomBasics, DocumentSetRootRejectionSetsFreshError) {
|
|
178
|
+
const char xml[] = "<a><b/></a>";
|
|
179
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
180
|
+
LeptrisDocument doc = leptris_parse_string(xml, std::strlen(xml), &st);
|
|
181
|
+
ASSERT_NE(doc, nullptr);
|
|
182
|
+
/* Poison the channel with an XPath syntax error and snapshot it. */
|
|
183
|
+
LeptrisXPathResult bad = leptris_xpath_eval(doc, nullptr, "<<");
|
|
184
|
+
if (bad) leptris_xpath_result_free(bad);
|
|
185
|
+
std::string poisoned = leptris_last_error() ? leptris_last_error() : "";
|
|
186
|
+
ASSERT_FALSE(poisoned.empty());
|
|
187
|
+
|
|
188
|
+
LeptrisElement b = (LeptrisElement)leptris_node_first_child(
|
|
189
|
+
leptris_element_as_node(leptris_document_root(doc)));
|
|
190
|
+
ASSERT_NE(b, nullptr);
|
|
191
|
+
EXPECT_EQ(leptris_document_set_root(doc, b), LEPTRIS_ERROR_INVALID_ARG);
|
|
192
|
+
/* The channel must now describe THIS failure, not the XPath one. */
|
|
193
|
+
const char* err = leptris_last_error();
|
|
194
|
+
ASSERT_TRUE(err != nullptr && err[0] != '\0');
|
|
195
|
+
EXPECT_STRNE(err, poisoned.c_str())
|
|
196
|
+
<< "stale XPath error leaked through set_root EINVAL: " << err;
|
|
197
|
+
EXPECT_TRUE(strstr(err, "root") != nullptr)
|
|
198
|
+
<< "expected a set_root-specific message, got: " << err;
|
|
199
|
+
|
|
200
|
+
/* Cross-document rejection refreshes the channel too. */
|
|
201
|
+
LeptrisDocument other = leptris_document_create();
|
|
202
|
+
ASSERT_NE(other, nullptr);
|
|
203
|
+
LeptrisElement foreign = leptris_element_create(other, "f");
|
|
204
|
+
ASSERT_NE(foreign, nullptr);
|
|
205
|
+
EXPECT_EQ(leptris_document_set_root(doc, foreign),
|
|
206
|
+
LEPTRIS_ERROR_INVALID_ARG);
|
|
207
|
+
err = leptris_last_error();
|
|
208
|
+
ASSERT_TRUE(err != nullptr && err[0] != '\0');
|
|
209
|
+
EXPECT_STRNE(err, poisoned.c_str());
|
|
210
|
+
EXPECT_TRUE(strstr(err, "document") != nullptr)
|
|
211
|
+
<< "expected a cross-document message, got: " << err;
|
|
212
|
+
leptris_document_free(other);
|
|
213
|
+
leptris_document_free(doc);
|
|
214
|
+
}
|
|
215
|
+
|
|
120
216
|
TEST(DomBasics, AttributeLookupByName) {
|
|
121
217
|
const char xml[] = "<r a='1' b='2' c='3'/>";
|
|
122
218
|
LeptrisStatus st = LEPTRIS_OK;
|
|
@@ -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.208",
|
|
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",
|