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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Rakefile +1 -1
  4. data/lib/leptris/version.rb +1 -1
  5. data/lib/libleptris.so +0 -0
  6. data/vendor-src/README.md +1 -1
  7. data/vendor-src/libleptris/CHANGELOG.md +10 -0
  8. data/vendor-src/libleptris/CMakeLists.txt +1 -1
  9. data/vendor-src/libleptris/TODO.SOTA.md +82 -56
  10. data/vendor-src/libleptris/benchmarks/baseline.json +1 -0
  11. data/vendor-src/libleptris/bindings/rust/Cargo.lock +1 -1
  12. data/vendor-src/libleptris/bindings/rust/descriptor/Cargo.toml +1 -1
  13. data/vendor-src/libleptris/cli/output.c +11 -0
  14. data/vendor-src/libleptris/docs/MISSION-PLAN.md +12 -9
  15. data/vendor-src/libleptris/docs/guide/memory-model.md +229 -0
  16. data/vendor-src/libleptris/docs/guide/migrating-from-libxml2.md +131 -0
  17. data/vendor-src/libleptris/packaging/README.md +120 -0
  18. data/vendor-src/libleptris/packaging/conan/conanfile.py +120 -0
  19. data/vendor-src/libleptris/packaging/conan/test_package/CMakeLists.txt +7 -0
  20. data/vendor-src/libleptris/packaging/conan/test_package/conanfile.py +27 -0
  21. data/vendor-src/libleptris/packaging/conan/test_package/src/example.c +22 -0
  22. data/vendor-src/libleptris/packaging/debian/README.md +87 -0
  23. data/vendor-src/libleptris/packaging/debian/changelog +8 -0
  24. data/vendor-src/libleptris/packaging/debian/control +55 -0
  25. data/vendor-src/libleptris/packaging/debian/copyright +26 -0
  26. data/vendor-src/libleptris/packaging/debian/leptris.install +1 -0
  27. data/vendor-src/libleptris/packaging/debian/libleptris-dev.install +5 -0
  28. data/vendor-src/libleptris/packaging/debian/libleptris1.install +1 -0
  29. data/vendor-src/libleptris/packaging/debian/rules +34 -0
  30. data/vendor-src/libleptris/packaging/debian/source/format +1 -0
  31. data/vendor-src/libleptris/packaging/homebrew/leptris.rb +75 -0
  32. data/vendor-src/libleptris/scripts/perf_gate.py +51 -0
  33. data/vendor-src/libleptris/scripts/verify_manifest.py +182 -0
  34. data/vendor-src/libleptris/src/leptris/dtd/content_check.c +31 -23
  35. data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +52 -8
  36. data/vendor-src/libleptris/src/leptris/xinclude/xinclude.c +359 -244
  37. data/vendor-src/libleptris/src/leptris/xpath/functions_ext31.c +17 -11
  38. data/vendor-src/libleptris/src/leptris/xsd/compile.c +110 -7
  39. data/vendor-src/libleptris/src/leptris/xsd/datatypes.c +14 -4
  40. data/vendor-src/libleptris/src/leptris/xsd/validate.c +179 -19
  41. data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +4 -0
  42. data/vendor-src/libleptris/test/differential/README.md +100 -0
  43. data/vendor-src/libleptris/test/differential/corpus/basic.xml +13 -0
  44. data/vendor-src/libleptris/test/differential/corpus/cdata.xml +5 -0
  45. data/vendor-src/libleptris/test/differential/corpus/comments_pis.xml +1 -0
  46. data/vendor-src/libleptris/test/differential/corpus/dtd_model.xml +1 -0
  47. data/vendor-src/libleptris/test/differential/corpus/entities_dtd.xml +1 -0
  48. data/vendor-src/libleptris/test/differential/corpus/mixed_deep.xml +1 -0
  49. data/vendor-src/libleptris/test/differential/corpus/namespaces.xml +11 -0
  50. data/vendor-src/libleptris/test/differential/corpus/xpath_target.xml +1 -0
  51. data/vendor-src/libleptris/test/differential/package.json +9 -0
  52. data/vendor-src/libleptris/test/differential/run.mjs +52 -0
  53. data/vendor-src/libleptris/test/differential/run.py +61 -0
  54. data/vendor-src/libleptris/test/differential/run.rb +48 -0
  55. data/vendor-src/libleptris/test/dtd/test_dtd_validate.cpp +39 -0
  56. data/vendor-src/libleptris/test/xinclude/test_xinclude.cpp +274 -10
  57. data/vendor-src/libleptris/test/xpath/test_xpath.cpp +5 -0
  58. data/vendor-src/libleptris/test/xquery/qt3-dispositions/README.md +47 -0
  59. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-deep-eq.md +187 -0
  60. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-kinds-misc.md +48 -0
  61. data/vendor-src/libleptris/test/xquery/qt3-dispositions/assert-type.md +285 -0
  62. data/vendor-src/libleptris/test/xquery/qt3-dispositions/dep-gates.md +57 -0
  63. data/vendor-src/libleptris/test/xquery/qt3-dispositions/empty-query.md +11 -0
  64. data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-inline-no-params.md +48 -0
  65. data/vendor-src/libleptris/test/xquery/qt3-dispositions/env-unsupported.md +143 -0
  66. data/vendor-src/libleptris/test/xquery/qt3-dispositions/error-channel.md +782 -0
  67. data/vendor-src/libleptris/test/xquery/qt3-dispositions/named-exclude.md +85 -0
  68. data/vendor-src/libleptris/test/xquery/qt3-dispositions/query-exclude.md +379 -0
  69. data/vendor-src/libleptris/test/xquery/test_qt3.cpp +192 -26
  70. data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +524 -2
  71. data/vendor-src/libleptris/test/xsd/xsd-dispositions/README.md +59 -0
  72. data/vendor-src/libleptris/vcpkg.json +1 -1
  73. metadata +47 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3770bdb85662c6961aedd258ccf0fd05acfe6b9973a118a405020dc3446c28ab
4
- data.tar.gz: 8cc82d14ef2abb6d4b5ed108863d6858009ec5ba8ac53a2926c5d15e32443a8e
3
+ metadata.gz: c9d57ba010010d0eafdafc080938bc02ebf7d508799cdd01fbde67176d5f1e5f
4
+ data.tar.gz: 8d560c49128668a65a82a098e0918b7378957f5a07aa9e1f76b939709acd5283
5
5
  SHA512:
6
- metadata.gz: 9beeff955522554c2a9dd0d59d73c44f1fa8c7b7116fa2cc26851afbfa0cc7be35a2196e14a022281d60934c75eb86ce0f992ccec00ecfcaa13cbab82fdb63ea
7
- data.tar.gz: bf39d054e9f806e0e6a57a57460d92848bfe1eba4d0da4a4ccb94788dce8f077778db20211653e4f49fced598b6efabcf9123c05676a6884e8cc51c67a99bfd6
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.379"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.379.3"
4
+ VERSION = "1.9.380.1"
5
5
  end
data/lib/libleptris.so CHANGED
Binary file
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.379
4
+ - libleptris v1.9.380
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.379
6
+ VERSION 1.9.380
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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: 1394/1555 → 1555/1555
31
- **Measured 2026-10-21 (main after #1800): 1535/1555 verdict,
32
- 1555/1555 tree, zero false positives; floor pinned at 1535.** The
33
- remaining 20 verdict misses: adoption-agency (tests2:10, tests19:98,
34
- tests19:99, tests26:1, tests26:9), menuitem strays (menuitem:10/12),
35
- table/caption singletons (tests9:16, tests10:15, tests16:197,
36
- pending-spec-changes:2, tests20:48), tokenizer singletons (tests10:2,
37
- tests2:18, tests2:58, tests19:41, tests19:82) and two-heads
38
- (tests3:13). The remaining ~270 code-set divergences are prose-dialect
39
- labels from older corpus datasets; the verdict gate is presence-only.
40
- The slices already shipped: initial mode, raw-text EOF, unterminated
41
- close, frameset, fenced walk, InBody EOF allowed-set, in-select,
42
- foster-by-kind, unknown-doctype (#1778-#1791), raw-text strays,
43
- non-html-root, end-tag-too-early, after-body/frameset phases,
44
- start-tag phases, select/table/foreign, need-space-after-doctype
45
- (#1794), misplaced/stray-p, after-html codes, image (#1797, #1800).
46
- Ground every further slice in html5lib's actual source
47
- (~/src/external/html5lib):
48
- - [ ] escaped-state EOF codes (`unexpected-eof-in-text-mode`,
49
- `eof-in-script-in-script`) — the script-data state machine's
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
- - [ ] re-pin fresh (the ~41–99 slice figures are dated)
66
- - [ ] every remaining pin gets a disposition file or a fix
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
- - [ ] the lane's own queue (769→795 → its ceiling) to zero
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
- - [ ] ENTITY/ENTITIES attribute resolution
74
- - [ ] parameter entities, choice-model backtracking
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
- - [ ] `parse="xml"` (ownership-transfer splice exists — wire it)
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-21: 1.86-1.92x** (moxml `sota/04-performance`
94
- branch + leptris 1.9.376.0; harness `wt/460-harness/build_bench.rb`,
95
- 31 interleaved reps: 4.08ms vs Nokogiri 2.20ms for 250 records).
96
- Was 2.95x at the 1.9.363 era; the add_child alias and same-doc fast
97
- path landed since. StackProf attribution: `NativeNode.create_element`
98
- 14.6%, GC mark+sweep 19%, `prime_contract` 7.7%, attach path 45% of
99
- tree; the `invalidate_namespace_cache!` line attribution is a
100
- sampling artifact (deleting both attach invalidations gains ~5%).
101
- Harness: wt/460-harness/build_bench.rb. Decomposition on record.
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
- - [ ] parse→query→serialize pipeline row vs nokogiri/libxml2
117
- - [ ] build→transform (XSLT) pipeline row
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
- - [ ] C4 security.md: advisory channel, CVE-response process,
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
- - [ ] D1 leptris-descriptor published (crates.io; feat/294 branch
147
- lands), rides lockstep
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
- - [ ] D4 libxml2→libleptris migration guide (docs/guide/) + the
151
- nokogiri-compat surface documented
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
 
@@ -11,4 +11,4 @@ dependencies = [
11
11
 
12
12
  [[package]]
13
13
  name = "leptris-descriptor"
14
- version = "0.1.0"
14
+ version = "1.9.379"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "leptris-descriptor"
3
- version = "0.1.0"
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 ~41–99 by slice (2026-09/10) — **re-pin fresh** |
27
- | A5 | WHATWG/html5lib (#659) | 0 reds on the tree gate; verdict parity climbing on the #1771 error-verdict gate | **tree lane CLOSED** — 1555/1555, redlist retired (found during Phase 0); verdict gate flipped 2026-10-19: 1312/1555, floor pinned |
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 | open (parse="text" + fallback ship) |
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 | open |
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 | 1535/1555 (floor 1535; **red**) |
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.86-1.92x (**red**, sota/04 branch) |
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; PR #1799) |
173
- | Rides 1.9.377 | registries | 2026-10-21 | rubygems/PyPI 1.9.377.0, npm 0.1.21 |
174
- | Issues | all repos | 2026-10-21 | 0 open (ruby#514/#515 closed with gem verification) |
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.