Skip to content

Conversation

@codeql-ci
Copy link
Collaborator

This PR merges back all of the changes from the release of codeql-cli-2.23.3. And it bumps the version version strings in semmle-code in preparation for the next release of 2.23.4.

@github-actions
Copy link
Contributor

QHelp previews:

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 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 calls super().__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 calling super().__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 using super(), resulting in Vehicle.__del__ being called twice. FixedSportsCar.__del__ fixes this by using super() consistently with the other delete methods.

#Calling a method multiple times by using explicit calls when a base uses super()
class Vehicle(object):
     
    def __del__(self):
        recycle(self.base_parts)
        super(Vehicle, self).__del__()
        
class Car(Vehicle):
    
    def __del__(self):
        recycle(self.car_parts)
        super(Car, self).__del__()
        
        
class SportsCar(Car, Vehicle):
    
    # BAD: Vehicle.__del__ will get called twice
    def __del__(self):
        recycle(self.sports_car_parts)
        Car.__del__(self)
        Vehicle.__del__(self)
        
        
# GOOD: super() is used ensuring each del method is called once.
class FixedSportsCar(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 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 calls super().__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 calls super().__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 using super() 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 class D calls B.__init__ and C.__init__, which each call A.__init__. This results in self.state being set to None as A.__init__ is called again after B.__init__ had finished. This may lead to unexpected results.

class A:
    def __init__(self):
        self.state = None 

class B(A):
    def __init__(self):
        A.__init__(self)
        self.state = "B"
        self.b = 3 

class C(A):
    def __init__(self):
        A.__init__(self)
        self.c = 2 

class D(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 to super().__init__ is made in each class in the inheritance hierarchy, ensuring each initializer is called exactly once.

class A:
    def __init__(self):
        self.state = None 

class B(A):
    def __init__(self):
        super().__init__()
        self.state = "B"
        self.b = 3 

class C(A):
    def __init__(self):
        super().__init__()
        self.c = 2 

class D(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 with super() calls, and C.__init__ is called twice.

class A:
    def __init__(self):
        print("A")
        self.state = None 

class B(A):
    def __init__(self):
        print("B")
        super().__init__() # When called from D, this calls C.__init__
        self.state = "B"
        self.b = 3 

class C(A):
    def __init__(self):
        print("C")
        super().__init__()
        self.c = 2 

class D(B,C):
    def __init__(self): 
        B.__init__(self)
        C.__init__(self) # BAD: C.__init__ is called a second time

References

@henrymercer henrymercer marked this pull request as ready for review October 14, 2025 11:18
Copilot AI review requested due to automatic review settings October 14, 2025 11:18
@henrymercer henrymercer requested review from a team as code owners October 14, 2025 11:18
@henrymercer henrymercer requested review from a team as code owners October 14, 2025 11:18
Copy link
Contributor

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 merges back all changes from the release of codeql-cli-2.23.3 and bumps version strings in semmle-code for the next release preparation (2.23.4).

  • Updates release version tracking across all language packs to reflect the 2.23.3 release
  • Bumps development version numbers for all CodeQL packages for the next release cycle
  • Consolidates change notes from individual files into published release notes

Reviewed Changes

Copilot reviewed 183 out of 183 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Various qlpack.yml files Version bumps from released versions to next development versions
Various codeql-pack.release.yml files Updates lastReleaseVersion to reflect completed 2.23.3 release
Various change-notes/released/*.md files New release notes documenting changes in the 2.23.3 release
Various CHANGELOG.md files Addition of release notes to the top of changelogs
Individual change-notes/*.md files Removal of individual change note files after consolidation into release notes
Test files and code fixes Minor typo corrections and test expectation updates

@henrymercer henrymercer merged commit c2309a9 into main Oct 14, 2025
74 checks passed
@henrymercer henrymercer deleted the post-release-prep/codeql-cli-2.23.3 branch October 14, 2025 12:03
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.

3 participants