leptris 1.9.366.2-ppc64le-linux → 1.9.368.1-ppc64le-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 401321147dcbb50a159d39d90befa2217ff70f1b11e2623cc517c168ca8388c9
4
- data.tar.gz: 24595ec7730ae70e60a0344041e5bd18cbb42202ca0b2bba6e9e223eb55fec0d
3
+ metadata.gz: a9f080aaa7146688d0de80520eb91b6377e22fa1ff6b45033ac349b7b24b274f
4
+ data.tar.gz: ee97afd2b4faaf64a9d57bec5633bc5a026bda9e6fb192d8b3cd34a6f0192672
5
5
  SHA512:
6
- metadata.gz: de9dbb58b20bc2c37bdf38f1b7995bfd2dea826586b46b968f08671f212ebda067a12c82dc886afff9c8a2665aa41d7f0e5ece2b1870fb8fd81b7bfd63e4b743
7
- data.tar.gz: 2bc6c5ca652dbf337103e28518f31c705800c9ea7310b9d7e3f765bf52d938f0407ad0bc0fe27c7c42d40143f82766afd1d0430ecba9b7421cf48319989fdd48
6
+ metadata.gz: 3abda08bb47737ca15bee7ec88e64c5f98887b8fe050e70defd72e961fb609398e56f9f80a23142a47a92f398234d0bf42f170a9e323c5fa9032dff33c9a0a22
7
+ data.tar.gz: 282b05fdc6b475a5075f3348d4d24186c9f0f4c10bd9cb119076661f9f909b88c7484a7ab9d41616d34ff6241a1c061919fccb2e9d8deb8f40f84846cb082e56
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.366"
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
@@ -39,6 +39,7 @@ typedef int (*xp_node_kind_fn)(void *, int);
39
39
  typedef const char *(*xp_node_name_fn)(void *, int);
40
40
  typedef const char *(*xp_node_value_fn)(void *, int);
41
41
  typedef size_t (*serialize_into_fn)(void *, char *, size_t, void *, void *);
42
+ typedef char *(*serialize_ext_sized_fn)(void *, const void *, const void *, size_t);
42
43
  typedef int (*ns_count_fn)(void *);
43
44
  typedef void *(*attr_first_fn)(void *);
44
45
  typedef void *(*attr_next_fn)(void *);
@@ -75,7 +76,8 @@ static xp_nodes_ex_fn f_xp_nodes_ex;
75
76
  static xp_node_kind_fn f_xp_node_kind;
76
77
  static xp_node_name_fn f_xp_node_name;
77
78
  static xp_node_value_fn f_xp_node_value;
78
- static serialize_into_fn f_doc_serialize, f_elem_serialize;
79
+ static serialize_ext_sized_fn f_doc_serialize_xml;
80
+ static serialize_into_fn f_elem_serialize;
79
81
  static ns_count_fn f_elem_ns_count;
80
82
  static attr_first_fn f_attr_first;
81
83
  static attr_next_fn f_attr_next;
@@ -272,7 +274,8 @@ static void resolve_symbols(const char *lib_path)
272
274
  f_xp_node_kind = (xp_node_kind_fn)lib_sym(h, "leptris_xpath_result_node_kind");
273
275
  f_xp_node_name = (xp_node_name_fn)lib_sym(h, "leptris_xpath_result_node_name");
274
276
  f_xp_node_value = (xp_node_value_fn)lib_sym(h, "leptris_xpath_result_node_value");
275
- f_doc_serialize = (serialize_into_fn)lib_sym(h, "leptris_document_serialize_into");
277
+ f_doc_serialize_xml = (serialize_ext_sized_fn)lib_sym(
278
+ h, "leptris_document_serialize_ext_sized");
276
279
  f_elem_serialize = (serialize_into_fn)lib_sym(h, "leptris_element_serialize_into");
277
280
  f_attr_first = (attr_first_fn)lib_sym(h, "leptris_element_first_attribute");
278
281
  f_attr_next = (attr_next_fn)lib_sym(h, "leptris_attribute_next");
@@ -352,7 +355,7 @@ static void resolve_symbols(const char *lib_path)
352
355
  !f_sax_pfree || !f_sax_set_flags ||
