Skip to content

Python: Modernize the init-calls-subclass query #19709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

joefarebrother
Copy link
Contributor

No description provided.

@Copilot Copilot AI review requested due to automatic review settings June 10, 2025 08:29
@joefarebrother joefarebrother requested a review from a team as a code owner June 10, 2025 08:29
@joefarebrother joefarebrother marked this pull request as draft June 10, 2025 08:30
Copy link
Contributor

github-actions bot commented Jun 10, 2025

QHelp previews:

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.qhelp

__init__ method calls overridden method

When initializing an instance of the class in the class' __init__ method, calls tha are made using the instance may receive an instance of the class that is not yet fully initialized. When a method called in an initializer is overridden in a subclass, the subclass method receives the instance in a potentially unexpected state. Fields that would be initialized after the call, including potentially in the subclass' __init__ method, will not be initialized. This may lead to runtime errors, as well as make the code more difficult to maintain, as future changes may not be aware of which fields would not be initialized.

Recommendation

If possible, refactor the initializer method such that initialization is complete before calling any overridden methods. For helper methods used as part of initialization, avoid overriding them, and instead call any additional logic required in the subclass' __init__ method.

If the overridden method does not depend on the instance self, and only on its class, consider making it a @classmethod or @staticmethod instead.

If calling an overridden method is absolutely required, consider marking it as an internal method (by using an _ prefix) to discourage external users of the library from overriding it and observing partially initialized state, and ensure that the fact it is called during initialization is mentioned in the documentation.

Example

In the following case, the __init__ method of Super calls the set_up method that is overridden by Sub. This results in `Sun.set_up` being called with a partially initialized instance of `Super` which may be unexpected.

class Super(object):

    def __init__(self, arg):
        self._state = "Not OK"
        self.set_up(arg) # BAD: This method is overridden, so `Sub.set_up` receives a partially initialized instance.
        self._state = "OK"

    def set_up(self, arg):
        "Do some setup"
        self.a = 2

class Sub(Super):

    def __init__(self, arg):
        super().__init__(arg)
        self.important_state = "OK"

    def set_up(self, arg):
        super().set_up(arg)
        "Do some more setup"
        # BAD: at this point `self._state` is set to `"Not OK"`, and `self.important_state` is not initialized.
        if self._state == "OK":
            self.b = self.a + 2

In the following case, the initialization methods are separate between the superclass and the subclass.

class Super(object):

    def __init__(self, arg):
        self._state = "Not OK"
        self.super_set_up(arg) # GOOD: This isn't overriden. Instead, additional setup the subclass needs is called by the subclass' `__init__ method.`
        self._state = "OK"

    def super_set_up(self, arg):
        "Do some setup"
        self.a = 2


class Sub(Super):

    def __init__(self, arg):
        super().__init__(arg)
        self.sub_set_up(self, arg)
        self.important_state = "OK"


    def sub_set_up(self, arg):
        "Do some more setup"
        if self._state == "OK":
            self.b = self.a + 2

References

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR modernizes the py/init-calls-subclass QL query by replacing the old implementation with a new dataflow-based approach.

  • Removes the legacy InitCallsSubclassMethod.ql.
  • Adds a new directory and file under InitCallsSubclass/ using DataFlow predicates.
  • Defines two predicates (initSelfCall and initSelfCallOverridden) to detect subclass-overridden calls in __init__.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
python/ql/src/Classes/InitCallsSubclassMethod.ql Deleted the legacy query file
python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql Added modernized dataflow-based query implementation
Comments suppressed due to low confidence (4)

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql:28

  • [nitpick] The parameter name override is ambiguous and matches a language keyword. Consider renaming it to something like overridingMethod for clarity.
predicate initSelfCallOverridden(Function init, DataFlow::MethodCallNode call, Function override) {

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql:18

  • [nitpick] It would be helpful to add a brief doc comment above this predicate explaining its purpose and parameters for future maintainers.
predicate initSelfCall(Function init, DataFlow::MethodCallNode call) {

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql:38

  • The new dataflow-based implementation should be accompanied by tests to validate that overridden __init__ calls are correctly detected; consider adding or updating QL tests.
from Function init, DataFlow::MethodCallNode call, Function override

python/ql/src/Classes/InitCallsSubclass/InitCallsSubclassMethod.ql:16

  • This import isn't used in the new query. Consider removing it to keep dependencies minimal.
import semmle.python.dataflow.new.internal.DataFlowDispatch

…f, or the call is last in the initialisation.
@@ -1 +1 @@
Classes/InitCallsSubclassMethod.ql
Classes/InitCallsSubclass/InitCallsSubclassMethod.ql

Check warning

Code scanning / CodeQL

Query test without inline test expectations Warning test

Query test does not use inline test expectations.
@joefarebrother joefarebrother force-pushed the python-qual-init-call-subclass branch from d92aeda to 0f7a24e Compare June 12, 2025 10:18
@joefarebrother joefarebrother force-pushed the python-qual-init-call-subclass branch from 0f7a24e to 2aad607 Compare June 12, 2025 10:20
@joefarebrother joefarebrother changed the title [Draft] Python: Modernize the init-calls-subclass query Python: Modernize the init-calls-subclass query Jun 13, 2025
@joefarebrother joefarebrother marked this pull request as ready for review June 13, 2025 09:45
@joefarebrother joefarebrother added the no-change-note-required This PR does not need a change note label Jun 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation no-change-note-required This PR does not need a change note Python
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant