Update qhelp + alert messages

This commit is contained in:
Joe Farebrother
2025-07-04 15:44:37 +01:00
parent 86bb0e8af2
commit ba8658491a
16 changed files with 186 additions and 212 deletions

View File

@@ -4,47 +4,49 @@
<qhelp>
<overview>
<p>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 <code>__del__</code> methods that need to be called.
<p>
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 <code>__del__</code>
methods are called.
</p>
<p>
If the <code>__del__</code> method of a superclass is not called during object destruction it is likely that
Classes with a <code>__del__</code> method (a finalizer) typically hold some resource such as a file handle that needs to be cleaned up.
If the <code>__del__</code> method of a superclass is not called during object finalization, it is likely that
that resources may be leaked.
</p>
<p>A call to the <code>__del__</code> method of a superclass during object destruction may be omitted:
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be unintentionally skipped:
</p>
<ul>
<li>When a subclass calls the <code>__del__</code> method of the wrong class.</li>
<li>When a call to the <code>__del__</code> method of one its base classes is omitted.</li>
<li>If a subclass calls the <code>__del__</code> method of the wrong class.</li>
<li>If a call to the <code>__del__</code> method of one its base classes is omitted.</li>
<li>If a call to <code>super().__del__</code> is used, but not all <code>__del__</code> methods in the Method Resolution Order (MRO)
chain themselves call <code>super()</code>. This in particular arises more often in cases of multiple inheritance. </li>
</ul>
</overview>
<recommendation>
<p>Either be careful to explicitly call the <code>__del__</code> of the correct base class, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
<p>Ensure that all superclass <code>__del__</code> methods are properly called.
Either each base class's finalize method should be explicitly called, or <code>super()</code> calls
should be consistently used throughout the inheritance hierarchy.</p>
</recommendation>
<example>
<p>In this example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
<p>In the following example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__del__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__del__</code>.
</p>
<sample src="MissingCallToDel.py" />
<sample src="examples/MissingCallToDel.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__del__">__del__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
</references>
</qhelp>

View File

@@ -30,18 +30,18 @@ where
not possiblyMissingSuper.isNone() and
possibleIssue = possiblyMissingSuper and
msg =
"This class does not call $@ during destruction. ($@ may be missing a call to super().__del__)"
"This class does not call $@ during finalization. ($@ may be missing a call to super().__del__)"
or
possiblyMissingSuper.isNone() and
(
possibleIssue.asSome() = getDelMethod(base) and
msg =
"This class does not call $@ during destruction. ($@ may be missing a call to a base class __del__)"
"This class does not call $@ during finalization. ($@ may be missing a call to a base class __del__)"
or
not exists(getDelMethod(base)) and
possibleIssue.isNone() and
msg =
"This class does not call $@ during destruction. (The class lacks an __del__ method to ensure every base class __del__ is called.)"
"This class does not call $@ during finalization. (The class lacks an __del__ method to ensure every base class __del__ is called.)"
)
)
)

View File

@@ -4,49 +4,47 @@
<qhelp>
<overview>
<p>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 <code>__init__</code> methods that need to be called.
<p>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 <code>__init__</code>
methods are called.
</p>
<p>
If the <code>__init__</code> method of a superclass is not called during object initialization it is likely that
that object will end up in an incorrect state.
If the <code>__init__</code> 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.
</p>
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be omitted:
<p>A call to the <code>__init__</code> method of a superclass during object initialization may be unintentionally skipped:
</p>
<ul>
<li>When a subclass calls the <code>__init__</code> method of the wrong class.</li>
<li>When a call to the <code>__init__</code> method of one its base classes is omitted.</li>
<li>When multiple inheritance is used and a class inherits from several base classes,
and at least one of those does not use <code>super()</code> in its own <code>__init__</code> method.</li>
<li>If a subclass calls the <code>__init__</code> method of the wrong class.</li>
<li>If a call to the <code>__init__</code> method of one its base classes is omitted.</li>
<li>If a call to <code>super().__init__</code> is used, but not all <code>__init__</code> methods in the Method Resolution Order (MRO)
chain themselves call <code>super()</code>. This in particular arises more often in cases of multiple inheritance. </li>
</ul>
</overview>
<recommendation>
<p>Either be careful to explicitly call the <code>__init__</code> of the correct base class, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
<p>Ensure that all superclass <code>__init__</code> methods are properly called.
Either each base class's initialize method should be explicitly called, or <code>super()</code> calls
should be consistently used throughout the inheritance hierarchy.</p>
</recommendation>
<example>
<p>In this example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
<p>In the following example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
<code>Vehicle.__init__</code>. This is fixed in <code>FixedSportsCar</code> by calling <code>Car.__init__</code>.
</p>
<sample src="MissingCallToInit.py" />
<sample src="examples/MissingCallToInit.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">__init__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
</references>
</qhelp>

View File

@@ -4,55 +4,52 @@
<qhelp>
<overview>
<p>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 <code>__del__</code> methods that need to be called.
<p>
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.
</p>
<p>
Calling a <code>__del__</code> method more than once during object destruction risks resources being released multiple
times. The relevant <code>__del__</code> method may not be designed to be called more than once.
Objects with a <code>__del__</code> 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.
</p>
<p>There are a number of ways that a <code>__del__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__del__</code> methods.</li>
<li>A class using multiple inheritance directly calls the <code>__del__</code> methods of its base types.
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
<li>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)</li>
<li>Another situation involving multiple inheritance arises when a subclass calls the <code>__del__</code> methods of each of its base classes,
one of which calls <code>super().__del__</code>. 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.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful not to explicitly call a <code>__del__</code> method more than once, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
<p>Ensure that each finalizer method is called exactly once during finalization.
This can be ensured by calling <code>super().__del__</code> for each finalizer methid in the inheritance chain.
</p>
</recommendation>
<example>
<p>In the first example, explicit calls to <code>__del__</code> are used, but <code>SportsCar</code> erroneously calls
both <code>Vehicle.__del__</code> and <code>Car.__del__</code>.
This can be fixed by removing the call to <code>Vehicle.__del__</code>, as shown in <code>FixedSportsCar</code>.
</p>
<sample src="SuperclassDelCalledMultipleTimes.py" />
<p>In the second example, there is a mixture of explicit calls to <code>__del__</code> and calls using <code>super()</code>.
To fix this example, <code>super()</code> should be used throughout.
<p>In the following example, there is a mixture of explicit calls to <code>__del__</code> and calls using <code>super()</code>, resulting in <code>Vehicle.__del__</code>
being called twice.
<code>FixedSportsCar.__del__</code> fixes this by using <code>super()</code> consistently with the other delete methods.
</p>
<sample src="SuperclassInitCalledMultipleTimes2.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__del__">__del__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">The Diamond Problem</a>.</li>
</references>
</qhelp>

View File

@@ -36,12 +36,12 @@ where
(
target1 != target2 and
msg =
"This deletion method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
"This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively."
or
target1 = target2 and
// The targets themselves are called multiple times (either is calledMulti, or something earlier in the MRO)
// Mentioning them again would be redundant.
msg = "This deletion method calls $@ multiple times, via $@ and $@."
msg = "This finalization method calls $@ multiple times, via $@ and $@."
)
select meth, msg, calledMulti, calledMulti.getQualifiedName(), call1, "this call", call2,
"this call", target1, target1.getQualifiedName(), target2, target2.getQualifiedName()

View File

@@ -4,54 +4,70 @@
<qhelp>
<overview>
<p>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 <code>__init__</code> methods that need to be called.
<p>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.
</p>
<p>
Calling an <code>__init__</code> method more than once during object initialization risks the object being incorrectly initialized.
It is unlikely that the relevant <code>__init__</code> method is designed to be called more than once.
Calling an <code>__init__</code> 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.
</p>
<p>There are a number of ways that an <code>__init__</code> method may be be called more than once.</p>
<ul>
<li>There may be more than one explicit call to the method in the hierarchy of <code>__init__</code> methods.</li>
<li>A class using multiple inheritance directly calls the <code>__init__</code> methods of its base types.
One or more of those base types uses <code>super()</code> to pass down the inheritance chain.</li>
<li>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)</li>
<li>Another situation involving multiple inheritance arises when a subclass calls the <code>__init__</code> methods of each of its base classes,
one of which calls <code>super().__init__</code>. 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.</li>
</ul>
</overview>
<recommendation>
<p>Either be careful not to explicitly call an <code>__init__</code> method more than once, or
use <code>super()</code> throughout the inheritance hierarchy.</p>
<p>
Take care whenever possible not to call an an initializer multiple times. If each <code>__init__</code> method in the hierarchy
calls <code>super().__init__()</code>, 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 <code>super()</code> calls in the base classes.
</p>
<p>
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.
</p>
<p>Alternatively refactor one or more of the classes to use composition rather than inheritance.</p>
</recommendation>
<example>
<p>In the first example, explicit calls to <code>__init__</code> are used, but <code>SportsCar</code> erroneously calls
both <code>Vehicle.__init__</code> and <code>Car.__init__</code>.
This can be fixed by removing the call to <code>Vehicle.__init__</code>, as shown in <code>FixedSportsCar</code>.
<p>In the following (BAD) example, the class <code>D</code> calls <code>B.__init__</code> and <code>C.__init__</code>,
which each call <code>A.__init__</code>. This results in <code>self.state</code> being set to <code>None</code> as
<code>A.__init__</code> is called again after <code>B.__init__</code> had finished. This may lead to unexpected results.
</p>
<sample src="SuperclassInitCalledMultipleTimes.py" />
<sample src="examples/SuperclassInitCalledMultipleTimesBad1.py" />
<p>In the second example, there is a mixture of explicit calls to <code>__init__</code> and calls using <code>super()</code>.
To fix this example, <code>super()</code> should be used throughout.
<p>In the following (GOOD) example, a call to <code>super().__init__</code> is made in each class
in the inheritance hierarchy, ensuring each initializer is called exactly once.
</p>
<sample src="SuperclassInitCalledMultipleTimes2.py" />
<sample src="examples/SuperclassInitCalledMultipleTimesGood2.py" />
<p>In the following (BAD) example, explicit base class calls are mixed with <code>super()</code> calls, and <code>C.__init__</code> is called twice.</p>
<sample src="examples/SuperclassInitCalledMultipleTimesBad3.py" />
</example>
<references>
<li>Python Tutorial: <a href="https://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/2/library/functions.html#super">super</a>.</li>
<li>Artima Developer: <a href="http://www.artima.com/weblogs/viewpost.jsp?thread=236275">Things to Know About Python Super</a>.</li>
<li>Wikipedia: <a href="http://en.wikipedia.org/wiki/Composition_over_inheritance">Composition over inheritance</a>.</li>
<li>Python Reference: <a href="https://docs.python.org/3/reference/datamodel.html#object.__init__">__init__</a>.</li>
<li>Python Standard Library: <a href="https://docs.python.org/3/library/functions.html#super">super</a>.</li>
<li>Python Glossary: <a href="https://docs.python.org/3/glossary.html#term-method-resolution-order">Method resolution order</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem">The Diamond Problem</a>.</li>
</references>

