leptris 1.9.379.3-arm-linux → 1.9.380.1-arm-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 +12 -0
- data/Rakefile +1 -1
- data/lib/leptris/version.rb +1 -1
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +10 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/TODO.SOTA.md +82 -56
- data/vendor-src/libleptris/benchmarks/baseline.json +1 -0
- data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
- data/vendor-src/libleptris/bindings/rust/descriptor/Cargo.toml +1 -1
- data/vendor-src/libleptris/cli/output.c +11 -0
- data/vendor-src/libleptris/docs/MISSION-PLAN.md +12 -9
- data/vendor-src/libleptris/docs/guide/memory-model.md +229 -0
- data/vendor-src/libleptris/docs/guide/migrating-from-libxml2.md +131 -0
- data/vendor-src/libleptris/packaging/README.md +120 -0
- data/vendor-src/libleptris/packaging/conan/conanfile.py +120 -0
- data/vendor-src/libleptris/packaging/conan/test_package/CMakeLists.txt +7 -0
- data/vendor-src/libleptris/packaging/conan/test_package/conanfile.py +27 -0
- data/vendor-src/libleptris/packaging/conan/test_package/src/example.c +22 -0
- data/vendor-src/libleptris/packaging/debian/README.md +87 -0
- data/vendor-src/libleptris/packaging/debian/changelog +8 -0
- data/vendor-src/libleptris/packaging/debian/control +55 -0
- data/vendor-src/libleptris/packaging/debian/copyright +26 -0
- data/vendor-src/libleptris/packaging/debian/leptris.install +1 -0
- data/vendor-src/libleptris/packaging/debian/libleptris-dev.install +5 -0
- data/vendor-src/libleptris/packaging/debian/libleptris1.install +1 -0
- data/vendor-src/libleptris/packaging/debian/rules +34 -0
- data/vendor-src/libleptris/packaging/debian/source/format +1 -0
- data/vendor-src/libleptris/packaging/homebrew/leptris.rb +75 -0
- data/vendor-src/libleptris/scripts/perf_gate.py +51 -0
- data/vendor-src/libleptris/scripts/verify_manifest.py +182 -0
- data/vendor-src/libleptris/src/leptris/dtd/content_check.c +31 -23
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +52 -8
- data/vendor-src/libleptris/src/leptris/xinclude/xinclude.c +359 -244
- data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +17 -11
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +110 -7
- data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +14 -4
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +179 -19
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +4 -0
- data/vendor-src/libleptris/test/differential/README.md +100 -0
- data/vendor-src/libleptris/test/differential/corpus/basic.xml +13 -0
- data/vendor-src/libleptris/test/differential/corpus/cdata.xml +5 -0
- data/vendor-src/libleptris/test/differential/corpus/comments_pis.xml +1 -0
- data/vendor-src/libleptris/test/differential/corpus/dtd_model.xml +1 -0
- data/vendor-src/libleptris/test/differential/corpus/entities_dtd.xml +1 -0
- data/vendor-src/libleptris/test/differential/corpus/mixed_deep.xml +1 -0
- data/vendor-src/libleptris/test/differential/corpus/namespaces.xml +11 -0
- data/vendor-src/libleptris/test/differential/corpus/xpath_target.xml +1 -0
- data/vendor-src/libleptris/test/differential/package.json +9 -0
- data/vendor-src/libleptris/test/differential/run.mjs +52 -0
- data/vendor-src/libleptris/test/differential/run.py +61 -0
- data/vendor-src/libleptris/test/differential/run.rb +48 -0
- data/vendor-src/libleptris/test/dtd/test_dtd_validate.cpp +39 -0
- data/vendor-src/libleptris/test/xinclude/test_xinclude.cpp +274 -10
- data/vendor-src/libleptris/test/xpath/test_xpath.cpp +5 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/README.md +47 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-deep-eq.md +187 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-kinds-misc.md +48 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-type.md +285 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/dep-gates.md +57 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/empty-query.md +11 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-inline-no-params.md +48 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-unsupported.md +143 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/error-channel.md +782 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/named-exclude.md +85 -0
- data/vendor-src/libleptris/test/xquery/qt3-dispositions/query-exclude.md +379 -0
- data/vendor-src/libleptris/test/xquery/test_qt3.cpp +192 -26
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +524 -2
- data/vendor-src/libleptris/test/xsd/xsd-dispositions/README.md +59 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +47 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9d57ba010010d0eafdafc080938bc02ebf7d508799cdd01fbde67176d5f1e5f
|
|
4
|
+
data.tar.gz: 8d560c49128668a65a82a098e0918b7378957f5a07aa9e1f76b939709acd5283
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7c175ea55f978f206ca9b8505350478c0693c6caa3381d29e3355ee995d25faf69d908a41f4af379e58ec603157c35fb03e91a308b610f887e63095927818bd
|
|
7
|
+
data.tar.gz: 57b50326e6a4979410884d2610468e2e52d0de4e99eaf084be3f84a9a026864456105529b9c9cccd1e0d63406919d68f71f62eff894ca4208c5c8278d3c49a77
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
### Fixed — engine ride
|
|
4
|
+
|
|
5
|
+
- **Ride libleptris 1.9.380**: the xsi:type conformance fix
|
|
6
|
+
(leptris/leptris#1832, lutaml/moxml#433) — an element carrying
|
|
7
|
+
`xsi:type` is assessed against the NAMED type (XSD 1.0
|
|
8
|
+
§2.6.2/§3.3.4), so valid xsi:type-derived instances validate
|
|
9
|
+
instead of being rejected against the declared type's content
|
|
10
|
+
model. Released 1.9.380.0 with the pin still at 1.9.379 (the
|
|
11
|
+
vendored dylib missed the fix); this pin bump is the real ride.
|
|
12
|
+
|
|
1
13
|
## [1.9.379.3] - 2026-10-22
|
|
2
14
|
|
|
3
15
|
### Fixed
|
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.380"
|
|
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/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.380] - 2026-10-22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- assess elements against the instance's xsi:type (moxml#433) (xsd)
|
|
8
|
+
- choice alternatives after a failed parenthesized branch were unreachable (dtd)
|
|
9
|
+
- iterative nested-include processing - no C-stack nesting (xinclude)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
3
13
|
## [1.9.379] - 2026-10-21
|
|
4
14
|
|
|
5
15
|
### Fixed
|
|
@@ -27,54 +27,62 @@ docs/MISSION-PLAN.md and is updated at every release.
|
|
|
27
27
|
|
|
28
28
|
## Gate A — Conformance: zero reds everywhere
|
|
29
29
|
|
|
30
|
-
### A1 WHATWG verdict parity:
|
|
31
|
-
**Measured 2026-10-21 (
|
|
32
|
-
1555/1555
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
EOF classification
|
|
51
|
-
- [ ] the after-body EOF phase rule (tests1:34 class; the exact
|
|
52
|
-
phaseAtBody.eof semantics)
|
|
53
|
-
- [ ] in-body `unexpected-start-tag` family (body/frameset/form
|
|
54
|
-
starts; the startTagBody/Forms/Frameset error sites)
|
|
55
|
-
- [ ] `unexpected-end-tag` family (p-in-button-scope synthesis,
|
|
56
|
-
form pointer, li/dd/dt scope, endTagOther walk)
|
|
57
|
-
- [ ] foster-parenting sites (start-tag/character/character-in-table)
|
|
58
|
-
- [ ] adoption-agency codes (`adoption-agency-1.3` et al.)
|
|
59
|
-
- [ ] in-table voodoo (`*-implies-table-voodoo`, cells, eof-in-table)
|
|
60
|
-
- [ ] unknown-doctype / unexpected-char-in-doctype (doctype
|
|
61
|
-
tokenization §13.2.5.28+)
|
|
62
|
-
- [ ] non-html-root, select-mode codes, heading/edge shapes
|
|
30
|
+
### A1 WHATWG verdict parity: DONE — 1555/1555
|
|
31
|
+
**Measured 2026-10-21 (PR #1803, tagged v1.9.378): verdict
|
|
32
|
+
1555/1555, ZERO misses; tree 1555/1555; floor pinned at the full
|
|
33
|
+
corpus count.** The gate is presence-only — every corpus document
|
|
34
|
+
now reports a parse error exactly when html5lib does. The final
|
|
35
|
+
tail closed at the html5lib source sites: adoption-agency-1.3
|
|
36
|
+
(7 cases), menuitem endTagOther pair, tokenizer
|
|
37
|
+
expected-dashes-or-doctype / solidus-in-tag / attributes-in-end-tag,
|
|
38
|
+
AfterAfterFrameset trailing chars, BeforeHead
|
|
39
|
+
end-tag-after-implied-root, BeforeHtml unexpected-end-tag-before-html,
|
|
40
|
+
foreign end-tag mismatch, form-in-table both flavors, </form> table
|
|
41
|
+
voodoo, template foster-parenting-character, two-heads. Earlier
|
|
42
|
+
slices: initial mode, raw-text EOF, unterminated close, frameset,
|
|
43
|
+
fenced walk, InBody EOF allowed-set, in-select, foster-by-kind,
|
|
44
|
+
unknown-doctype (#1778-#1791), raw-text strays, non-html-root,
|
|
45
|
+
end-tag-too-early, after-body/frameset phases, start-tag phases,
|
|
46
|
+
select/table/foreign, need-space-after-doctype (#1794),
|
|
47
|
+
misplaced/stray-p, after-html codes, image (#1797, #1800). The
|
|
48
|
+
~270 remaining code-set divergences are prose-dialect labels from
|
|
49
|
+
older corpus datasets — reported, not gated.
|
|
63
50
|
|
|
64
51
|
### A2 QT3 / XQuery 3.1: zero unexplained pins
|
|
65
|
-
- [
|
|
66
|
-
- [
|
|
52
|
+
- [x] re-pin fresh (2026-10-22: 1723 pins, 62/62 green, zero reds; 106 sets, 72 pins moved)
|
|
53
|
+
- [x] every remaining pin gets a disposition file or a fix
|
|
54
|
+
(test/xquery/qt3-dispositions/ — 10 files, 1723/1723 dispositioned;
|
|
55
|
+
fixes: fn:codepoint-equal empty-operand propagation, driver
|
|
56
|
+
assert-count/assert-empty, role="." context sources, QT3_REPORT
|
|
57
|
+
inventory channel)
|
|
67
58
|
- [ ] ctor node-yield family: owned_docs transfer completion
|
|
68
59
|
|
|
69
60
|
### A3 XSD conformance lane: suite-complete
|
|
70
|
-
- [
|
|
61
|
+
- [x] the lane's own queue to its dispositioned ceiling — **2026-10-22:
|
|
62
|
+
903 → 989**/1056 (PR #1825; the board's 795 was stale — five slices
|
|
63
|
+
had landed). 9 root-caused classes retired:
|
|
64
|
+
(2026-10-22: 903 → **989**/1056 — 9 root-caused classes retired:
|
|
65
|
+
xsi-attr exemption, empty content models, unprefixed builtins,
|
|
66
|
+
no-tns `##other`, xs:attribute 3.2.3 clauses (+55), g-regional
|
|
67
|
+
timezone, token whitespace, unknown-xs-builtin refs. 67 left,
|
|
68
|
+
every row dispositioned in test/xsd/xsd-dispositions/README.md:
|
|
69
|
+
substitution groups in content models, xsi:type, attribute form
|
|
70
|
+
defaults, identity-constraint checks, derivation-ok attribute
|
|
71
|
+
clauses; 1 oracle bug (bug322411) not chased)
|
|
71
72
|
|
|
72
73
|
### A4 DTD phase 8 (TODO 91)
|
|
73
|
-
- [
|
|
74
|
-
|
|
74
|
+
- [x] ENTITY/ENTITIES attribute resolution (undeclared entity, parsed
|
|
75
|
+
entity without NDATA, undeclared notation, bad ENTITIES token —
|
|
76
|
+
all falsifiably spec'd; shipped bd21acc6/9d3b3a0e)
|
|
77
|
+
- [x] parameter entities (internal, chained, ATTLIST/ELEMENT bodies,
|
|
78
|
+
SYSTEM PEs via loader, recursion cap, conditional sections) and
|
|
79
|
+
choice-model backtracking — group-alternative failure walker
|
|
80
|
+
fixed so a failed parenthesized alternative still reaches later
|
|
81
|
+
`|` branches (DtdPhase8.GroupAlternativeFailureStillTriesLaterAlternatives)
|
|
75
82
|
|
|
76
83
|
### A5 XInclude completeness (TODO 92)
|
|
77
|
-
- [
|
|
84
|
+
- [x] `parse="xml"` (ownership-transfer splice + canonical insert face,
|
|
85
|
+
depth-guard resource-error fallback; verified by specs)
|
|
78
86
|
- [ ] xpointer
|
|
79
87
|
|
|
80
88
|
### A6 The universality proof (differential corpus)
|
|
@@ -90,15 +98,15 @@ Ground every further slice in html5lib's actual source
|
|
|
90
98
|
## Gate B — Performance: every published row ≥1.00x
|
|
91
99
|
|
|
92
100
|
### B1 build row: ~2.9x → ≤1.0x (the #242 campaign)
|
|
93
|
-
**Measured 2026-10-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
**Measured 2026-10-22: ~1.47-1.54x median after NINE merged levers**
|
|
102
|
+
(honest baseline 2.72x; guard retirement −22%, namespace gate,
|
|
103
|
+
nil-ivar priming, children-cache guard, mint direct-dispatch +
|
|
104
|
+
native set_attribute −19% (PR #431), prepare_node inline (PR #432)).
|
|
105
|
+
Harness `wt/460-harness/build_bench.rb`; A/B pairs alternate within
|
|
106
|
+
ONE window (drift ±20%). Rejected within noise: fast_attach,
|
|
107
|
+
native-cache deletion, doc_klass memo, identity-probe deletion,
|
|
108
|
+
add_child alias. Residual: wrapper allocation (owner-level node
|
|
109
|
+
layout).
|
|
102
110
|
- [ ] per-element object budget: one Ruby object per created node
|
|
103
111
|
(drop the shell entirely post-create-priming; the mint cache
|
|
104
112
|
insert policy; the context registration)
|
|
@@ -113,8 +121,16 @@ scaffolding. Memo law holds (dropping @children memo = 2x GC).
|
|
|
113
121
|
- [ ] publish the per-face table (the honest much-faster story)
|
|
114
122
|
|
|
115
123
|
### B3 whole-pipeline rows (new, published)
|
|
116
|
-
-
|
|
117
|
-
-
|
|
124
|
+
**Measured 2026-10-21: parse→query→serialize is GREEN — leptris
|
|
125
|
+
finishes in 0.533-0.573x of Nokogiri's CPU time (~1.8x faster),
|
|
126
|
+
three interleaved-median runs (`wt/460-harness/pipeline_bench.rb`,
|
|
127
|
+
250 records, 31 reps; same document and query both sides).**
|
|
128
|
+
- [x] parse→query→serialize pipeline row vs nokogiri (green, dated)
|
|
129
|
+
- [x] build→transform (XSLT) pipeline row — **GREEN 2026-10-21:
|
|
130
|
+
build 250 records, XSLT 1.0 transform, serialize — leptris
|
|
131
|
+
finishes in 0.492-0.515x of Nokogiri's CPU time (~2x faster),
|
|
132
|
+
three interleaved-median runs
|
|
133
|
+
(`wt/460-harness/xslt_pipeline_bench.rb`)**
|
|
118
134
|
- [ ] both to serialbench as the public scoreboard
|
|
119
135
|
|
|
120
136
|
### B4 durability
|
|
@@ -136,19 +152,29 @@ scaffolding. Memo law holds (dropping @children memo = 2x GC).
|
|
|
136
152
|
- [ ] C3 reproducible builds everywhere: darwin toolchain pin done
|
|
137
153
|
(ruby#512) — extend to linux/windows legs; publish a per-
|
|
138
154
|
release build manifest (engine + binding hashes per platform)
|
|
139
|
-
- [
|
|
140
|
-
dependency-free attestation
|
|
155
|
+
- [x] C4 security.md: advisory channel, CVE-response process,
|
|
156
|
+
dependency-free attestation — **shipped 2026-10-21 (PR #1804,
|
|
157
|
+
v1.9.378)**: GitHub Security Advisories channel, 5-business-day
|
|
158
|
+
acknowledgment target, coordinated disclosure, CVE assignment,
|
|
159
|
+
scope table, hardening inventory tied to the lanes that actually
|
|
160
|
+
run
|
|
141
161
|
- [ ] C5 the memory story documented: pool arena model, leak gates,
|
|
142
162
|
the borrowed-slice lifetime contract
|
|
143
163
|
|
|
144
164
|
## Gate D — Universality: same semantics everywhere
|
|
145
165
|
|
|
146
|
-
- [
|
|
147
|
-
|
|
166
|
+
- [x] D1 leptris-descriptor published — **2026-10-22: live on
|
|
167
|
+
crates.io as leptris-descriptor v1.9.379** (lockstep with the
|
|
168
|
+
engine; #1827 fixed the workspace publish to target the
|
|
169
|
+
descriptor package; issue #1829 records the one-time manual
|
|
170
|
+
establishment — crates.io trusted publishing cannot create new
|
|
171
|
+
crates — after which versions publish via the OIDC workflow)
|
|
148
172
|
- [ ] D2 system packaging: homebrew, conan, deb (apt repo)
|
|
149
173
|
- [ ] D3 cross-runtime differential CI (= A6, wired)
|
|
150
|
-
- [
|
|
151
|
-
|
|
174
|
+
- [x] D4 libxml2→libleptris migration guide — **shipped 2026-10-21
|
|
175
|
+
(PR #1810): docs/guide/migrating-from-libxml2.md** (ownership
|
|
176
|
+
model, parse/tree/XPath/serialize/SAX/DTD mappings, deliberate
|
|
177
|
+
differences); nokogiri-compat surface documented in the guide
|
|
152
178
|
|
|
153
179
|
## Gate E — 1.0 (the seal)
|
|
154
180
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "leptris-descriptor"
|
|
3
|
-
version = "
|
|
3
|
+
version = "1.9.379"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Shared descriptor-plan ABI for the leptris engine family (leptris, yeptris, teptris) — repr(C) mirrors of the tree-shaped descriptor plan records"
|
|
6
6
|
license = "MIT"
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
* @brief Output formatting implementation for Leptris CLI
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/* fileno is POSIX, not ISO C: under -std=c11 (C_EXTENSIONS OFF,
|
|
7
|
+
* __STRICT_ANSI__) glibc hides its declaration from <stdio.h> and
|
|
8
|
+
* both gcc-14 and clang-18 reject the implicit call, so declare the
|
|
9
|
+
* POSIX surface before ANY include pulls a system header in. macOS
|
|
10
|
+
* never hides fileno, which is why only the Linux lanes caught it
|
|
11
|
+
* (found by the ci/tsan lane's ubuntu build). Windows reaches
|
|
12
|
+
* fileno/isatty via <io.h> below. */
|
|
13
|
+
#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)
|
|
14
|
+
# define _POSIX_C_SOURCE 200809L
|
|
15
|
+
#endif
|
|
16
|
+
|
|
6
17
|
#include "output.h"
|
|
7
18
|
#include "error.h"
|
|
8
19
|
#include <stdlib.h>
|
|
@@ -23,11 +23,11 @@ nothing to libxml2.
|
|
|
23
23
|
| A1 | W3C XPath 1.0 | 438/438 pinned in CI | **done** — 438/438 |
|
|
24
24
|
| A2 | libxslt (XSLT 1.0) | 205/205 pinned (Linux-authoritative; macOS skips bug-169 iconv-encoding) | **done** — 205/205 (v1.9.19) |
|
|
25
25
|
| A3 | XsltFull | 27/27 + full suite green; XSLT gate (v1.10.0) stays met | **done** — gate MET (#1761) |
|
|
26
|
-
| A4 | QT3 / XQuery 3.1 | 0 unexplained pins; every pin has a recorded disposition | pins
|
|
27
|
-
| A5 | WHATWG/html5lib (#659) | 0 reds on
|
|
26
|
+
| A4 | QT3 / XQuery 3.1 | 0 unexplained pins; every pin has a recorded disposition | **done** 2026-10-22 — 1723 pins, all dispositioned (test/xquery/qt3-dispositions/); 62/62 DUCET + 59/59 no-utf8proc green |
|
|
27
|
+
| A5 | WHATWG/html5lib (#659) | 0 reds on BOTH gates — tree and error-verdict | **BOTH LANES CLOSED 2026-10-21** — tree 1555/1555; verdict 1555/1555, zero misses (PR #1803), floor pinned at the full corpus count |
|
|
28
28
|
| A6 | XSD conformance lane | suite-complete per the lane's own queue | 769→795 (2026-10-14, queue #1691) |
|
|
29
29
|
| A7 | DTD phase 8 (TODO 91) | ENTITY/ENTITIES attribute resolution, parameter entities, choice-model backtracking | open |
|
|
30
|
-
| A8 | XInclude parse="xml" + xpointer (TODO 92) | spec-complete |
|
|
30
|
+
| A8 | XInclude parse="xml" + xpointer (TODO 92) | spec-complete | **closed 2026-10-22** — parse="xml" splice via canonical insert face + depth-guard fallback (PR #1815); xpointer schemes REC-conformant with pointer-miss=resource-error (PR #1820); windows iterative nesting fix (PR #1822) |
|
|
31
31
|
| A9 | RelaxNG / C14N / SAX | parity maintained, no reds | green |
|
|
32
32
|
|
|
33
33
|
**Working rule (proven across A1–A3):** suite-driven fixes only —
|
|
@@ -87,7 +87,7 @@ lands (TAG-RACE).
|
|
|
87
87
|
| C2 | leptris-rs descriptor crate | `leptris-descriptor` published; safe-wrapper crate rides lockstep | feat/294 in flight, not published |
|
|
88
88
|
| C3 | moxml gem | cut (owner-dispatched) carries the walk+read adoption | merged on main, release pending owner |
|
|
89
89
|
| C4 | Prebuilt binaries everywhere consumers lack compilers | rubygems platform matrix ✓, PyPI wheels ✓, npm WASM ✓ | **done**, keep green |
|
|
90
|
-
| C5 | System packaging | vcpkg ✓; add homebrew / conan / deb formulas so "install libleptris" is one command per platform |
|
|
90
|
+
| C5 | System packaging | vcpkg ✓; add homebrew / conan / deb formulas so "install libleptris" is one command per platform | **formulas shipped in-repo; channel submission pending owner (2026-10-22)** |
|
|
91
91
|
| C6 | Cargo publish lane | OIDC-free trusted publishing (crates-io-auth-action@v1) wired into the rust-release workflow; CARGO_REGISTRY_TOKEN secret set once | workflow shipped; secret/token state to verify in Phase 0 |
|
|
92
92
|
|
|
93
93
|
**Exit:** a consumer on any of the four runtimes installs a prebuilt
|
|
@@ -165,11 +165,14 @@ tag that says so.
|
|
|
165
165
|
| Gate | Command | Last run | Verdict |
|
|
166
166
|
|------|---------|----------|---------|
|
|
167
167
|
| A1 WHATWG tree parity | `test_html5lib --gtest_filter=Html5LibCorpus.*` | 2026-10-21 | green, 1555/1555 |
|
|
168
|
-
| A1 WHATWG verdict parity | `Html5LibCorpus.ErrorVerdictParity` | 2026-10-21 |
|
|
168
|
+
| A1 WHATWG verdict parity | `Html5LibCorpus.ErrorVerdictParity` | 2026-10-21 | **green, 1555/1555 — zero misses** (PR #1803; floor pinned at the full corpus) |
|
|
169
169
|
| A7 libxslt | `LibxsltSuite` | 2026-10-20 | 205/205 (204 + 1 macOS skip) |
|
|
170
|
-
| B1 build row | `wt/460-harness/build_bench.rb` | 2026-10-21 | 1.
|
|
170
|
+
| B1 build row | `wt/460-harness/build_bench.rb` | 2026-10-21 | **~1.9x red** (sota/04 after five levers; honest baseline re-measured 2.72x this day — the old 1.86x record is not reproducible) |
|
|
171
|
+
| B3 pipeline row (parse→query→serialize) | `wt/460-harness/pipeline_bench.rb` | 2026-10-21 | **green** — 0.533-0.573x of Nokogiri's time (~1.8x faster), three runs |
|
|
172
|
+
| B3 pipeline row (build→transform, XSLT) | `wt/460-harness/xslt_pipeline_bench.rb` | 2026-10-21 | **green** — 0.492-0.515x of Nokogiri's time (~2x faster), three runs |
|
|
171
173
|
| B2 walk row | `wt/460-harness/walk_read_bench.rb` | 2026-10-20 | 0.88x (green) |
|
|
172
|
-
| Test-suite runtime (fidelity) | `ctest`, 2074 cases | 2026-10-21 | 66.0 s serial → 31.0 s `-j4` (same 2074/2074;
|
|
173
|
-
|
|
|
174
|
-
|
|
|
174
|
+
| Test-suite runtime (fidelity) | `ctest`, 2074 cases | 2026-10-21 | 66.0 s serial → 31.0 s `-j4` (same 2074/2074; **shipped in v1.9.378**) |
|
|
175
|
+
| XPath index vs mutations | `XPathIndexInvalidation.*` | 2026-10-21 | green — splice paths invalidate (PR #1802; ruby#519 closed on the 1.9.378.0 gem) |
|
|
176
|
+
| Rides 1.9.379 | registries | 2026-10-21 | rubygems/PyPI 1.9.379.0, npm 0.1.22 (engine v1.9.379; v1.9.378 rides blocked by the GCC-14 cast, fixed in #1806) |
|
|
177
|
+
| Issues | all repos | 2026-10-21 | 0 open (ruby#519 closed with gem verification) |
|
|
175
178
|
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# The libleptris Memory Model
|
|
2
|
+
|
|
3
|
+
This guide documents how libleptris allocates, who owns what, and which
|
|
4
|
+
calls hand back memory you must free. Every public function that returns
|
|
5
|
+
a string or handle carries a `Memory:` comment in `src/include/leptris.h`;
|
|
6
|
+
this page is the map of the underlying rules.
|
|
7
|
+
|
|
8
|
+
## The pool arena model
|
|
9
|
+
|
|
10
|
+
Every byte a parsed document references lives in one memory arena owned
|
|
11
|
+
by the document. There is no per-node allocation and no per-node free.
|
|
12
|
+
|
|
13
|
+
- **One allocation per document.** The parser sizes a contiguous arena
|
|
14
|
+
from the input (`src/leptris/flat/direct_parse.c`), then creates an
|
|
15
|
+
arena-backed pool over it (`leptris_pool_create_arena_backed` in
|
|
16
|
+
`src/leptris/memory/pool.c`). Elements, attributes, text nodes, and
|
|
17
|
+
strings are all bump-allocated inside that span, so any two allocations
|
|
18
|
+
are within the arena size of each other by construction — the property
|
|
19
|
+
the compact-pointer tree edges require.
|
|
20
|
+
- **O(1) allocation.** `leptris_pool_alloc` is a bump-pointer advance on
|
|
21
|
+
the current page; the slow path (span exhausted) allocates a new page,
|
|
22
|
+
still O(1). Requests larger than a page go to the allocator directly and
|
|
23
|
+
are tracked on an oversized list so `leptris_pool_destroy` can free them.
|
|
24
|
+
- **No per-node free.** There is no `leptris_element_free` and no
|
|
25
|
+
reference counting. Individual nodes are never released; removing a
|
|
26
|
+
node from the tree returns its bytes to no one. This is deliberate —
|
|
27
|
+
the allocator never needs to reason about partial teardown.
|
|
28
|
+
- **Batch release.** `leptris_document_free` destroys the pool and every
|
|
29
|
+
byte reachable from the document in one pass: pages, oversized blocks,
|
|
30
|
+
the input copy, and the document struct itself (which is also
|
|
31
|
+
pool-allocated). Callers never free anything else.
|
|
32
|
+
|
|
33
|
+
Empty documents behave the same: `leptris_document_create` gives the
|
|
34
|
+
document its own arena-backed pool, and programmatic nodes created via
|
|
35
|
+
`leptris_element_create` land in it.
|
|
36
|
+
|
|
37
|
+
Ownership transfers exist for two composition cases:
|
|
38
|
+
|
|
39
|
+
- `leptris_document_adopt_child(parent, child)` keeps a freshly parsed
|
|
40
|
+
child document alive until the parent is freed (used by XInclude).
|
|
41
|
+
- `leptris_document_absorb(dst, src)` moves `src`'s pool into `dst` so
|
|
42
|
+
cross-document splices can move nodes by reference; after the call the
|
|
43
|
+
`src` handle is handle-only and the memory dies with `dst`.
|
|
44
|
+
|
|
45
|
+
A foreign element passed across documents (for example to
|
|
46
|
+
`leptris_document_set_root`) is **adopted by copy**: nodes are pool-owned
|
|
47
|
+
by their document, so a move would dangle after the source document's
|
|
48
|
+
free. Always wrap the installed handle the API returns, never the
|
|
49
|
+
borrowed source pointer.
|
|
50
|
+
|
|
51
|
+
## String ownership
|
|
52
|
+
|
|
53
|
+
Every string-returning accessor falls into one of two classes. The
|
|
54
|
+
`Memory:` comment in the header says which, per function.
|
|
55
|
+
|
|
56
|
+
### Document-owned: do not free
|
|
57
|
+
|
|
58
|
+
Accessors returning `const char*` against a document-taking handle
|
|
59
|
+
return pointers into the document's arena. Examples:
|
|
60
|
+
`leptris_element_name`, `leptris_element_expanded_name`,
|
|
61
|
+
`leptris_comment_node_get_content`, the diff op accessors. They are
|
|
62
|
+
valid until `leptris_document_free` and must not be freed or modified:
|
|
63
|
+
|
|
64
|
+
```c
|
|
65
|
+
const char* name = leptris_element_name(root); /* document-owned */
|
|
66
|
+
/* no free */
|
|
67
|
+
leptris_document_free(doc); /* releases name too */
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Node handles are document-owned the same way
|
|
71
|
+
(`leptris_document_root`, `leptris_xpath_result_get`,
|
|
72
|
+
`leptris_node_children`): never free a node separately.
|
|
73
|
+
|
|
74
|
+
### Caller-owned: free with `leptris_free_string`
|
|
75
|
+
|
|
76
|
+
Functions that build a fresh string return `char*` and must be released
|
|
77
|
+
with `leptris_free_string` (which routes through the installed
|
|
78
|
+
allocator, so it matches how the string was made):
|
|
79
|
+
|
|
80
|
+
- `leptris_document_serialize` and the other serializers
|
|
81
|
+
- `leptris_xpath_result_string` (the string value of any result type)
|
|
82
|
+
- `leptris_node_get_xpath`
|
|
83
|
+
- `leptris_load_file` (the file buffer)
|
|
84
|
+
- `leptris_alloc_buffer` (when the caller keeps the buffer)
|
|
85
|
+
|
|
86
|
+
```c
|
|
87
|
+
char* xml = leptris_document_serialize(doc, NULL);
|
|
88
|
+
/* ... */
|
|
89
|
+
leptris_free_string(xml);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
XPath results are their own class: `leptris_xpath_eval` returns a
|
|
93
|
+
caller-owned `LeptrisXPathResult` released with
|
|
94
|
+
`leptris_xpath_result_free` — but the node handles inside a nodeset
|
|
95
|
+
remain document-owned, so the result may be freed before the document,
|
|
96
|
+
never after.
|
|
97
|
+
|
|
98
|
+
### The borrowed-slice lifetime contract
|
|
99
|
+
|
|
100
|
+
During parse, text is handled as `LeptrisStringView` — a
|
|
101
|
+
`(data, length)` pair that borrows, not owns
|
|
102
|
+
(`src/leptris/common/string_view.h`). The mechanics:
|
|
103
|
+
|
|
104
|
+
1. The parser copies the input into the document's arena span (the
|
|
105
|
+
parse scratch) and NUL-terminates tokens **in that copy** as it
|
|
106
|
+
scans. Element names, attribute values, and text content are slices
|
|
107
|
+
of this document-owned buffer — no per-string allocation happens.
|
|
108
|
+
2. What borrows what: node strings borrow from the document's private
|
|
109
|
+
input copy; the document's copy borrows from nothing. A second
|
|
110
|
+
pristine copy of the input is kept for line/column diagnostics,
|
|
111
|
+
because the scratch gets NUL substitutions.
|
|
112
|
+
3. When a copy is made: entity-expanded content, interned strings (the
|
|
113
|
+
intern table copies both key and value into the pool so nothing
|
|
114
|
+
points into a reused buffer), and any string the public API returns
|
|
115
|
+
as caller-owned.
|
|
116
|
+
|
|
117
|
+
Consequences:
|
|
118
|
+
|
|
119
|
+
- After `leptris_parse_string` returns, the document is self-contained;
|
|
120
|
+
the header nevertheless documents `parse_string`'s input as valid for
|
|
121
|
+
the call. `leptris_parse_string_inplace` is the zero-copy variant with
|
|
122
|
+
a stricter contract: the document retains a reference to the caller's
|
|
123
|
+
writable buffer, which the caller must keep alive for the document's
|
|
124
|
+
lifetime (the parser writes NUL terminators into it).
|
|
125
|
+
- Nothing a `const char*` accessor returns survives
|
|
126
|
+
`leptris_document_free`. Store copies, not pointers, if the data must
|
|
127
|
+
outlive the document.
|
|
128
|
+
|
|
129
|
+
## Leak gates
|
|
130
|
+
|
|
131
|
+
Three layers catch leaks before they merge.
|
|
132
|
+
|
|
133
|
+
### CI
|
|
134
|
+
|
|
135
|
+
`.github/workflows/asan.yml` runs two jobs:
|
|
136
|
+
|
|
137
|
+
- **AddressSanitizer (Linux)** — full `ctest` under ASAN with
|
|
138
|
+
`ASAN_OPTIONS=detect_leaks=1:halt_on_error=1:abort_on_error=1`.
|
|
139
|
+
- **leaks (macOS)** — builds and tests, then runs the CLI over fixtures
|
|
140
|
+
under `leaks --atExit` and fails unless it reports
|
|
141
|
+
`0 leaks for 0 total`.
|
|
142
|
+
|
|
143
|
+
The macOS job matters because LeakSanitizer's leak mode is not supported
|
|
144
|
+
on macOS ASAN; leaks there are only caught by this job (or Linux CI).
|
|
145
|
+
|
|
146
|
+
### Locally
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# macOS
|
|
150
|
+
leaks --atExit -- ./build/test/c/test_dom
|
|
151
|
+
|
|
152
|
+
# Linux
|
|
153
|
+
valgrind --leak-check=full --error-exitcode=1 ./build/test/c/test_dom
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or configure an ASAN build and run any suite:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cmake -B build -S . -DLEPTRIS_ENABLE_ASAN=ON -DCMAKE_BUILD_TYPE=Debug
|
|
160
|
+
cmake --build build
|
|
161
|
+
ctest --test-dir build --output-on-failure
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A finishing thread must not hold libleptris memory: call
|
|
165
|
+
`leptris_thread_cleanup` after the last `leptris_document_free` on that
|
|
166
|
+
thread to drain the per-thread caches and the pool-block recycler.
|
|
167
|
+
|
|
168
|
+
## The FFI / binding contract
|
|
169
|
+
|
|
170
|
+
`docs/FFI.md` states the boundary rules; the memory-relevant ones:
|
|
171
|
+
|
|
172
|
+
- **One release function.** A binding needs only `leptris_document_free`
|
|
173
|
+
to release a parsed document. Everything reachable from the document
|
|
174
|
+
dies with it.
|
|
175
|
+
- **Strings are read, then released per class.** Document-owned strings
|
|
176
|
+
are copied out (or read in place) and never freed; caller-owned
|
|
177
|
+
strings are freed with `leptris_free_string` before the binding object
|
|
178
|
+
is collected.
|
|
179
|
+
- **Binding wrapper pointers are borrowed both ways.**
|
|
180
|
+
`leptris_node_set_binding_wrapper` stores a pointer the binding owns;
|
|
181
|
+
libleptris never dereferences or frees it. The binding must clear it
|
|
182
|
+
or guarantee the wrapper outlives the document.
|
|
183
|
+
- **Finalizer-driven release must be exact.** The shipped Ruby binding
|
|
184
|
+
does not use a GC finalizer for the document, precisely because a
|
|
185
|
+
finalizer cannot tell an already-freed pointer from a valid one. If a
|
|
186
|
+
binding uses finalizers, it must track freed state itself and release
|
|
187
|
+
exactly once.
|
|
188
|
+
|
|
189
|
+
### The free-before-pool-recycle hazard
|
|
190
|
+
|
|
191
|
+
This is the #506-class failure for embedders. Destroyed arena blocks are
|
|
192
|
+
recycled: `leptris_pool_destroy` parks default-shape blocks on capped
|
|
193
|
+
thread-local lists and the next `leptris_parse_*` on the same thread
|
|
194
|
+
pops an exact-size match. This makes document churn cheap — and it
|
|
195
|
+
means **use-after-free does not crash at the bug site**. A stale handle
|
|
196
|
+
written after `leptris_document_free` lands inside the *next*
|
|
197
|
+
document's live tree and corrupts it silently; a double free hands the
|
|
198
|
+
allocator a block the next parse now owns.
|
|
199
|
+
|
|
200
|
+
Corollary hazards already guarded in the engine (do not reproduce them
|
|
201
|
+
in embedder code): a binding finalizer releasing a document after a
|
|
202
|
+
replacement document was created, and a caller freeing an absorbed
|
|
203
|
+
document's handle a second time.
|
|
204
|
+
|
|
205
|
+
## Rules for embedders
|
|
206
|
+
|
|
207
|
+
1. Free the document **last**. `leptris_document_free` after every
|
|
208
|
+
borrowed pointer (nodes, document-owned strings, XPath results
|
|
209
|
+
holding nodes into it) is dead.
|
|
210
|
+
2. Never free a `const char*` the API handed you. Only `char*` returns
|
|
211
|
+
documented as caller-owned are yours — release those with
|
|
212
|
+
`leptris_free_string`, once, on the same side of the boundary that
|
|
213
|
+
received them.
|
|
214
|
+
3. Never write through a handle after its document is freed, and never
|
|
215
|
+
free a document twice. With the block recycler, both corrupt the next
|
|
216
|
+
parse instead of crashing.
|
|
217
|
+
4. Keep `leptris_parse_string_inplace`'s buffer alive and unmodified for
|
|
218
|
+
the document's lifetime; prefer plain `leptris_parse_string` unless
|
|
219
|
+
the zero-copy path is measured to matter.
|
|
220
|
+
5. Wrap installed handles, not borrowed sources — cross-document
|
|
221
|
+
element adoption copies, so the handle you passed in may not be the
|
|
222
|
+
node that got installed.
|
|
223
|
+
6. Call `leptris_thread_cleanup` before a worker thread that used
|
|
224
|
+
libleptris exits.
|
|
225
|
+
7. Install custom allocators (`leptris_set_memory_management_functions`,
|
|
226
|
+
or per-document `leptris_document_set_allocators`) before the first
|
|
227
|
+
allocation they should govern, and route host-language allocations
|
|
228
|
+
the engine will free through `leptris_alloc_buffer` so allocator and
|
|
229
|
+
CRT always match.
|