leptris 1.9.312.1 → 1.9.313.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0f5231d3973de22dc4db540fb5edfac2e567500de54b9f68901806e29d99ebc
4
- data.tar.gz: 2480900d27aa291e5e5bed4f6481cb680051e1ddabefeb9f419b3adca9bc1815
3
+ metadata.gz: 716410c15581403307f3b94bd529c21d2bc778478bcfca064b6778f8e1826a9b
4
+ data.tar.gz: c566d9e1828cd1803523b0eb99ffc9b861995aee576ef7fae6d24ac536bea7d0
5
5
  SHA512:
6
- metadata.gz: bdf5c50d694d51b5db9a859e1c24a4f11ed90c49f977e12fd6fe882e190d84078470c0ade2ab0307b314b9f8de6c628e24a0d57313b8df4d0655eead19d7dbd0
7
- data.tar.gz: e1b78d323c99d8033aef4132475c5dd74d8ac532fa524d1714054391afd5fcd9e72403f71257384dc3a8e5d6edaabd627d2d155cc454f4b7a9bef88618e95db2
6
+ metadata.gz: 66c08d6699d345e0a74c313c9d02bef8b84a6f25e0b310e846a5694dd01e78c45c983f1871bba57e4711564208d90e6710d08d2a499d88582c1ec4cf3ab3f211
7
+ data.tar.gz: ecda89d28b0ad3e7954a93064c6abeaf430159bad758c0dcf88db4c4ad20fe7e6b56a17201142ead36aac05c64a025c759dfff7736e36969dc2635265f302083
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [1.9.313.0] - 2026-10-07
2
+
3
+ ### Fixed — engine sync (libleptris 1.9.313)
4
+
5
+ - **Absorption-aware release at the C ABI (#1557, engine #1566)**:
6
+ the follow-up to the #386 mitigation — `leptris_document_free`
7
+ now consults the absorption state itself. The anchor dying while
8
+ `absorbed_handle > 0` detaches the source and defers its release
9
+ to the outstanding holder's free (`absorbed_deferred`); handles
10
+ released before the anchor keep the #1548 handle-only no-op. C
11
+ consumers no longer need to mirror the absorbed flag at their own
12
+ release sites; the binding's 1.9.311.2 mitigation stays as
13
+ defense-in-depth. Both release orderings spec-pinned.
14
+
1
15
  ## [1.9.312.1] - 2026-10-07
2
16
 
3
17
  ### Added
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.312"
11
+ LIBLEPTRIS_VERSION = "1.9.313"
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.312.1"
4
+ VERSION = "1.9.313.0"
5
5
  end
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.312
4
+ - libleptris v1.9.313
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.313] - 2026-10-07
4
+
5
+ ### Fixed
6
+
7
+ - **Absorbed sources defer their release to outstanding handles
8
+ (#1557, PR #1566).** Follow-up to #1548's
9
+ `leptris_document_absorb`: the absorber's free cleared the
10
+ absorbed state before recursing, so a source handle that
11
+ OUTLIVED the absorber read the cleared flag and re-released the
12
+ already-destroyed pool — heap corruption (glibc abort /
13
+ `malloc_zone_error`), deterministic under exit-time finalizers
14
+ (leptris-ruby#386, 50-iteration repro). When the anchor dies
15
+ while `absorbed_handle > 0` it now detaches the source and marks
16
+ `absorbed_deferred`: the holder's `leptris_document_free`
17
+ performs the FULL release; handles released before the anchor
18
+ keep the #1548 handle-only no-op. Consumers no longer need to
19
+ mirror the absorbed flag at their own release sites. Gates: the
20
+ repro red under ASAN at leptris.c:937, green after; mutation
21
+ suite 27/27 ASAN + Release; full ctest 1866/1866; leaks 0.
22
+
23
+
24
+
3
25
  ## [1.9.312] - 2026-10-06
4
26
 
5
27
  ### Added
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.312
6
+ VERSION 1.9.313
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -933,9 +933,11 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
933
933
 
934
934
  /* #1548: an absorbed document's MEMORY belongs to its absorber
935
935
  * (it rides the absorber's child_docs chain). A caller free
936
- * releases only the outstanding handle. */
937
- if (doc->absorbed_by) {
938
- doc->absorbed_handle = 0;
936
+ * releases only the outstanding handle. #1557: unless the
937
+ * anchor died first and DEFERRED the release to this holder —
938
+ * then this free is the real one. */
939
+ if (doc->absorbed_by && !doc->absorbed_deferred) {
940
+ if (doc->absorbed_handle > 0) doc->absorbed_handle--;
939
941
  return;
940
942
  }
941
943
 
@@ -1021,12 +1023,24 @@ LEPTRIS_API void leptris_document_free(struct leptris_document* doc) {
1021
1023
  * doesn't try to walk the
1022
1024
  * adopted subtree. */
1023
1025
  ch->root = NULL;
1024
- /* #1548: the anchor's free is the REAL one — clear the
1025
- * absorbed state so the early handle-return above does
1026
- * not swallow it. */
1027
- ch->absorbed_by = NULL;
1028
- ch->absorbed_handle = 0;
1029
- leptris_document_free(ch);
1026
+ if (ch->absorbed_by && ch->absorbed_handle > 0) {
1027
+ /* #1557: a caller still holds this absorbed handle.
1028
+ * Freeing here would strand it on freed memory (its
1029
+ * later free read the cleared flag and re-released
1030
+ * the pool — the leptris-ruby#386 double free).
1031
+ * DEFER: detach from our chain and hand the release
1032
+ * to the holder's leptris_document_free, which sees
1033
+ * absorbed_deferred and runs the full release. */
1034
+ ch->absorbed_deferred = 1;
1035
+ ch->next_adopted = NULL;
1036
+ } else {
1037
+ /* #1548: the anchor's free is the REAL one — clear
1038
+ * the absorbed state so the early handle-return
1039
+ * above does not swallow it. */
1040
+ ch->absorbed_by = NULL;
1041
+ ch->absorbed_handle = 0;
1042
+ leptris_document_free(ch);
1043
+ }
1030
1044
  ch = next;
1031
1045
  }
1032
1046
  doc->child_docs = NULL;
@@ -195,6 +195,11 @@ struct leptris_document {
195
195
  * (the anchor frees the memory later). */
196
196
  struct leptris_document* absorbed_by;
197
197
  int absorbed_handle;
198
+ /* #1557: the absorber died while this handle was outstanding —
199
+ * the anchor deferred the pool release to THIS holder's free,
200
+ * so leptris_document_free must run the full release (not the
201
+ * handle-only early return). */
202
+ int absorbed_deferred;
198
203
  /* XML Declaration support */
199
204
  char* xml_version; /* "1.0", "1.1", etc. or NULL if not present */
200
205
  int standalone; /* -1=not set, 0=no, 1=yes */
@@ -670,6 +670,32 @@ TEST(CrossDocumentLeafAdoption, RootLevelLeafArgumentAdoptsByKind) {
670
670
  }
671
671
  }
672
672
 
673
+ // #1557: an absorbed source's handle may OUTLIVE the absorber. The
674
+ // anchor's free used to clear the absorbed state, so the holder's
675
+ // later free re-released the same pool — heap corruption. The anchor
676
+ // now defers the release to the outstanding handle.
677
+ TEST(AbsorbAdoption, SourceHandleMayOutliveAnchor) {
678
+ for (int i = 0; i < 50; i++) {
679
+ LeptrisDocument dest = Parse("<main><keep/></main>");
680
+ ASSERT_NE(dest, nullptr);
681
+ LeptrisDocument src = Parse("<scratch><a/></scratch>");
682
+ ASSERT_NE(src, nullptr);
683
+ ASSERT_EQ(leptris_document_absorb(dest, src), LEPTRIS_OK);
684
+ leptris_document_free(dest); /* anchor dies first */
685
+ leptris_document_free(src); /* holder releases after */
686
+ }
687
+ }
688
+
689
+ TEST(AbsorbAdoption, SourceHandleFreedBeforeAnchorStillNoOp) {
690
+ LeptrisDocument dest = Parse("<main><keep/></main>");
691
+ ASSERT_NE(dest, nullptr);
692
+ LeptrisDocument src = Parse("<scratch><a/></scratch>");
693
+ ASSERT_NE(src, nullptr);
694
+ ASSERT_EQ(leptris_document_absorb(dest, src), LEPTRIS_OK);
695
+ leptris_document_free(src); /* handle-only: memory stays with anchor */
696
+ leptris_document_free(dest); /* releases everything incl. the source */
697
+ }
698
+
673
699
  TEST(AbsorbAdoption, SpliceAfterAbsorbIsZeroCopy) {
674
700
  LeptrisDocument dst = Parse("<main><keep/></main>");
675
701
  ASSERT_NE(dst, nullptr);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
3
3
  "name": "leptris",
4
- "version": "1.9.312",
4
+ "version": "1.9.313",
5
5
  "description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
6
6
  "homepage": "https://github.com/leptris/leptris",
7
7
  "license": "MIT",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.312.1
4
+ version: 1.9.313.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-06 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi