Skip to content

Commit 08e1150

Browse files
authored
Merge pull request #21519 from github/tausbn/python-port-no-alert-change
2 parents c4c363d + 059693c commit 08e1150

File tree

20 files changed

+274
-82
lines changed

20 files changed

+274
-82
lines changed

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ module Builtins {
3232
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
3333
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
3434
// Added for compatibility
35-
"exec"
35+
"exec",
36+
// Added by the `site` module (available by default unless `-S` is used)
37+
"copyright", "credits", "exit", "quit"
3638
]
3739
or
3840
// Built-in constants shared between Python 2 and 3
@@ -51,8 +53,8 @@ module Builtins {
5153
or
5254
// Python 2 only
5355
result in [
54-
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
55-
"unicode", "xrange"
56+
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
57+
"unichr", "unicode", "xrange"
5658
]
5759
}
5860

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

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,3 +1977,185 @@ 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` directly assigns to an attribute named `name` in its class body.
2060+
* This covers attribute assignments like `x = value`, but not method definitions.
2061+
*/
2062+
predicate declaresAttribute(Class cls, string name) { exists(getAnAttributeValue(cls, name)) }
2063+
2064+
/**
2065+
* Gets the value expression assigned to attribute `name` directly in the class body of `cls`.
2066+
*/
2067+
Expr getAnAttributeValue(Class cls, string name) {
2068+
exists(Assign a |
2069+
a.getScope() = cls and
2070+
a.getATarget().(Name).getId() = name and
2071+
result = a.getValue()
2072+
)
2073+
}
2074+
2075+
/**
2076+
* Holds if `cls` is callable, i.e. it declares `__call__`.
2077+
*/
2078+
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
2079+
2080+
/**
2081+
* Holds if `cls` supports the mapping protocol, i.e. it declares
2082+
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
2083+
*/
2084+
predicate isMapping(Class cls) {
2085+
hasMethod(cls, "__getitem__") and
2086+
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
2087+
}
2088+
2089+
/**
2090+
* Holds if `cls` is a new-style class. In Python 3, all classes are new-style.
2091+
* In Python 2, a class is new-style if it (transitively) inherits from `object`,
2092+
* or has a declared `__metaclass__`, or is in a module with a module-level
2093+
* `__metaclass__` declaration, or has an unresolved base class.
2094+
*/
2095+
predicate isNewStyle(Class cls) {
2096+
major_version() = 3
2097+
or
2098+
major_version() = 2 and
2099+
(
2100+
cls.getABase() = API::builtin("object").getAValueReachableFromSource().asExpr()
2101+
or
2102+
isNewStyle(getADirectSuperclass(cls))
2103+
or
2104+
hasUnresolvedBase(cls)
2105+
or
2106+
exists(cls.getMetaClass())
2107+
or
2108+
// Module-level __metaclass__ = type makes all classes in the module new-style
2109+
exists(Assign a |
2110+
a.getScope() = cls.getEnclosingModule() and
2111+
a.getATarget().(Name).getId() = "__metaclass__" and
2112+
a.getValue() = API::builtin("type").getAValueReachableFromSource().asExpr()
2113+
)
2114+
)
2115+
}
2116+
2117+
/**
2118+
* Gets the `__init__` function that will be invoked when `cls` is constructed,
2119+
* resolved according to the MRO.
2120+
*/
2121+
Function getInit(Class cls) { result = invokedFunctionFromClassConstruction(cls, "__init__") }
2122+
2123+
/**
2124+
* Holds if `cls` or any of its superclasses uses multiple inheritance, or
2125+
* has an unresolved base class. In these cases, our MRO approximation may
2126+
* resolve to the wrong `__init__`, so we should not flag argument mismatches.
2127+
*/
2128+
predicate hasUnreliableMro(Class cls) {
2129+
exists(Class sup | sup = getADirectSuperclass*(cls) |
2130+
exists(sup.getBase(1))
2131+
or
2132+
hasUnresolvedBase(sup)
2133+
)
2134+
}
2135+
2136+
/**
2137+
* Holds if `f` overrides a method in a superclass with the same name.
2138+
*/
2139+
predicate overridesMethod(Function f) { overridesMethod(f, _, _) }
2140+
2141+
/**
2142+
* Holds if `f` overrides `overridden` declared in `superclass`.
2143+
*/
2144+
predicate overridesMethod(Function f, Class superclass, Function overridden) {
2145+
exists(Class cls |
2146+
f.getScope() = cls and
2147+
superclass = getADirectSuperclass+(cls) and
2148+
overridden = superclass.getMethod(f.getName())
2149+
)
2150+
}
2151+
2152+
/**
2153+
* Holds if `f` is a property accessor (decorated with `@property`, `@name.setter`,
2154+
* or `@name.deleter`).
2155+
*/
2156+
predicate isPropertyAccessor(Function f) {
2157+
exists(Attribute a | a = f.getADecorator() | a.getName() = "setter" or a.getName() = "deleter")
2158+
or
2159+
f.getADecorator().(Name).getId() = "property"
2160+
}
2161+
}

python/ql/src/Classes/PropertyInOldStyleClass.ql

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@
1111
*/
1212

1313
import python
14-
private import LegacyPointsTo
14+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1515

16-
from PropertyObject prop, ClassObject cls
17-
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
18-
select prop,
16+
from Function prop, Class cls, Name decorator
17+
where
18+
prop.getScope() = cls and
19+
decorator = prop.getADecorator() and
20+
decorator.getId() = "property" and
21+
not DuckTyping::isNewStyle(cls)
22+
select decorator,
1923
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
2024
" is an old-style class."

python/ql/src/Classes/ShouldBeContextManager.ql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
*/
1515

1616
import python
17-
private import LegacyPointsTo
17+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1818

19-
from ClassValue c
20-
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
19+
from Class c
20+
where
21+
not DuckTyping::isContextManager(c) and
22+
exists(c.getMethod("__del__"))
2123
select c,
2224
"Class " + c.getName() +
2325
" implements __del__ (presumably to release some resource). Consider making it a context manager."

python/ql/src/Classes/SlotsInOldStyleClass.ql

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
*/
1313

1414
import python
15-
private import LegacyPointsTo
15+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1616

17-
from ClassObject c
18-
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
17+
from Class c
18+
where
19+
not DuckTyping::isNewStyle(c) and
20+
DuckTyping::declaresAttribute(c, "__slots__")
1921
select c,
2022
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."

python/ql/src/Classes/SuperInOldStyleClass.ql

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
*/
1212

1313
import python
14-
private import LegacyPointsTo
14+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1515

1616
predicate uses_of_super_in_old_style_class(Call s) {
17-
exists(Function f, ClassObject c |
17+
exists(Function f, Class c |
1818
s.getScope() = f and
19-
f.getScope() = c.getPyClass() and
20-
not c.failedInference() and
21-
not c.isNewStyle() and
19+
f.getScope() = c and
20+
not DuckTyping::isNewStyle(c) and
2221
s.getFunc().(Name).getId() = "super"
2322
)
2423
}

python/ql/src/Classes/UselessClass.ql

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import python
16-
private import LegacyPointsTo
16+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1717

1818
predicate fewer_than_two_public_methods(Class cls, int methods) {
1919
(methods = 0 or methods = 1) and
@@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
2525
}
2626

2727
predicate no_inheritance(Class c) {
28-
not exists(ClassValue cls, ClassValue other |
29-
cls.getScope() = c and
30-
other != ClassValue::object()
31-
|
32-
other.getABaseType() = cls or
33-
cls.getABaseType() = other
34-
) and
28+
not exists(getADirectSubclass(c)) and
29+
not exists(getADirectSuperclass(c)) and
3530
not exists(Expr base | base = c.getABase() |
3631
not base instanceof Name or base.(Name).getId() != "object"
3732
)

python/ql/src/Expressions/UseofApply.ql

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
*/
1111

1212
import python
13-
private import LegacyPointsTo
14-
private import semmle.python.types.Builtins
13+
private import semmle.python.ApiGraphs
1514

16-
from CallNode call, ControlFlowNodeWithPointsTo func
17-
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
15+
from CallNode call
16+
where
17+
major_version() = 2 and
18+
call = API::builtin("apply").getACall().asCfgNode()
1819
select call, "Call to the obsolete builtin function 'apply'."

python/ql/src/Functions/DeprecatedSliceMethod.ql

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@
1010
*/
1111

1212
import python
13-
private import LegacyPointsTo
13+
private import semmle.python.dataflow.new.internal.DataFlowDispatch
1414

1515
predicate slice_method_name(string name) {
1616
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
1717
}
1818

19-
from PythonFunctionValue f, string meth
19+
from Function f, string meth
2020
where
21-
f.getScope().isMethod() and
22-
not f.isOverridingMethod() and
21+
f.isMethod() and
2322
slice_method_name(meth) and
24-
f.getName() = meth
23+
f.getName() = meth and
24+
not DuckTyping::overridesMethod(f) and
25+
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(f.getScope()))
2526
select f, meth + " method has been deprecated since Python 2.0."

python/ql/src/Imports/DeprecatedModule.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import python
14-
private import LegacyPointsTo
14+
private import semmle.python.ApiGraphs
1515

1616
/**
1717
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
@@ -80,7 +80,7 @@ where
8080
name = imp.getName() and
8181
deprecated_module(name, instead, _, _) and
8282
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
83-
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and
83+
except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
8484
except.containsInScope(imp)
8585
)
8686
select imp, deprecation_message(name) + replacement_message(name)

0 commit comments

Comments
 (0)