353
356
  !f_elem_prefix || !f_xp_count || !f_xp_nodes_ex ||
354
357
  !f_xp_node_kind || !f_xp_node_name || !f_xp_node_value ||
355
- !f_doc_serialize || !f_elem_serialize || !f_attr_first ||
358
+ !f_doc_serialize_xml || !f_elem_serialize || !f_attr_first ||
356
359
  !f_elem_ns_count || !f_elem_ns ||
357
360
  !f_attr_next || !f_attr_name || !f_attr_value ||
358
361
  !f_node_line || !f_node_offset)
@@ -2120,15 +2123,52 @@ static VALUE fast_serialize(serialize_into_fn fn, void *node,
2120
2123
  : rb_utf8_str_new_cstr("");
2121
2124
  }
2122
2125
 
2126
+ /* rb_utf8_str_new_cstr may raise; the engine allocation remains owned
2127
+ * until the ensure callback runs on either return or exception. */
2128
+ struct document_xml_result {
2129
+ char *xml;
2130
+ };
2131
+
2132
+ static VALUE document_xml_string(VALUE arg)
2133
+ {
2134
+ struct document_xml_result *result = (void *)(uintptr_t)arg;
2135
+ return rb_utf8_str_new_cstr(result->xml);
2136
+ }
2137
+
2138
+ static VALUE document_xml_free(VALUE arg)
2139
+ {
2140
+ struct document_xml_result *result = (void *)(uintptr_t)arg;
2141
+ f_free_str(result->xml);
2142
+ return Qnil;
2143
+ }
2144
+
2123
2145
  static VALUE nf_fast_document_xml(VALUE self, VALUE addr,
2124
2146
  VALUE indent, VALUE decl,
2125
2147
  VALUE encoding)
2126
2148
  {
2149
+ /* Prefix of LeptrisSerializeExtOptions through html_method.
2150
+ * Pass its size so later engine fields remain at their defaults. */
2151
+ struct {
2152
+ int indent_text;
2153
+ const char *indent_unit;
2154
+ int expand_empty;
2155
+ int html_method;
2156
+ } ext = {0, NULL, 0, -1};
2157
+ struct serialize_opts opts;
2158
+ struct document_xml_result result;
2159
+
2127
2160
  (void)self;
2128
- return fast_serialize(f_doc_serialize,
2129
- (void *)(uintptr_t)NUM2ULL(addr),
2130
- NUM2INT(indent), RTEST(decl) ? 1 : 0,
2131
- encoding);
2161
+ opts.indent = NUM2INT(indent);
2162
+ opts.xml_declaration = RTEST(decl) ? 1 : 0;
2163
+ opts.encoding = NIL_P(encoding) ? NULL : StringValueCStr(encoding);
2164
+ /* HTML documents default to HTML output in the generic entry.
2165
+ * This XML face forces XML without changing the document's mode. */
2166
+ result.xml = f_doc_serialize_xml((void *)(uintptr_t)NUM2ULL(addr),
2167
+ &opts, &ext, sizeof(ext));
2168
+ if (!result.xml)
2169
+ return rb_utf8_str_new_cstr("");
2170
+ return rb_ensure(document_xml_string, (VALUE)(uintptr_t)&result,
2171
+ document_xml_free, (VALUE)(uintptr_t)&result);
2132
2172
  }
2133
2173
 
2134
2174
  static VALUE nf_fast_element_xml(VALUE self, VALUE addr,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.366.2"
4
+ VERSION = "1.9.368.1"
5
5
  end
Binary file
Binary file
@@ -28,6 +28,15 @@ module Leptris::XML::Serialization
28
28
  # Public within the library: Element#to_xml and Document#to_xml
29
29
  # pass the hoisted bound methods straight through.
30
30
 
31
+ # Document XML output must override an HTML document's default
32
+ # serialization method. Element serialization already defaults to XML.
33
+ XML_METHOD_EXT = begin
34
+ ext = Leptris::XML::FFI::SerializeExtStruct.new
35
+ ext[:html_method] = -1
36
+ ext
37
+ end
38
+ private_constant :XML_METHOD_EXT
39
+
31
40
  # +ffi_function+ is a bound _serialize_into function. The buffer
32
41
  # cycle lives at the FFI seam (FFI.serialize_into_string); this
33
42
  # module owns only options selection and construction.
@@ -52,6 +61,11 @@ module Leptris::XML::Serialization
52
61
  indent: indent, no_decl: no_decl, encoding: encoding)
