Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package radare2 for openSUSE:Factory checked 
in at 2026-04-15 20:42:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/radare2 (Old)
 and      /work/SRC/openSUSE:Factory/.radare2.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "radare2"

Wed Apr 15 20:42:11 2026 rev:13 rq:1347121 version:6.1.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/radare2/radare2.changes  2025-12-08 
11:55:45.474021265 +0100
+++ /work/SRC/openSUSE:Factory/.radare2.new.21863/radare2.changes       
2026-04-15 20:42:19.142701757 +0200
@@ -1,0 +2,135 @@
+Tue Apr 14 22:46:31 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.1.4 (bsc#1262142, CVE-2026-40499):
+  * Analysis: improve autoname scoring, jmptbl detection, and performance
+  * Add callargs modifier, rnum expressions, and typed function context
+  * Refactor autoname into plugin; extend RAnalPlugin hooks
+  * Fix leaks, overflows, and command injection in analysis scripts
+  * Improve string detection, wide strings, and switch/case analysis
+  * Arch: fix v850/nds32 ESIL, optimize to O(1), improve pseudo support
+  * Cache capstone options and improve multi-arch disassembly
+  * ASM: add camel syntax support, unify via RArch API
+  * Bin: major parser fixes (ELF, Mach-O, PE, DEX, PDB, WAD, XCOFF)
+  * Fix leaks, OOB reads/writes, overflows, and improve bounds checks
+  * Improve Swift demangling, ARM hints, relocations, and imports
+  * Add nds32 reloc support and optimize kernelcache parsing
+  * Build: install to lib64, fix illumos and packaging issues
+  * CI: add GitHub Actions and FilC builds
+  * Console: fix multiple overflows, OOB issues, and improve performance
+  * Core: API renames, plugin load order, sandbox/config fixes
+  * Crash: extensive fixes (UAF, OOB, overflows, injections, fuzz bugs)
+  * Harden ELF, PDB, kernelcache, regex, disassemblers, and webserver
+  * Debug: improve ptrace, winkd support, breakpoints, checkpoints
+  * Disasm: cache flag lookups for performance
+  * FS/IO: fix leaks, bounds, sparse IO, and device handling
+  * HTTP/socket: webserver fixes and SSL fallback handling
+  * Print/projects: improve formatting, endian handling, project metadata
+  * Pseudo: add while/switch support and cleaner control flow
+  * Search/shell: improve commands, parsing, and usability
+  * Security: fix widespread command injection and sandbox escapes
+  * Tests/tools: improve r2r, CLI tools, fuzzing, and plugin support
+  * Types/util: parsing improvements, JSON/base64 updates, optimizations
+  * Visual: fix UAF/leaks, improve panels and UX
+  * Full changelog is available at:
+    https://github.com/radareorg/radare2/releases/tag/6.1.4
+
+-------------------------------------------------------------------
+Tue Apr 14 22:43:19 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.1.2:
+  * Analysis: preserve timeouts, improve bb/jmptbl validation and limits
+  * Optimize string detection and hot-path functions
+  * Add APIs for function signatures, vars limits, and instruction hints
+  * Fix overlapped functions, invalid code checks, and large bb handling
+  * API: remove deprecated librmagic/filetype APIs and name filter
+  * Arch: fix Thumb/endianness issues, add Python pseudo plugin
+  * ASM: unify settings via RArch, fix directives, add bf pseudo plugin
+  * Bin: improve ELF/Mach-O stripped detection and parsing safety
+  * Harden Mach-O bounds, optimize kernelcache and XNU parsing
+  * Fix many leaks (DEX, demangler, parsers) and infinite loops
+  * Improve DWARF handling and symbol/type extraction
+  * Build: improve meson, toolchains, and add ISO/docker support
+  * Console: preserve timeout, fix themes and UTF-8 handling
+  * Core: fix config bugs, improve startup and addressing support
+  * Crash: fix UAF, OOB, race conditions, regex bugs, and overflows
+  * Add safety checks across dotnet, Mach-O, DWARF, and webserver
+  * Debug/ESIL: safer execution and divide-by-zero handling
+  * FS/IO: fix HFS+, dyldcache speedups, safer zip handling
+  * Graph: add bb size limit option
+  * Print: merge commands, improve UTF-8 and formatting
+  * Projects/tools: new configs, plugin support, CLI improvements
+  * Search: faster analysis search and block buffering
+  * Shell: improve grep/macros and file operations
+  * Types: lazy-load, cache, and improve parsing (varargs, structs)
+  * Tests: expand fuzzing and test suites
+  * General cleanup, performance tuning, and safety improvements
+  * Full changelog is available at:
+    https://github.com/radareorg/radare2/releases/tag/6.1.4
+
+-------------------------------------------------------------------
+Tue Apr 14 22:40:51 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.1.0:
+  * Reimplement RBufRef using RRef; fix RLibDelHandler API
+  * Remove stale JAY code; improve analysis performance and CI speed
+  * Optimize type propagation, jump tables, and plugin integration
+  * Fix infinite loops, antidisasm tricks, and function autonaming
+  * Add new analysis options and trace import plugin (DRCOV)
+  * Improve RCore seek operations and naming APIs
+  * API: add RNum.getErr, enforce safe alloc macros, new helpers
+  * Arch: update ARC disasm, refactor sessions, remove unsafe string ops
+  * ASM: improve x86 validation, add CIL and ARC pseudo plugins
+  * Bin: major fixes for PE, ELF, Java, MDMP, LE, DEX; reduce memory use
+  * Add/import DWARF types, improve relocations and symbol handling
+  * Extensive memory leak fixes and parser hardening across formats
+  * Improve string handling, caching, and zero-copy optimizations
+  * Build: improve meson, remove zip deps, add 3rd-party plugin support
+  * Console: fix UTF-8 graphs and color propagation
+  * Core: improve plugin handling and background task stability
+  * Crash: fix multiple UAF, OOB, overflows, and injection issues
+  * Sanitize inputs (function names, demangler, callconv)
+  * Debug: add source breakpoints, ARM64/XNU support, FPU regs
+  * Disasm: improve string handling, comments, and color logic
+  * ESIL: extend x86 FPU emulation
+  * FS/IO: fixes and plugin reorganizations
+  * HTTP: fix sandbox webserver issues
+  * Hash/tools: minor fixes and output improvements
+  * General cleanup, safety checks, and performance optimizations
+  * Full changelog is available at:
+    https://github.com/radareorg/radare2/releases/tag/6.1.0
+
+-------------------------------------------------------------------
+Tue Apr 14 22:35:05 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.0.8:
+  * Migrate r_vector to RVec across core components
+  * Refactor and optimize type propagation (now plugin-based)
+  * Remove redundant anal.a2f and related duplication
+  * Improve caching, memoization, and performance in analysis
+  * Fix file corruption, null asserts, and command issues
+  * Enhance x86 (AT&T syntax, enter instruction) and z80 support
+  * Add initial .NET (CIL) disasm/asm support
+  * Improve Java, ELF, Mach-O, APK, and PDB handling
+  * Fix demangling, symbols, and relocation issues
+  * Resolve multiple memory leaks and parser bugs
+  * Fix UAF, OOB, overflows, and command injection vulnerabilities
+  * Improve GDB debugging and breakpoint handling
+  * Enhance disassembly visuals and color options
+  * Update ESIL operators and behavior
+  * Add support for APFS, GPT, BSD, APM partitions
+  * Improve IO handling and add new plugins
+  * Optimize performance (strbuf, memory usage)
+  * Improve console UI, themes, and terminal handling
+  * Refine SDK builds and CI pipelines
+  * Improve CLI tools (rabin2, rasm2, rafs2)
+  * Add JSON support and better help/version info
+  * Expand type parsing (typedef, enum, union)
+  * Improve socket/HTTP handling and downloads
+  * Add and refine tests and reporting
+  * General cleanup, safety checks, and code modernization
+  * Full changelog is available at:
+    https://github.com/radareorg/radare2/releases/tag/6.0.8
+
+- Expand %{bindir}/*
+
+-------------------------------------------------------------------

Old:
----
  quickjs-v0.11.0.tar.gz
  radare2-6.0.7.tar.gz
  radare2-testbins-65539cb70eba0901995e04b471975beca0c42c69.tar.gz
  sdb-2.2.4.tar.gz

New:
----
  quickjs-3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9.tar.gz
  radare2-6.1.4.tar.gz
  radare2-testbins-d6a4529fd6c8439a309abbcf00bb7a9efc9e8271.tar.gz
  sdb-2.4.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ radare2.spec ++++++
--- /var/tmp/diff_new_pack.EnMS70/_old  2026-04-15 20:42:22.438837698 +0200
+++ /var/tmp/diff_new_pack.EnMS70/_new  2026-04-15 20:42:22.446838028 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package radare2
 #
-# Copyright (c) 2025 SUSE LLC and contributors
+# Copyright (c) 2026 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,15 +16,15 @@
 #
 
 
-%global sdb_rev    2.2.4
-%global sdb_soname 2_2_4
+%global sdb_rev    2.4.2
+%global sdb_soname 2_4_2
 
-%global qjs_rev v0.11.0
+%global qjs_rev 3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9
 
-%global tests_rev 65539cb70eba0901995e04b471975beca0c42c69
+%global tests_rev d6a4529fd6c8439a309abbcf00bb7a9efc9e8271
 
 Name:           radare2
-Version:        6.0.7
+Version:        6.1.4
 Release:        0
 Summary:        Reverse Engineering Framework
 License:        GPL-3.0-only AND LGPL-3.0-only
@@ -89,7 +89,7 @@
 storage and supports JSON and arrays introspection.
 
 %ldconfig_scriptlets -n libsdb%{sdb_soname}
-%ldconfig_scriptlets -n %name
+%ldconfig_scriptlets -n %{name}
 
 %prep
 %autosetup -p1 -n %{name}-%{version}
@@ -106,7 +106,7 @@
 tar   -C test/bins --strip-components=1 -x -f %{SOURCE3}
 
 %build
-%__meson subprojects packagefiles --apply
+%{__meson} subprojects packagefiles --apply
 
 %meson \
   -Duse_sys_capstone=true \
@@ -123,9 +123,6 @@
 %install
 %meson_install
 
-mkdir -p %{buildroot}%{_docdir}/radare2
-mv %{buildroot}%{_datadir}/doc/radare2/* %{buildroot}%{_docdir}/radare2/
-
 chrpath -d %{buildroot}%{_bindir}/r2sdb
 
 # Conflict with snobol4 package
@@ -142,22 +139,39 @@
 %{_libdir}/libr_*.so
 %{_libdir}/pkgconfig/*.pc
 %{_includedir}/libr
-%dir %{_datadir}/radare2/%{version}/charsets
 %dir %{_datadir}/radare2/%{version}/fcnsign
 %dir %{_datadir}/radare2/%{version}/format
 %dir %{_datadir}/radare2/%{version}/opcodes
 %dir %{_datadir}/radare2/%{version}/syscall
-%{_datadir}/radare2/%{version}/charsets/*
+%dir %{_datadir}/radare2/%{version}/fortunes/
 %{_datadir}/radare2/%{version}/fcnsign/*
 %{_datadir}/radare2/%{version}/format/*
 %{_datadir}/radare2/%{version}/opcodes/*
 %{_datadir}/radare2/%{version}/syscall/*
+%{_datadir}/radare2/%{version}/fortunes/*
 
 %files
 %doc COMMUNITY.md CONTRIBUTING.md DEVELOPERS.md README.md
 %license COPYING.md
-%{_docdir}/radare2/*
-%{_bindir}/*
+%{_bindir}/r2
+%{_bindir}/r2agent
+%{_bindir}/r2pm
+%{_bindir}/r2r
+%{_bindir}/r2sdb
+%{_bindir}/rabin2
+%{_bindir}/radare2
+%{_bindir}/radiff2
+%{_bindir}/rafind2
+%{_bindir}/rafs2
+%{_bindir}/ragg2
+%{_bindir}/rahash2
+%{_bindir}/rapatch2
+%{_bindir}/rarun2
+%{_bindir}/rasign2
+%{_bindir}/rasm2
+%{_bindir}/ravc2
+%{_bindir}/rax2
+
 %{_libdir}/libr_*.so.*
 
 %dir %{_datadir}/radare2

++++++ pkgconfig.patch ++++++
--- /var/tmp/diff_new_pack.EnMS70/_old  2026-04-15 20:42:22.482839512 +0200
+++ /var/tmp/diff_new_pack.EnMS70/_new  2026-04-15 20:42:22.490839842 +0200
@@ -1,21 +1,21 @@
-diff --git a/pkgcfg/r_muta.pc.acr b/pkgcfg/r_muta.pc.acr
-index 11293840cb..62c9d945bb 100644
---- a/pkgcfg/r_muta.pc.acr
-+++ b/pkgcfg/r_muta.pc.acr
+Index: radare2-6.1.2/pkgcfg/r_muta.pc.acr
+===================================================================
+--- radare2-6.1.2.orig/pkgcfg/r_muta.pc.acr
++++ radare2-6.1.2/pkgcfg/r_muta.pc.acr
 @@ -8,4 +8,4 @@ Description: radare foundation libraries
  Version: @VERSION@
- Requires: r_util r_util r_util r_util
+ Requires: r_util
  Libs: -L${libdir} -lr_muta  
 -Cflags: -I${includedir}/libr 
 +Cflags: -I${includedir}/libr -I${includedir}/libr/r_muta
-diff --git a/pkgcfg/r_util.pc.acr b/pkgcfg/r_util.pc.acr
-index aa4f3cb60b..c3baf841ec 100644
---- a/pkgcfg/r_util.pc.acr
-+++ b/pkgcfg/r_util.pc.acr
-@@ -8,4 +8,4 @@ Description: radare foundation libraries
- Version: @VERSION@
+Index: radare2-6.1.2/pkgcfg/r_util.pc.acr
+===================================================================
+--- radare2-6.1.2.orig/pkgcfg/r_util.pc.acr
++++ radare2-6.1.2/pkgcfg/r_util.pc.acr
+@@ -9,4 +9,4 @@ Version: @VERSION@
  Requires: 
- Libs: -L${libdir} -lr_util  @LIBZIP@ @DL_LIBS@
+ Libs: -L${libdir} -lr_util  @DL_LIBS@ @LZ4_LDFLAGS@
+ Libs.private: @PKGCFG_ZLIBS@
 -Cflags: -I${includedir}/libr 
 +Cflags: -I${includedir}/libr -I${includedir}/libr/r_util
 

++++++ quickjs-v0.11.0.tar.gz -> 
quickjs-3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9.tar.gz ++++++
++++ 29572 lines of diff (skipped)

++++++ radare2-6.0.7.tar.gz -> radare2-6.1.4.tar.gz ++++++
/work/SRC/openSUSE:Factory/radare2/radare2-6.0.7.tar.gz 
/work/SRC/openSUSE:Factory/.radare2.new.21863/radare2-6.1.4.tar.gz differ: char 
13, line 1

++++++ radare2-testbins-65539cb70eba0901995e04b471975beca0c42c69.tar.gz -> 
radare2-testbins-d6a4529fd6c8439a309abbcf00bb7a9efc9e8271.tar.gz ++++++
/work/SRC/openSUSE:Factory/radare2/radare2-testbins-65539cb70eba0901995e04b471975beca0c42c69.tar.gz
 
/work/SRC/openSUSE:Factory/.radare2.new.21863/radare2-testbins-d6a4529fd6c8439a309abbcf00bb7a9efc9e8271.tar.gz
 differ: char 14, line 1

++++++ sdb-2.2.4.tar.gz -> sdb-2.4.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/.github/workflows/ci.yml 
new/sdb-2.4.2/.github/workflows/ci.yml
--- old/sdb-2.2.4/.github/workflows/ci.yml      2025-11-23 14:37:31.000000000 
+0100
+++ new/sdb-2.4.2/.github/workflows/ci.yml      2026-03-11 20:13:35.000000000 
+0100
@@ -10,20 +10,20 @@
   build-windows:
     runs-on: windows-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Windows Dependencies
       run: pip install meson ninja
     - name: Building Sdb
       run: meson build && ninja -C build
     - name: Pub
-      uses: actions/upload-artifact@v5
+      uses: actions/upload-artifact@v7
       with:
         name: sdb_windows
         path: build/*.exe
   build-cxx:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Building Sdb
       run: make xxx
     - name: Testing gperf
@@ -31,7 +31,7 @@
   build-python:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Building Sdb
       run: export CFLAGS=-O0 ; make
     - name: Building python bindings
@@ -42,7 +42,7 @@
     runs-on: ubuntu-latest
     continue-on-error: true
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: install gperf
       run: sudo apt install gperf
     - name: building sdb
@@ -55,7 +55,7 @@
 #    runs-on: ubuntu-latest
 #    continue-on-error: true
 #    steps:
-#    - uses: actions/checkout@v5
+#    - uses: actions/checkout@v6
 #    - name: install gperf
 #      run: sudo apt install gperf
 #    - name: building sdb
@@ -67,7 +67,7 @@
   build-linux:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Meson Sdb
       run: |
         pip install meson ninja
@@ -83,25 +83,24 @@
     - name: Testing gperf
       run: make -C test/gperf
     - name: Pub
-      uses: actions/upload-artifact@v5
+      uses: actions/upload-artifact@v7
       with:
         name: sdb_linux
         path: dist/debian/sdb/*.deb
   build-heap:
     runs-on: ubuntu-latest
-    continue-on-error: true
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Building Sdb
-      run: export CFLAGS="-DUSE_SDB_HEAP=1 -Werror -Wall" && make -j4
+      run: make heapasan
     - name: Running tests
-      run: make test
+      run: make heapasantest
 #    - name: Testing gperf
 #      run: make -C test/gperf
   build-macos:
     runs-on: macos-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Building Sdb
       run: make -j4 && cd ..
     - name: Packaging
@@ -109,7 +108,7 @@
     - name: Running tests
       run: make test
     - name: Pub
-      uses: actions/upload-artifact@v5
+      uses: actions/upload-artifact@v7
       with:
         name: sdb_macos
         path: dist/macos/*.pkg
@@ -123,7 +122,7 @@
     - name: Packaging
       run: cd sdb/dist/cydia && make && cd ../../..
     - name: Pub
-      uses: actions/upload-artifact@v5
+      uses: actions/upload-artifact@v7
       with:
         name: sdb_cydia
         path: sdb/dist/cydia/*/*.deb
@@ -134,7 +133,7 @@
         sys: [MINGW64, UCRT64]
     runs-on: windows-latest
     steps:
-    - uses: actions/checkout@v5
+    - uses: actions/checkout@v6
     - name: Setup MSYS2
       uses: msys2/setup-msys2@v2
       with:
@@ -157,3 +156,72 @@
       shell: msys2 {0}
       run: meson compile -C build
 
+  check_release:
+    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
+    outputs:
+      is_release: ${{ steps.release.outputs.is }}
+      tag_name: ${{ steps.release.outputs.tag }}
+    needs:
+      - build-windows
+      - build-cxx
+      - build-python
+      - test-asan
+      - build-linux
+      - build-heap
+      - build-macos
+      - build-cydia
+      - msys2-w64-meson
+    runs-on: ubuntu-24.04
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v6
+        with:
+          fetch-depth: 0 # Download all git history and tags
+      - name: Check if is a release
+        run: |
+          TAG="`git describe --exact-match --tags ${{ github.sha }} || true`"
+          if [ -n "$TAG" ]; then
+            echo "tag=$TAG" >> $GITHUB_OUTPUT
+            echo "is=yes" >> $GITHUB_OUTPUT
+          else
+            echo "is=no" >> $GITHUB_OUTPUT
+          fi
+        id: release
+
+  release:
+    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' 
&& needs.check_release.outputs.is_release == 'yes' }}
+    needs:
+      - check_release
+    runs-on: ubuntu-24.04
+    env:
+      ASSET_FILES: |
+        dist/artifacts/sdb_windows/*.exe
+        dist/artifacts/sdb_linux/*.deb
+        dist/artifacts/sdb_macos/*.pkg
+        dist/artifacts/sdb_cydia/*.deb
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v6
+        with:
+          fetch-depth: 0 # Download all git history and tags
+      - name: Extract sdb version
+        shell: bash
+        run: echo "branch=`grep SDB_VERSION include/sdb/version.h | cut -d '"' 
-f 2`" >> $GITHUB_OUTPUT
+        id: version
+      - name: Download artifacts
+        uses: actions/download-artifact@v8
+        with:
+          path: dist/artifacts
+      - name: Create GitHub release
+        id: create_release
+        uses: 
softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
+        with:
+          name: ${{ steps.version.outputs.branch }}
+          tag_name: ${{ needs.check_release.outputs.tag_name }}
+          body_path: ./RELEASE_NOTES.md
+          draft: false
+          prerelease: false
+          generate_release_notes: false
+          files: |
+            ${{ env.ASSET_FILES }}
+            checksums.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/.github/workflows/codeql.yml 
new/sdb-2.4.2/.github/workflows/codeql.yml
--- old/sdb-2.2.4/.github/workflows/codeql.yml  2025-11-23 14:37:31.000000000 
+0100
+++ new/sdb-2.4.2/.github/workflows/codeql.yml  2026-03-11 20:13:35.000000000 
+0100
@@ -24,7 +24,7 @@
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v5
+        uses: actions/checkout@v6
 
       - name: Initialize CodeQL
         uses: github/codeql-action/init@v4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/Makefile new/sdb-2.4.2/Makefile
--- old/sdb-2.2.4/Makefile      2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/Makefile      2026-03-11 20:13:35.000000000 +0100
@@ -8,6 +8,8 @@
 LEAKOPTS=leak
 CFLAGS_ASAN=$(addprefix -fsanitize=,$(ASANOPTS)) $(CFLAGS)
 CFLAGS_LEAK=$(addprefix -fsanitize=,$(LEAKOPTS)) -lasan -lubsan
+SAN_CFLAGS=$(addprefix -fsanitize=,$(ASANOPTS)) -fno-omit-frame-pointer -O0 -g
+HEAP_CFLAGS=-DUSE_SDB_HEAP=1
 MKDIR=mkdir
 
 all: pkgconfig include/sdb/version.h
@@ -24,7 +26,7 @@
        @echo Nothing to do.
 endif
 
-.PHONY: test sdb.js pkgconfig dist w32dista asan
+.PHONY: test sdb.js pkgconfig dist w32dista asan asantest heapasan heapasantest
 
 include wasi.mk
 
@@ -42,25 +44,33 @@
        file src/sdb.wasm
 
 test:
-       ${MAKE} -C test
+       ${MAKE} -C test all
 
 heap:
        CFLAGS=-DUSE_SDB_HEAP=1 $(MAKE) -C src all
 
 asan:
        $(MAKE) include/sdb/version.h
-       CC=gcc LDFLAGS="$(CFLAGS_ASAN)" CFLAGS="$(CFLAGS_ASAN)" ${MAKE} -C src 
all
+       CC=gcc LDFLAGS="$(SAN_CFLAGS)" CFLAGS="$(SAN_CFLAGS)" ${MAKE} -C src 
all WITHPIC=0
 
 asantest:
        export ASAN_OPTIONS=detect_leaks=0 ; \
-       CC=gcc CFLAGS="$(CFLAGS_ASAN)" ${MAKE} -C test
+       CC=gcc CFLAGS="$(SAN_CFLAGS)" LDFLAGS="$(SAN_CFLAGS)" ${MAKE} -C test 
all
+
+heapasan:
+       $(MAKE) include/sdb/version.h
+       CC=gcc LDFLAGS="$(SAN_CFLAGS)" CFLAGS="$(HEAP_CFLAGS) $(SAN_CFLAGS)" 
${MAKE} -C src all WITHPIC=0
+
+heapasantest:
+       export ASAN_OPTIONS=detect_leaks=0 ; \
+       CC=gcc CFLAGS="$(HEAP_CFLAGS) $(SAN_CFLAGS)" LDFLAGS="$(SAN_CFLAGS)" 
${MAKE} -C test all
 
 leak:
        $(MAKE) include/sdb/version.h
        CC=gcc LDFLAGS="$(CFLAGS_LEAK)" CFLAGS="$(CFLAGS_LEAK)" $(MAKE) -C src 
all
 
 leaktest:
-       CC=gcc CFLAGS="$(CFLAGS_LEAK)" LDFLAGS="$(CFLAGS_LEAK)" $(MAKE) -C test
+       CC=gcc CFLAGS="$(CFLAGS_LEAK)" LDFLAGS="$(CFLAGS_LEAK)" $(MAKE) -C test 
all
 
 pkgconfig:
        [ -d pkgconfig ] && ${MAKE} -C pkgconfig || true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/config.mk new/sdb-2.4.2/config.mk
--- old/sdb-2.2.4/config.mk     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/config.mk     2026-03-11 20:13:35.000000000 +0100
@@ -1,4 +1,4 @@
-SDBVER=2.2.4
+SDBVER=2.4.2
 
 PREFIX?=/usr
 BINDIR=${PREFIX}/bin
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/include/sdb/ht_inc.h 
new/sdb-2.4.2/include/sdb/ht_inc.h
--- old/sdb-2.2.4/include/sdb/ht_inc.h  2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/include/sdb/ht_inc.h  2026-03-11 20:13:35.000000000 +0100
@@ -61,6 +61,10 @@
        ut32 value_len;
 } HT_(Kv);
 
+/*
+ * Free resources owned by kv, but do not free kv itself.
+ * Hashtable entries live inline inside bucket arrays.
+ */
 typedef void (*HT_(KvFreeFunc))(HT_(Kv) *);
 typedef KEY_TYPE (*HT_(DupKey))(const KEY_TYPE);
 typedef VALUE_TYPE (*HT_(DupValue))(const VALUE_TYPE);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/include/sdb/sdb.h 
new/sdb-2.4.2/include/sdb/sdb.h
--- old/sdb-2.2.4/include/sdb/sdb.h     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/include/sdb/sdb.h     2026-03-11 20:13:35.000000000 +0100
@@ -368,6 +368,7 @@
 SDB_API Sdb *sdb_ns_path(Sdb *s, const char *path, int create);
 SDB_API void sdb_ns_init(Sdb* s);
 SDB_API void sdb_ns_free(Sdb* s);
+SDB_API void sdb_ns_reset(Sdb* s);
 SDB_API void sdb_ns_lock(Sdb *s, int lock, int depth);
 SDB_API void sdb_ns_sync(Sdb* s);
 SDB_API int sdb_ns_set(Sdb *s, const char *name, Sdb *r);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/include/sdb/version.h 
new/sdb-2.4.2/include/sdb/version.h
--- old/sdb-2.2.4/include/sdb/version.h 2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/include/sdb/version.h 2026-03-11 20:13:35.000000000 +0100
@@ -1 +1 @@
-#define SDB_VERSION "2.2.4"
+#define SDB_VERSION "2.4.2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/memcache/protocol.c 
new/sdb-2.4.2/memcache/protocol.c
--- old/sdb-2.2.4/memcache/protocol.c   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/memcache/protocol.c   2026-03-11 20:13:35.000000000 +0100
@@ -207,6 +207,10 @@
                        net_printf (fd, "ERROR\r\n");
                        return 0;
                }
