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; +}
