diff --git a/CHANGELOG.md b/CHANGELOG.md index cfeb46710..6e1cf53b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - set a default error if NDN does not provide an error - python: avoid exceptions when messages/contacts/chats are compared with `None` +- python: avoid careless calls to unref functions ### API-Changes - python: added `Message.get_status_updates()` #3416 diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 09242b6b3..13d35fe08 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -19,6 +19,7 @@ from .cutil import ( from_dc_charpointer, from_optional_dc_charpointer, iter_array, + safe_unref, ) from .events import EventThread from .message import Message @@ -38,7 +39,7 @@ def get_core_info(): return get_dc_info_as_dict( ffi.gc( lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL), - lib.dc_context_unref, + safe_unref(lib.dc_context_unref), ) ) @@ -84,7 +85,7 @@ class Account(object): self._dc_context = ffi.gc( lib.dc_context_new_closed(db_path) if closed else lib.dc_context_new(ffi.NULL, db_path, ffi.NULL), - lib.dc_context_unref, + safe_unref(lib.dc_context_unref), ) if self._dc_context == ffi.NULL: raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path)) @@ -342,7 +343,7 @@ class Account(object): :returns: list of :class:`deltachat.contact.Contact` objects. """ - dc_array = ffi.gc(lib.dc_get_blocked_contacts(self._dc_context), lib.dc_array_unref) + dc_array = ffi.gc(lib.dc_get_blocked_contacts(self._dc_context), safe_unref(lib.dc_array_unref)) return list(iter_array(dc_array, lambda x: Contact(self, x))) def get_contacts( @@ -365,12 +366,12 @@ class Account(object): flags |= const.DC_GCL_VERIFIED_ONLY if with_self: flags |= const.DC_GCL_ADD_SELF - dc_array = ffi.gc(lib.dc_get_contacts(self._dc_context, flags, query), lib.dc_array_unref) + dc_array = ffi.gc(lib.dc_get_contacts(self._dc_context, flags, query), safe_unref(lib.dc_array_unref)) return list(iter_array(dc_array, lambda x: Contact(self, x))) def get_fresh_messages(self) -> Generator[Message, None, None]: """yield all fresh messages from all chats.""" - dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), lib.dc_array_unref) + dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), safe_unref(lib.dc_array_unref)) yield from iter_array(dc_array, lambda x: Message.from_db(self, x)) def create_chat(self, obj) -> Chat: @@ -404,7 +405,7 @@ class Account(object): :returns: a list of :class:`deltachat.chat.Chat` objects. """ - dc_chatlist = ffi.gc(lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0), lib.dc_chatlist_unref) + dc_chatlist = ffi.gc(lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0), safe_unref(lib.dc_chatlist_unref)) assert dc_chatlist != ffi.NULL chatlist = [] @@ -545,7 +546,7 @@ class Account(object): def check_qr(self, qr): """check qr code and return :class:`ScannedQRCode` instance representing the result""" - res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref) + res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), safe_unref(lib.dc_lot_unref)) lot = DCLot(res) if lot.state() == const.DC_QR_ERROR: raise ValueError("invalid or unknown QR code: {}".format(lot.text1())) diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index d546d4775..a39267a41 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -14,6 +14,7 @@ from .cutil import ( from_dc_charpointer, from_optional_dc_charpointer, iter_array, + safe_unref, ) from .message import Message @@ -44,7 +45,7 @@ class Chat(object): @property def _dc_chat(self): - return ffi.gc(lib.dc_get_chat(self.account._dc_context, self.id), lib.dc_chat_unref) + return ffi.gc(lib.dc_get_chat(self.account._dc_context, self.id), safe_unref(lib.dc_chat_unref)) def delete(self) -> None: """Delete this chat and all its messages. @@ -416,7 +417,7 @@ class Chat(object): """ dc_array = ffi.gc( lib.dc_get_chat_msgs(self.account._dc_context, self.id, 0, 0), - lib.dc_array_unref, + safe_unref(lib.dc_array_unref), ) return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x))) @@ -469,7 +470,7 @@ class Chat(object): dc_array = ffi.gc( lib.dc_get_chat_contacts(self.account._dc_context, self.id), - lib.dc_array_unref, + safe_unref(lib.dc_array_unref), ) return list(iter_array(dc_array, lambda id: Contact(self.account, id))) @@ -477,7 +478,7 @@ class Chat(object): """return number of contacts in this chat.""" dc_array = ffi.gc( lib.dc_get_chat_contacts(self.account._dc_context, self.id), - lib.dc_array_unref, + safe_unref(lib.dc_array_unref), ) return lib.dc_array_get_cnt(dc_array) diff --git a/python/src/deltachat/cutil.py b/python/src/deltachat/cutil.py index ad9810219..0f62e7f35 100644 --- a/python/src/deltachat/cutil.py +++ b/python/src/deltachat/cutil.py @@ -6,6 +6,14 @@ from .capi import ffi, lib T = TypeVar("T") +def safe_unref(unref: Callable) -> Callable: + def wrapper(obj) -> None: + if obj != ffi.NULL: + unref(obj) + + return wrapper + + def as_dc_charpointer(obj): if obj == ffi.NULL or obj is None: return ffi.NULL diff --git a/python/src/deltachat/events.py b/python/src/deltachat/events.py index 812b07c6a..c3c534410 100644 --- a/python/src/deltachat/events.py +++ b/python/src/deltachat/events.py @@ -11,7 +11,7 @@ from queue import Empty, Queue import deltachat from .capi import ffi, lib -from .cutil import from_optional_dc_charpointer +from .cutil import from_optional_dc_charpointer, safe_unref from .hookspec import account_hookimpl from .message import map_system_message @@ -229,7 +229,7 @@ class EventThread(threading.Thread): def _inner_run(self): event_emitter = ffi.gc( lib.dc_get_event_emitter(self.account._dc_context), - lib.dc_event_emitter_unref, + safe_unref(lib.dc_event_emitter_unref), ) while not self._marked_for_shutdown: event = lib.dc_get_next_event(event_emitter) diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index a7a9d9b58..b07f6f271 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -8,7 +8,12 @@ from typing import Optional, Union from . import const, props from .capi import ffi, lib -from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_charpointer +from .cutil import ( + as_dc_charpointer, + from_dc_charpointer, + from_optional_dc_charpointer, + safe_unref, +) class Message(object): @@ -49,7 +54,7 @@ class Message(object): @classmethod def from_db(cls, account, id): assert id > 0 - return cls(account, ffi.gc(lib.dc_get_msg(account._dc_context, id), lib.dc_msg_unref)) + return cls(account, ffi.gc(lib.dc_get_msg(account._dc_context, id), safe_unref(lib.dc_msg_unref))) @classmethod def new_empty(cls, account, view_type): @@ -64,7 +69,7 @@ class Message(object): view_type_code = get_viewtype_code_from_name(view_type) return Message( account, - ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref), + ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), safe_unref(lib.dc_msg_unref)), ) def create_chat(self): @@ -278,7 +283,7 @@ class Message(object): mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id) if mime_headers: - s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref)) + s = ffi.string(ffi.gc(mime_headers, safe_unref(lib.dc_str_unref))) if isinstance(s, bytes): return email.message_from_bytes(s) return email.message_from_string(s) @@ -337,7 +342,7 @@ class Message(object): dc_msg = self._dc_msg else: # load message from db to get a fresh/current state - dc_msg = ffi.gc(lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref) + dc_msg = ffi.gc(lib.dc_get_msg(self.account._dc_context, self.id), safe_unref(lib.dc_msg_unref)) return lib.dc_msg_get_state(dc_msg) def is_in_fresh(self): diff --git a/python/src/deltachat/provider.py b/python/src/deltachat/provider.py index 76d5cf780..800f6f325 100644 --- a/python/src/deltachat/provider.py +++ b/python/src/deltachat/provider.py @@ -1,7 +1,7 @@ """Provider info class.""" from .capi import ffi, lib -from .cutil import as_dc_charpointer, from_dc_charpointer +from .cutil import as_dc_charpointer, from_dc_charpointer, safe_unref class ProviderNotFoundError(Exception): @@ -17,7 +17,7 @@ class Provider(object): def __init__(self, account, addr) -> None: provider = ffi.gc( lib.dc_provider_new_from_email(account._dc_context, as_dc_charpointer(addr)), - lib.dc_provider_unref, + safe_unref(lib.dc_provider_unref), ) if provider == ffi.NULL: raise ProviderNotFoundError("Provider not found")