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 +4 -4
- data/Rakefile +1 -1
- data/ext/leptris/native/native.c +47 -7
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/leptris/xml/serialization.rb +16 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +41 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/TODO.xslt-full/README.md +7 -2
- data/vendor-src/libleptris/src/leptris/dom/element.h +9 -10
- data/vendor-src/libleptris/src/leptris/dom/element_modify.c +2 -2
- data/vendor-src/libleptris/src/leptris/dom/root_doc_map.c +8 -0
- data/vendor-src/libleptris/test/CMakeLists.txt +14 -11
- data/vendor-src/libleptris/test/abi/test_descriptor.cpp +126 -0
- data/vendor-src/libleptris/test/dom/test_dom.cpp +36 -0
- data/vendor-src/libleptris/test/dom/test_namespace_cache.cpp +89 -0
- data/vendor-src/libleptris/test/fuzz/fuzz_mutate.c +146 -0
- data/vendor-src/libleptris/test/fuzz/fuzz_parse.c +38 -29
- data/vendor-src/libleptris/test/fuzz/fuzz_xpath.c +67 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9f080aaa7146688d0de80520eb91b6377e22fa1ff6b45033ac349b7b24b274f
|
|
4
|
+
data.tar.gz: ee97afd2b4faaf64a9d57bec5633bc5a026bda9e6fb192d8b3cd34a6f0192672
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
!
|
|
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
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
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,
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/native.so
CHANGED
|
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,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
|
|
@@ -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
|
-
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
263
|
-
# Not
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
${
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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-
|
|
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
|