Three DynamicScope/set_dynamic_attr issues:
1. Missing return -1 after read-only descriptor error (dynamicscope.cpp:296-306)
When a read-only descriptor is found (has __get__ but no __set__), PyErr_Format(PyExc_AttributeError, ...) is called, but the code falls through to the "step up to parent" logic instead of returning -1. The error is set but never delivered — setting a read-only attribute silently succeeds.
Reproducer:
import gc; gc.disable()
import os
from enaml.core._dynamicscope import _DynamicScope
class ReadOnlyDescr:
def __get__(self, obj, objtype=None):
return 42
class Owner:
readonly = ReadOnlyDescr()
ds = _DynamicScope(Owner(), {}, {}, {})
ds["readonly"] = 99 # Should raise AttributeError, but silently succeeds!
os._exit(0)
Fix: add return -1; after the PyErr_Format call.
2. return -1 without exception at bottom of set_dynamic_attr (dynamicscope.cpp:308)
When the while loop exits (reached Py_None at top of scope chain), the function returns -1 without setting an exception. The callers (Nonlocals_setattro, Nonlocals_setitem) check if(res < 0 && !PyErr_Occurred()) and set their own exception, so this doesn't crash — but it violates the Python/C API contract.
3. Py_TPFLAGS_DICT_SUBCLASS on DynamicScope (dynamicscope.cpp:869)
DynamicScope sets Py_TPFLAGS_DICT_SUBCLASS but is NOT a dict subclass — it just implements the mapping protocol. Testing shows PyType_FromSpec currently strips this flag (so isinstance(ds, dict) returns False), but the flag should still be removed to avoid confusion and future breakage if PyType_FromSpec behavior changes.
Found by cext-review-toolkit.
Three
DynamicScope/set_dynamic_attrissues:1. Missing
return -1after read-only descriptor error (dynamicscope.cpp:296-306)When a read-only descriptor is found (has
__get__but no__set__),PyErr_Format(PyExc_AttributeError, ...)is called, but the code falls through to the "step up to parent" logic instead of returning -1. The error is set but never delivered — setting a read-only attribute silently succeeds.Reproducer:
Fix: add
return -1;after thePyErr_Formatcall.2.
return -1without exception at bottom ofset_dynamic_attr(dynamicscope.cpp:308)When the while loop exits (reached
Py_Noneat top of scope chain), the function returns -1 without setting an exception. The callers (Nonlocals_setattro,Nonlocals_setitem) checkif(res < 0 && !PyErr_Occurred())and set their own exception, so this doesn't crash — but it violates the Python/C API contract.3.
Py_TPFLAGS_DICT_SUBCLASSonDynamicScope(dynamicscope.cpp:869)DynamicScopesetsPy_TPFLAGS_DICT_SUBCLASSbut is NOT a dict subclass — it just implements the mapping protocol. Testing showsPyType_FromSpeccurrently strips this flag (soisinstance(ds, dict)returnsFalse), but the flag should still be removed to avoid confusion and future breakage ifPyType_FromSpecbehavior changes.Found by cext-review-toolkit.