Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Python: Modernize 4 queries for missing/multiple calls to init/del methods#19932

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
joefarebrother wants to merge27 commits intogithub:main
base:main
Choose a base branch
Loading
fromjoefarebrother:python-qual-init-del-calls

Conversation

joefarebrother
Copy link
Contributor

@joefarebrotherjoefarebrother commentedJun 30, 2025
edited
Loading

Modernizes py/missing-call-to-init, py/missing-call-to-del, py/multiple-calls-to-init,, and py/multiplecalls-to-del.
Uses DataFlowDispatch methods rather than. pointsTo.

Improves precision (to reduce FPs) in resolving relevant calls as precisely as possible using known MRO information, improves descriptiveness of alert messages by including more possible relevant locations, and improves documentation.

@github-actionsGitHub Actions
Copy link
Contributor

github-actionsbot commentedJun 30, 2025
edited
Loading

QHelp previews:

python/ql/src/Classes/CallsToInitDel/MissingCallToDel.qhelp

Missing call to superclass__del__ during object destruction

Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in when and how superclass finalizers are called during object finalization. However, the developer has responsibility for ensuring that objects are properly cleaned up, and that all superclass__del__ methods are called.

Classes with a__del__ method (a finalizer) typically hold some resource such as a file handle that needs to be cleaned up. If the__del__ method of a superclass is not called during object finalization, it is likely that resources may be leaked.

A call to the__init__ method of a superclass during object initialization may be unintentionally skipped:

  • If a subclass calls the__del__ method of the wrong class.
  • If a call to the__del__ method of one its base classes is omitted.
  • If a call tosuper().__del__ is used, but not all__del__ methods in the Method Resolution Order (MRO) chain themselves callsuper(). This in particular arises more often in cases of multiple inheritance.

Recommendation

Ensure that all superclass__del__ methods are properly called. Either each base class's finalize method should be explicitly called, orsuper() calls should be consistently used throughout the inheritance hierarchy.

Example

In the following example, explicit calls to__del__ are used, butSportsCar erroneously callsVehicle.__del__. This is fixed inFixedSportsCar by callingCar.__del__.

classVehicle(object):def__del__(self):recycle(self.base_parts)classCar(Vehicle):def__del__(self):recycle(self.car_parts)Vehicle.__del__(self)#BAD: Car.__del__ is not called.classSportsCar(Car,Vehicle):def__del__(self):recycle(self.sports_car_parts)Vehicle.__del__(self)#GOOD: Car.__del__ is called correctly.classFixedSportsCar(Car,Vehicle):def__del__(self):recycle(self.sports_car_parts)Car.__del__(self)

References

python/ql/src/Classes/CallsToInitDel/MissingCallToInit.qhelp

Missing call to superclass__init__ during object initialization

Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in when and how superclass initializers are called during object initialization. However, the developer has responsibility for ensuring that objects are properly initialized, and that all superclass__init__ methods are called.

If the__init__ method of a superclass is not called during object initialization, this can lead to errors due to the object not being fully initialized, such as having missing attributes.

A call to the__init__ method of a superclass during object initialization may be unintentionally skipped:

  • If a subclass calls the__init__ method of the wrong class.
  • If a call to the__init__ method of one its base classes is omitted.
  • If a call tosuper().__init__ is used, but not all__init__ methods in the Method Resolution Order (MRO) chain themselves callsuper(). This in particular arises more often in cases of multiple inheritance.

Recommendation

Ensure that all superclass__init__ methods are properly called. Either each base class's initialize method should be explicitly called, orsuper() calls should be consistently used throughout the inheritance hierarchy.

Example

In the following example, explicit calls to__init__ are used, butSportsCar erroneously callsVehicle.__init__. This is fixed inFixedSportsCar by callingCar.__init__.

classVehicle(object):def__init__(self):self.mobile=TrueclassCar(Vehicle):def__init__(self):Vehicle.__init__(self)self.car_init()# BAD: Car.__init__ is not called.classSportsCar(Car,Vehicle):def__init__(self):Vehicle.__init__(self)self.sports_car_init()# GOOD: Car.__init__ is called correctly.classFixedSportsCar(Car,Vehicle):def__init__(self):Car.__init__(self)self.sports_car_init()

