CodeQL queries 1.24

Skip to end of metadata
Go to start of metadata

Name: Multiple calls to __del__ during object destruction

Description: A duplicated call to a super-class __del__ method may lead to class instances not be cleaned up properly.

ID: py/multiple-calls-to-delete

Kind: problem

Severity: warning

Precision: very-high

Query: SuperclassDelCalledMultipleTimes.ql
/**
 * @name Multiple calls to __del__ during object destruction
 * @description A duplicated call to a super-class __del__ method may lead to class instances not be cleaned up properly.
 * @kind problem
 * @tags efficiency
 *       correctness
 * @problem.severity warning
 * @sub-severity high
 * @precision very-high
 * @id py/multiple-calls-to-delete
 */

import python
import MethodCallOrder


from ClassObject self, FunctionObject multi
where 
multiple_calls_to_superclass_method(self, multi, "__del__") and
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
not exists(FunctionObject better |
    multiple_calls_to_superclass_method(self, better, "__del__") and
    better.overrides(multi)
) and
not self.failedInference()
select self, "Class " + self.getName() + " may not be cleaned up properly as $@ may be called multiple times during destruction.",
multi, multi.descriptiveString()

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