53
62
  opts
54
63
  end
64
+ if ffi_function == DOCUMENT_SERIALIZE_INTO
65
+ return Leptris::XML::FFI.read_owned_string(
66
+ Leptris::XML::FFI.leptris_document_serialize_ext_sized(
67
+ c_ptr, opts.pointer, XML_METHOD_EXT.pointer, XML_METHOD_EXT.size))
68
+ end
55
69
  Leptris::XML::FFI.serialize_into_string(ffi_function, c_ptr, opts.pointer)
56
70
  end
57
71
 
@@ -140,6 +154,7 @@ module Leptris::XML::Serialization
140
154
  INDENT_TEXT_EXT = begin
141
155
  ext = Leptris::XML::FFI::SerializeExtStruct.new
142
156
  ext[:indent_text] = 1
157
+ ext[:html_method] = -1
143
158
  ext
144
159
  end
145
160
  private_constant :INDENT_TEXT_EXT
@@ -167,6 +182,7 @@ module Leptris::XML::Serialization
167
182
  unit_anchor = ::FFI::MemoryPointer.from_string(
168
183
  indent.positive? ? unit.to_s * indent : unit.to_s)
169
184
  ext[:indent_unit] = unit_anchor
185
+ ext[:html_method] = -1
170
186
  str_ptr = Leptris::XML::FFI.leptris_document_serialize_ext_sized(
171
187
  c_ptr, opts.pointer, ext.pointer, ext.size)