View File

@@ -10,14 +10,14 @@ class Car(Vehicle):
recycle(self.car_parts)
Vehicle.__del__(self)
#Car.__del__ is missed out.
#BAD: Car.__del__ is not called.
class SportsCar(Car, Vehicle):
def __del__(self):
recycle(self.sports_car_parts)
Vehicle.__del__(self)
#Fix SportsCar by calling Car.__del__
#GOOD: Car.__del__ is called correctly.
class FixedSportsCar(Car, Vehicle):
def __del__(self):

View File

@@ -1,29 +1,32 @@
#Calling a method multiple times by using explicit calls when a base inherits from other base
#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)
Vehicle.__del__(self)
super(Car, self).__del__()
class SportsCar(Car, Vehicle):
# Vehicle.__del__ will get called twice
# BAD: 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__
# GOOD: super() is used ensuring each del method is called once.
class FixedSportsCar(Car, Vehicle):
def __del__(self):
recycle(self.sports_car_parts)
Car.__del__(self)
super(SportsCar, self).__del__()

View File

@@ -1,32 +0,0 @@
#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):
# Vehicle.__del__ will get called twice
def __del__(self):
recycle(self.sports_car_parts)
Car.__del__(self)
Vehicle.__del__(self)
#Fix SportsCar by using super()
class FixedSportsCar(Car, Vehicle):
def __del__(self):
recycle(self.sports_car_parts)
super(SportsCar, self).__del__()

View File

@@ -1,36 +0,0 @@
#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

View File

@@ -1,38 +0,0 @@
#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

View File

@@ -0,0 +1,20 @@
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.

View File

@@ -0,0 +1,22 @@
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

View File

@@ -0,0 +1,22 @@
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.stat =e will be set to "B".
super().__init__()
self.d = 1

View File

@@ -1 +1 @@
| missing_del.py:13:1:13:13 | Class X3 | This class does not call $@ during destruction. ($@ may be missing a call to a base class __del__) | missing_del.py:9:5:9:22 | Function __del__ | X2.__del__ | missing_del.py:15:5:15:22 | Function __del__ | X3.__del__ |
| missing_del.py:13:1:13:13 | Class X3 | This class does not call $@ during finalization. ($@ may be missing a call to a base class __del__) | missing_del.py:9:5:9:22 | Function __del__ | X2.__del__ | missing_del.py:15:5:15:22 | Function __del__ | X3.__del__ |

View File

@@ -1,2 +1,2 @@
| multiple_del.py:21:5:21:22 | Function __del__ | This deletion method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:23:9:23:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:24:9:24:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:15:5:15:22 | Function __del__ | Y2.__del__ |
| multiple_del.py:43:5:43:22 | Function __del__ | This deletion method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:45:9:45:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:46:9:46:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:37:5:37:22 | Function __del__ | Z2.__del__ |
| multiple_del.py:21:5:21:22 | Function __del__ | This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:23:9:23:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:24:9:24:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:9:5:9:22 | Function __del__ | Y1.__del__ | multiple_del.py:15:5:15:22 | Function __del__ | Y2.__del__ |
| multiple_del.py:43:5:43:22 | Function __del__ | This finalization method calls $@ multiple times, via $@ and $@, resolving to $@ and $@ respectively. | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:45:9:45:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:46:9:46:24 | ControlFlowNode for Attribute() | this call | multiple_del.py:32:5:32:22 | Function __del__ | Z1.__del__ | multiple_del.py:37:5:37:22 | Function __del__ | Z2.__del__ |