Skip to content

Commit f6e53f6

Browse files
committed
Python: Introduce DuckTyping module
This module (which for convenience currently resides inside `DataFlowDispatch`, but this may change later) contains convenience predicates for bridging the gap between the data-flow layer and the old points-to analysis.
1 parent 63b497d commit f6e53f6

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,3 +1977,95 @@ private module OutNodes {
19771977
* `kind`.
19781978
*/
19791979
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
1980+
1981+
/**
1982+
* Provides predicates for approximating type properties of user-defined classes
1983+
* based on their structure (method declarations, base classes).
1984+
*
1985+
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
1986+
* on layers that themselves build upon the call graph (e.g. API graphs).
1987+
*/
1988+
module DuckTyping {
1989+
private import semmle.python.ApiGraphs
1990+
1991+
/**
1992+
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
1993+
*/
1994+
predicate hasMethod(Class cls, string name) {
1995+
cls.getAMethod().getName() = name
1996+
or
1997+
hasMethod(getADirectSuperclass(cls), name)
1998+
}
1999+
2000+
/**
2001+
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
2002+
* and is not just `object`, meaning it may inherit methods from an unknown class.
2003+
*/
2004+
predicate hasUnresolvedBase(Class cls) {
2005+
exists(Expr base | base = cls.getABase() |
2006+
not base = classTracker(_).asExpr() and
2007+
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
2008+
)
2009+
}
2010+
2011+
/**
2012+
* Holds if `cls` supports the container protocol, i.e. it declares
2013+
* `__contains__`, `__iter__`, or `__getitem__`.
2014+
*/
2015+
predicate isContainer(Class cls) {
2016+
hasMethod(cls, "__contains__") or
2017+
hasMethod(cls, "__iter__") or
2018+
hasMethod(cls, "__getitem__")
2019+
}
2020+
2021+
/**
2022+
* Holds if `cls` supports the iterable protocol, i.e. it declares
2023+
* `__iter__` or `__getitem__`.
2024+
*/
2025+
predicate isIterable(Class cls) {
2026+
hasMethod(cls, "__iter__") or
2027+
hasMethod(cls, "__getitem__")
2028+
}
2029+
2030+
/**
2031+
* Holds if `cls` supports the iterator protocol, i.e. it declares
2032+
* both `__iter__` and `__next__`.
2033+
*/
2034+
predicate isIterator(Class cls) {
2035+
hasMethod(cls, "__iter__") and
2036+
hasMethod(cls, "__next__")
2037+
}
2038+
2039+
/**
2040+
* Holds if `cls` supports the context manager protocol, i.e. it declares
2041+
* both `__enter__` and `__exit__`.
2042+
*/
2043+
predicate isContextManager(Class cls) {
2044+
hasMethod(cls, "__enter__") and
2045+
hasMethod(cls, "__exit__")
2046+
}
2047+
2048+
/**
2049+
* Holds if `cls` supports the descriptor protocol, i.e. it declares
2050+
* `__get__`, `__set__`, or `__delete__`.
2051+
*/
2052+
predicate isDescriptor(Class cls) {
2053+
hasMethod(cls, "__get__") or
2054+
hasMethod(cls, "__set__") or
2055+
hasMethod(cls, "__delete__")
2056+
}
2057+
2058+
/**
2059+
* Holds if `cls` is callable, i.e. it declares `__call__`.
2060+
*/
2061+
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
2062+
2063+
/**
2064+
* Holds if `cls` supports the mapping protocol, i.e. it declares
2065+
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
2066+
*/
2067+
predicate isMapping(Class cls) {
2068+
hasMethod(cls, "__getitem__") and
2069+
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
2070+
}
2071+
}

0 commit comments

Comments
 (0)