leptris 1.9.324.0-aarch64-linux → 1.9.331.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +5 -0
- data/lib/leptris/xml/xsd.rb +18 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +145 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/cli/commands/validate.c +5 -7
- data/vendor-src/libleptris/src/include/leptris.h +8 -0
- data/vendor-src/libleptris/src/leptris/dom/node_public.c +1 -1
- data/vendor-src/libleptris/src/leptris/dom/text.c +4 -2
- data/vendor-src/libleptris/src/leptris/dom/text.h +7 -2
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +32 -4
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +2 -1
- data/vendor-src/libleptris/src/leptris/leptris.c +7 -2
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +793 -78
- data/vendor-src/libleptris/src/leptris/xsd/content_model.c +20 -2
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +57 -19
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +62 -3
- data/vendor-src/libleptris/src/leptris/xslt/xslt_exec.c +1 -1
- data/vendor-src/libleptris/test/CMakeLists.txt +5 -0
- data/vendor-src/libleptris/test/parser/test_parser.cpp +116 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +399 -4
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d5bec9cdcf6ab2aaf75b3d2ed0e505cabf44230734514074bfc6d9e4277c7e7
|
|
4
|
+
data.tar.gz: 5345c0c049bd9e5aeab14492802f5a9106c5ce429ba2b92139a6260e26602351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5b7f11e204fb2f807b9d2b162ed91b2783466575aa9fbc622efc1949fb397f0ae8040a5a648e84e55a92dbd52b8efe97786ec4d48a627127da70c3fb0d5c342
|
|
7
|
+
data.tar.gz: bd2aac14b94807b2d72c65340ea920f735db1bfc39793aa63511f8b3fcaf0718eb715cc74c67090ca9dd9900b2a22a73c47b883bc3c11ff063d87647fdef5b77
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
## [1.9.331.0] - 2026-10-10
|
|
2
|
+
|
|
3
|
+
### Added — engine sync (libleptris 1.9.325-1.9.331)
|
|
4
|
+
|
|
5
|
+
- **XSD.compile_file (1.9.326)**: the file-path compile face —
|
|
6
|
+
unreadable files raise; malformed schemas compile to
|
|
7
|
+
error-carrying handles, exactly like the string face.
|
|
8
|
+
Audit 382/381.
|
|
9
|
+
- **XSD charter completion (1.9.326)**: xs:union / xs:list
|
|
10
|
+
derivation (typed items, length semantics), groups,
|
|
11
|
+
attributeGroups, complexContent/simpleContent derivation,
|
|
12
|
+
substitution groups, fixed values, includes — pinned for
|
|
13
|
+
unions (any-member acceptance, lexical + instance) and lists
|
|
14
|
+
(per-item typing). KNOWN GAP filed upstream (leptris#1615):
|
|
15
|
+
memberTypes + an inline anonymous member together over-accept;
|
|
16
|
+
each spelling alone is correct and pinned.
|
|
17
|
+
|
|
18
|
+
### Fixed — engine sync
|
|
19
|
+
|
|
20
|
+
- 1.9.325: local particle declarations govern nested children —
|
|
21
|
+
particle-only children's attributes and content models validate
|
|
22
|
+
(the `<item qty="nan"/>` shape), pinned.
|
|
23
|
+
- 1.9.327-1.9.331: five nightly-fuzz finds (NUL-leading text
|
|
24
|
+
runs, two dp-fail-path leaks, the fast-path declaration probe
|
|
25
|
+
read, duplicate DOCTYPE model replacement) — the observable one
|
|
26
|
+
pinned: a duplicate DOCTYPE keeps the FIRST internal subset
|
|
27
|
+
(keep-first, matching the duplicate-attribute rule).
|
|
28
|
+
|
|
29
|
+
## [1.9.324.0] - 2026-10-09
|
|
30
|
+
|
|
31
|
+
### Added — engine sync (libleptris 1.9.324)
|
|
32
|
+
|
|
33
|
+
- **XSD identity constraints — tier-1 scope complete (#1075)**:
|
|
34
|
+
`xs:key` / `xs:unique` / `xs:keyref` capture on top-level
|
|
35
|
+
declarations (incl. inline complexTypes) and two-pass instance
|
|
36
|
+
validation: key/unique tuple collection with uniqueness +
|
|
37
|
+
non-empty-field enforcement, then keyref resolution — attribute
|
|
38
|
+
fields included, selectors through the engine's own XPath.
|
|
39
|
+
Completes compile → datatypes/facets → content models →
|
|
40
|
+
instance validation → identity constraints. Pinned: resolved
|
|
41
|
+
refs, duplicate tuples, dangling refs, empty fields.
|
|
42
|
+
|
|
1
43
|
## [1.9.323.0] - 2026-10-08
|
|
2
44
|
|
|
3
45
|
### Fixed — engine sync (libleptris 1.9.323)
|
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.331"
|
|
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/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -1289,6 +1289,11 @@ attach_function :leptris_parse_string,
|
|
|
1289
1289
|
# schema-level errors. Validation is a later tier.
|
|
1290
1290
|
attach_function :leptris_xsd_compile,
|
|
1291
1291
|
[:string, :size_t, :pointer], :leptris_xsd_schema
|
|
1292
|
+
# libleptris 1.9.326: compile from a file path — NULL only
|
|
1293
|
+
# when unreadable; malformed schemas yield error-carrying
|
|
1294
|
+
# handles, exactly like the string face.
|
|
1295
|
+
attach_function :leptris_xsd_compile_file,
|
|
1296
|
+
[:string, :pointer], :leptris_xsd_schema
|
|
1292
1297
|
attach_function :leptris_xsd_free,
|
|
1293
1298
|
[:leptris_xsd_schema], :void
|
|
1294
1299
|
attach_function :leptris_xsd_declaration_count,
|
data/lib/leptris/xml/xsd.rb
CHANGED
|
@@ -9,6 +9,13 @@ module Leptris::XML::XSD
|
|
|
9
9
|
Schema.compile(schema_xml)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Compile from a file path (libleptris >= 1.9.326): unreadable
|
|
13
|
+
# files raise; malformed schemas compile to an error-carrying
|
|
14
|
+
# handle, exactly like .compile.
|
|
15
|
+
def compile_file(path)
|
|
16
|
+
Schema.compile_file(path)
|
|
17
|
+
end
|
|
18
|
+
|
|
12
19
|
# Slice 2 (libleptris 1.9.319, #1075): built-in lexical table.
|
|
13
20
|
# +builtin+ takes the "xs:NAME" reference spelling. Returns
|
|
14
21
|
# true/false; an unknown table name raises — a silent false
|
|
@@ -61,6 +68,17 @@ module Leptris::XML::XSD
|
|
|
61
68
|
@handle = handle
|
|
62
69
|
end
|
|
63
70
|
|
|
71
|
+
# Compile from a file path: the engine reads it directly.
|
|
72
|
+
def self.compile_file(path)
|
|
73
|
+
status = ::FFI::MemoryPointer.new(:int)
|
|
74
|
+
raw = Leptris::XML::FFI.leptris_xsd_compile_file(path.to_s, status)
|
|
75
|
+
if raw.null?
|
|
76
|
+
raise Leptris::XML::Error,
|
|
77
|
+
"schema file unreadable (status #{status.read_int}): #{path}"
|
|
78
|
+
end
|
|
79
|
+
new(Handle.new(raw))
|
|
80
|
+
end
|
|
81
|
+
|
|
64
82
|
# Number of top-level schema declarations the compiler
|
|
65
83
|
# recognized (elements, types, groups, ...).
|
|
66
84
|
def declaration_count
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,150 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.331] - 2026-10-09
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **a second DOCTYPE keeps the first model (PR #1613).** A
|
|
8
|
+
duplicate `<!DOCTYPE` re-entered the direct-parse DOCTYPE handler
|
|
9
|
+
and replaced the already-parsed model — the orphan's heap-grown
|
|
10
|
+
attribute-default array leaked (fifth nightly-fuzz find,
|
|
11
|
+
artifact `leak-168da36a`). The lane stays lenient about the
|
|
12
|
+
duplicate, but the FIRST model wins now, matching the
|
|
13
|
+
duplicate-attribute keep-first rule — so the first internal
|
|
14
|
+
subset's entities and defaults apply (previously the second
|
|
15
|
+
silently won). The replacement model is freed on arrival.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [1.9.330] - 2026-10-09
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **dp fail path frees the parsed internal-subset DTD (PR #1611).**
|
|
24
|
+
A DOCTYPE internal subset with a default attribute declaration
|
|
25
|
+
grows the DTD model's heap array; the parse-success epilogue
|
|
26
|
+
transfers the model to the document, but a parse failure after
|
|
27
|
+
the DOCTYPE never did — and the fail teardown freed the diags
|
|
28
|
+
and line-break table but not the model (fourth nightly-fuzz
|
|
29
|
+
find, artifact `leak-e3246602`). The fail block frees the model
|
|
30
|
+
now, beside the other heap-owned pieces.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## [1.9.329] - 2026-10-09
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **fast-path declaration probe bounds against the span
|
|
39
|
+
(PR #1609).** `leptris_parse_string`'s fast-path `<?xml` probe
|
|
40
|
+
checked the ABSOLUTE length while indexing from the first
|
|
41
|
+
non-whitespace offset, so an input whose markup starts after
|
|
42
|
+
leading whitespace read one byte past the caller's buffer
|
|
43
|
+
(heap-buffer-overflow; third nightly-fuzz find, artifact
|
|
44
|
+
`crash-aac3c9bb`). The probe bounds against the remaining span
|
|
45
|
+
now. The regression spec reproduces the fuzzer's exact shape —
|
|
46
|
+
a terminator-free 9-byte heap buffer: a string literal's
|
|
47
|
+
implicit NUL lands the read in bounds and 32-byte stack slot
|
|
48
|
+
padding hides the overread from ASAN.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## [1.9.328] - 2026-10-09
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **dp fail path frees the parse-diag array (PR #1607).** A RECOVER
|
|
57
|
+
diagnostic (duplicate attribute) emitted before an eventual parse
|
|
58
|
+
failure leaked its heap-grown `parse_diags` array: the dp fail
|
|
59
|
+
teardown freed the line-break table and destroyed the pool but
|
|
60
|
+
never the diag array or its offender names. Second nightly-fuzz
|
|
61
|
+
find (artifact `leak-75e0ee81`, 2304 bytes). The fail path runs
|
|
62
|
+
`leptris_diag_free` beside the line-break free now.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## [1.9.327] - 2026-10-09
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **NUL-leading text runs keep a real content pointer — ASAN
|
|
71
|
+
global-buffer-overflow (PR #1605).** The text-content setter
|
|
72
|
+
collapsed any run whose first byte is NUL onto the shared EMPTY
|
|
73
|
+
sentinel regardless of its length, so a NUL-leading run stored
|
|
74
|
+
content `""` with its real `content_len` — the serializer then
|
|
75
|
+
read past the 1-byte global. Found by the nightly libFuzzer run
|
|
76
|
+
(artifact `crash-0a24082b`, the harness's first working session
|
|
77
|
+
after #1604 repaired it). The setter is length-aware now: EMPTY
|
|
78
|
+
only for genuinely empty runs; NUL-leading runs serialize
|
|
79
|
+
verbatim, consistent with the never-trimmed content contract.
|
|
80
|
+
- **Nightly fuzz binary lands at the documented path (PR #1604).**
|
|
81
|
+
`fuzz_parse` is a `test/` subdirectory target, so it built under
|
|
82
|
+
`build/test/` while `fuzz-nightly.yml` invoked `./build/fuzz_parse`
|
|
83
|
+
— every scheduled run since 2026-09-30 failed before fuzzing
|
|
84
|
+
started. The target now sets its runtime output directory to the
|
|
85
|
+
build root, where the workflow and the harness's own docs expect
|
|
86
|
+
it.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## [1.9.326] - 2026-10-09
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- **XSD: tier-1 charter completion — unions, lists, groups,
|
|
95
|
+
attributeGroups, derivation, substitution groups, fixed values,
|
|
96
|
+
includes (#1075, PR #1602).** Every remaining charter feature
|
|
97
|
+
ships: `xs:union`/`xs:list` derivation (lists type every token,
|
|
98
|
+
length counts items; unions accept any named or inline member);
|
|
99
|
+
`xs:group` definitions with refs that expand lazily at NFA build
|
|
100
|
+
(forward references need no capture order, cyclic refs splice
|
|
101
|
+
nothing); `xs:attributeGroup` refs plus top-level attribute refs
|
|
102
|
+
and inline anonymous attribute simpleTypes;
|
|
103
|
+
complexContent/simpleContent extension and restriction through a
|
|
104
|
+
shared complexType capture (extension splices base content and
|
|
105
|
+
attribute rows, restriction replaces them, simpleContent types
|
|
106
|
+
element text); `xs:substitutionGroup` members bind in content
|
|
107
|
+
models; `@fixed` enforced on attributes and element text; and
|
|
108
|
+
`leptris_xsd_compile_file` resolves `xs:include`/`xs:import`/
|
|
109
|
+
`xs:redefine` schemaLocations against the schema's own directory
|
|
110
|
+
— the CLI `--xsd` path and the corpus gate now use it, so the
|
|
111
|
+
gate's 23 multi-file fixtures resolve their includes. Multi-
|
|
112
|
+
particle derivation splices normalize under a sequence (the NFA
|
|
113
|
+
consumes one root particle). 14 new red-first specs; ctest
|
|
114
|
+
1937/1937; corpus gate green with its floor held.
|
|
115
|
+
|
|
116
|
+
### Fixed
|
|
117
|
+
|
|
118
|
+
- **follow-up on the same PR (52a5a8d9):** the direct-content
|
|
119
|
+
fallback re-captured attribute rows when a derivation had already
|
|
120
|
+
captured them (attributes-only restriction), orphaning the chain
|
|
121
|
+
— 1309 bytes across 69 allocations under ASAN; the fallback now
|
|
122
|
+
captures only when no derivation did. `leptris_xsd_compile_file`
|
|
123
|
+
split the base directory on `/` only; Windows paths carry
|
|
124
|
+
backslashes, so includes never resolved there.
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## [1.9.325] - 2026-10-09
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **XSD: local particle declarations govern nested children —
|
|
133
|
+
tier-1 boundary closed (#1075, PR #1600).** Children declared
|
|
134
|
+
only as particles of a parent's content model — no global
|
|
135
|
+
`xs:element` of that name — skipped attribute and content-model
|
|
136
|
+
validation: the instance walk resolved every child through the
|
|
137
|
+
global element table and at most text-typed local particles, so
|
|
138
|
+
`<order><item id="nan"/></order>` against an `xs:integer`
|
|
139
|
+
particle attribute validated clean. When no global declaration
|
|
140
|
+
exists, the parent's particle declaration now governs the child:
|
|
141
|
+
the particle's type drives the full element check — attribute
|
|
142
|
+
rows, content model, lexical text — through the shared
|
|
143
|
+
`validate_local` tail that global declarations use, and
|
|
144
|
+
grandchildren type through the particle chain.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
3
148
|
## [1.9.324] - 2026-10-08
|
|
4
149
|
|
|
5
150
|
### Added
|
|
@@ -266,17 +266,15 @@ static cli_result_t validate_execute(int argc, char** argv) {
|
|
|
266
266
|
leptris_rng_free(rng);
|
|
267
267
|
}
|
|
268
268
|
if (xsd_path) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
/* compile_file resolves schemaLocations relative to the
|
|
270
|
+
* schema's own directory */
|
|
271
|
+
LeptrisStatus st = LEPTRIS_OK;
|
|
272
|
+
LeptrisXsdSchema xsd = leptris_xsd_compile_file(xsd_path, &st);
|
|
273
|
+
if (!xsd) {
|
|
272
274
|
cli_error("cannot read XSD: %s", xsd_path);
|
|
273
275
|
leptris_document_free(doc);
|
|
274
276
|
return CLI_ERROR_IO;
|
|
275
277
|
}
|
|
276
|
-
LeptrisStatus st = LEPTRIS_OK;
|
|
277
|
-
LeptrisXsdSchema xsd =
|
|
278
|
-
leptris_xsd_compile(xsd_content, xsd_len, &st);
|
|
279
|
-
free(xsd_content);
|
|
280
278
|
/* compile returns an error-carrying handle on failure — the
|
|
281
279
|
* status, not the pointer, says whether it succeeded. */
|
|
282
280
|
if (st != LEPTRIS_OK) {
|
|
@@ -900,6 +900,14 @@ LEPTRIS_API void leptris_rng_free(LeptrisRelaxNG rng);
|
|
|
900
900
|
* @return compiled schema; on failure an error-carrying handle
|
|
901
901
|
* (leptris_xsd_error says why — free it the same way)
|
|
902
902
|
*/
|
|
903
|
+
/* Compile a schema from a file: relative xs:include/xs:import/
|
|
904
|
+
* xs:redefine schemaLocations resolve against the schema's own
|
|
905
|
+
* directory. Returns NULL only when the file cannot be read; a
|
|
906
|
+
* malformed schema yields an error-carrying handle, exactly like
|
|
907
|
+
* leptris_xsd_compile. (Memory: free with leptris_xsd_free.) */
|
|
908
|
+
LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile_file(const char* path,
|
|
909
|
+
LeptrisStatus* status);
|
|
910
|
+
|
|
903
911
|
LEPTRIS_API LeptrisXsdSchema leptris_xsd_compile(const char* xsd_text,
|
|
904
912
|
size_t len,
|
|
905
913
|
LeptrisStatus* status);
|
|
@@ -416,7 +416,7 @@ LEPTRIS_API LeptrisStatus leptris_text_node_set_content(LeptrisNodeRef node,
|
|
|
416
416
|
if (!doc || !doc->pool) return LEPTRIS_ERROR_INVALID_ARG;
|
|
417
417
|
char* copy = node_public_pool_strdup(doc->pool, content, len);
|
|
418
418
|
if (!copy) return LEPTRIS_ERROR_MEMORY;
|
|
419
|
-
leptris_textnode_set_content_ptr_doc(t, copy, doc);
|
|
419
|
+
leptris_textnode_set_content_ptr_doc(t, copy, len, doc);
|
|
420
420
|
t->content_len = (uint32_t)len;
|
|
421
421
|
return LEPTRIS_OK;
|
|
422
422
|
}
|
|
@@ -42,7 +42,8 @@ LeptrisTextNode* leptris_text_create(const char* content,
|
|
|
42
42
|
/* Contiguous with the node in the same pool alloc — the offset
|
|
43
43
|
* is a small positive delta; doc tags the oversized-content
|
|
44
44
|
* fallback should it ever leave the inline window. */
|
|
45
|
-
leptris_textnode_set_content_ptr_doc(node, content_storage,
|
|
45
|
+
leptris_textnode_set_content_ptr_doc(node, content_storage,
|
|
46
|
+
content_len, doc);
|
|
46
47
|
node->content_len = (uint32_t)content_len;
|
|
47
48
|
|
|
48
49
|
return node;
|
|
@@ -76,7 +77,8 @@ LeptrisTextNode* leptris_text_create_borrowed(const char* content,
|
|
|
76
77
|
* lifetime + termination (#1285 slice 4). The buffer-to-node
|
|
77
78
|
* distance crosses allocations — the doc-tagged stamp is
|
|
78
79
|
* MANDATORY here (TLS is unset on parse worker threads). */
|
|
79
|
-
leptris_textnode_set_content_ptr_doc(node, content,
|
|
80
|
+
leptris_textnode_set_content_ptr_doc(node, content, content_len,
|
|
81
|
+
doc);
|
|
80
82
|
node->content_len = (uint32_t)content_len;
|
|
81
83
|
node->parent_off = 0;
|
|
82
84
|
node->next_sibling_off = 0;
|
|
@@ -162,10 +162,15 @@ static inline const char* leptris_textnode_content(const LeptrisTextNode* t) {
|
|
|
162
162
|
* use this form; the pooled-create contiguous path never spills but
|
|
163
163
|
* takes doc anyway so the oversized-content fallback is covered. */
|
|
164
164
|
static inline void leptris_textnode_set_content_ptr_doc(
|
|
165
|
-
LeptrisTextNode* t, const char* p,
|
|
165
|
+
LeptrisTextNode* t, const char* p, size_t len,
|
|
166
|
+
struct leptris_document* doc) {
|
|
166
167
|
if (!t) return;
|
|
167
168
|
if (!p) { t->content_off = 0; return; }
|
|
168
|
-
|
|
169
|
+
/* EMPTY only for genuinely empty runs. A NUL FIRST byte is
|
|
170
|
+
* content (nightly-fuzz 2026-10-09: NUL-leading runs collapsed
|
|
171
|
+
* here while content_len kept the length — the serializer then
|
|
172
|
+
* walked past the 1-byte EMPTY global). */
|
|
173
|
+
if (len == 0) { t->content_off = LEPTRIS_COMPACT_INT32_EMPTY; return; }
|
|
169
174
|
ptrdiff_t d = (char*)p - (char*)t;
|
|
170
175
|
if (d < INT32_MIN || d >= INT32_MAX)
|
|
171
176
|
t->content_off = leptris_compact_int32_encode_doc(
|
|
@@ -845,7 +845,16 @@ static LEPTRIS_NOINLINE int dp_parse_doctype(char** pos_io, char* end,
|
|
|
845
845
|
subset_start, (size_t)(subset_end - subset_start),
|
|
846
846
|
pool);
|
|
847
847
|
if (parsed) {
|
|
848
|
-
|
|
848
|
+
extern void leptris_dtd_free(struct LeptrisDTD*);
|
|
849
|
+
if (dtd) {
|
|
850
|
+
/* a second <!DOCTYPE: the first model wins (the
|
|
851
|
+
* duplicate-attribute keep-first rule); the
|
|
852
|
+
* replacement's heap default_decls array would
|
|
853
|
+
* leak otherwise (nightly-fuzz leak-168da36a) */
|
|
854
|
+
leptris_dtd_free(parsed);
|
|
855
|
+
} else {
|
|
856
|
+
dtd = parsed;
|
|
857
|
+
}
|
|
849
858
|
}
|
|
850
859
|
}
|
|
851
860
|
*pos_io = pos;
|
|
@@ -1260,7 +1269,8 @@ static inline LeptrisTextNode* dp_text_create(DParser* p,
|
|
|
1260
1269
|
* carve (dispatch-entry dp_nul / walker '&' NUL / EOF sentinel)
|
|
1261
1270
|
* — the terminator write lives with the byte's last reader, not
|
|
1262
1271
|
* here. */
|
|
1263
|
-
leptris_textnode_set_content_ptr_doc(tn, content,
|
|
1272
|
+
leptris_textnode_set_content_ptr_doc(tn, content, content_len,
|
|
1273
|
+
p->doc);
|
|
1264
1274
|
tn->content_len = (uint32_t)content_len;
|
|
1265
1275
|
tn->parent_off = 0;
|
|
1266
1276
|
tn->next_sibling_off = 0;
|
|
@@ -2336,7 +2346,24 @@ fail:
|
|
|
2336
2346
|
}
|
|
2337
2347
|
/* The eagerly-built line-break table is malloc'd, not pool-owned
|
|
2338
2348
|
* — the fail path never reaches leptris_document_free. Free it
|
|
2339
|
-
* BEFORE pool_destroy reclaims the doc struct.
|
|
2349
|
+
* BEFORE pool_destroy reclaims the doc struct. The parse-diag
|
|
2350
|
+
* array is the same: heap-realloc'd (leptris_diag_emit), and a
|
|
2351
|
+
* RECOVER diag (e.g. duplicate attribute) can precede the
|
|
2352
|
+
* failure — nightly-fuzz leak-75e0ee81, 2304 bytes. */
|
|
2353
|
+
{
|
|
2354
|
+
extern void leptris_diag_free(struct LeptrisDiag**, int*, int*);
|
|
2355
|
+
leptris_diag_free(&doc->parse_diags, &doc->parse_diag_count,
|
|
2356
|
+
&doc->parse_diag_cap);
|
|
2357
|
+
}
|
|
2358
|
+
/* The parsed internal-subset DTD is heap-modelled
|
|
2359
|
+
* (default_decls realloc, ttdtd_add_attribute) and never
|
|
2360
|
+
* transferred to the doc on this path — the success epilogue
|
|
2361
|
+
* assigns doc->dtd; without this free the model leaks
|
|
2362
|
+
* (nightly-fuzz leak-e3246602, 8 bytes). */
|
|
2363
|
+
{
|
|
2364
|
+
extern void leptris_dtd_free(struct LeptrisDTD*);
|
|
2365
|
+
if (p.dtd) leptris_dtd_free(p.dtd);
|
|
2366
|
+
}
|
|
2340
2367
|
free(doc->line_breaks);
|
|
2341
2368
|
/* Round 10: the elem_pos journal + lazy index are pool-carved —
|
|
2342
2369
|
* pool_destroy below reclaims them. */
|
|
@@ -2959,7 +2986,8 @@ static struct leptris_document* dp_il_build(
|
|
|
2959
2986
|
tn->base.type = LEPTRIS_NODE_TYPE_TEXT;
|
|
2960
2987
|
tn->base.frozen = 1;
|
|
2961
2988
|
tn->base.line = r->line;
|
|
2962
|
-
leptris_textnode_set_content_ptr_doc(tn, content,
|
|
2989
|
+
leptris_textnode_set_content_ptr_doc(tn, content,
|
|
2990
|
+
content_len, doc);
|
|
2963
2991
|
tn->content_len = (uint32_t)content_len;
|
|
2964
2992
|
/* (slice 4: pool/borrowed fields gone; the il scratch
|
|
2965
2993
|
* run is already NUL-terminated at carve) */
|
|
@@ -4005,7 +4005,8 @@ static void h_append(HBuilder* b, LeptrisNodeRef n) {
|
|
|
4005
4005
|
w9++;
|
|
4006
4006
|
if (t9 && w9 && t9[w9]) {
|
|
4007
4007
|
LeptrisTextNode* tn9 = (LeptrisTextNode*)n;
|
|
4008
|
-
leptris_textnode_set_content_ptr_doc(
|
|
4008
|
+
leptris_textnode_set_content_ptr_doc(
|
|
4009
|
+
tn9, t9 + w9, (size_t)(tn9->content_len - w9), b->doc);
|
|
4009
4010
|
tn9->content_len -= w9;
|
|
4010
4011
|
} else if (t9 && w9 && !t9[w9]) {
|
|
4011
4012
|
return; /* pure before-head ws: dropped (tests7:7) */
|
|
@@ -471,8 +471,13 @@ LEPTRIS_API LeptrisDocument leptris_parse_string(const char* xml, size_t length,
|
|
|
471
471
|
(d[i] == 0xFE && d[i + 1] == 0xFF))) {
|
|
472
472
|
/* Fall through to slow path. */
|
|
473
473
|
} else if (i < length && d[i] == '<' &&
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
/* bound against the REMAINING span: i indexes
|
|
475
|
+
* past leading whitespace, so an absolute-length
|
|
476
|
+
* check read d[i+4] one past the buffer
|
|
477
|
+
* (nightly-fuzz crash-aac3c9bb) */
|
|
478
|
+
!(length - i >= 5 && d[i + 1] == '?' &&
|
|
479
|
+
d[i + 2] == 'x' && d[i + 3] == 'm' &&
|
|
480
|
+
d[i + 4] == 'l')) {
|
|
476
481
|
/* Not a "<?xml" declaration. Check for embedded NUL bytes
|
|
477
482
|
* in the first 64 bytes (UTF-16 without BOM indicator). */
|
|
478
483
|
int has_nul = 0;
|