Skip to content

[Draft] 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 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 6 commits into
base: main
Choose a base branch
from

Conversation

joefarebrother
Copy link
Contributor

@joefarebrother joefarebrother commented Jun 30, 2025

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.

Copy link
Contributor

QHelp previews:

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

Missing call to superclass __del__ during object destruction

Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object destruction. However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. Therefore the developer has responsibility for ensuring that objects are properly cleaned up when there are multiple __del__ methods that need to be called.

If the __del__ method of a superclass is not called during object destruction it is likely that that resources may be leaked.

A call to the __del__ method of a superclass during object destruction may be omitted:

  • When a subclass calls the __del__ method of the wrong class.
  • When a call to the __del__ method of one its base classes is omitted.

Recommendation

Either be careful to explicitly call the __del__ of the correct base class, or use super() throughout the inheritance hierarchy.

Alternatively refactor one or more of the classes to use composition rather than inheritance.

Example

In this example, explicit calls to __del__ are used, but SportsCar erroneously calls Vehicle.__del__. This is fixed in FixedSportsCar by calling Car.__del__.

class Vehicle(object):
    
    def __del__(self):
        recycle(self.base_parts)
        
class Car(Vehicle):
    
    def __del__(self):
        recycle(self.car_parts)
        Vehicle.__del__(self)
        
#Car.__del__ is missed out.
class SportsCar(Car, Vehicle):
    
    def __del__(self):
        recycle(self.sports_car_parts)
        Vehicle.__del__(self)
        