References

python/ql/src/Classes/CallsToInitDel/SuperclassDelCalledMultipleTimes.qhelp

Multiple calls to__del__ during object destruction

Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in when and how superclass finalizers are called during object finalization. However, the developer has responsibility for ensuring that objects are properly cleaned up.

Objects with a__del__ method (a finalizer) often hold resources such as file handles that need to be cleaned up. If a superclass finalizer is called multiple times, this may lead to errors such as closing an already closed file, and lead to objects not being cleaned up properly as expected.

There are a number of ways that a__del__ method may be be called more than once.

  • There may be more than one explicit call to the method in the hierarchy of__del__ methods.
  • In situations involving multiple inheritance, an finalization method may call the finalizers of each of its base types, which themselves both call the finalizer of a shared base type. (This is an example of the Diamond Inheritance problem)
  • Another situation involving multiple inheritance arises when a subclass calls the__del__ methods of each of its base classes, one of which callssuper().__del__. This super call resolves to the next class in the Method Resolution Order (MRO) of the subclass, which may be another base class that already has its initializer explicitly called.

Recommendation

Ensure that each finalizer method is called exactly once during finalization. This can be ensured by callingsuper().__del__ for each finalizer method in the inheritance chain.

Example

In the following example, there is a mixture of explicit calls to__del__ and calls usingsuper(), resulting inVehicle.__del__ being called twice.FixedSportsCar.__del__ fixes this by usingsuper() consistently with the other delete methods.

#Calling a method multiple times by using explicit calls when a base uses super()classVehicle(object):def__del__(self):recycle(self.base_parts)super(Vehicle,self).__del__()classCar(Vehicle):def__del__(self):recycle(self.car_parts)super(Car,self).__del__()classSportsCar(Car,Vehicle):# BAD: Vehicle.__del__ will get called twicedef__del__(self):recycle(self.sports_car_parts)Car.__del__(self)Vehicle.__del__(self)# GOOD: super() is used ensuring each del method is called once.classFixedSportsCar(Car,Vehicle):def__del__(self):recycle(self.sports_car_parts)super(SportsCar,self).__del__()

References

python/ql/src/Classes/CallsToInitDel/SuperclassInitCalledMultipleTimes.qhelp

Multiple calls to__init__ during object initialization

Python, unlike some other object-oriented languages such as Java, allows the developer complete freedom in when and how superclass initializers are called during object initialization. However, the developer has responsibility for ensuring that objects are properly initialized.

Calling an__init__ method more than once during object initialization risks the object being incorrectly initialized, as the method and the rest of the inheritance chain may not have been written with the expectation that it could be called multiple times. For example, it may set attributes to a default value in a way that unexpectedly overwrites values setting those attributes in a subclass.

There are a number of ways that an__init__ method may be be called more than once.

  • There may be more than one explicit call to the method in the hierarchy of__init__ methods.
  • In situations involving multiple inheritance, an initialization method may call the initializers of each of its base types, which themselves both call the initializer of a shared base type. (This is an example of the Diamond Inheritance problem)
  • Another situation involving multiple inheritance arises when a subclass calls the__init__ methods of each of its base classes, one of which callssuper().__init__. This super call resolves to the next class in the Method Resolution Order (MRO) of the subclass, which may be another base class that already has its initializer explicitly called.

Recommendation

Take care whenever possible not to call an an initializer multiple times. If each__init__ method in the hierarchy callssuper().__init__(), then each initializer will be called exactly once according to the MRO of the subclass. When explicitly calling base class initializers (such as to pass different arguments to different initializers), ensure this is done consistently throughout, rather than usingsuper() calls in the base classes.

In some cases, it may not be possible to avoid calling a base initializer multiple times without significant refactoring. In this case, carefully check that the initializer does not interfere with subclass initializers when called multiple times (such as by overwriting attributes), and ensure this behavior is documented.

