EffectEngine: Refactoring, stabalizing and fixes#2011
Conversation
- EffectModule/json2python: add null check after PyList_New to prevent null-pointer dereference in PyList_SetItem/Py_DECREF on alloc failure - EffectModule/json2python: add null check after PyDict_New for the same class of crash - EffectModule/json2python: correct misleading comment on PyList_SetItem to state reference stealing is unconditional even on failure - Effect/setModuleParameters: split PyModule_AddObject error handling to avoid double-DECREF of capsule on Python < 3.10 (same stealing semantics as PyList_SetItem fixed in #2010) - Effect/setModuleParameters: guard json2python return value before passing to PyObject_SetAttrString to prevent null-pointer dereference Follows up on #2010.
|
Hello @Lord-Grey 👋 I'm the Hyperion Project Bot and I want to thank you for To help you and other users test your pull requests faster, 🔗 https://github.com/hyperion-project/hyperion.ng/actions/runs/25624002367 Of course, if you make changes to your PR, I will create a new link. Best regards, |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes additional Python C-API refcounting/null-pointer issues in EffectEngine discovered during a broader audit following #2010, and documents the fix in the changelog.
Changes:
- Add missing null checks after
PyList_New()/PyDict_New()and adjust related cleanup paths. - Make
PyModule_AddObject()failure cleanup version-aware to avoid double-DECREF on Python < 3.10. - Guard
PyObject_SetAttrString()againstjson2python()returningnullptr, and update CHANGELOG.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| libsrc/effectengine/EffectModule.cpp | Adds allocation null checks and corrects ref-stealing assumptions around PyList_SetItem() usage. |
| libsrc/effectengine/Effect.cpp | Splits capsule creation vs add failure handling and adds a nullptr guard before PyObject_SetAttrString(). |
| CHANGELOG.md | Notes the EffectEngine Python C-API refcounting/null-guard fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
libsrc/effectengine/EffectModule.cpp:1155
wrapImageCOffset()ignores both invalid argument counts andPyArg_ParseTuplefailure: forargCount != 2it silently applies a (0,0) translation and returnsNone, and if parsing fails it may returnNonewith a pending exception. Align this with other wrappers by validatingargCount == 2 && PyArg_ParseTuple(...)and settingTypeError(or propagating the parse error) when arguments are invalid.
int offsetX = 0;
int offsetY = 0;
Py_ssize_t argCount = PyTuple_Size(args);
if (argCount == 2)
{
PyArg_ParseTuple(args, "ii", &offsetX, &offsetY);
}
getEffect()->_painter->translate(QPoint(offsetX, offsetY));
Py_RETURN_NONE;
}
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
…n-project/hyperion.ng into fix/python-refcount-effectengine
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
|
Hey @Lord-Grey I created a new link to your workflow artifacts: |
Summary
Follow-up to #2010, which fixed a double-DECREF after a failed
PyList_SetItemcall.A broader audit of the effectengine related code was refactored, stabilized and fixed.
What kind of change does this PR introduce?
Does this PR introduce a breaking change?
The PR fulfills these requirements:
Fixes: #xxx[,#xxx], where "xxx" is the issue number)PLEASE DON'T FORGET TO ADD YOUR CHANGES TO CHANGELOG.MD
Related issue
Follows up on #2009 / #2010.
Description
A broader audit of the effectengine Python C-API usage after #2010 found additional reference-counting and null-pointer bugs of the same class.
Changes
Bug fixes (
EffectModule.cpp,Effect.cpp)json2python: Missing null checks afterPyList_New/PyDict_New— null-pointer dereference on allocation failure. Added null guards and corrected cleanup paths.setModuleParameters:PyModule_AddObjectversion-dependent double-DECREF on Python < 3.10. Split capsule-creation and add-failure paths; guardPy_DECREF(capsule)with#if PY_VERSION_HEX >= 0x030A0000.setModuleParameters:json2pythonnull return passed directly toPyObject_SetAttrStringwithout a null check → undefined behaviour. Added null guard.wrap*): All wrappers returnednullptrsilently on argument-parse failure without setting a Python exception, causingSystemErrorin the interpreter. AddedPyErr_SetStringbefore eachreturn nullptr.wrapImageShow: Out-of-range image ID raisedTypeErrorinstead ofIndexError. Corrected exception type.wrapGetImage:QImageReader::imageCount()returns 0 for non-animated formats (JPEG, PNG). The result was allocated as a zero-element list, silently dropping the image. Now treated as 1 frame with aqMax(count, 1)fallback.setupImageSource:file://URLs passed toQImageReader::setFileNameas raw URL strings instead of local paths. Fixed usingQUrl::isLocalFile()/url.toLocalFile().Security fixes (
EffectFileHandler.cpp)resolveEffectFilePath: Effect name used to construct the JSON file path without sanitising path separators or..segments — path traversal allowing writes outside the effects directory.saveEffectImage:args["file"]appended to the effects directory path without validation — path traversal allowing arbitrary file writes. Fixed by verifying the resolved absolute path stays within the effects directory.New effect
Refactoring & improvements
EffectDefinition: MovedQ_DECLARE_LOGGING_CATEGORY(effect)toEffectDefinition.h; addedEffectDefinition.cppwithQ_LOGGING_CATEGORY(effect, "hyperion.effect"). AddedqCDebug(effect)traces throughoutEffectEngine,EffectModule, andEffectFileHandler. Debug logging is opt-in viaqtlogging.ini(#hyperion.effect.debug=true).EffectModule.cpp: ExtractedsetupImageSourceandimageToRGBhelpers to reduce cognitive complexity and nesting depth inwrapGetImage.EffectFileHandler.cpp: SplitsaveEffectintoresolveEffectFilePath,saveEffectImage, andcleanImageSourcefor clarity. Converted string concatenation in error messages toQString::arg()for safety and readability.Effect scripts: Minor stability and style fixes in
pacman.py,traces.py, andtrails.py.Empty image:
0×0is now the canonical empty image;1×1is no longer treated as empty.ImageResampler: I422 Format - Consistently use adjusted local cropLeft computed earlier