#Fix SportsCar by calling Car.__del__
class FixedSportsCar(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 statically typed languages such as Java, allows complete freedom when calling methods during object initialization. However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. Therefore the developer has responsibility for ensuring that objects are properly initialized when there are multiple __init__ methods that need to be called.

If the __init__ method of a superclass is not called during object initialization it is likely that that object will end up in an incorrect state.

A call to the __init__ method of a superclass during object initialization may be omitted:

  • When a subclass calls the __init__ method of the wrong class.
  • When a call to the __init__ method of one its base classes is omitted.
  • When multiple inheritance is used and a class inherits from several base classes, and at least one of those does not use super() in its own __init__ method.

Recommendation

Either be careful to explicitly call the __init__ of the correct base class, or use super() throughout the inheritance hierarchy.

Alternatively refactor one or more of the classes to use composition rather than inheritance.

Example

In this example, explicit calls to __init__ are used, but SportsCar erroneously calls Vehicle.__init__. This is fixed in FixedSportsCar by calling Car.__init__.

class Vehicle(object):
    
    def __init__(self):
        self.mobile = True
        
class Car(Vehicle):
    
    def __init__(self):
        Vehicle.__init__(self)
        self.car_init()
        
#Car.__init__ is missed out.
class SportsCar(Car, Vehicle):
    
    def __init__(self):
        Vehicle.__init__(self)
        self.sports_car_init()
        
#Fix SportsCar by calling Car.__init__
class FixedSportsCar(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 statically typed languages such as Java, allows complete freedom when calling methods during object destruction. However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. Therefore the developer has responsibility for ensuring that objects are properly cleaned up when there are multiple __del__ methods that need to be called.

Calling a __del__ method more than once during object destruction risks resources being released multiple times. The relevant __del__ method may not be designed to be called more than once.

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.
  • A class using multiple inheritance directly calls the __del__ methods of its base types. One or more of those base types uses super() to pass down the inheritance chain.

Recommendation

Either be careful not to explicitly call a __del__ method more than once, or use super() throughout the inheritance hierarchy.

Alternatively refactor one or more of the classes to use composition rather than inheritance.

Example

In the first example, explicit calls to __del__ are used, but SportsCar erroneously calls both Vehicle.__del__ and Car.__del__. This can be fixed by removing the call to Vehicle.__del__, as shown in FixedSportsCar.

#Calling a method multiple times by using explicit calls when a base inherits from other base
class Vehicle(object):
    
    def __del__(self):
        recycle(self.base_parts)
        
        
class Car(Vehicle):
    
    def __del__(self):
        recycle(self.car_parts)
        Vehicle.__del__(self)
    
    
class SportsCar(Car, Vehicle):
    
    # Vehicle.__del__ will get called twice
    def __del__(self):
        recycle(self.sports_car_parts)
        Car.__del__(self)
        Vehicle.__del__(self)
        
        
#Fix SportsCar by only calling Car.__del__
class FixedSportsCar(Car, Vehicle):
    
    def __del__(self):
        recycle(self.sports_car_parts)
        Car.__del__(self)

In the second example, there is a mixture of explicit calls to __del__ and calls using super(). To fix this example, super() should be used throughout.

#Calling a method multiple times by using explicit calls when a base uses super()
class Vehicle(object):
     
    def __init__(self):
        super(Vehicle, self).__init__()
        self.mobile = True
        
class Car(Vehicle):
    
    def __init__(self):
        super(Car, self).__init__()
        self.car_init()
        
    def car_init(self):
        pass
        
class SportsCar(Car, Vehicle):
    
    # Vehicle.__init__ will get called twice
    def __init__(self):
        Vehicle.__init__(self)
        Car.__init__(self)
        self.sports_car_init()
        
    def sports_car_init(self):
        pass
        
#Fix SportsCar by using super()
class FixedSportsCar(Car, Vehicle):
    
    def __init__(self):
        super(SportsCar, self).__init__()
        self.sports_car_init()
        
    def sports_car_init(self):
        pass

References

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

Multiple calls to __init__ during object initialization

Python, unlike statically typed languages such as Java, allows complete freedom when calling methods during object initialization. However, standard object-oriented principles apply to Python classes using deep inheritance hierarchies. Therefore the developer has responsibility for ensuring that objects are properly initialized when there are multiple __init__ methods that need to be called.

Calling an __init__ method more than once during object initialization risks the object being incorrectly initialized. It is unlikely that the relevant __init__ method is designed to be called more than once.

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.
  • A class using multiple inheritance directly calls the __init__ methods of its base types. One or more of those base types uses super() to pass down the inheritance chain.

Recommendation

Either be careful not to explicitly call an __init__ method more than once, or use super() throughout the inheritance hierarchy.

Alternatively refactor one or more of the classes to use composition rather than inheritance.

Example

In the first example, explicit calls to __init__ are used, but SportsCar erroneously calls both Vehicle.__init__ and Car.__init__. This can be fixed by removing the call to Vehicle.__init__, as shown in FixedSportsCar.

#Calling a method multiple times by using explicit calls when a base inherits from other base
class Vehicle(object):
    
    def __init__(self):
        self.mobile = True
        
class Car(Vehicle):
    
    def __init__(self):
        Vehicle.__init__(self)
        self.car_init()
        
    def car_init(self):
        pass
    
class SportsCar(Car, Vehicle):
    
    # Vehicle.__init__ will get called twice
    def __init__(self):
        Vehicle.__init__(self)
        Car.__init__(self)
        self.sports_car_init()
        
    def sports_car_init(self):
        pass
        
#Fix SportsCar by only calling Car.__init__
class FixedSportsCar(Car, Vehicle):
    
    def __init__(self):
        Car.__init__(self)
        self.sports_car_init()
        
    def sports_car_init(self):
        pass
 

In the second example, there is a mixture of explicit calls to __init__ and calls using super(). To fix this example, super() should be used throughout.

#Calling a method multiple times by using explicit calls when a base uses super()
class Vehicle(object):
     
    def __init__(self):
        super(Vehicle, self).__init__()
        self.mobile = True
        
class Car(Vehicle):
    
    def __init__(self):
        super(Car, self).__init__()
        self.car_init()
        
    def car_init(self):
        pass
        
class SportsCar(Car, Vehicle):
    
    # Vehicle.__init__ will get called twice
    def __init__(self):
        Vehicle.__init__(self)
        Car.__init__(self)
        self.sports_car_init()
        
    def sports_car_init(self):
        pass
        
#Fix SportsCar by using super()
class FixedSportsCar(Car, Vehicle):
    
    def __init__(self):
        super(SportsCar, self).__init__()
        self.sports_car_init()
        
    def sports_car_init(self):
        pass

References

call.calls(callTarget, name) and
self.getParameter() = meth.getArg(0) and
self.(DataFlow::LocalSourceNode).flowsTo(call.getArg(0)) and
not exists(Class target | callTarget = classTracker(target))

Check warning

Code scanning / CodeQL

Omittable 'exists' variable Warning

This exists variable can be omitted by using a don't-care expression
in this argument
.
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