Skip to content

Commit c38e534

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 e6f5cef commit c38e534

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
@@ -1921,3 +1921,95 @@ private module OutNodes {
19211921
* `kind`.
19221922
*/
19231923
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
1924+
1925+
/**
1926+
* Provides predicates for approximating type properties of user-defined classes
1927+
* based on their structure (method declarations, base classes).
1928+
*
1929+
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
1930+
* on layers that themselves build upon the call graph (e.g. API graphs).
1931+
*/
1932+
module DuckTyping {
1933+
private import semmle.python.ApiGraphs
1934+
1935+
/**
1936+
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
1937+
*/
1938+
predicate hasMethod(Class cls, string name) {
1939+
cls.getAMethod().getName() = name
1940+
or
1941+
hasMethod(getADirectSuperclass(cls), name)
1942+
}
1943+
1944+
/**
1945+
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
1946+
* and is not just `object`, meaning it may inherit methods from an unknown class.
1947+
*/
1948+
predicate hasUnresolvedBase(Class cls) {
1949+
exists(Expr base | base = cls.getABase() |
1950+
not base = classTracker(_).asExpr() and
1951+
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
1952+
)
1953+
}
1954+
1955+
/**
1956+
* Holds if `cls` supports the container protocol, i.e. it declares
1957+
* `__contains__`, `__iter__`, or `__getitem__`.
1958+
*/
1959+
predicate isContainer(Class cls) {
1960+
hasMethod(cls, "__contains__") or
1961+
hasMethod(cls, "__iter__") or
1962+
hasMethod(cls, "__getitem__")
1963+
}
1964+
1965+
/**
1966+
* Holds if `cls` supports the iterable protocol, i.e. it declares
1967+
* `__iter__` or `__getitem__`.
1968+
*/
1969+
predicate isIterable(Class cls) {
1970+
hasMethod(cls, "__iter__") or
1971+
hasMethod(cls, "__getitem__")
1972+
}
1973+
1974+
/**
1975+
* Holds if `cls` supports the iterator protocol, i.e. it declares
1976+
* both `__iter__` and `__next__`.
1977+
*/
1978+
predicate isIterator(Class cls) {
1979+
hasMethod(cls, "__iter__") and
1980+
hasMethod(cls, "__next__")
1981+
}
1982+
1983+
/**
1984+
* Holds if `cls` supports the context manager protocol, i.e. it declares
1985+
* both `__enter__` and `__exit__`.
1986+
*/
1987+
predicate isContextManager(Class cls) {
1988+
hasMethod(cls, "__enter__") and
1989+
hasMethod(cls, "__exit__")
1990+
}
1991+
1992+
/**
1993+
* Holds if `cls` supports the descriptor protocol, i.e. it declares
1994+
* `__get__`, `__set__`, or `__delete__`.
1995+
*/
1996+
predicate isDescriptor(Class cls) {
1997+
hasMethod(cls, "__get__") or
1998+
hasMethod(cls, "__set__") or
1999+
hasMethod(cls, "__delete__")
2000+
}
2001+
2002+
/**
2003+
* Holds if `cls` is callable, i.e. it declares `__call__`.
2004+
*/
2005+
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
2006+
2007+
/**
2008+
* Holds if `cls` supports the mapping protocol, i.e. it declares
2009+
* `__getitem__` and `__keys__`, or `__getitem__` and `__iter__`.
2010+
*/
2011+
predicate isMapping(Class cls) {
2012+
hasMethod(cls, "__getitem__") and
2013+
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
2014+
}
2015+
}

0 commit comments

Comments
 (0)