Skip to content

Commit ed6a6bc

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 61b4980 commit ed6a6bc

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

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

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

0 commit comments

Comments
 (0)