From 9101c1ad61ea342a0587ff12f2daba3ffe4a0ce1 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Mon, 4 May 2026 10:01:48 +0200 Subject: [PATCH 1/3] added test --- python_tests/test_issues_15.py | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 python_tests/test_issues_15.py diff --git a/python_tests/test_issues_15.py b/python_tests/test_issues_15.py new file mode 100644 index 00000000..e691ac1a --- /dev/null +++ b/python_tests/test_issues_15.py @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (c) 2025 DBZero Software sp. z o.o. + +""" +Regression test: segfault when db0.find() is called from inside a @db0.memo +object's __init__ after db0.tags(self).add() has been called on that object. + +Minimal sequence: + 1. Any @db0.memo class TypeA exists + 2. A @db0.memo class TypeB calls db0.tags(self).add(tag) in __init__, + then calls db0.find(TypeA, tag) in the same __init__ + 3. Creating an instance of TypeB → segfault in db0.find + +Root cause: db0.tags(self).add() partially registers the in-progress object +in the tag index; the subsequent db0.find() traverses that index and +dereferences the not-yet-committed object, causing a null-pointer crash. +""" + +import gc +import os +import shutil + +import pytest +import dbzero as db0 + +from .conftest import DB0_DIR + + +@db0.memo +class FindTarget: + def __init__(self, value: int): + self.value = value + + +@db0.memo +class TaggerAndFinder: + def __init__(self): + db0.tags(self).add("my-tag") + self._results = list(db0.find(FindTarget, "my-tag")) + + +@pytest.fixture() +def db0_rw_fixture(): + if os.path.exists(DB0_DIR): + shutil.rmtree(DB0_DIR) + os.mkdir(DB0_DIR) + db0.init(DB0_DIR, read_write=True) + db0.open("test_prefix", "rw") + yield db0 + gc.collect() + db0.close() + if os.path.exists(DB0_DIR): + shutil.rmtree(DB0_DIR) + + +def test_find_inside_memo_init_after_tagging_self(db0_rw_fixture): + """db0.find() inside a memo __init__ must not segfault after db0.tags(self).add().""" + obj = TaggerAndFinder() + assert obj._results == [] From ebf1f8fb803eacfed1d8a1d97d67297898e9b4e7 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Mon, 4 May 2026 10:53:27 +0200 Subject: [PATCH 2/3] bugfix(ObjectAnyBase): fixed problem with hasAnyRefs in construction --- src/dbzero/object_model/object/ObjectAnyBase.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dbzero/object_model/object/ObjectAnyBase.cpp b/src/dbzero/object_model/object/ObjectAnyBase.cpp index b2b78fc8..de86316d 100644 --- a/src/dbzero/object_model/object/ObjectAnyBase.cpp +++ b/src/dbzero/object_model/object/ObjectAnyBase.cpp @@ -102,6 +102,9 @@ namespace db0::object_model template bool ObjectAnyBase::hasAnyRefs() const { + if (!this->hasInstance()) { + return true; + } return (*this)->hasAnyRefs(); } From ab3fc909c00badb05139e37d0e2c5505f4a2b630 Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Mon, 4 May 2026 10:54:36 +0200 Subject: [PATCH 3/3] release 0.2.3 --- dbzero/setup.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dbzero/setup.py b/dbzero/setup.py index 4dea166c..e37567b6 100644 --- a/dbzero/setup.py +++ b/dbzero/setup.py @@ -10,7 +10,7 @@ setup( name='dbzero', - version='0.2.2', + version='0.2.3', description='DBZero community edition', packages=['dbzero'], python_requires='>=3.9', diff --git a/pyproject.toml b/pyproject.toml index fb8535a5..5288d19a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ['meson-python'] [project] name = 'dbzero' -version = '0.2.2' +version = '0.2.3' description = 'A state management system for Python 3.x that unifies your applications business logic, data persistence, and caching into a single, efficient layer.' readme = 'README.md' requires-python = '>=3.9'