172
188
  Leptris::XML::FFI.read_owned_string(str_ptr)
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.366
4
+ - libleptris v1.9.368
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,46 @@
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
+
26
+ ## [1.9.367] - 2026-10-17
27
+
28
+ ### Fixed
29
+
30
+ - **dom: zero-initialize the namespace cache at every allocation site (#1742, PR #1742).**
31
+ The three cache-creating sites partially initialized the new
32
+ `leptris_ns_cache` — prefix/URI/declarations (and `raw_attrs` at only one of
33
+ the three) — leaving `doc_next`, the heap-ownership flags, and the
34
+ child-iteration cache (generations, index, node pointer) on **arena
35
+ garbage**. Any read before its explicit set was an uninitialized read: the
36
+ document-free walk chain and the child-cache generation compare could act on
37
+ poisoned bytes. All three sites now zero the whole struct. New
38
+ poisoned-arena regressions fill the remaining arena with `0xa5` and assert
39
+ every field comes up clean through the prefix setter, the URI setter, and
40
+ `cache_ensure` (red on main's header, green on the fix). Engine suite
41
+ 2040/2040 on the PR, 2048/2048 on merged main.
42
+
43
+
3
44
  ## [1.9.366] - 2026-10-17
4
45
 
5
46
  ### Fixed
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.366
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
@@ -576,9 +582,7 @@ static inline void leptris_elem_set_prefix(LeptrisElement e, char* prefix,
576
582
  nc = (struct leptris_ns_cache*)
577
583
  leptris_pool_alloc(pool, sizeof(struct leptris_ns_cache));
578
584
  if (!nc) return;
579
- nc->prefix = NULL;
580
- nc->namespace_uri = NULL;
581
- nc->declarations = NULL;
585
+ memset(nc, 0, sizeof(*nc));
582
586
  elem_set_ns_cache(e, nc);
583
587
  }
584
588
  nc->prefix = prefix;
@@ -594,9 +598,7 @@ static inline void leptris_elem_set_ns_uri(LeptrisElement e, char* uri,
594
598
  nc = (struct leptris_ns_cache*)
595
599
  leptris_pool_alloc(pool, sizeof(struct leptris_ns_cache));
596
600
  if (!nc) return;
597
- nc->prefix = NULL;
598
- nc->namespace_uri = NULL;
599
- nc->declarations = NULL;
601
+ memset(nc, 0, sizeof(*nc));
600
602
  elem_set_ns_cache(e, nc);
601
603
  }
602
604
  nc->namespace_uri = uri;
@@ -694,10 +696,7 @@ static inline struct leptris_ns_cache* leptris_elem_cache_ensure(
694
696
  nc = (struct leptris_ns_cache*)
695
697
  leptris_pool_alloc(pool, sizeof(struct leptris_ns_cache));
696
698
  if (!nc) return NULL;
697
- nc->prefix = NULL;
698
- nc->namespace_uri = NULL;
699
- nc->declarations = NULL;
700
- nc->raw_attrs = NULL;
699
+ memset(nc, 0, sizeof(*nc));
701
700
  elem_set_ns_cache(e, nc);
702
701
  return nc;
703
702
  }
@@ -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-
@@ -105,6 +105,7 @@ leptris_add_test(test_large_docs parser/test_large_documents.cpp)
105
105
  leptris_add_test(test_encoding parser/test_encoding.cpp)
106
106
  leptris_add_test(test_dom dom/test_dom.cpp)
107
107
  leptris_add_test(test_element_children dom/test_element_children.cpp)
108
+ leptris_add_test(test_namespace_cache dom/test_namespace_cache.cpp)
108
109
  leptris_add_test(test_map_lifecycle dom/test_map_lifecycle.cpp)
109
110
  leptris_add_test(test_simd_count parser/test_simd_count.cpp)
110
111
  leptris_add_test(test_ws_shape parser/test_whitespace_shape.cpp)
@@ -259,16 +260,18 @@ if(Python3_FOUND)
259
260
  ENVIRONMENT "PYTHONPATH=")
260
261
  endif()
261
262
 
262
- # libFuzzer harness (TODO 40) — built only when LEPTRIS_ENABLE_FUZZING=ON.
263
- # 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).
264
267
  if(LEPTRIS_ENABLE_FUZZING)
265
- add_executable(fuzz_parse ${CMAKE_CURRENT_SOURCE_DIR}/fuzz/fuzz_parse.c)
266
- target_link_libraries(fuzz_parse PRIVATE leptris)
267
- target_include_directories(fuzz_parse PRIVATE
268
- ${PROJECT_SOURCE_DIR}/src/include)
269
- # The harness doc and the nightly workflow invoke the binary at
270
- # the build root, not under build/test/ where subdirectory
271
- # targets land by default.
272
- set_target_properties(fuzz_parse PROPERTIES
273
- 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()
274
277
  endif()
@@ -407,6 +407,132 @@ TEST(PlanWalk, NestedScalarsAttributesCollections) {
407
407
  leptris_plan_free(plan);
408
408
  }
409
409
 
410
+ /* leptris#1743: diamond-shared plan graphs — two sibling rows
411
+ * referencing the SAME plan index (lutaml-model's PlanCompiler
412
+ * emits recursive content models as a DAG; the binding flattens it
413
+ * identity-memoized). Each parent must walk the shared plan
414
+ * independently: b's <leaf> must materialize even though plan 1
415
+ * was already walked under a. */
416
+ TEST(PlanWalk, SharedChildPlanIndexWalksPerParent) {
417
+ LeptrisDocument doc = parse(
418
+ "<n><a><leaf>x</leaf></a><b><leaf>y</leaf></b></n>");
419
+ ASSERT_NE(doc, nullptr);
420
+ LeptrisElement root = leptris_document_root(doc);
421
+
422
+ leptris_child_plan n_kids[2] = {
423
+ {"a", LEPTRIS_PLAN_KIND_NESTED, 1, 1},
424
+ {"b", LEPTRIS_PLAN_KIND_NESTED, 1, 1},
425
+ };
426
+ leptris_child_plan shared_kids[1] = {
427
+ {"leaf", LEPTRIS_PLAN_KIND_NESTED, 2, 2}};
428
+ leptris_element_plan plans[3] = {};
429
+ plans[0].element_name = "n";
430
+ plans[0].child_count = 2;
431
+ plans[0].child_plans = n_kids;
432
+ plans[1].element_name = "a";
433
+ plans[1].child_count = 1;
434
+ plans[1].child_plans = shared_kids;
435
+ plans[2].element_name = "leaf";
436
+
437
+ leptris_plan_spec spec = {};
438
+ spec.abi_version = leptris_plan_abi_version();
439
+ spec.plan_count = 3;
440
+ spec.plans = plans;
441
+
442
+ LeptrisStatus st = LEPTRIS_OK;
443
+ LeptrisPlan plan = leptris_plan_build(&spec, &st);
444
+ ASSERT_NE(plan, nullptr);
445
+ EXPECT_EQ(st, LEPTRIS_OK);
446
+
447
+ LeptrisPlanResult r = leptris_plan_walk(doc, root, plan, &st);
448
+ ASSERT_NE(r, nullptr);
449
+ ASSERT_EQ(leptris_plan_value_count(r), 2u);
450
+
451
+ LeptrisPlanResult a = leptris_plan_value_at(r, 0);
452
+ ASSERT_NE(a, nullptr);
453
+ EXPECT_STREQ(leptris_plan_value_name(a), "a");
454
+ ASSERT_EQ(leptris_plan_value_count(a), 1u);
455
+ LeptrisPlanResult al = leptris_plan_value_at(a, 0);
456
+ EXPECT_STREQ(leptris_plan_value_name(al), "leaf");
457
+ EXPECT_EQ(leptris_plan_value_kind(al), LEPTRIS_PLAN_VALUE_ELEMENT);
458
+
459
+ LeptrisPlanResult b = leptris_plan_value_at(r, 1);
460
+ ASSERT_NE(b, nullptr);
461
+ EXPECT_STREQ(leptris_plan_value_name(b), "b");
462
+ ASSERT_EQ(leptris_plan_value_count(b), 1u);
463
+ LeptrisPlanResult bl = leptris_plan_value_at(b, 0);
464
+ EXPECT_STREQ(leptris_plan_value_name(bl), "leaf");
465
+ EXPECT_EQ(leptris_plan_value_kind(bl), LEPTRIS_PLAN_VALUE_ELEMENT);
466
+
467
+ leptris_plan_result_free(r);
468
+ leptris_plan_free(plan);
469
+ leptris_document_free(doc);
470
+ }
471
+
472
+ /* leptris#1743: RECURSIVE plan graphs — lutaml-model's
473
+ * PlanCompiler emits recursive content models as a plan CYCLE
474
+ * (entry -> sub -> entry); a NESTED row's target plan describes
475
+ * the element the row matches, so the cycle rides an
476
+ * intermediate plan. The binding flattens these identity-memoized
477
+ * (the flatten is where the STS runaway lived). The walk follows
478
+ * the DOCUMENT tree, so a finite document must materialize
479
+ * through every re-entry of the shared plans. */
480
+ TEST(PlanWalk, RecursivePlanCycleWalksFiniteDocument) {
481
+ LeptrisDocument doc = parse(
482
+ "<entry><t>1</t><sub><entry><t>2</t></entry></sub></entry>");
483
+ ASSERT_NE(doc, nullptr);
484
+ LeptrisElement root = leptris_document_root(doc);
485
+
486
+ leptris_child_plan entry_kids[2] = {
487
+ {"t", LEPTRIS_PLAN_KIND_SCALAR, 1, -1},
488
+ {"sub", LEPTRIS_PLAN_KIND_NESTED, 2, 1},
489
+ };
490
+ leptris_child_plan sub_kids[1] = {
491
+ {"entry", LEPTRIS_PLAN_KIND_NESTED, 3, 0}};
492
+ leptris_element_plan plans[2] = {};
493
+ plans[0].element_name = "entry";
494
+ plans[0].child_count = 2;
495
+ plans[0].child_plans = entry_kids;
496
+ plans[1].element_name = "sub";
497
+ plans[1].child_count = 1;
498
+ plans[1].child_plans = sub_kids;
499
+
500
+ leptris_plan_spec spec = {};
501
+ spec.abi_version = leptris_plan_abi_version();
502
+ spec.plan_count = 2;
503
+ spec.plans = plans;
504
+
505
+ LeptrisStatus st = LEPTRIS_OK;
506
+ LeptrisPlan plan = leptris_plan_build(&spec, &st);
507
+ ASSERT_NE(plan, nullptr);
508
+ EXPECT_EQ(st, LEPTRIS_OK);
509
+
510
+ LeptrisPlanResult r = leptris_plan_walk(doc, root, plan, &st);
511
+ ASSERT_NE(r, nullptr);
512
+ ASSERT_EQ(leptris_plan_value_count(r), 2u);
513
+
514
+ LeptrisPlanResult t1 = leptris_plan_value_at(r, 0);
515
+ ASSERT_NE(t1, nullptr);
516
+ EXPECT_STREQ(leptris_plan_value_string(t1), "1");
517
+
518
+ LeptrisPlanResult sub = leptris_plan_value_at(r, 1);
519
+ ASSERT_NE(sub, nullptr);
520
+ EXPECT_STREQ(leptris_plan_value_name(sub), "sub");
521
+ /* the cycle re-enters plan 0 for the entry inside sub */
522
+ ASSERT_EQ(leptris_plan_value_count(sub), 1u);
523
+ LeptrisPlanResult inner = leptris_plan_value_at(sub, 0);
524
+ ASSERT_NE(inner, nullptr);
525
+ EXPECT_STREQ(leptris_plan_value_name(inner), "entry");
526
+ ASSERT_EQ(leptris_plan_value_count(inner), 1u);
527
+ LeptrisPlanResult t2 = leptris_plan_value_at(inner, 0);
528
+ ASSERT_NE(t2, nullptr);
529
+ EXPECT_STREQ(leptris_plan_value_string(t2), "2");
530
+
531
+ leptris_plan_result_free(r);
532
+ leptris_plan_free(plan);
533
+ leptris_document_free(doc);
534
+ }
535
+
410
536
  TEST(PlanWalk, RawCallbackAndMixedContent) {
411
537
  LeptrisDocument doc = parse(
412
538
  "<p>intro <b>bold</b> mid"
@@ -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,89 @@
1
+ #include <gtest/gtest.h>
2
+ #include <cstring>
3
+
4
+ #include "leptris.h"
5
+ extern "C" {
6
+ #include "element.h"
7
+ #include "pool.h"
8
+ #include "arena.h"
9
+ }
10
+
11
+ namespace {
12
+
13
+ class NamespaceCacheInitialization : public ::testing::Test {
14
+ protected:
15
+ LeptrisMemoryPool* pool = nullptr;
16
+ LeptrisElement element = nullptr;
17
+
18
+ void SetUp() override {
19
+ LeptrisArena* arena = leptris_arena_create(4096);
20
+ ASSERT_NE(arena, nullptr);
21
+ pool = leptris_pool_create_arena_backed(arena, 1);
22
+ if (!pool) leptris_arena_destroy(arena);
23
+ ASSERT_NE(pool, nullptr);
24
+ // Pool allocations are deliberately not zeroed. Poison the remaining
25
+ // arena so a new cache cannot pass by inheriting fresh OS zero pages.
26
+ std::memset(arena->base + arena->used, 0xa5, arena->size - arena->used);
27
+ element = static_cast<LeptrisElement>(
28
+ leptris_pool_alloc(pool, sizeof(*element)));
29
+ ASSERT_NE(element, nullptr);
30
+ std::memset(element, 0, sizeof(*element));
31
+ }
32
+
33
+ void TearDown() override {
34
+ leptris_pool_destroy(pool);
35
+ }
36
+
37
+ void ExpectEmptyAuxiliaryState(const leptris_ns_cache* cache) {
38
+ ASSERT_NE(cache, nullptr);
39
+ EXPECT_EQ(cache->declarations, nullptr);
40
+ EXPECT_EQ(cache->raw_attrs, nullptr);
41
+ EXPECT_EQ(cache->doc_next, nullptr);
42
+ EXPECT_EQ(cache->prefix_heap, 0);
43
+ EXPECT_EQ(cache->uri_heap, 0);
44
+ EXPECT_EQ(cache->child_gen, 0u);
45
+ EXPECT_EQ(cache->child_cached_gen, 0u);
46
+ EXPECT_EQ(cache->child_idx, 0u);
47
+ EXPECT_EQ(cache->child_node, nullptr);
48
+ // The public raw-attribute read must see no phantom journal entries.
49
+ // Avoid following the poisoned pointer if the assertion above failed.
50
+ if (!cache->raw_attrs) {
51
+ EXPECT_EQ(leptris_element_attributes_raw(element, nullptr, nullptr, 0), 0u);
52
+ }
53
+ }
54
+ };
55
+
56
+ TEST_F(NamespaceCacheInitialization, PrefixSetterStartsWithEmptyAuxiliaryState) {
57
+ char prefix[] = "p";
58
+ leptris_elem_set_prefix(element, prefix, pool);
59
+ const auto* cache = elem_get_ns_cache(element);
60
+ ASSERT_NE(cache, nullptr);
61
+ EXPECT_EQ(cache->prefix, prefix);
62
+ EXPECT_EQ(cache->namespace_uri, nullptr);
63
+ ExpectEmptyAuxiliaryState(cache);
64
+ }
65
+
66
+ TEST_F(NamespaceCacheInitialization, NamespaceSetterStartsWithEmptyAuxiliaryState) {
67
+ char uri[] = "urn:example";
68
+ leptris_elem_set_ns_uri(element, uri, pool);
69
+ const auto* cache = elem_get_ns_cache(element);
70
+ ASSERT_NE(cache, nullptr);
71
+ EXPECT_EQ(cache->prefix, nullptr);
72
+ EXPECT_EQ(cache->namespace_uri, uri);
73
+ ExpectEmptyAuxiliaryState(cache);
74
+ }
75
+
76
+ TEST_F(NamespaceCacheInitialization, EnsureStartsEmptyAndPreservesExistingCache) {
77
+ auto* cache = leptris_elem_cache_ensure(element, pool);
78
+ ASSERT_NE(cache, nullptr);
79
+ EXPECT_EQ(cache->prefix, nullptr);
80
+ EXPECT_EQ(cache->namespace_uri, nullptr);
81
+ ExpectEmptyAuxiliaryState(cache);
82
+
83
+ // Reusing a cache must preserve the state its consumers already stored.
84
+ cache->child_gen = 7;
85
+ EXPECT_EQ(leptris_elem_cache_ensure(element, pool), cache);
86
+ EXPECT_EQ(cache->child_gen, 7u);
87
+ }
88
+
89
+ } // namespace
@@ -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.366",
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.366.2
4
+ version: 1.9.368.1
5
5
  platform: ppc64le-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
@@ -745,6 +745,7 @@ files:
745
745
  - vendor-src/libleptris/test/dom/test_dom.cpp
746
746
  - vendor-src/libleptris/test/dom/test_element_children.cpp
747
747
  - vendor-src/libleptris/test/dom/test_map_lifecycle.cpp
748
+ - vendor-src/libleptris/test/dom/test_namespace_cache.cpp
748
749
  - vendor-src/libleptris/test/dom/test_node_surface_parity.cpp
749
750
  - vendor-src/libleptris/test/dom/test_text_borrowed.cpp
750
751
  - vendor-src/libleptris/test/dom/test_vtable.cpp
@@ -755,7 +756,9 @@ files:
755
756
  - vendor-src/libleptris/test/fixtures/malformed.xml
756
757
  - vendor-src/libleptris/test/fixtures/namespaces.xml
757
758
  - vendor-src/libleptris/test/fixtures/utf16_decl.xml
759
+ - vendor-src/libleptris/test/fuzz/fuzz_mutate.c
758
760
  - vendor-src/libleptris/test/fuzz/fuzz_parse.c
761
+ - vendor-src/libleptris/test/fuzz/fuzz_xpath.c
759
762
  - vendor-src/libleptris/test/html/html5lib-redlist.txt
760
763
  - vendor-src/libleptris/test/html/html5lib-tests/LICENSE
761
764
  - vendor-src/libleptris/test/html/html5lib-tests/README.md