+               if (bytes > (MCSDB_MAX_BUFFER - 2)) {
+                       net_printf (fd, "CLIENT_ERROR data too big\r\n");
+                       return 0;
+               }
                c->mode = 1; // read N bytes
                //c->next = 0;
                c->idx = c->next;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/meson.build new/sdb-2.4.2/meson.build
--- old/sdb-2.2.4/meson.build   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/meson.build   2026-03-11 20:13:35.000000000 +0100
@@ -1,4 +1,4 @@
-project('sdb', 'c', meson_version : '>=0.60.0', version : '2.2.4')
+project('sdb', 'c', meson_version : '>=0.60.0', version : '2.4.2')
 
 pkgconfig_mod = import('pkgconfig')
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/array.c new/sdb-2.4.2/src/array.c
--- old/sdb-2.2.4/src/array.c   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/array.c   2026-03-11 20:13:35.000000000 +0100
@@ -183,10 +183,10 @@
                ptr = (char *)Aindexof (nstr, idx);
                if (ptr) {
                        int lptr = (nstr + lstr + 1) - ptr;
-                       char *p_1 = ptr > nstr? ptr - 1: ptr;
-                       *p_1 = 0;
-                       lnstr = ptr - nstr - 1;
-                       if (lnstr < 0) {
+                       if (ptr > nstr) {
+                               ptr[-1] = 0;
+                               lnstr = ptr - nstr - 1;
+                       } else {
                                lnstr = 0;
                        }
                        memcpy (x, nstr, lnstr);
@@ -282,13 +282,11 @@
        if (str_lp < str_e) {
                memcpy (nstr_p, str_lp, str_e - str_lp);
                nstr_p += str_e - str_lp;
-               *(nstr_p) = '\0';
+               *nstr_p = '\0';
+       } else if (nstr_p != nstr) {
+               *--nstr_p = '\0';
        } else {
-               if (nstr_p > nstr) {
-                       *(--nstr_p) = '\0';
-               } else {
-                       *nstr_p = '\0';
-               }
+               *nstr_p = '\0';
        }
        sdb_set_owned (s, key, nstr, cas);
        sdb_gh_free (vals);
@@ -361,7 +359,6 @@
        }
        // XXX: should we cache sdb_alen value inside kv?
        len = sdb_alen (str);
-       lstr--;
        if (idx < 0 || idx == len) { // append
                return sdb_array_insert (s, key, -1, val, cas);
        }
@@ -389,13 +386,15 @@
                        return false;
                }
                ptr = nstr + diff;
