diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll index d4444c6795b..9ef775f7e3f 100644 --- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll +++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll @@ -304,6 +304,22 @@ predicate hasContextmanagerDecorator(Function func) { ) } +/** + * Holds if the function `func` has a `typing.overload` decorator. + * Such functions are type stubs that declare an overload signature but are + * not the actual implementation. + */ +overlay[local] +predicate hasOverloadDecorator(Function func) { + exists(ControlFlowNode overload | + overload.(NameNode).getId() = "overload" and overload.(NameNode).isGlobal() + or + overload.(AttrNode).getObject("overload").(NameNode).isGlobal() + | + func.getADecorator() = overload.getNode() + ) +} + // ============================================================================= // Callables // ============================================================================= @@ -849,7 +865,8 @@ private Class getNextClassInMro(Class cls) { */ Function findFunctionAccordingToMro(Class cls, string name) { result = cls.getAMethod() and - result.getName() = name + result.getName() = name and + not hasOverloadDecorator(result) or not class_has_method(cls, name) and result = findFunctionAccordingToMro(getNextClassInMro(cls), name) @@ -891,6 +908,7 @@ Class getNextClassInMroKnownStartingClass(Class cls, Class startingClass) { Function findFunctionAccordingToMroKnownStartingClass(Class cls, Class startingClass, string name) { result = cls.getAMethod() and result.getName() = name and + not hasOverloadDecorator(result) and cls = getADirectSuperclass*(startingClass) or not class_has_method(cls, name) and diff --git a/python/ql/test/library-tests/dataflow/calls-overload/test.py b/python/ql/test/library-tests/dataflow/calls-overload/test.py index db0e4841936..bb385b9de38 100644 --- a/python/ql/test/library-tests/dataflow/calls-overload/test.py +++ b/python/ql/test/library-tests/dataflow/calls-overload/test.py @@ -11,8 +11,8 @@ class OverloadedInit: def __init__(self, x, y=None): pass -OverloadedInit(1) # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9 -OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11 SPURIOUS: init=OverloadedInit.__init__:6 init=OverloadedInit.__init__:9 +OverloadedInit(1) # $ init=OverloadedInit.__init__:11 +OverloadedInit("a", "b") # $ init=OverloadedInit.__init__:11 from typing import overload @@ -28,8 +28,8 @@ class OverloadedInitFromImport: def __init__(self, x, y=None): pass -OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26 -OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28 SPURIOUS: init=OverloadedInitFromImport.__init__:23 init=OverloadedInitFromImport.__init__:26 +OverloadedInitFromImport(1) # $ init=OverloadedInitFromImport.__init__:28 +OverloadedInitFromImport("a", "b") # $ init=OverloadedInitFromImport.__init__:28 class NoOverloads: