Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dbzero/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
59 changes: 59 additions & 0 deletions python_tests/test_issues_15.py
Original file line number Diff line number Diff line change
@@ -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 == []
3 changes: 3 additions & 0 deletions src/dbzero/object_model/object/ObjectAnyBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ namespace db0::object_model

template <typename T, typename ImplT>
bool ObjectAnyBase<T, ImplT>::hasAnyRefs() const {
if (!this->hasInstance()) {
return true;
}
return (*this)->hasAnyRefs();
}

Expand Down
Loading