-               //memcpy (nstr, str, lstr+1);
                memcpy (nstr, str, diff);
                memcpy (ptr, val, lval + 1);
                usr = Aindexof (str, idx + 1);
                if (usr) {
+                       size_t usr_len = (str + lstr + 1) - usr;
                        ptr[lval] = SDB_RS;
-                       strcpy (ptr + lval + 1, usr);
+                       memcpy (ptr + lval + 1, usr, usr_len);
+               } else {
+                       nstr[diff + lval] = '\0';
                }
                int ret = sdb_set (s, key, nstr, cas);
                sdb_gh_free (nstr);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/buffer.inc.c 
new/sdb-2.4.2/src/buffer.inc.c
--- old/sdb-2.2.4/src/buffer.inc.c      2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/buffer.inc.c      2026-03-11 20:13:35.000000000 +0100
@@ -3,6 +3,9 @@
 #include "sdb/buffer.h"
 
 void buffer_initialize(buffer *s, BufferOp op, int fd, char *buf, ut32 len) {
+       if (!s || !buf || len == 0) {
+               return;
+       }
        s->x = buf;
        s->fd = fd;
        s->op = op;
@@ -37,7 +40,10 @@
        if (!s || !s->x || !buf) {
                return 0;
        }
-       while (len > (n = s->n - s->p)) {
+       while (len > 0 && (n = s->n - s->p) < len) {
+               if (s->p > s->n) {
+                       return 0; /* invalid buffer state */
+               }
                memcpy (s->x + s->p, buf, n);
                s->p += n; buf += n; len -= n;
                if (!buffer_flush (s)) {
@@ -45,8 +51,10 @@
                }
        }
        /* now len <= s->n - s->p */
-       memcpy (s->x + s->p, buf, len);
-       s->p += len;
+       if (len > 0) {
+               memcpy (s->x + s->p, buf, len);
+               s->p += len;
+       }
        return 1;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/fmt.c new/sdb-2.4.2/src/fmt.c
--- old/sdb-2.2.4/src/fmt.c     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/fmt.c     2026-03-11 20:13:35.000000000 +0100
@@ -2,62 +2,59 @@
 
 #include "sdb/sdb.h"
 
-// TODO: convert into a function
-// TODO: Add 'a' format for array of pointers null terminated??
-// XXX SLOW CONCAT
-#define concat(x) if (x) { \
-       int size = 2 + strlen (x? x: "")+(out? strlen (out) + 4: 0); \
-       if (out) { \
-               char *o = (char *)sdb_gh_realloc (out, size); \
-               if (o) { \
-                       strcat (o, ","); \
-                       strcat (o, x); \
-                       out = o; \
-               } \
-       } else { \
-               out = sdb_strdup (x); \
-       } \
-}
-
 SDB_API char *sdb_fmt_tostr(void *p, const char *fmt) {
-       char buf[SDB_NUM_BUFSZ], *e_str, *out = NULL;
+       char buf[SDB_NUM_BUFSZ], *e_str = NULL;
        int n, len = 0;
        if (!p || !fmt) {
                return NULL;
        }
+       StrBuf *sb = strbuf_new ();
+       if (!sb) {
+               return NULL;
+       }
        for (; *fmt; fmt++) {
                n = 4;
                const ut8 *nbuf = ((ut8*)p) + len;
+               const char *val = NULL;
                switch (*fmt) {
                case 'b':
-                       concat (sdb_itoa ((ut64)*(nbuf), 10, buf, sizeof 
(buf)));
+                       val = sdb_itoa ((ut64)*(nbuf), 10, buf, sizeof (buf));
                        break;
                case 'h':
-                       concat (sdb_itoa ((ut64)*((short*)nbuf), 10, buf, 
sizeof (buf)));
+                       val = sdb_itoa ((ut64)*((short*)nbuf), 10, buf, sizeof 
(buf));
                        break;
                case 'd':
-                       concat (sdb_itoa ((ut64)*((int*)nbuf), 10, buf, sizeof 
(buf)));
+                       val = sdb_itoa ((ut64)*((int*)nbuf), 10, buf, sizeof 
(buf));
                        break;
                case 'q':
-                       concat (sdb_itoa (*((ut64*)nbuf), 10, buf, sizeof 
(buf)));
+                       val = sdb_itoa (*((ut64*)nbuf), 10, buf, sizeof (buf));
                        n = 8;
                        break;
                case 'z':
-                       concat ((char*)p + len);
+                       val = (char*)p + len;
                        break;
                case 's':
                        e_str = sdb_encode ((const ut8*)*((char**)nbuf), -1);
-                       concat (e_str);
-                       sdb_gh_free (e_str);
+                       val = e_str;
                        break;
                case 'p':
-                       concat (sdb_itoa ((ut64)*((size_t*)(nbuf)), 16, buf, 
sizeof (buf)));
+                       val = sdb_itoa ((ut64)*((size_t*)(nbuf)), 16, buf, 
sizeof (buf));
                        n = sizeof (size_t);
                        break;
                }
+               if (val && *val) {
+                       if (sb->len > 0) {
+                               strbuf_append (sb, ",", 0);
+                       }
+                       strbuf_append (sb, val, 0);
+               }
+               if (*fmt == 's' && e_str) {
+                       sdb_gh_free (e_str);
+                       e_str = NULL;
+               }
                len += R_MAX ((long)sizeof (void*), n); // align
        }
-       return out;
+       return strbuf_drain (sb);
 }
 
 // TODO: return false if array length != fmt length
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/heap.c new/sdb-2.4.2/src/heap.c
--- old/sdb-2.2.4/src/heap.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/heap.c    2026-03-11 20:13:35.000000000 +0100
@@ -2,8 +2,8 @@
 
 #include <stdio.h>
 #include <stdbool.h>
-#include <math.h>
 #include <stdint.h>
+#include <limits.h>
 #include "sdb/sdb.h"
 #include "sdb/heap.h"
 
@@ -48,19 +48,27 @@
 
 typedef struct sdb_heap_t {
        // Globals
-       int *last_address;
+       ut8 *last_address;
        free_list *free_list_start;
        // To reduce number of mmap calls.
-       int last_mapped_size; // 1;
+       size_t last_mapped_size; // pages, starts at 1.
 } SdbHeap;
 
 SDB_API void sdb_heap_fini(SdbHeap *heap);
-SDB_API void *sdb_heap_realloc(SdbHeap *heap, void *ptr, int size);
+SDB_API void *sdb_heap_realloc(SdbHeap *heap, void *ptr, size_t size);
+
+static void sdb_heap_fini_cb(void *data) {
+       sdb_heap_fini ((SdbHeap *)data);
+}
+
+static void *sdb_heap_realloc_cb(void *data, void *ptr, size_t size) {
+       return sdb_heap_realloc ((SdbHeap *)data, ptr, size);
+}
 
 static SdbHeap sdb_gh_custom_data = { NULL, NULL, 1};
 const SdbGlobalHeap sdb_gh_custom = {
-       (SdbHeapRealloc)sdb_heap_realloc,
-       (SdbHeapFini)sdb_heap_fini,
+       sdb_heap_realloc_cb,
+       sdb_heap_fini_cb,
        &sdb_gh_custom_data
 };
 
@@ -84,10 +92,11 @@
 #define ALIGNMENT 8
 #define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1))
 #define SDB_PAGE_SIZE 131072 // 96096*2 //sysconf(_SC_PAGESIZE)
-#define CEIL(X) ((X - (int)(X)) > 0 ? (int)(X + 1) : (int)(X))
-#define PAGES(size) (CEIL(size / (double)SDB_PAGE_SIZE))
+#define PAGES(size) (((size) + SDB_PAGE_SIZE - 1) / SDB_PAGE_SIZE)
 #define MIN_SIZE (ALIGN(sizeof(free_list) + META_SIZE))
 #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
+#define MAX_BLOCK_SIZE ((size_t)INT_MAX)
+#define MAX_MAPPED_PAGES (MAX_BLOCK_SIZE / SDB_PAGE_SIZE)
 
 // Meta sizes.
 #define META_SIZE ALIGN(sizeof(Header) + sizeof(Footer))
@@ -108,6 +117,12 @@
        return (Footer *)((const ut8*)header_ptr + header_ptr->size - 
FOOTER_SIZE);
 }
 
