Fix #111649: 3rd party Python modules can cause Python error on exit

Disabling all add-ons on exit could raise exceptions when `sys.modules`
contained modules that ran logic in their `__getattr__` function.

Resolve by accessing the modules name-space directly,
bypassing the `__getattr__` function.
This commit is contained in:
Campbell Barton
2023-08-30 15:45:41 +10:00
parent 46edf61180
commit e1b72e569d
+14 -1
View File
@@ -505,9 +505,22 @@ def reset_all(*, reload_scripts=False):
def disable_all():
import sys
# Collect modules to disable first because dict can be modified as we disable.
# NOTE: don't use `getattr(item[1], "__addon_enabled__", False)` because this runs on all modules,
# including 3rd party modules unrelated to Blender.
#
# Some modules may have their own `__getattr__` and either:
# - Not raise an `AttributeError` (is they should),
# causing `hasattr` & `getattr` to raise an exception instead of treating the attribute as missing.
# - Generate modules dynamically, modifying `sys.modules` which is being iterated over,
# causing a RuntimeError: "dictionary changed size during iteration".
#
# Either way, running 3rd party logic here can cause undefined behavior.
# Use direct `__dict__` access to bypass `__getattr__`, see: #111649.
addon_modules = [
item for item in sys.modules.items()
if getattr(item[1], "__addon_enabled__", False)
if type(mod_dict := getattr(item[0], "__dict__", None)) is dict
if mod_dict.get("__addon_enabled__")
]
# Check the enabled state again since it's possible the disable call
# of one add-on disables others.