Example

In the following (BAD) example, the classD callsB.__init__ andC.__init__, which each callA.__init__. This results inself.state being set toNone asA.__init__ is called again afterB.__init__ had finished. This may lead to unexpected results.

classA:def__init__(self):self.state=NoneclassB(A):def__init__(self):A.__init__(self)self.state="B"self.b=3classC(A):def__init__(self):A.__init__(self)self.c=2classD(B,C):def__init__(self):B.__init__(self)C.__init__(self)# BAD: This calls A.__init__ a second time, setting self.state to None.

In the following (GOOD) example, a call tosuper().__init__ is made in each class in the inheritance hierarchy, ensuring each initializer is called exactly once.

classA:def__init__(self):self.state=NoneclassB(A):def__init__(self):super().__init__()self.state="B"self.b=3classC(A):def__init__(self):super().__init__()self.c=2classD(B,C):def__init__(self):# GOOD: Each method calls super, so each init method runs once. self.state will be set to "B".super().__init__()self.d=1

In the following (BAD) example, explicit base class calls are mixed withsuper() calls, andC.__init__ is called twice.

classA:def__init__(self):print("A")self.state=NoneclassB(A):def__init__(self):print("B")super().__init__()# When called from D, this calls C.__init__self.state="B"self.b=3classC(A):def__init__(self):print("C")super().__init__()self.c=2classD(B,C):def__init__(self):B.__init__(self)C.__init__(self)# BAD: C.__init__ is called a second time

References

@joefarebrotherjoefarebrother marked this pull request as ready for reviewJuly 4, 2025 15:30
@joefarebrotherjoefarebrother requested a review froma team as acode ownerJuly 4, 2025 15:30
@joefarebrotherjoefarebrother changed the title[Draft] Python: Modernize 4 queries for missing/multiple calls to init/del methodsPython: Modernize 4 queries for missing/multiple calls to init/del methodsJul 4, 2025
@joefarebrotherjoefarebrotherforce-pushed thepython-qual-init-del-calls branch from3a1ccc1 toe8a65b8CompareJuly 7, 2025 09:52
Copy link
Contributor

@tausbntausbn left a comment

Choose a reason for hiding this comment

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

I think overall this looks really solid, but I must admit I didn't have time to fully digest the new implementation. (However, as I'm going away on PTO, I figured it was best to give you my interim review regardless.)

I'mslightly worried about the manynot exists(...) constructions in the modelling (as these may result in large joins). Make sure the performance testing is run with the tuple-counting option enabled.

Other than that, if the performance looks good, I would be happy to merge this.

Comment on lines +66 to +75
Function getASuperCallTargetFromClass(Class mroBase, Class cls, string name) {
exists(Function target |
target = findFunctionAccordingToMroKnownStartingClass(cls, mroBase, name) and
(
result = target
or
result = getASuperCallTargetFromCall(mroBase, target, _, name)
)
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This MRO business is a bit awkward. I wonder if we could clean it up by creating a new IPA type representing "the MRO starting at a particular base class", and thus avoid having to threadmroBase through everything (which might mean we could use the built-in transitive closure operators).

This is just some musing on my part -- not necessarily an immediately actionable suggestion.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

That could be a useful component of a public-facing dataflow dispatch / call graph resolution API

)
}

/** Holds if `meth` calls `super().<name>` where `name` is the name of the method. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment needs updating? I don't seename anywhere.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It was meant to refer to the name of the method; updated for more clarity

@joefarebrotherjoefarebrotherforce-pushed thepython-qual-init-del-calls branch from78235f2 to7dad89fCompareJuly 18, 2025 09:27
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@tausbntausbntausbn left review comments

@intrigus-lgtmintrigus-lgtmintrigus-lgtm left review comments

At least 1 approving review is required to merge this pull request.

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@joefarebrother@tausbn@intrigus-lgtm

[8]ページ先頭

©2009-2025 Movatter.jp