leptris 1.9.367.1-aarch64-linux → 1.9.368.1-aarch64-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee050b0ad71bfb9889c853fe2e76ee5ab7a064cd40d9d993f5e114c7e579fcf1
4
- data.tar.gz: 51d3e883a57d35f07df28541ac80f899a37d072e79545d76003f8c8b03b5672c
3
+ metadata.gz: 3a55d9429274a6351b83c25eb8a3e366a5cd1ea7effc953661ee0ba125607d58
4
+ data.tar.gz: 824310d407eefe2202433914bf71a25eae55ac5bd6ddbae8e8577837c0a24955
5
5
  SHA512:
6
- metadata.gz: 978f3b2b8849f891a60888218edebcd106eb473b68ef541c40d7f821a115c51a58e185fbb1d615b036f2cbc85430c8f7da003977236e8f79aec73e547b0147f8
7
- data.tar.gz: 7bb6e2bda28e5b2ff5030019c25456fa79b8aa89c7461f6e2c47d207b2689dc444b5624bb9006229354909e2c7157325103ce2b2b4bc79665323dee0f2fa97f3
6
+ metadata.gz: 191bd7787891ea2ef62337b6f858cb43db048f58ad1cb83cba4dd5635298430884d221fdabbe8d663797a5e405ecaa804bc53fef2ef6b3abb9484f3a99f6a7cf
7
+ data.tar.gz: 0defbddcc67344faef208aaecc7d06b833b87d967c8731a7f08d95eacf142600052af1f304aff6e92323ac624bc3999eb91e7836390f559e787390ae873e990c
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.367"
11
+ LIBLEPTRIS_VERSION = "1.9.368"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.367.1"
4
+ VERSION = "1.9.368.1"
5
5
  end
data/lib/libleptris.so CHANGED
Binary file
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.367
4
+ - libleptris v1.9.368
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.368] - 2026-10-18
4
+
5
+ ### Fixed
6
+
7
+ - **dom: `leptris_element_get_document` is kind-safe (#487, PR #1763).**
8
+ The addr-based attach face called get_document on the RAW child before
9
+ any kind routing. For the TEXT nodes a hydration loop appends,
10
+ get_document read element-form fields — the namebp fast-outs and
11
+ element parent offsets. A live inline text usually survived the
12
+ misread; a recycled or heap-backed text handed the walk a wild pointer
13
+ and the process died: the **Ruby 4.0 + darwin Bus error at
14
+ element.rb:809** in the lutaml/moxml DOCX hydration suite
15
+ (leptris-ruby#487) — deterministic across randomized runs, with
16
+ Ruby 3.3/3.4 and 4.0-ubuntu clean on identical code (GC timing, not
17
+ semantics). Non-element nodes now route through the per-struct
18
+ `owner_doc` read (the same authority adopt_for_splice's #1534 kind
19
+ routing uses); NULL answers for unstamped detached leaves stay part of
20
+ the contract. Verified on Ruby 4.0.5 darwin with the reporter's
21
+ harness: the uniword DOCX suite runs 385/385 plain AND under
22
+ RUBY_GC_STRESS (deterministic SIGSEGV before). Engine suite
23
+ 2036/2036 with a new kind-safety contract test.
24
+
25
+
3
26
  ## [1.9.367] - 2026-10-17
4
27
 
5
28
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.367
6
+ VERSION 1.9.368
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -16,8 +16,13 @@ Rules (per repo conventions):
16
16
  (benchmarks/xslt/*) — conformance may not cost the lead. A fix
17
17
  that regresses a benchmark by >3% needs an algorithmic answer.
18
18
 
19
- Live census (157 open, by failure signature; refresh with the
20
- triage script in 00):
19
+ **GATE MET (verified 2026-10-17, main @ 75cd5f6e):**
20
+ libxslt suite **205/205** (open_cases.txt empty; macOS legitimately
21
+ skips bug-169 — encoding-sensitive via iconv, Linux CI is
22
+ authoritative), XsltFull **27/27** (the suite carries 27 cases; the
23
+ historical 45 count predates the case consolidation), full suite
24
+ **2053/2053** locally. The census below is the campaign-start
25
+ snapshot, kept for the record:
21
26
  - 84 PI/COMMENT-OUT — fragment-node serialization + content edges
22
27
  - 28 EMPTY — root-node `/` semantics, doc() chains, early exits
23
28
  - 24 DIFF — number corners, attr-set precedence, sort, AVT edges
@@ -501,6 +501,12 @@ int leptris_elem_pos_lookup(const struct leptris_document* doc,
501
501
  uint32_t* start_tag_end, uint32_t* element_end);
502
502
 
503
503
  struct leptris_document* leptris_element_get_document(LeptrisElement elem);
504
+
505
+ /* #487: the KIND-SAFE document resolver for non-element nodes
506
+ * (text/cdata/comment/pi) — reads each struct's own owner_doc
507
+ * field instead of element-form views. get_document routes
508
+ * non-elements here; element callers keep the element path. */
509
+ struct leptris_document* leptris_leaf_owner_doc(LeptrisNode* n);
504
510
  LeptrisMemoryPool* leptris_element_get_pool(LeptrisElement elem);
505
511
 
506
512
  /* Round 21: header.flags bit 6 — "elem->name is preceded by 8 bytes
@@ -299,7 +299,7 @@ static int node_in_current_mut_block(struct leptris_document* doc,
299
299
  * #1320-family doc stamps: text an int32 offset, cdata/comment/pi
300
300
  * a direct pointer. Unstamped = the #540 same-doc detached-chain
301
301
  * contract (public creators stamp; hand-built chains don't). */
302
- static struct leptris_document* leaf_owner_doc(LeptrisNode* n) {
302
+ struct leptris_document* leptris_leaf_owner_doc(LeptrisNode* n) {
303
303
  switch (n->type) {
304
304
  case LEPTRIS_NODE_TYPE_TEXT:
305
305
  return leptris_textnode_owner_doc((LeptrisTextNode*)n);
@@ -390,7 +390,7 @@ static LeptrisNode* adopt_for_splice(LeptrisNode* node,
390
390
  src = leptris_element_get_document(
391
391
  (LeptrisElement)top);
392
392
  } else {
393
- src = leaf_owner_doc(top);
393
+ src = leptris_leaf_owner_doc(top);
394
394
  }
395
395
  if (!src || src == target) return node;
396
396
  /* #1548: the source is a doomed scratch doc absorbed into
@@ -400,6 +400,14 @@ LeptrisElement leptris_root_doc_memo_root_for_tests(void) {
400
400
 
401
401
  struct leptris_document* leptris_element_get_document(LeptrisElement elem) {
402
402
  if (!elem) return NULL;
403
+ /* #487: a non-element node must never read element-form fields
404
+ * (the namebp fast-outs and element parent offsets below) — a
405
+ * recycled or heap-backed text handed the walk a wild pointer
406
+ * and the process died. Route through the kind-safe owner
407
+ * read; NULL answers are part of the contract (unstamped
408
+ * detached leaves). */
409
+ if (((LeptrisNodeRef)elem)->type != LEPTRIS_NODE_TYPE_ELEMENT)
410
+ return leptris_leaf_owner_doc((LeptrisNode*)elem);
403
411
  /* Lane 18 R3: an UNATTACHED namebp-carrying element roots
404
412
  * itself in the name slot (#1189) — that slot is its
405
413
  * authoritative document and must never see the recyclable-
@@ -260,16 +260,18 @@ if(Python3_FOUND)
260
260
  ENVIRONMENT "PYTHONPATH=")
261
261
  endif()
262
262
 
263
- # libFuzzer harness (TODO 40) — built only when LEPTRIS_ENABLE_FUZZING=ON.
264
- # Not a ctest target; invoked manually with corpus.
263
+ # libFuzzer harnesses — built only when LEPTRIS_ENABLE_FUZZING=ON.
264
+ # Not ctest targets; invoked manually with a corpus (or by the
265
+ # nightly fuzz workflow, which invokes them at the build root —
266
+ # subdirectory targets land under build/test/ by default).
265
267
  if(LEPTRIS_ENABLE_FUZZING)
266
- add_executable(fuzz_parse ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/fuzz_parse.c)
267
- target_link_libraries(fuzz_parse PRIVATE leptris)
268
- target_include_directories(fuzz_parse PRIVATE
269
- ${PROJECT_SOURCE_DIR}/src/include)
270
- # The harness doc and the nightly workflow invoke the binary at
271
- # the build root, not under build/test/ where subdirectory
272
- # targets land by default.
273
- set_target_properties(fuzz_parse PROPERTIES
274
- RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
268
+ foreach(target fuzz_parse fuzz_mutate fuzz_xpath)
269
+ add_executable(${target}
270
+ ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/${target}.c)
271
+ target_link_libraries(${target} PRIVATE leptris)
272
+ target_include_directories(${target} PRIVATE
273
+ ${PROJECT_SOURCE_DIR}/src/include)
274
+ set_target_properties(${target} PROPERTIES
275
+ RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
276
+ endforeach()
275
277
  endif()
@@ -3071,6 +3071,42 @@ TEST(DomAttachInstalled, ForeignMixedChainTextBeforeElement) {
3071
3071
  leptris_document_free(target);
3072
3072
  }
3073
3073
 
3074
+ /* leptris-ruby#487: the addr-based attach face ran
3075
+ * leptris_element_get_document on the RAW child before any kind
3076
+ * routing — for a TEXT child that reads element-form fields
3077
+ * (namebp fast-outs, element parent offsets). A live inline text
3078
+ * usually survives the misread; a recycled or heap-backed text
3079
+ * hands the walk a wild pointer and the process dies. The face (and
3080
+ * get_document itself) must be kind-safe. */
3081
+ TEST(DomAttachInstalled, TextChildAttachIsKindSafe) {
3082
+ LeptrisStatus st = LEPTRIS_OK;
3083
+ LeptrisDocument d = leptris_parse_string("<r/>", 4, &st);
3084
+ ASSERT_NE(d, nullptr);
3085
+ LeptrisElement root = leptris_document_root(d);
3086
+
3087
+ /* detached text via the public face — a non-element child */
3088
+ LeptrisNodeRef text =
3089
+ leptris_text_node_create(d, "hello");
3090
+ ASSERT_NE(text, nullptr);
3091
+ EXPECT_EQ(leptris_node_get_type(text), LEPTRIS_NODE_TYPE_TEXT);
3092
+
3093
+ /* get_document on the text must not crash and must not
3094
+ * manufacture a bogus document pointer */
3095
+ LeptrisDocument tdoc = leptris_element_get_document(
3096
+ (LeptrisElement)text);
3097
+ EXPECT_TRUE(tdoc == d || tdoc == nullptr);
3098
+
3099
+ /* the attach face with the text child must succeed */
3100
+ EXPECT_EQ(leptris_element_add_child_addr_installed(
3101
+ (uintptr_t)root, (uintptr_t)text, NULL),
3102
+ LEPTRIS_OK);
3103
+ char* out = leptris_element_serialize(root, nullptr);
3104
+ ASSERT_NE(out, nullptr);
3105
+ EXPECT_STREQ(out, "<r>hello</r>");
3106
+ leptris_free_string(out);
3107
+ leptris_document_free(d);
3108
+ }
3109
+
3074
3110
  TEST(DomAttachInstalled, SameDocumentReturnsArgument) {
3075
3111
  LeptrisStatus st = LEPTRIS_OK;
3076
3112
  LeptrisDocument d = leptris_parse_string("<r/>", 4, &st);
@@ -0,0 +1,146 @@
1
+ /* test/fuzz/fuzz_mutate.c — libFuzzer harness for the MUTATION API
2
+ * (approach ported from teptris's op-driven fuzz_dump): the input
3
+ * bytes drive a deterministic builder/mutation sequence — element
4
+ * creation, attribute and text edits, append/insert/remove, and
5
+ * cross-document adoption — against three documents (a RECOVER
6
+ * parse, a second parse, and a fresh programmatic one), then the
7
+ * final trees serialize and re-parse. Invariants under ASan/UBSan:
8
+ * no crash, no leak, every op result consumed; status errors from
9
+ * rejected ops (cycles, foreign shapes) are fine — that IS the
10
+ * contract. The mutation faces are where the adopt/copy and
11
+ * sibling-chain bug classes lived (#1755, #1717, #1636). */
12
+ #include "leptris.h"
13
+
14
+ #include <stdint.h>
15
+ #include <stdlib.h>
16
+ #include <string.h>
17
+
18
+ static const char* const kNames[] = {
19
+ "a", "b", "r", "item", "p", "x"};
20
+ #define MUT_ALPHA 6
21
+
22
+ static const char* name_at(const uint8_t* d, size_t sz, size_t i) {
23
+ return sz ? kNames[d[i % sz] % MUT_ALPHA] : kNames[0];
24
+ }
25
+
26
+ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
27
+ if (size < 4 || size > (1u << 20)) return 0;
28
+
29
+ /* First half: markup for two documents. Second half: ops. */
30
+ size_t cut = 1 + (size % (size / 2 ? size / 2 : 1));
31
+ if (cut >= size) cut = size / 2;
32
+ const uint8_t* ops = data + cut;
33
+ size_t nops = size - cut;
34
+ if (nops > 256) nops = 256;
35
+
36
+ LeptrisParseOptions opts;
37
+ memset(&opts, 0, sizeof(opts));
38
+ opts.recover = 1;
39
+ LeptrisStatus st = LEPTRIS_OK;
40
+ LeptrisDocument doc =
41
+ leptris_parse_string_ex((const char*)data, cut, &opts, &st);
42
+ if (!doc) return 0;
43
+ LeptrisDocument doc2 = leptris_parse_string_ex(
44
+ (const char*)data + cut, nops, &opts, &st);
45
+ if (!doc2) {
46
+ leptris_document_free(doc);
47
+ return 0;
48
+ }
49
+ LeptrisDocument fresh = leptris_document_create();
50
+
51
+ /* A stable pool of nodes to point ops at: the docs' roots, the
52
+ * fresh doc's created children, and a few created elements. */
53
+ enum { POOL = 8 };
54
+ LeptrisElement pool[POOL] = {0};
55
+ LeptrisElement root = leptris_document_root(doc);
56
+ LeptrisElement root2 = leptris_document_root(doc2);
57
+ if (root) pool[0] = root;
58
+ if (root2) pool[1] = root2;
59
+ if (root)
60
+ for (int i = 2, j = 0; i < POOL && j < 3; j++) {
61
+ LeptrisElement c = leptris_element_create(fresh, "c");
62
+ if (!c) break;
63
+ pool[i++] = c;
64
+ }
65
+
66
+ for (size_t k = 0; k + 1 < nops; k += 2) {
67
+ unsigned op = ops[k] % 10u;
68
+ LeptrisElement tgt = pool[ops[k + 1] % POOL];
69
+ if (!tgt) continue;
70
+ const char* name = name_at(data, size, k);
71
+ switch (op) {
72
+ case 0: {
73
+ LeptrisElement e = leptris_element_create(doc, name);
74
+ if (e) (void)leptris_element_append_child(tgt, e);
75
+ break;
76
+ }
77
+ case 1: {
78
+ /* cross-document adopt-by-copy (#1528/#1755) */
79
+ LeptrisElement e =
80
+ leptris_element_create(doc2, name);
81
+ if (e) (void)leptris_element_append_child(tgt, e);
82
+ break;
83
+ }
84
+ case 2:
85
+ (void)leptris_element_set_text(tgt, name);
86
+ break;
87
+ case 3:
88
+ (void)leptris_element_set_attribute(tgt, name, "v");
89
+ break;
90
+ case 4:
91
+ (void)leptris_element_remove_attribute(tgt, name);
92
+ break;
93
+ case 5: {
94
+ LeptrisElement kid =
95
+ leptris_element_first_child_any(tgt);
96
+ if (kid)
97
+ (void)leptris_element_remove_child(tgt, kid);
98
+ break;
99
+ }
100
+ case 6:
101
+ (void)leptris_element_remove_children(tgt);
102
+ break;
103
+ case 7: {
104
+ /* insert_before with a fresh sibling of the target's
105
+ * parent (cycle rejection walks the parent chain) */
106
+ LeptrisElement p = leptris_element_parent(tgt);
107
+ if (p) {
108
+ LeptrisElement e =
109
+ leptris_element_create(doc, name);
110
+ if (e) (void)leptris_element_insert_before(
111
+ tgt, e);
112
+ }
113
+ break;
114
+ }
115
+ case 8: {
116
+ char* out = leptris_document_serialize(doc, NULL);
117
+ if (out) leptris_free_string(out);
118
+ break;
119
+ }
120
+ default: {
121
+ LeptrisXPathResult r =
122
+ leptris_xpath_eval(doc, NULL, "//node()");
123
+ if (r) leptris_xpath_result_free(r);
124
+ break;
125
+ }
126
+ }
127
+ }
128
+
129
+ /* Final trees must serialize and re-parse without incident. */
130
+ LeptrisDocument docs[3] = {doc, doc2, fresh};
131
+ for (int i = 0; i < 3; i++) {
132
+ char* out = leptris_document_serialize(docs[i], NULL);
133
+ if (out) {
134
+ LeptrisParseOptions back_opts;
135
+ memset(&back_opts, 0, sizeof(back_opts));
136
+ back_opts.recover = 1;
137
+ LeptrisStatus st2 = LEPTRIS_OK;
138
+ LeptrisDocument back = leptris_parse_string_ex(
139
+ out, strlen(out), &back_opts, &st2);
140
+ if (back) leptris_document_free(back);
141
+ leptris_free_string(out);
142
+ }
143
+ leptris_document_free(docs[i]);
144
+ }
145
+ return 0;
146
+ }
@@ -1,40 +1,49 @@
1
- /* test/fuzz/fuzz_parse.c — libFuzzer harness for leptris_parse_string.
2
- *
3
- * Run:
4
- * cmake -B build -S . -DLEPTRIS_ENABLE_FUZZING=ON
5
- * cmake --build build --target fuzz_parse
6
- * mkdir -p corpus && cp test/fixtures/*.xml corpus/
7
- * ./build/fuzz_parse -max_total_time=600 corpus/
8
- *
9
- * Crashes are written to crash-*; add them as regression tests.
10
- *
11
- * See TODO 40.
12
- */
13
-
1
+ /* test/fuzz/fuzz_parse.c — libFuzzer harness for the parse surface
2
+ * (approach ported from teptris test/fuzz): parse -> serialize ->
3
+ * re-parse, under both the default and the RECOVER parse mode. The
4
+ * invariants under fuzz are the memory-safety contracts (no crash
5
+ * under ASan/UBSan, no leak — every parse path frees or transfers
6
+ * its document), not semantic correctness — the conformance suites
7
+ * own that. The serialize output is engine-owned memory; re-parsing
8
+ * it exercises the serializer's guarantee that its bytes parse
9
+ * back. */
14
10
  #include "leptris.h"
15
11
 
16
12
  #include <stdint.h>
17
13
  #include <stdlib.h>
14
+ #include <string.h>
18
15
 
19
- int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
20
- /* Avoid pathological inputs that immediately exceed the depth limit
21
- * (already covered by tests). Cap input size for speed. */
22
- if (size > 1024 * 1024) return 0;
16
+ static void round_trip(const char* data, size_t size, int recover) {
17
+ LeptrisParseOptions opts;
18
+ memset(&opts, 0, sizeof(opts));
19
+ opts.recover = recover;
20
+ LeptrisStatus st = LEPTRIS_OK;
21
+ LeptrisDocument doc = leptris_parse_string_ex(data, size, &opts, &st);
22
+ if (st != LEPTRIS_OK && doc == NULL) return;
23
23
 
24
- LeptrisStatus st;
25
- LeptrisDocument doc = leptris_parse_string((const char*)data, size, &st);
24
+ /* exercise XPath against whatever tree came back */
25
+ LeptrisXPathResult r = leptris_xpath_eval(doc, NULL, "//node()");
26
+ if (r) leptris_xpath_result_free(r);
26
27
 
27
- if (doc) {
28
- /* Exercise serialize + free to catch use-after-free in cleanup. */
29
- char* out = leptris_document_serialize(doc, NULL);
30
- if (out) leptris_free_string(out);
28
+ char* out = leptris_document_serialize(doc, NULL);
29
+ if (out) {
30
+ LeptrisParseOptions opts2;
31
+ memset(&opts2, 0, sizeof(opts2));
32
+ opts2.recover = recover;
33
+ LeptrisStatus st2 = LEPTRIS_OK;
34
+ LeptrisDocument doc2 = leptris_parse_string_ex(
35
+ out, strlen(out), &opts2, &st2);
36
+ if (doc2) leptris_document_free(doc2);
37
+ leptris_free_string(out);
38
+ }
39
+ leptris_document_free(doc);
40
+ }
31
41
 
32
- /* Also exercise XPath on the parsed document — catches bugs
33
- * in the evaluator's interaction with the DOM. */
34
- LeptrisXPathResult r = leptris_xpath_eval(doc, NULL, "//node()");
35
- if (r) leptris_xpath_result_free(r);
42
+ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
43
+ /* Cap input size for speed; the depth cap is tested elsewhere. */
44
+ if (size == 0 || size > (1u << 20)) return 0;
36
45
 
37
- leptris_document_free(doc);
38
- }
46
+ round_trip((const char*)data, size, 0);
47
+ round_trip((const char*)data, size, 1);
39
48
  return 0;
40
49
  }
@@ -0,0 +1,67 @@
1
+ /* test/fuzz/fuzz_xpath.c — libFuzzer harness for the XPath evaluator
2
+ * (approach ported from teptris's fuzz_plan): the input parses into
3
+ * a document, then a fixed ALPHABET of expressions — paths and
4
+ * predicates that overlap the corpus shapes — evaluates against it
5
+ * and every result accessor drains, including kind-mismatched reads
6
+ * (the contract answers those with NULL/0/false, never a crash).
7
+ * Invariants under ASan/UBSan: evaluator and result lifetimes, node
8
+ * handles into a live document, no leaks. */
9
+ #include "leptris.h"
10
+
11
+ #include <stdint.h>
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+
15
+ static const char* const kExprs[] = {
16
+ "//node()",
17
+ "//*",
18
+ "//@*",
19
+ "/a/b",
20
+ "//item[@id='1']",
21
+ "//p[@*]",
22
+ "count(//*)",
23
+ "string(//a)",
24
+ "number(//@n)",
25
+ "boolean(//x)",
26
+ "//a[2]/following-sibling::*",
27
+ "//@xmlns:*/..",
28
+ "//* | //@* | //text()",
29
+ };
30
+ #define XPATH_ALPHA ((int)(sizeof(kExprs) / sizeof(kExprs[0])))
31
+
32
+ static void drain(LeptrisXPathResult r) {
33
+ if (!r) return;
34
+ size_t n = leptris_xpath_result_count(r);
35
+ for (size_t i = 0; i < n; i++) {
36
+ LeptrisElement e = leptris_xpath_result_get(r, i);
37
+ (void)e; /* a handle into the live document — no deref race */
38
+ (void)leptris_xpath_result_node_kind(r, i);
39
+ }
40
+ LeptrisNodeRef nodes[8];
41
+ LeptrisXPathNodeKind kinds[8];
42
+ (void)leptris_xpath_result_get_nodes_ex(r, nodes, kinds, 8);
43
+ char* whole = leptris_xpath_result_string(r);
44
+ if (whole) leptris_free_string(whole);
45
+ }
46
+
47
+ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
48
+ if (size == 0 || size > (1u << 20)) return 0;
49
+
50
+ LeptrisParseOptions opts;
51
+ memset(&opts, 0, sizeof(opts));
52
+ opts.recover = 1;
53
+ LeptrisStatus st = LEPTRIS_OK;
54
+ LeptrisDocument doc =
55
+ leptris_parse_string_ex((const char*)data, size, &opts, &st);
56
+ if (!doc) return 0;
57
+
58
+ for (int i = 0; i < XPATH_ALPHA; i++) {
59
+ LeptrisXPathResult r =
60
+ leptris_xpath_eval(doc, NULL, kExprs[i]);
61
+ drain(r);
62
+ if (r) leptris_xpath_result_free(r);
63
+ }
64
+
65
+ leptris_document_free(doc);
66
+ return 0;
67
+ }
@@ -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.367",
4
+ "version": "1.9.368",
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",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.367.1
4
+ version: 1.9.368.1
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-17 00:00:00.000000000 Z
11
+ date: 2026-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -756,7 +756,9 @@ files:
756
756
  - vendor-src/libleptris/test/fixtures/malformed.xml
757
757
  - vendor-src/libleptris/test/fixtures/namespaces.xml
758
758
  - vendor-src/libleptris/test/fixtures/utf16_decl.xml
759
+ - vendor-src/libleptris/test/fuzz/fuzz_mutate.c
759
760
  - vendor-src/libleptris/test/fuzz/fuzz_parse.c
761
+ - vendor-src/libleptris/test/fuzz/fuzz_xpath.c
760
762
  - vendor-src/libleptris/test/html/html5lib-redlist.txt
761
763
  - vendor-src/libleptris/test/html/html5lib-tests/LICENSE
762
764
  - vendor-src/libleptris/test/html/html5lib-tests/README.md