Semmle 1.21
Skip to end of metadata
Go to start of metadata

Name: __init__ method calls overridden method

Description: Calling a method from __init__ that is overridden by a subclass may result in a partially initialized instance being observed.

ID: py/init-calls-subclass

Kind: problem

Severity: warning

Precision: high

Query: InitCallsSubclassMethod.ql
/**
 * @name __init__ method calls overridden method
 * @description Calling a method from __init__ that is overridden by a subclass may result in a partially
 *              initialized instance being observed.
 * @kind problem
 * @tags reliability
 *       correctness
 * @problem.severity warning
 * @sub-severity low
 * @precision high
 * @id py/init-calls-subclass
 */

import python


from ClassObject supercls, string method, Call call,
     FunctionObject overriding, FunctionObject overridden

where
exists(FunctionObject init, SelfAttribute sa |
       supercls.declaredAttribute("__init__") = init and
       call.getScope() = init.getFunction() and call.getFunc() = sa |
       sa.getName() = method and
       overridden = supercls.declaredAttribute(method) and
       overriding.overrides(overridden)
)

select call, "Call to self.$@ in __init__ method, which is overridden by $@.",
  overridden, method,
  overriding, overriding.descriptiveString()




When an instance of a class is initialized, the super-class state should be fully initialized before it becomes visible to the subclass. Calling methods of the subclass in the superclass' __init__ method violates this important invariant.

Recommendation

Do not use methods that are subclassed in the construction of an object. For simpler cases move the initialization into the superclass' __init__ method, preventing it being overridden. Additional initialization of subclass should be done in the __init__ method of the subclass. For more complex cases, it is advisable to use a static method or function to manage object creation.

Alternatively, avoid inheritance altogether using composition instead.

Example

#Superclass __init__ calls subclass method

class Super(object):

    def __init__(self, arg):
        self._state = "Not OK"
        self.set_up(arg)
        self._state = "OK"

    def set_up(self, arg):
        "Do some set up"

class Sub(Super):

    def __init__(self, arg):
        Super.__init__(self, arg)
        self.important_state = "OK"

    def set_up(self, arg):
        Super.set_up(self, arg)
        "Do some more set up" # Dangerous as self._state is "Not OK"


#Improved version with inheritance:

class Super(object):

    def __init__(self, arg):
        self._state = "Not OK"
        self.super_set_up(arg)
        self._state = "OK"

    def super_set_up(self, arg):
        "Do some set up"


class Sub(Super):

    def __init__(self, arg):
        Super.__init__(self, arg)
        self.sub_set_up(self, arg)
        self.important_state = "OK"


    def sub_set_up(self, arg):
        "Do some more set up"


References