Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ module Builtins {
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
// Added for compatibility
"exec"
"exec",
// Added by the `site` module (available by default unless `-S` is used)
"copyright", "credits", "exit", "quit"
]
or
// Built-in constants shared between Python 2 and 3
Expand All @@ -51,8 +53,8 @@ module Builtins {
or
// Python 2 only
result in [
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
"unicode", "xrange"
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
"unichr", "unicode", "xrange"
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1977,3 +1977,174 @@ private module OutNodes {
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }

/**
* Provides predicates for approximating type properties of user-defined classes
* based on their structure (method declarations, base classes).
*
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
* on layers that themselves build upon the call graph (e.g. API graphs).
*/
module DuckTyping {
private import semmle.python.ApiGraphs

/**
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
*/
predicate hasMethod(Class cls, string name) {
cls.getAMethod().getName() = name
or
hasMethod(getADirectSuperclass(cls), name)
}

/**
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
* and is not just `object`, meaning it may inherit methods from an unknown class.
*/
predicate hasUnresolvedBase(Class cls) {
exists(Expr base | base = cls.getABase() |
not base = classTracker(_).asExpr() and
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
)
}

/**
* Holds if `cls` supports the container protocol, i.e. it declares
* `__contains__`, `__iter__`, or `__getitem__`.
*/
predicate isContainer(Class cls) {
hasMethod(cls, "__contains__") or
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}

/**
* Holds if `cls` supports the iterable protocol, i.e. it declares
* `__iter__` or `__getitem__`.
*/
predicate isIterable(Class cls) {
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}

/**
* Holds if `cls` supports the iterator protocol, i.e. it declares
* both `__iter__` and `__next__`.
*/
predicate isIterator(Class cls) {
hasMethod(cls, "__iter__") and
hasMethod(cls, "__next__")
}

/**
* Holds if `cls` supports the context manager protocol, i.e. it declares
* both `__enter__` and `__exit__`.
*/
predicate isContextManager(Class cls) {
hasMethod(cls, "__enter__") and
hasMethod(cls, "__exit__")
}

/**
* Holds if `cls` supports the descriptor protocol, i.e. it declares
* `__get__`, `__set__`, or `__delete__`.
*/
predicate isDescriptor(Class cls) {
hasMethod(cls, "__get__") or
hasMethod(cls, "__set__") or
hasMethod(cls, "__delete__")
}

/**
* Holds if `cls` directly assigns to an attribute named `name` in its class body.
* This covers attribute assignments like `x = value`, but not method definitions.
*/
predicate declaresAttribute(Class cls, string name) { exists(getAnAttributeValue(cls, name)) }

/**
* Gets the value expression assigned to attribute `name` directly in the class body of `cls`.
*/
Expr getAnAttributeValue(Class cls, string name) {
exists(Assign a |
a.getScope() = cls and
a.getATarget().(Name).getId() = name and
result = a.getValue()
)
}

/**
* Holds if `cls` is callable, i.e. it declares `__call__`.
*/
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }

/**
* Holds if `cls` supports the mapping protocol, i.e. it declares
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
*/
predicate isMapping(Class cls) {
hasMethod(cls, "__getitem__") and
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
}

/**
* Holds if `cls` is a new-style class. In Python 3, all classes are new-style.
* In Python 2, a class is new-style if it (transitively) inherits from `object`,
* or has a declared `__metaclass__`, or has an unresolved base class.
*/
predicate isNewStyle(Class cls) {
major_version() = 3
or
major_version() = 2 and
(
cls.getABase() = API::builtin("object").getAValueReachableFromSource().asExpr()
or
isNewStyle(getADirectSuperclass(cls))
or
hasUnresolvedBase(cls)
or
exists(cls.getMetaClass())
or
// Module-level __metaclass__ = type makes all classes in the module new-style
exists(Assign a |
a.getScope() = cls.getEnclosingModule() and
a.getATarget().(Name).getId() = "__metaclass__"
)
)
}

/**
* Gets the `__init__` function that will be invoked when `cls` is constructed,
* resolved according to the MRO.
*/
Function getInit(Class cls) { result = invokedFunctionFromClassConstruction(cls, "__init__") }

/**
* Holds if `cls` or any of its superclasses uses multiple inheritance, or
* has an unresolved base class. In these cases, our MRO approximation may
* resolve to the wrong `__init__`, so we should not flag argument mismatches.
*/
predicate hasUnreliableMro(Class cls) {
exists(Class sup | sup = getADirectSuperclass*(cls) |
exists(sup.getBase(1))
or
hasUnresolvedBase(sup)
)
}

/**
* Holds if `f` overrides a method in a superclass with the same name.
*/
predicate overridesMethod(Function f) {
exists(Class cls | f.getScope() = cls | hasMethod(getADirectSuperclass(cls), f.getName()))
}

/**
* Holds if `f` is a property accessor (decorated with `@property`, `@name.setter`,
* or `@name.deleter`).
*/
predicate isPropertyAccessor(Function f) {
exists(Attribute a | a = f.getADecorator() | a.getName() = "setter" or a.getName() = "deleter")
or
f.getADecorator().(Name).getId() = "property"
}
}
9 changes: 6 additions & 3 deletions python/ql/src/Classes/PropertyInOldStyleClass.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

from PropertyObject prop, ClassObject cls
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
from Function prop, Class cls
where
prop.getScope() = cls and
prop.getADecorator().(Name).getId() = "property" and
not DuckTyping::isNewStyle(cls)
select prop,
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
" is an old-style class."
8 changes: 5 additions & 3 deletions python/ql/src/Classes/ShouldBeContextManager.ql
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

from ClassValue c
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
from Class c
where
not DuckTyping::isContextManager(c) and
DuckTyping::hasMethod(c, "__del__")
select c,
"Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager."
8 changes: 5 additions & 3 deletions python/ql/src/Classes/SlotsInOldStyleClass.ql
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

from ClassObject c
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
from Class c
where
not DuckTyping::isNewStyle(c) and
DuckTyping::declaresAttribute(c, "__slots__")
select c,
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."
9 changes: 4 additions & 5 deletions python/ql/src/Classes/SuperInOldStyleClass.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

predicate uses_of_super_in_old_style_class(Call s) {
exists(Function f, ClassObject c |
exists(Function f, Class c |
s.getScope() = f and
f.getScope() = c.getPyClass() and
not c.failedInference() and
not c.isNewStyle() and
f.getScope() = c and
not DuckTyping::isNewStyle(c) and
s.getFunc().(Name).getId() = "super"
)
}
Expand Down
11 changes: 3 additions & 8 deletions python/ql/src/Classes/UselessClass.ql
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

predicate fewer_than_two_public_methods(Class cls, int methods) {
(methods = 0 or methods = 1) and
Expand All @@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
}

predicate no_inheritance(Class c) {
not exists(ClassValue cls, ClassValue other |
cls.getScope() = c and
other != ClassValue::object()
|
other.getABaseType() = cls or
cls.getABaseType() = other
) and
not exists(getADirectSubclass(c)) and
not exists(getADirectSuperclass(c)) and
not exists(Expr base | base = c.getABase() |
not base instanceof Name or base.(Name).getId() != "object"
)
Expand Down
9 changes: 5 additions & 4 deletions python/ql/src/Expressions/UseofApply.ql
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.types.Builtins
private import semmle.python.ApiGraphs

from CallNode call, ControlFlowNodeWithPointsTo func
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
from CallNode call
where
major_version() = 2 and
call = API::builtin("apply").getACall().asCfgNode()
select call, "Call to the obsolete builtin function 'apply'."
11 changes: 6 additions & 5 deletions python/ql/src/Functions/DeprecatedSliceMethod.ql
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

predicate slice_method_name(string name) {
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
}

from PythonFunctionValue f, string meth
from Function f, string meth
where
f.getScope().isMethod() and
not f.isOverridingMethod() and
f.isMethod() and
slice_method_name(meth) and
f.getName() = meth
f.getName() = meth and
not DuckTyping::overridesMethod(f) and
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(f.getScope()))
select f, meth + " method has been deprecated since Python 2.0."
4 changes: 2 additions & 2 deletions python/ql/src/Imports/DeprecatedModule.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs

/**
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
Expand Down Expand Up @@ -80,7 +80,7 @@ where
name = imp.getName() and
deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and
except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
except.containsInScope(imp)
)
select imp, deprecation_message(name) + replacement_message(name)
14 changes: 7 additions & 7 deletions python/ql/src/Statements/DocStrings.ql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch

predicate needs_docstring(Scope s) {
s.isPublic() and
Expand All @@ -29,15 +29,15 @@ predicate needs_docstring(Scope s) {
}

predicate function_needs_docstring(FunctionMetrics f) {
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f |
not function_needs_docstring(base.getScope())
not exists(Function base |
DuckTyping::overridesMethod(f) and
base.getScope() = getADirectSuperclass+(f.getScope()) and
base.getName() = f.getName() and
not function_needs_docstring(base)
) and
f.getName() != "lambda" and
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
not exists(PythonPropertyObject p |
p.getGetter().getFunction() = f or
p.getSetter().getFunction() = f
)
not DuckTyping::isPropertyAccessor(f)
}

string scope_type(Scope s) {
Expand Down
Loading
Loading