Skip to content

[Draft] 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

Draft
wants to merge 5 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, which may lead to runtime errors from accessing uninitialized fields, and generally makes the code more difficult to maintain.

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 calling an overridden method is 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.

Example

In the following case, the `init` method of `Super` calls the `set_up` method that is overriden 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant