[From nobody Tue Sep 22 15:53:06 2026
Received: (at submit) by bugs.debian.org; 19 Sep 2026 18:52:27 +0000
X-Spam-Checker-Version: SpamAssassin 4.0.1-bugs.debian.org_2005_01_02
 (2024-03-25) on buxtehude.debian.org
X-Spam-Level: 
X-Spam-Status: No, score=-118.2 required=4.0 tests=ALL_TRUSTED,BAYES_00,
 BODY_INCLUDES_PACKAGE,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,
 DKIM_VALID_AU,DKIM_VALID_EF,FROMDEVELOPER,HAS_PACKAGE,SPF_HELO_NONE,
 SPF_PASS,USER_IN_DKIM_WELCOMELIST autolearn=ham autolearn_force=no
 version=4.0.1-bugs.debian.org_2005_01_02
X-Spam-Bayes: score:0.0000 Tokens: new, 86; hammy, 150; neutral, 205; spammy,
 0. spammytokens:
 hammytokens:0.000-+--Hx-spam-relays-external:sk:stravin,
 0.000-+--H*RT:sk:stravin, 0.000-+--Hx-spam-relays-external:311,
 0.000-+--H*RT:311, 0.000-+--H*RT:108
Return-path: &lt;maxy@debian.org&gt;
Received: from stravinsky.debian.org ([2001:41b8:202:deb::311:108]:45198)
 by buxtehude.debian.org with esmtps
 (TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256)
 (Exim 4.96) (envelope-from &lt;maxy@debian.org&gt;) id 1x80Ak-005vQB-2c
 for submit@bugs.debian.org; Sat, 19 Sep 2026 18:52:27 +0000
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; 
 s=smtpauto.stravinsky;
 h=X-Debian-User:Content-Type:MIME-Version:Message-ID:
 Subject:To:From:Date:Reply-To:Cc:Content-Transfer-Encoding:Content-ID:
 Content-Description:In-Reply-To:References;
 bh=mPo6y1/rJYNyZXdDVYs3RTXaUGPfGKfOUmuRaNDuTK4=; b=duWPvLMzuWkfVkUp4berMdGjRk
 b3tGwy9VASWwXecWzjxGEPUSBVKueOSU75eZfDbqf3lHd8LRj/KDqqU+9eOnjT9qqq3H9mhVjYXO5
 mGkNVHy9QWjZD5Sc/BS2aYiaixv5hxm2VvxXfWfHytynMH7xQUvHoJhdzeDRt2573wZotbqHDJFcW
 2JvAQIucqrOUGPXexpZdoLfYu7cMtqoTLuwB8Ce3XeA+oGB7g27x1+uWGJbjMvEbRPho5Wf+WuoQE
 QFpgyzeerkChbzfubjSjh1Qsu9zAqPAiAaJij6w4D+68l2qSjr1DImsOx1+M7TXnkuBwNYrAUBqJV
 zNCzHFlA==;
Received: from authenticated-user by stravinsky.debian.org with esmtpsa
 (TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256)
 (Exim 4.96) (envelope-from &lt;maxy@debian.org&gt;) id 1x80Ai-000r8F-28
 for submit@bugs.debian.org; Sat, 19 Sep 2026 18:52:25 +0000
Date: Sat, 19 Sep 2026 20:52:15 +0200
From: Maximiliano Curia &lt;maxy@debian.org&gt;
To: submit@bugs.debian.org
Subject: jellyfish: FTBFS against python 3.15rc2
Message-ID: &lt;aq7Z3xd4LuoWFfCx@hecuba.gnuservers.com.ar&gt;
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=&quot;jqAm1ivm20HHoSbk&quot;
Content-Disposition: inline
X-Debian-User: maxy
Delivered-To: submit@bugs.debian.org

--jqAm1ivm20HHoSbk
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Disposition: inline

Package: src:jellyfish
Version: 2.3.1-6
User: debian-python@lists.debian.org
Usertags: python3.15-default
Tags: patch, ftbfs, forky, sid
Severity: important

Hi!

While rebuilding the python related packages against the Python 3.15rc2
as the default version we found that jellyfish fails to build from
source [1]. 

This is the error:

AIL: test_add (__main__.TestHashCounter.test_add)
Traceback (most recent call last):
   File &quot;/build/reproducible-path/jellyfish-2.3.1/swig/python/test_hash_counter.py&quot;, line 43, in test_add
     self.assertTrue(good)
AssertionError: False is not true

This failure is only present in arm64, and it happens when adding a
value that is an exact multiple of 2^val_len (where lower bits are 0),
an overflow on the large-value carry produced carry_shift == v. This
caused hash_counter::add to incorrectly assume the key was never
claimed, resulting in false duplicate detection (is_new = false) and
duplicate additions after size doubling.

I've created a patch that explicitly tracks whether the key was
claimed/found in large_hash_array instead of guessing via carry_shift ==
v. And I've also added a regression test. I've sent this patch upstream
as PR#214

I've applied this fix in the sandbox [3] to verify that it builds
successfully, please consider applying the patch to support the upcoming
3.15 version.

Setting the severity to important for now. Once Python 3.15 becomes the
default Python version this bug will become release critical.

Happy hacking,

[1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4704166/
[2]: https://github.com/gmarcais/Jellyfish/pull/214
[3]: https://debusine.debian.net/debian/r-python-python3.15/

-- 
&quot;Can you imagine what I would do if I could do all I can?&quot; -- Sun Tzu
Saludos /\/\ /\ &gt;&lt; `/

--jqAm1ivm20HHoSbk
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename=fix_hash_counter_resizing_carry.patch

Description: Fix key claim tracking during hash resizing on carry overflow
 When a value is a multiple of 2^val_len and overflows to carry during insertion,
 carry_shift equals the original value even though the key was claimed and its
 lower bits stored. Track key claim status explicitly to prevent duplicate adds
 and incorrect is_new return values after table resizing.
Author: Maximiliano Curia &lt;maxy@debian.org&gt;
Forwarded: no

Index: jellyfish/include/jellyfish/hash_counter.hpp
===================================================================
--- jellyfish.orig/include/jellyfish/hash_counter.hpp
+++ jellyfish/include/jellyfish/hash_counter.hpp
@@ -97,16 +97,17 @@ public:
 
     //    while(!ary_-&gt;add(k, v, &amp;carry_shift, is_new_ptr, id_ptr)) {
     while(true) {
-      if(ary_-&gt;add(k, v, &amp;carry_shift, is_new_ptr, id_ptr)) break;
+      bool key_claimed = false;
+      if(ary_-&gt;add(k, v, &amp;carry_shift, is_new_ptr, id_ptr, &amp;key_claimed)) break;
       handle_full_ary();
 
-      // If carry_shift == v, failed to allocate the first field for
+      // If !key_claimed, failed to allocate the first field for
       // key, hence status of is_new and value for id are not
-      // determined yet. On the other hand, if carry_shift &lt; v, we
+      // determined yet. On the other hand, if key_claimed is true, we
       // failed while adding extra field for large key, so the status
       // of is_new and value of id are known. We do not update them in future
       // calls.
-      if(carry_shift != v) {
+      if(key_claimed) {
         is_new_ptr = &amp;is_new_void;
         id_ptr     = &amp;id_void;
         v          = carry_shift;
@@ -156,9 +157,10 @@ public:
     uint64_t carry_shift = 0;
 
     while(true) {
-      if(ary_-&gt;update_add(k, v, &amp;carry_shift, tmp_key))
+      bool key_found = false;
+      if(ary_-&gt;update_add(k, v, &amp;carry_shift, tmp_key, &amp;key_found))
         return true;
-      if(carry_shift == v)
+      if(!key_found)
         return false;
       handle_full_ary();
       v = carry_shift;
Index: jellyfish/include/jellyfish/large_hash_array.hpp
===================================================================
--- jellyfish.orig/include/jellyfish/large_hash_array.hpp
+++ jellyfish/include/jellyfish/large_hash_array.hpp
@@ -288,10 +288,11 @@ public:
    * proper size doubling is perform, one need to call add again with
    * val set to carry_shift.
    */
-  inline bool add(const key_type&amp; key, mapped_type val, word* carry_shift, bool* is_new, size_t* id) {
+  inline bool add(const key_type&amp; key, mapped_type val, word* carry_shift, bool* is_new, size_t* id, bool* key_claimed = 0) {
     uint64_t hash = hash_matrix_.times(key);
     *carry_shift  = 0;
-    return add_rec(hash &amp; size_mask_, key, val, false, is_new, id, carry_shift);
+    if(key_claimed) *key_claimed = false;
+    return add_rec(hash &amp; size_mask_, key, val, false, is_new, id, carry_shift, key_claimed);
   }
 
   inline bool add(const key_type&amp; key, mapped_type val, word* carry_shift) {
@@ -332,12 +333,14 @@ public:
 
 
   // Optimization. Use tmp_key as buffer. Avoids allocation if update_add is called repeatedly.
-  bool update_add(const key_type&amp; key, mapped_type val, word* carry_shift, key_type&amp; tmp_key) {
+  bool update_add(const key_type&amp; key, mapped_type val, word* carry_shift, key_type&amp; tmp_key, bool* key_found = 0) {
     size_t          id;
     word*           w;
     const offset_t* o;
 
+    if(key_found) *key_found = false;
     if(get_key_id(key, &amp;id, tmp_key, (const word**)&amp;w, &amp;o)) {
+      if(key_found) *key_found = true;
       *carry_shift = 0;
       return add_rec_at(id, key, val, o, w, carry_shift);
     } else {
@@ -654,24 +657,27 @@ public:
   // carry_shift is properly set to the value of the carry shifted by
   // the right number of bits so a further call to add with
   // carry_shift as val finishes the operation.
-  bool add_rec(size_t id, const key_type&amp; key, word val, bool large, bool* is_new, size_t* eid, word* carry_shift) {
+  bool add_rec(size_t id, const key_type&amp; key, word val, bool large, bool* is_new, size_t* eid, word* carry_shift, bool* key_claimed = 0) {
     const offset_t *ao = 0;
     word	   *w  = 0;
 
     bool claimed = false;
     if(large)
       claimed = claim_large_key(&amp;id, &amp;ao, &amp;w);
-    else
+    else {
       claimed = claim_key(key, is_new, &amp;id, &amp;ao, &amp;w);
+      if(claimed &amp;&amp; key_claimed)
+        *key_claimed = true;
+    }
     if(!claimed) {
       *carry_shift = val &lt;&lt; (*carry_shift);
       return false;
     }
     *eid = id;
-    return add_rec_at(id, key, val, ao, w, carry_shift);
+    return add_rec_at(id, key, val, ao, w, carry_shift, key_claimed);
   }
 
-  bool add_rec_at(size_t id, const key_type&amp; key, word val, const offset_t* ao, word* w, word* carry_shift) {
+  bool add_rec_at(size_t id, const key_type&amp; key, word val, const offset_t* ao, word* w, word* carry_shift, bool* key_claimed = 0) {
     // Increment value
     word *vw       = w + ao-&gt;val.woff;
     word  cary     = add_val(vw, val, ao-&gt;val.boff, ao-&gt;val.mask1);
@@ -690,7 +696,7 @@ public:
     id = (id + reprobes_[0]) &amp; size_mask_;
     size_t ignore_eid;
     bool   ignore_is_new;
-    return add_rec(id, key, cary, true, &amp;ignore_is_new, &amp;ignore_eid, carry_shift);
+    return add_rec(id, key, cary, true, &amp;ignore_is_new, &amp;ignore_eid, carry_shift, key_claimed);
 
       // // Adding failed, table is full. Need to back-track and
       // // substract val.

--jqAm1ivm20HHoSbk--
]