leptris 1.9.367.1 → 1.9.368.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +23 -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 +6 -0
- 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 +13 -11
- data/vendor-src/libleptris/test/dom/test_dom.cpp +36 -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 +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30686ca983cb245b5c714d377fcfc15564120e9a9d07b214404d421ecc061ad6
|
|
4
|
+
data.tar.gz: 9533b2b213a8223e0e44ac82869859e9db9d9459e61531679cf8e9f27f1e6eaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2aac2d092f0d80717257a49e7cdbee6607ce52759c9d711d48704320ca52a0b6b6ea3e536cc115d86631669d81072e9da6e50ee568fc9c41686e5e1fbb32be53
|
|
7
|
+
data.tar.gz: d260eb007e6a21cc5fc7f295b3707c49012576397688fa929e630eb9a8fe7e14664ae05da4971ed951f718077699faed8b9bef841e5c61e7d45752a338921aac
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/vendor-src/README.md
CHANGED
|
@@ -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
|
|
@@ -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
|
|
@@ -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-
|
|
@@ -260,16 +260,18 @@ if(Python3_FOUND)
|
|
|
260
260
|
ENVIRONMENT "PYTHONPATH=")
|
|
261
261
|
endif()
|
|
262
262
|
|
|
263
|
-
# libFuzzer
|
|
264
|
-
# 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).
|
|
265
267
|
if(LEPTRIS_ENABLE_FUZZING)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
${
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
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: ruby
|
|
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
|
|
@@ -773,7 +773,9 @@ files:
|
|
|
773
773
|
- vendor-src/libleptris/test/fixtures/malformed.xml
|
|
774
774
|
- vendor-src/libleptris/test/fixtures/namespaces.xml
|
|
775
775
|
- vendor-src/libleptris/test/fixtures/utf16_decl.xml
|
|
776
|
+
- vendor-src/libleptris/test/fuzz/fuzz_mutate.c
|
|
776
777
|
- vendor-src/libleptris/test/fuzz/fuzz_parse.c
|
|
778
|
+
- vendor-src/libleptris/test/fuzz/fuzz_xpath.c
|
|
777
779
|
- vendor-src/libleptris/test/html/html5lib-redlist.txt
|
|
778
780
|
- vendor-src/libleptris/test/html/html5lib-tests/LICENSE
|
|
779
781
|
- vendor-src/libleptris/test/html/html5lib-tests/README.md
|