+static inline void write_footer(Header *ptr) {
+       Footer *footer = get_footer (ptr);
+       footer->size = ptr->size;
+       footer->free = ptr->free;
+}
+
 static Footer *get_prev_footer(const Header *header_ptr) {
        if (!header_ptr->has_prev) {
                return NULL;
@@ -132,10 +147,7 @@
 
 static void setFree(Header *ptr, int val) {
        ptr->free = val;
-       Footer *footer = get_footer(ptr);
-       footer->free = val;
-       // Copy size to footer size field.
-       footer->size = ptr->size;
+       write_footer (ptr);
 }
 
 // Set size in the header.
@@ -155,6 +167,35 @@
        return remove_offset (ptr)->size;
 }
 
+static bool size_to_block_size(size_t size, int *required_size) {
+       if (size == 0 || size > MAX_BLOCK_SIZE - META_SIZE) {
+               return false;
+       }
+       size_t total = ALIGN (size + META_SIZE);
+       if (total < MIN_SIZE) {
+               total = MIN_SIZE;
+       }
+       if (total > MAX_BLOCK_SIZE) {
+               return false;
+       }
+       *required_size = (int)total;
+       return true;
+}
+
+static size_t clamp_mapped_pages(size_t mapped_pages) {
+       if (mapped_pages > MAX_MAPPED_PAGES) {
+               return MAX_MAPPED_PAGES;
+       }
+       return mapped_pages;
+}
+
+static size_t grow_mapped_pages(size_t mapped_pages) {
+       if (mapped_pages >= (MAX_MAPPED_PAGES / 2)) {
+               return MAX_MAPPED_PAGES;
+       }
+       return mapped_pages * 2;
+}
+
 static void remove_from_free_list(SdbHeap *heap, Header *block) {
        setFree (block, USED);
 
@@ -234,12 +275,12 @@
        bool had_next = start_header->has_next;
        setSizeHeader(start_header, requested);
        start_header->has_next = true;
+       write_footer (start_header);
 
        // Add a header for newly created block (right block).
        Header header = {block_size, FREE, true, had_next};
        *new_block_header = header;
-       Footer footer = {block_size, FREE};
-       *get_footer(new_block_header) = footer;
+       write_footer (new_block_header);
        // Ensure the next block (if present) points back to the new block.
        Header *next_header = get_next_header(new_block_header);
        if (next_header) {
@@ -248,13 +289,13 @@
        append_to_free_list (heap, new_block_header);
 }
 
-static void *sdb_heap_malloc(SdbHeap *heap, int size) {
-       if (size <= 0) {
+static void *sdb_heap_malloc(SdbHeap *heap, size_t size) {
+       int required_size = 0;
+       if (!size_to_block_size (size, &required_size)) {
                return NULL;
        }
        // Size of the block can't be smaller than MIN_SIZE, as we need to store
        // free list in the body + header and footer on each side respectively.
-       int required_size = MAX (ALIGN (size + META_SIZE), MIN_SIZE);
        // Try to find a block big enough in already allocated memory.
        free_list *free_block = find_free_block (heap, required_size);
 
@@ -272,9 +313,17 @@
        // No free block was found. Allocate size requested + header (in full 
pages).
        // Each next allocation will be doubled in size from the previous one
        // (to decrease the number of mmap sys calls we make).
-       // int bytes = MAX (PAGES (required_size), heap->last_mapped_size) * 
SDB_PAGE_SIZE;
-       size_t bytes = PAGES (MAX (PAGES (required_size), 
heap->last_mapped_size)) * SDB_PAGE_SIZE;
-       heap->last_mapped_size *= 2;
+       size_t required_pages = PAGES ((size_t)required_size);
+       if (required_pages > MAX_MAPPED_PAGES) {
+               return NULL;
+       }
+       size_t mapped_pages = required_pages > heap->last_mapped_size
+               ? required_pages
+               : heap->last_mapped_size;
+       mapped_pages = clamp_mapped_pages (mapped_pages);
+       size_t bytes = mapped_pages * SDB_PAGE_SIZE;
+       int block_size = (int)bytes;
+       heap->last_mapped_size = grow_mapped_pages (mapped_pages);
 
        // last_address my not be returned by mmap, but makes it more efficient 
if it happens.
        void *new_region = mmap (NULL, bytes, PROT_READ | PROT_WRITE, 
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@@ -283,12 +332,10 @@
                return NULL;
        }
        // Create a header/footer for new block.
-       Header header = {(int)bytes, USED, false, false};
+       Header header = {block_size, USED, false, false};
        Header *header_ptr = (Header *)new_region;
        *header_ptr = header;
-       Footer footer = {};
-       footer.free = USED;
-       *get_footer (header_ptr) = footer;
+       write_footer (header_ptr);
 
        if (new_region == heap->last_address && heap->last_address != 0) {
                // if we got a block of memory after the last block, as we 
requested.
@@ -301,14 +348,14 @@
                }
        }
        // Split new region.
-       split (heap, header_ptr, bytes, required_size);
+       split (heap, header_ptr, block_size, required_size);
        // Update last_address for the next allocation.
-       heap->last_address = (int*)((ut8*)new_region + bytes);
+       heap->last_address = (ut8 *)new_region + block_size;
        // Return address behind the header (i.e. header is hidden).
        return add_offset (header_ptr);
 }
 
-static void coalesce(SdbHeap *heap, Header *current_header) {
+static Header *coalesce(SdbHeap *heap, Header *current_header) {
        Header *merged_header = current_header;
        Footer *merged_footer = get_footer (merged_header);
 
@@ -344,6 +391,7 @@
        }
 
        setFree (merged_header, FREE);
+       return merged_header;
 }
 
 static int unmap(SdbHeap *heap, Header *start_header, int size) {
@@ -359,8 +407,12 @@
        }
 
        // If this is the last block we've allocated using mmap, need to change 
last_address.
-       if ((void *)heap->last_address == (void *)start_header) {
-               heap->last_address = (int *)((ut8*)start_header - size);
+       if ((ut8 *)heap->last_address == (ut8 *)start_header + size) {
+               // Keep the mmap tail hint only when a mapped predecessor still 
exists.
+               heap->last_address = prev_header ? (ut8 *)start_header : NULL;
+               if (!heap->last_address) {
+                       heap->last_mapped_size = 1;
+               }
        }
        return munmap ((void *)start_header, (size_t)size);
 }
@@ -377,19 +429,17 @@
                return;
        }
 
+       append_to_free_list (heap, start_header);
+       start_header = coalesce (heap, start_header);
        int size = start_header->size;
        uintptr_t addr = (uintptr_t)start_header;
-       if (size % SDB_PAGE_SIZE == 0 && (addr % SDB_PAGE_SIZE) == 0) {
-               // if: full page is free (or multiple consecutive pages), 
page-aligned -> can munmap it.
-               unmap (heap, start_header, size);
-       } else {
-               append_to_free_list (heap, start_header);
-               coalesce (heap, start_header);
-               // if we are left with a free block of size bigger than 
PAGE_SIZE that is
-               // page-aligned, munmap that part.
-               if (size >= SDB_PAGE_SIZE && (addr % SDB_PAGE_SIZE) == 0) {
-                       split (heap, start_header, size, (size / SDB_PAGE_SIZE) 
* SDB_PAGE_SIZE);
-                       unmap (heap, start_header, (size / SDB_PAGE_SIZE) * 
SDB_PAGE_SIZE);
+       if (size >= SDB_PAGE_SIZE && (addr % SDB_PAGE_SIZE) == 0) {
+               int unmap_size = (size / SDB_PAGE_SIZE) * SDB_PAGE_SIZE;
+               if (unmap_size == size || size - unmap_size >= (int)MIN_SIZE) {
+                       if (unmap_size != size) {
+                               split (heap, start_header, size, unmap_size);
+                       }
+                       unmap (heap, start_header, unmap_size);
                }
        }
 }
@@ -411,26 +461,29 @@
 #endif
 }
 
-SDB_API void *sdb_heap_realloc(SdbHeap *heap, void *ptr, int size) {
+SDB_API void *sdb_heap_realloc(SdbHeap *heap, void *ptr, size_t size) {
        // If ptr is NULL, realloc() is identical to a call to malloc() for 
size bytes.
        if (!ptr) {
                return sdb_heap_malloc (heap, size);
        }
        // If size is zero and ptr is not NULL, a new, minimum sized object 
(MIN_SIZE) is
-       // allocated and the original object is freed.
+       // treated as free().
        if (size == 0 && ptr) {
                sdb_heap_free (heap, ptr);
-               return sdb_heap_malloc (heap, 1);
+               return NULL;
        }
 
-       int required_size = MAX (ALIGN (size + META_SIZE), MIN_SIZE);
+       int required_size = 0;
+       if (!size_to_block_size (size, &required_size)) {
+               return NULL;
+       }
        // If there is enough space, expand the block.
        Header *current_header = remove_offset (ptr);
        int current_size = current_header->size;
        int payload_size = current_size - HEADER_SIZE - FOOTER_SIZE;
 
        // if user requests to shorten the block.
-       if (size <= payload_size) {
+       if (size <= (size_t)payload_size) {
                return ptr;
        }
        Footer *current_footer = get_footer (current_header);
@@ -461,7 +514,10 @@
 
        // Not enough room to enlarge -> allocate new region.
        void *new_ptr = sdb_heap_malloc (heap, size);
-       int copy_size = payload_size < size ? payload_size : size;
+       if (!new_ptr) {
+               return NULL;
+       }
+       size_t copy_size = (size_t)payload_size < size ? (size_t)payload_size : 
size;
        memcpy (new_ptr, ptr, copy_size);
 
        // Free old location.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/ht.inc.c new/sdb-2.4.2/src/ht.inc.c
--- old/sdb-2.2.4/src/ht.inc.c  2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/ht.inc.c  2026-03-11 20:13:35.000000000 +0100
@@ -96,7 +96,8 @@
 // == (for storing ints).
 // keydup - function to duplicate to key (eg sdb_strdup), if NULL just does 
strup.
 // valdup - same as keydup, but for values but if NULL just assign
-// pair_free - function for freeing a keyvaluepair - if NULL just does free.
+// pair_free - function for freeing resources owned by a keyvaluepair.
+//             Callbacks may free owned payloads, but must not free kv itself.
 // calcsize - function to calculate the size of a value. if NULL, just stores 
0.
 static HtName_(Ht)* internal_ht_new(ut32 size, ut32 prime_idx, HT_(Options) 
*opt) {
        HtName_(Ht)* ht = (HtName_(Ht)*)sdb_gh_calloc (1, sizeof (*ht));
@@ -127,16 +128,13 @@
 SDB_API void Ht_(free)(HtName_(Ht)* ht) {
        if (SDB_LIKELY (ht)) {
                ut32 i, htsize = ht->size;
-               HT_(KvFreeFunc) freefn = ht->opt.freefn;
                HT_(Bucket) *table = ht->table;
                for (i = 0; i < htsize; i++) {
                        HT_(Bucket) *bt = &table[i];
                        HT_(Kv) *kv;
                        ut32 j;
-                       if (freefn) {
-                               BUCKET_FOREACH (ht, bt, j, kv) {
-                                       freefn (kv);
-                               }
+                       BUCKET_FOREACH (ht, bt, j, kv) {
+                               freefn (ht, kv);
                        }
                        sdb_gh_free (bt->arr);
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/ht_su.c new/sdb-2.4.2/src/ht_su.c
--- old/sdb-2.2.4/src/ht_su.c   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/ht_su.c   2026-03-11 20:13:35.000000000 +0100
@@ -71,11 +71,8 @@
 
        HtSU__Entry entry = { .key = key_copy, .val = value };
        HtSU__Insert result = HtSU__insert (&hm->inner, &entry);
-       if (!result.inserted) {
-               sdb_gh_free (key_copy);
-               return false;
-       }
-       return true;
+       sdb_gh_free (key_copy);
+       return result.inserted;
 }
 
 SDB_API bool ht_su_update(HtSU *hm, const char *key, ut64 value) {
@@ -126,8 +123,9 @@
                return false;
        }
 
-       // Then remove entry for the old key
-       HtSU__erase_at (iter);
+       // Then remove entry for the old key. Use key-based erase because 
insertion
+       // above may trigger a rehash and invalidate previously acquired 
iterators.
+       HtSU__erase (&hm->inner, (const HtSU__Key*) &old_key);
        return true;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/ht_uu.c new/sdb-2.4.2/src/ht_uu.c
--- old/sdb-2.2.4/src/ht_uu.c   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/ht_uu.c   2026-03-11 20:13:35.000000000 +0100
@@ -59,17 +59,18 @@
        if (!entry) {
                return false;
        }
+       const ut64 old_value = entry->val;
 
        // First try inserting the new key
-       HtUU__Entry new_entry = { .key = new_key, .val = entry->val };
+       HtUU__Entry new_entry = { .key = new_key, .val = old_value };
        HtUU__Insert result = HtUU__insert (&hm->inner, &new_entry);
        if (!result.inserted) {
                return false;
        }
 
-       // Then remove entry for the old key
-       HtUU__erase_at (iter);
-       return true;
+       // Then remove entry for the old key. Re-find by key to avoid using
+       // iterators that may have been invalidated by insertion rehash.
+       return HtUU__erase (&hm->inner, &old_key);
 }
 
 SDB_API bool ht_uu_delete(HtUU *hm, const ut64 key) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/json/indent.c 
new/sdb-2.4.2/src/json/indent.c
--- old/sdb-2.2.4/src/json/indent.c     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/json/indent.c     2026-03-11 20:13:35.000000000 +0100
@@ -15,6 +15,7 @@
 SDB_API char *sdb_json_indent(const char *s, const char *tab) {
        int idx, indent = 0;
        int instr = 0;
+       int sz_instr = 0;
        size_t o_size = 0;
        char *o, *O;
        if (!s) {
@@ -23,6 +24,24 @@
 
        size_t tab_len = strlen (tab);
        for (idx = 0; s[idx]; idx++) {
+               if (sz_instr) {
+                       if (s[idx] == '"') {
+                               sz_instr = 0;
+                       } else if (s[idx] == '\\' && s[idx + 1] == '"') {
+                               if (o_size == SIZE_MAX) {
+                                       return NULL;
+                               }
+                               o_size++;
+                       }
+                       if (o_size == SIZE_MAX) {
+                               return NULL;
+                       }
+                       o_size++;
+                       continue;
+               } else if (s[idx] == '"') {
+                       sz_instr = 1;
+               }
+
                if (o_size > INT_MAX - (indent * tab_len + 2)) {
                        return NULL;
                }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/json.c new/sdb-2.4.2/src/json.c
--- old/sdb-2.2.4/src/json.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/json.c    2026-03-11 20:13:35.000000000 +0100
@@ -150,15 +150,14 @@
                size_t buf_len = jslen + strlen (p) + strlen (v) + 7;
                char *buf = (char *)sdb_gh_malloc (buf_len);
                if (buf) {
-                       int curlen, is_str = isstring (v);
+                       int is_str = isstring (v);
                        const char *quote = is_str ? "\"" : "";
                        const char *comma = ""; // XX: or comma
                        if (js[0] && js[1] != '}') {
                                comma = ",";
                        }
-                       curlen = snprintf (buf, buf_len, "{\"%s\":%s%s%s%s",
-                               p, quote, v, quote, comma);
-                       strcpy (buf + curlen, js + 1);
+                       snprintf (buf, buf_len, "{\"%s\":%s%s%s%s%s",
+                               p, quote, v, quote, comma, js + 1);
                        // transfer ownership
                        sdb_set_owned (s, k, buf, cas);
                        return true;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/lock.c new/sdb-2.4.2/src/lock.c
--- old/sdb-2.2.4/src/lock.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/lock.c    2026-03-11 20:13:35.000000000 +0100
@@ -4,16 +4,10 @@
 #include "sdb/sdb.h"
 
 SDB_API bool sdb_lock_file(const char *f, char *buf, size_t buf_size) {
-       size_t len;
        if (!f || !*f || !buf || !buf_size) {
                return false;
        }
-       len = strlen (f);
-       if (len + 10 > buf_size) {
-               return false;
-       }
-       memcpy (buf, f, len);
-       strcpy (buf + len, ".lock");
+       snprintf (buf, buf_size, "%s.lock", f);
        return true;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/main.c new/sdb-2.4.2/src/main.c
--- old/sdb-2.2.4/src/main.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/main.c    2026-03-11 20:13:35.000000000 +0100
@@ -1,3 +1,5 @@
+/* sdb - MIT - Copyright 2011-2026 - pancake */
+
 # include <signal.h>
 #include <fcntl.h>
 #ifndef HAVE_SYSTEM
@@ -243,9 +245,13 @@
                }
                l--;
        }
-       char *n = sdb_strdup (name);
+       size_t name_len = strlen(name);
+       char *n = (char *)sdb_gh_malloc (name_len + 1);
+       if (!n) {
+               return NULL;
+       }
        char *v, *d = n;
-       for (v = (char*)n; *v; v++) {
+       for (v = (char*)name; *v; v++) {
                if (*v == '/' || *v == '-') {
                        *d++ = '_';
                        continue;
@@ -255,7 +261,7 @@
                }
                *d++ = *v;
        }
-       *d++ = 0;
+       *d = 0;
        return n;
 }
 
@@ -660,6 +666,11 @@
        int wd = open (out, O_RDWR, 0644);
        if (wd == -1) {
                wd = open (out, O_RDWR | O_CREAT, 0644);
+               if (wd == -1) {
+                       sdb_gh_free (out);
+                       sdb_gh_free (buf);
+                       return -1;
+               }
        } else {
                if (ftruncate (wd, 0) == -1) {
                        sdb_gh_free (out);
@@ -680,6 +691,7 @@
                fflush (stdout);
                close (wd);
                dup2 (999, 1);
+               close (999);
 #endif
        } else {
                eprintf ("Cannot create .%s\n", out);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/ns.c new/sdb-2.4.2/src/ns.c
--- old/sdb-2.2.4/src/ns.c      2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/ns.c      2026-03-11 20:13:35.000000000 +0100
@@ -44,25 +44,40 @@
                next.n = it->n;
                if (!in_list (list, ns)) {
                        ls_delete (s->ns, it); // free (it)
-                       free (ns->name);
-                       ns->name = NULL;
                        deleted = 1;
+                       ls_append (list, ns);
                        if (ns->sdb) {
-                               if (sdb_free (ns->sdb)) {
-                                       ns->sdb = NULL;
-                                       free (ns->name);
-                                       ns->name = NULL;
+                               Sdb *child = ns->sdb;
+                               ns->sdb = NULL;
+                               if (!in_list (list, child)) {
+                                       ls_append (list, child);
+                                       ns_free (child, list);
+                                       // force refs to 1 so sdb_free will 
actually free it
+                                       if (child->refs > 1) {
+                                               child->refs = 1;
+                                       }
+                                       sdb_free (child);
                                }
                        }
-                       ls_append (list, ns);
-                       ls_append (list, ns->sdb);
-                       ns_free (ns->sdb, list);
-                       sdb_free (ns->sdb);
+                       free (ns->name);
+                       ns->name = NULL;
                }
                if (!deleted) {
-                       sdb_free (ns->sdb);
+                       Sdb *child = ns->sdb;
+                       ns->sdb = NULL;
+                       if (child) {
+                               if (!in_list (list, child)) {
+                                       ls_append (list, child);
+                                       if (child->refs > 1) {
+                                               child->refs = 1;
+                                       }
+                                       sdb_free (child);
+                               }
+                       }
                        s->ns->free = NULL;
                        ls_delete (s->ns, it); // free (it)
+                       free (ns->name);
+                       ns->name = NULL;
                }
                free (ns);
                it = &next;
@@ -83,6 +98,32 @@
        ls_free (s->ns);
        s->ns = NULL;
 }
+
+// clear namespace references, freeing child sdbs whose refs drop to zero
+SDB_API void sdb_ns_reset(Sdb *s) {
+       SdbListIter *it;
+       SdbNs *ns;
+       if (!s || !s->ns) {
+               return;
+       }
+       ls_foreach_cast (s->ns, it, SdbNs*, ns) {
+               if (ns->sdb) {
+                       if (ns->sdb->refs > 1) {
+                               ns->sdb->refs--;
+                       } else if (ns->sdb->refs == 1) {
+                               // This SDB is only owned by this namespace, so 
we need to free it.
+                               // First recursively reset its namespaces to 
avoid freeing externally owned SDbs.
+                               sdb_ns_reset (ns->sdb);
+                               // Now safe to free since namespaces are 
already cleared (ns is NULL)
+                               sdb_free (ns->sdb);
+                       }
+               }
+               free (ns->name);
+               free (ns);
+       }
+       ls_free (s->ns);
+       s->ns = NULL;
+}
 
 static SdbNs *sdb_ns_new (Sdb *s, const char *name, ut32 hash) {
        char dir[SDB_MAX_PATH];
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/query.c new/sdb-2.4.2/src/query.c
--- old/sdb-2.2.4/src/query.c   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/query.c   2026-03-11 20:13:35.000000000 +0100
@@ -213,7 +213,7 @@
 next_quote:
                quot = (char *)strchr (quot, '"');
                if (quot) {
-                       if (quot > val && *(quot - 1) == '\\') {
+                       if (quot != val && *(quot - 1) == '\\') {
                                memmove (quot - 1, quot, strlen (quot) + 1);
                                goto next_quote;
                        }
@@ -778,8 +778,7 @@
                sdb_gh_free (buf);
        }
        if (out) {
-               res = out->buf;
-               sdb_gh_free (out);
+               res = strbuf_drain (out);
        } else {
                sdb_gh_free (res);
                res = NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/sdb.c new/sdb-2.4.2/src/sdb.c
--- old/sdb-2.2.4/src/sdb.c     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/sdb.c     2026-03-11 20:13:35.000000000 +0100
@@ -1,4 +1,4 @@
-/* sdb - MIT - Copyright 2011-2023 - pancake */
+/* sdb - MIT - Copyright 2011-2026 - pancake */
 
 #include <fcntl.h>
 #include <errno.h>
@@ -481,27 +481,28 @@
 }
 
 static char lastChar(const char *str) {
-       int len = strlen (str);
+       size_t len = strlen (str);
        return str[(len > 0)? len - 1: 0];
 }
 
 static bool match(const char *str, const char *expr) {
-       bool startsWith = *expr == '^';
-       bool endsWith = lastChar (expr) == '$';
+       const bool startsWith = *expr == '^';
+       const bool endsWith = lastChar (expr) == '$';
+       size_t str_len = strlen (str);
+       size_t expr_len = strlen (expr);
        if (startsWith && endsWith) {
-               return strlen (str) == strlen (expr) - 2 && \
-                       !strncmp (str, expr + 1, strlen (expr) - 2);
+               return str_len == expr_len - 2 && \
+                       !strncmp (str, expr + 1, expr_len - 2);
        }
        if (startsWith) {
-               return !strncmp (str, expr + 1, strlen (expr) - 1);
+               return !strncmp (str, expr + 1, expr_len - 1);
        }
        if (endsWith) {
-               int alen = strlen (str);
-               int blen = strlen (expr) - 1;
-               if (alen <= blen) {
+               size_t blen = expr_len - 1;
+               if (str_len <= blen) {
                        return false;
                }
-               const char *a = str + strlen (str) - blen;
+               const char *a = str + str_len - blen;
                return (!strncmp (a, expr, blen));
        }
        return strstr (str, expr);
@@ -614,6 +615,9 @@
                        }
                        if (vlen == sdbkv_value_len (kv) && !strcmp 
(sdbkv_value (kv), val)) {
                                sdb_hook_call (s, key, val);
+                               if (owned) {
+                                       sdb_gh_free (val);
+                               }
                                return kv->cas;
                        }
                        kv->cas = cas = nextcas (kv);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/strbuf.c new/sdb-2.4.2/src/strbuf.c
--- old/sdb-2.2.4/src/strbuf.c  2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/strbuf.c  2026-03-11 20:13:35.000000000 +0100
@@ -76,15 +76,17 @@
 }
 
 SDB_API char* strbuf_drain(StrBuf *sb) {
-       if (!sb || !sb->buf) {
+       if (!sb) {
+               return NULL;
+       }
+       if (!sb->buf) {
+               sdb_gh_free (sb);
                return NULL;
        }
-       
        char *buf = sb->buf;
        sb->buf = NULL;
        sb->len = 0;
        sb->size = 0;
-       
        sdb_gh_free (sb);
        return buf;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/tool.c new/sdb-2.4.2/src/tool.c
--- old/sdb-2.2.4/src/tool.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/tool.c    2026-03-11 20:13:35.000000000 +0100
@@ -1,3 +1,5 @@
+/* sdb - MIT - Copyright 2011-2026 - pancake */
+
 #include <sdb/sdb.h>
 #include <string.h>
 #include <stdlib.h>
@@ -17,6 +19,8 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 
+#define D if (0)
+
 #if HAVE_GPERF
 // #define COMPILE_GPERF 1
 #define COMPILE_GPERF 0
@@ -241,7 +245,7 @@
        sdb_gh_free (content);
        content = NULL;
 
-       fprintf (stdout, "SDBTOOL gperf=%s\n", file_gperf);
+       D fprintf (stdout, "SDBTOOL gperf=%s\n", file_gperf);
        if (compile_gperf) {
                char cmd[1024];
                snprintf (cmd, sizeof (cmd), "gperf -aclEDCIG --null-strings -H 
sdb_hash_c_%s"
@@ -322,7 +326,7 @@
        }
        Sdb *db = sdb_new (NULL, file_sdb, 0);
        if (sdb_text_load (db, file_txt)) {
-               fprintf (stderr, "maked %s\n", file_sdb);
+               D fprintf (stderr, "maked %s\n", file_sdb);
                if (mirror_mode) {
                        mirror_sdb (db);
                }
@@ -402,24 +406,13 @@
                sdb_gh_free(file_sdb);
                return false;
        }
-#if 0
-       // Add .gperf extension
-       size_t gperf_len = strlen(file_gperf) + 7; // + .gperf + null terminator
-       char *gperf_with_ext = (char *)sdb_gh_malloc(gperf_len);
-       if (gperf_with_ext) {
-               snprintf(gperf_with_ext, gperf_len, "%s.gperf", file_gperf);
-               sdb_gh_free(file_gperf);
-               file_gperf = gperf_with_ext;
-       }
-#endif
-
        const char *file_ref = compile_gperf? file_c: file_gperf;
        if (!file_exists(file_ref) || is_newer(file_txt, file_ref)) {
-               fprintf (stdout, "newer %s\n", file_c);
+               D fprintf (stdout, "newer %s\n", file_c);
                dothec (file_txt, file_gperf, file_c, compile_gperf, 
mirror_mode, output_dir);
        }
        if (!file_exists(file_sdb) || is_newer(file_txt, file_sdb)) {
-               fprintf (stdout, "newer %s\n", file_sdb);
+               D fprintf (stdout, "newer %s\n", file_sdb);
                dothesdb (file_txt, file_sdb, mirror_mode, output_dir);
        }
        sdb_gh_free(file_c);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/src/util.c new/sdb-2.4.2/src/util.c
--- old/sdb-2.2.4/src/util.c    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/src/util.c    2026-03-11 20:13:35.000000000 +0100
@@ -127,10 +127,12 @@
                if (!os) {
                        return sdb_strdup ("0");
                }
-               if (sl > 1) {
+               if (sl >= 2) {
                        memcpy (os, "0", 2);
-               } else {
+               } else if (sl == 1) {
                        *os = 0;
+               } else {
+                       return NULL;
                }
                return os;
        }
@@ -179,17 +181,24 @@
 // NOTE: Reuses memory. probably not bindings friendly..
 SDB_API char *sdb_array_compact(char *p) {
        char *e;
+       char *start = p;
+       int changed;
        // remove empty elements
-       while (*p) {
-               if (!strncmp (p, ",,", 2)) {
-                       p++;
-                       for (e = p + 1; *e == ','; e++) {};
-                       memmove (p, e, strlen (e) + 1);
-               } else {
-                       p++;
-               }
-       }
-       return p;
+       do {
+               changed = 0;
+               while (*p) {
+                       if (!strncmp (p, ",,", 2)) {
+                               p++;
+                               for (e = p + 1; *e == ','; e++) {};
+                               memmove (p, e, strlen (e) + 1);
+                               changed = 1;
+                       } else {
+                               p++;
+                       }
+               }
+               p = start;
+       } while (changed && *p);
+       return start;
 }
 
 // NOTE: Reuses memory. probably not bindings friendly..
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/Makefile new/sdb-2.4.2/test/Makefile
--- old/sdb-2.2.4/test/Makefile 2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/Makefile 2026-03-11 20:13:35.000000000 +0100
@@ -28,6 +28,9 @@
 $(SCRIPTS):
        ./$@
 
+$(BINS): $(SDB_LIB)
+$(SCRIPTS): $(SDB)
+
 $(BINS):
        $(CC) -o $@ [email protected] $(LDFLAGS) $(CFLAGS) $(SDB_CFLAGS) $(SDB_LDFLAGS)
        bash -c "time ./$@"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/api/Makefile 
new/sdb-2.4.2/test/api/Makefile
--- old/sdb-2.2.4/test/api/Makefile     2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/api/Makefile     2026-03-11 20:13:35.000000000 +0100
@@ -13,6 +13,8 @@
 leaks:
        $(MAKE) CFLAGS="-g -fsanitize=undefined -fsanitize=leak 
-fsanitize=address"
 
+array refs: $(SDB_LIB)
+
 array: array.c
        @$(CC) -o $@ [email protected] $(CFLAGS) $(LDFLAGS) $(SDB_CFLAGS) $(SDB_LDFLAGS)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/reset/Makefile 
new/sdb-2.4.2/test/reset/Makefile
--- old/sdb-2.2.4/test/reset/Makefile   2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/reset/Makefile   2026-03-11 20:13:35.000000000 +0100
@@ -1,6 +1,6 @@
 include ../sdb-test.mk
 
-all:
+all: $(SDB_LIB) $(SDB)
        rm -f db.test
        ${SDB} db.test foo=old
        $(CC) test-reset.c -o test-reset $(CFLAGS) $(LDFLAGS) $(SDB_CFLAGS) 
$(SDB_LDFLAGS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/sdb-test.mk 
new/sdb-2.4.2/test/sdb-test.mk
--- old/sdb-2.2.4/test/sdb-test.mk      2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/sdb-test.mk      2026-03-11 20:13:35.000000000 +0100
@@ -4,6 +4,13 @@
 SRCDIR=${CURRENT_DIR}/../src
 INCDIR=${CURRENT_DIR}/../include
 BASEDIR?=${SRCDIR}
+SDB_LIB?=${BASEDIR}/libsdb.a
 SDB_CFLAGS+=-I${INCDIR} -I${BASEDIR} ${USER_CFLAGS}
-SDB_LDFLAGS+=${BASEDIR}/libsdb.a ${USER_LDFLAGS}
+SDB_LDFLAGS+=${SDB_LIB} ${USER_LDFLAGS}
 SDB=${BASEDIR}/sdb
+
+${SDB_LIB}:
+       $(MAKE) -C ${BASEDIR} libsdb.a
+
+${SDB}:
+       $(MAKE) -C ${BASEDIR} sdb
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/unit/Makefile 
new/sdb-2.4.2/test/unit/Makefile
--- old/sdb-2.2.4/test/unit/Makefile    2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/unit/Makefile    2026-03-11 20:13:35.000000000 +0100
@@ -16,6 +16,8 @@
 asan:
        $(MAKE) CFLAGS="$(CFLAGS_ASAN)"
 
+$(OBJECTS): $(SDB_LIB)
+
 $(OBJECTS):%:%.c
        $(CC) $(SDB_CFLAGS) $(CFLAGS) $< -o $@ $(LDFLAGS) $(SDB_LDFLAGS)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/unit/test_hash.c 
new/sdb-2.4.2/test/unit/test_hash.c
--- old/sdb-2.2.4/test/unit/test_hash.c 2025-11-23 14:37:31.000000000 +0100
+++ new/sdb-2.4.2/test/unit/test_hash.c 2026-03-11 20:13:35.000000000 +0100
@@ -227,6 +227,36 @@
        free (kv->value);
 }
 
+static HtPPKv *expected_freed_kv;
+static bool received_embedded_kv;
+
+static void free_key_value_track_kv(HtPPKv *kv) {
+       if (kv == expected_freed_kv) {
+               received_embedded_kv = true;
+       }
+       free (kv->key);
+       free (kv->value);
+}
+
+bool test_freefn_receives_embedded_kv(void) {
+       HtPP *ht = ht_pp_new ((HtPPDupValue)strdup, free_key_value_track_kv, 
NULL);
+       mu_assert_notnull (ht, "ht alloc failed");
+       mu_assert ("insert key1", ht_pp_insert (ht, "key1", "value1"));
+
+       bool found = false;
+       expected_freed_kv = ht_pp_find_kv (ht, "key1", &found);
+       mu_assert ("key1 should exist", found);
+       mu_assert_notnull (expected_freed_kv, "key1 kv missing");
+       received_embedded_kv = false;
+
+       mu_assert ("delete key1", ht_pp_delete (ht, "key1"));
+       mu_assert ("freefn should receive embedded kv", received_embedded_kv);
+
+       expected_freed_kv = NULL;
+       ht_pp_free (ht);
+       mu_end;
+}
+
 bool should_not_be_caled(void *user, const char *k, void *v) {
        mu_fail ("this function should not be called");
        return false;
@@ -510,6 +540,7 @@
        mu_run_test (test_ht_grow);
        mu_run_test (test_ht_kvp);
        mu_run_test (test_ht_general);
+       mu_run_test (test_freefn_receives_embedded_kv);
        mu_run_test (test_empty_ht);
        mu_run_test (test_insert);
        mu_run_test (test_update);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.2.4/test/unit/test_heap.c 
new/sdb-2.4.2/test/unit/test_heap.c
--- old/sdb-2.2.4/test/unit/test_heap.c 1970-01-01 01:00:00.000000000 +0100
+++ new/sdb-2.4.2/test/unit/test_heap.c 2026-03-11 20:13:35.000000000 +0100
@@ -0,0 +1,95 @@
+#include "minunit.h"
+#include <string.h>
+#include <sdb/sdb.h>
+#include <sdb/heap.h>
+
+extern const SdbGlobalHeap sdb_gh_custom;
+
+static void use_custom_heap(void) {
+       sdb_gh_use (&sdb_gh_custom);
+}
+
+static bool test_heap_realloc_zero_returns_null(void) {
+       use_custom_heap ();
+       char *ptr = sdb_gh_malloc (64);
+       mu_assert_notnull (ptr, "custom heap malloc failed");
+       memset (ptr, 0x41, 64);
+       mu_assert_null (sdb_gh_realloc (ptr, 0), "realloc(size=0) must return 
NULL");
+       mu_end;
+}
+
+static bool test_heap_coalesces_middle_blocks(void) {
+       use_custom_heap ();
+       char *prefix = sdb_gh_malloc (64);
+       char *left = sdb_gh_malloc (64);
+       char *right = sdb_gh_malloc (64);
+       mu_assert_notnull (prefix, "prefix allocation failed");
+       mu_assert_notnull (left, "left allocation failed");
+       mu_assert_notnull (right, "right allocation failed");
+
+       sdb_gh_free (left);
+       sdb_gh_free (right);
+
+       char *merged = sdb_gh_malloc (96);
+       mu_assert_notnull (merged, "merged allocation failed");
+       mu_assert_ptreq (merged, left, "coalesced block should be reused in 
place");
+
+       sdb_gh_free (merged);
+       sdb_gh_free (prefix);
+       mu_end;
+}
+
+static bool test_heap_realloc_grows_in_place(void) {
+       use_custom_heap ();
+       char *prefix = sdb_gh_malloc (64);
+       char *ptr = sdb_gh_malloc (32);
+       char *next = sdb_gh_malloc (128);
+       char pattern[32];
+       memset (pattern, 0x5a, sizeof (pattern));
+       mu_assert_notnull (prefix, "prefix allocation failed");
+       mu_assert_notnull (ptr, "pointer allocation failed");
+       mu_assert_notnull (next, "next allocation failed");
+       memcpy (ptr, pattern, sizeof (pattern));
+
+       sdb_gh_free (next);
+       char *grown = sdb_gh_realloc (ptr, 96);
+       mu_assert_notnull (grown, "realloc grow failed");
+       mu_assert_ptreq (grown, ptr, "realloc should grow in place when next 
block is free");
+       mu_assert_memeq (grown, pattern, sizeof (pattern), "realloc must 
preserve payload data");
+
+       sdb_gh_free (grown);
+       sdb_gh_free (prefix);
+       mu_end;
+}
+
+static bool test_heap_page_free_smoke(void) {
+       enum { EXACT_PAGE_PAYLOAD = 131056 };
+       use_custom_heap ();
+       for (int i = 0; i < 64; i++) {
+               char *ptr = sdb_gh_malloc (EXACT_PAGE_PAYLOAD);
+               mu_assert_notnull (ptr, "page-sized allocation failed");
+               memset (ptr, 0x23, EXACT_PAGE_PAYLOAD);
+               sdb_gh_free (ptr);
+
+               // Repeatedly remap after a full-page free to catch stale tail 
hints.
+               ptr = sdb_gh_malloc (32);
+               mu_assert_notnull (ptr, "allocation after page free failed");
+               memset (ptr, 0x42, 32);
+               sdb_gh_free (ptr);
+       }
+       mu_end;
+}
+
+static int all_tests(void) {
+       mu_run_test (test_heap_realloc_zero_returns_null);
+       mu_run_test (test_heap_coalesces_middle_blocks);
+       mu_run_test (test_heap_realloc_grows_in_place);
+       mu_run_test (test_heap_page_free_smoke);
+       return tests_passed != tests_run;
+}
+
+int main(int argc, char **argv) {
+       int rc = all_tests ();
+       sdb_gh_use (NULL);
+       return rc;
+}

Reply via email to