Skip to content

copt: refcount leaks in declaration import and module init error paths #363

@jensens

Description

@jensens

A detailed analysis of _zope_interface_coptimizations.c was published at
https://gist.github.com/devdanzin/5afbad864934b165a2cc080f110aa720 by @devdanzin.
We reviewed the report with agent support and verified the findings against the
current source (zope.interface 8.2). This issue is one of several extracted from
that review.

Problem

Declaration import leaks:
Both import_declarations (static types, lines 213-238) and
_zic_state_load_declarations (heap types, lines 2241-2275) leak the
declarations module reference and intermediate attribute references on every
early-return error path. Py_DECREF(declarations) only executes on the success
path.

Module init leaks:
_zic_module_exec at line 2652 does Py_INCREF(rec->adapter_hooks) to account
for a later PyModule_AddObject (which steals a ref). If the function fails
before reaching PyModule_AddObject, the extra reference is never consumed.
Same pattern for heap types created via PyType_FromModuleAndSpec — each gets
Py_INCREF for a later PyModule_AddObject that may never execute.

Fix

Declaration imports — use a goto error cleanup pattern:

declarations = PyImport_ImportModule("zope.interface.declarations");
if (declarations == NULL) return -1;

builtin_impl_specs = PyObject_GetAttrString(
    declarations, "BuiltinImplementationSpecifications");
if (builtin_impl_specs == NULL) goto error;

empty = PyObject_GetAttrString(declarations, "_empty");
if (empty == NULL) goto error;

// ... success path ...
Py_DECREF(declarations);
return 0;

error:
    Py_DECREF(declarations);
    return -1;

Module init — either defer Py_INCREF until just before PyModule_AddObject,
or switch to PyModule_AddObjectRef (available since 3.10, does not steal
references) and remove the extra Py_INCREF calls entirely.

Scope

Largest of the seven issues. Refactors error paths in import_declarations,
_zic_state_load_declarations, and _zic_module_exec. Only affects error and
init-failure paths.

Tests

Not feasible from pure Python. These leaks only occur when module
initialization or declaration import partially fails — conditions that require
internal C-level failures (e.g., PyObject_GetAttrString returning NULL for a
valid module attribute). Cannot be triggered from Python without monkeypatching
the import machinery in unsafe ways.

Code review of the goto error pattern is the appropriate verification.
The existing test suite implicitly tests the success path of these functions on
every test run.

Performance impact

Zero. These functions run once at module import time. No runtime paths affected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions