Python: Autoformat everything using qlformat.

Will need subsequent PRs fixing up test failures (due to deprecated
methods moving around), but other than that everything should be
straight-forward.
This commit is contained in:
Taus Brock-Nannestad
2020-07-07 15:43:52 +02:00
parent 993506d781
commit f07a7bf8cf
602 changed files with 26777 additions and 26790 deletions

View File

@@ -11,6 +11,6 @@ import python
from ExceptStmt ex, ClassValue cls from ExceptStmt ex, ClassValue cls
where where
cls.getName() = "MyExceptionClass" and cls.getName() = "MyExceptionClass" and
ex.getType().pointsTo(cls) ex.getType().pointsTo(cls)
select ex select ex

View File

@@ -12,7 +12,7 @@ import python
from IfExp e, ClassObject cls1, ClassObject cls2 from IfExp e, ClassObject cls1, ClassObject cls2
where where
e.getBody().refersTo(_, cls1, _) and e.getBody().refersTo(_, cls1, _) and
e.getOrelse().refersTo(_, cls2, _) and e.getOrelse().refersTo(_, cls2, _) and
cls1 != cls2 cls1 != cls2
select e select e

View File

@@ -14,8 +14,8 @@ import python
from If i from If i
where where
not exists(Stmt s | not exists(Stmt s |
i.getStmt(_) = s and i.getStmt(_) = s and
not s instanceof Pass not s instanceof Pass
) )
select i select i

View File

@@ -14,6 +14,6 @@ import python
from ClassObject sub, ClassObject base from ClassObject sub, ClassObject base
where where
base.getName() = "MyClass" and base.getName() = "MyClass" and
sub.getABaseType() = base sub.getABaseType() = base
select sub select sub

View File

@@ -10,6 +10,6 @@ import python
from AstNode call, PythonFunctionValue method from AstNode call, PythonFunctionValue method
where where
method.getQualifiedName() = "MyClass.methodName" and method.getQualifiedName() = "MyClass.methodName" and
method.getACall().getNode() = call method.getACall().getNode() = call
select call select call

View File

@@ -11,6 +11,6 @@ import python
from Call new, ClassValue cls from Call new, ClassValue cls
where where
cls.getName() = "MyClass" and cls.getName() = "MyClass" and
new.getFunc().pointsTo(cls) new.getFunc().pointsTo(cls)
select new select new

View File

@@ -10,6 +10,6 @@ import python
from FunctionObject override, FunctionObject base from FunctionObject override, FunctionObject base
where where
base.getQualifiedName() = "MyClass.methodName" and base.getQualifiedName() = "MyClass.methodName" and
override.overrides(base) override.overrides(base)
select override select override

View File

@@ -9,9 +9,9 @@ import python
from AstNode print from AstNode print
where where
/* Python 2 without `from __future__ import print_function` */ /* Python 2 without `from __future__ import print_function` */
print instanceof Print print instanceof Print
or or
/* Python 3 or with `from __future__ import print_function` */ /* Python 3 or with `from __future__ import print_function` */
print.(Call).getFunc().pointsTo(Value::named("print")) print.(Call).getFunc().pointsTo(Value::named("print"))
select print select print

View File

@@ -9,12 +9,12 @@
import python import python
predicate is_private(Attribute a) { predicate is_private(Attribute a) {
a.getName().matches("\\_%") and a.getName().matches("\\_%") and
not a.getName().matches("\\_\\_%\\_\\_") not a.getName().matches("\\_\\_%\\_\\_")
} }
from Attribute access from Attribute access
where where
is_private(access) and is_private(access) and
not access.getObject().(Name).getId() = "self" not access.getObject().(Name).getId() = "self"
select access select access

View File

@@ -11,6 +11,6 @@ import python
from Raise raise, ClassValue ex from Raise raise, ClassValue ex
where where
ex.getName() = "AnException" and ex.getName() = "AnException" and
raise.getException().pointsTo(ex.getASuperType()) raise.getException().pointsTo(ex.getASuperType())
select raise, "Don't raise instances of 'AnException'" select raise, "Don't raise instances of 'AnException'"

View File

@@ -13,6 +13,6 @@ import python
from SubscriptNode store from SubscriptNode store
where where
store.isStore() and store.isStore() and
store.getIndex().pointsTo(Value::named("None")) store.getIndex().pointsTo(Value::named("None"))
select store select store

View File

@@ -11,6 +11,6 @@ import python
from Try t from Try t
where where
exists(t.getFinalbody()) and exists(t.getFinalbody()) and
not exists(t.getAHandler()) not exists(t.getAHandler())
select t select t

View File

@@ -3,140 +3,140 @@ private import semmle.python.pointsto.PointsTo
/** Helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */ /** Helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */
class CheckClass extends ClassObject { class CheckClass extends ClassObject {
private predicate ofInterest() { private predicate ofInterest() {
not this.unknowableAttributes() and not this.unknowableAttributes() and
not this.getPyClass().isProbableMixin() and not this.getPyClass().isProbableMixin() and
this.getPyClass().isPublic() and this.getPyClass().isPublic() and
not this.getPyClass().getScope() instanceof Function and not this.getPyClass().getScope() instanceof Function and
not this.probablyAbstract() and not this.probablyAbstract() and
not this.declaresAttribute("__new__") and not this.declaresAttribute("__new__") and
not this.selfDictAssigns() and not this.selfDictAssigns() and
not this.lookupAttribute("__getattribute__") != object_getattribute() and not this.lookupAttribute("__getattribute__") != object_getattribute() and
not this.hasAttribute("__getattr__") and not this.hasAttribute("__getattr__") and
not this.selfSetattr() and not this.selfSetattr() and
/* If class overrides object.__init__, but we can't resolve it to a Python function then give up */ /* If class overrides object.__init__, but we can't resolve it to a Python function then give up */
forall(ClassObject sup | forall(ClassObject sup |
sup = this.getAnImproperSuperType() and sup = this.getAnImproperSuperType() and
sup.declaresAttribute("__init__") and sup.declaresAttribute("__init__") and
not sup = theObjectType() not sup = theObjectType()
| |
sup.declaredAttribute("__init__") instanceof PyFunctionObject sup.declaredAttribute("__init__") instanceof PyFunctionObject
) )
} }
predicate alwaysDefines(string name) { predicate alwaysDefines(string name) {
auto_name(name) or auto_name(name) or
this.hasAttribute(name) or this.hasAttribute(name) or
this.getAnImproperSuperType().assignedInInit(name) or this.getAnImproperSuperType().assignedInInit(name) or
this.getMetaClass().assignedInInit(name) this.getMetaClass().assignedInInit(name)
} }
predicate sometimesDefines(string name) { predicate sometimesDefines(string name) {
this.alwaysDefines(name) this.alwaysDefines(name)
or
exists(SelfAttributeStore sa |
sa.getScope().getScope+() = this.getAnImproperSuperType().getPyClass()
|
name = sa.getName()
)
}
private predicate selfDictAssigns() {
exists(Assign a, SelfAttributeRead self_dict, Subscript sub |
self_dict.getName() = "__dict__" and
(
self_dict = sub.getObject()
or or
exists(SelfAttributeStore sa | /* Indirect assignment via temporary variable */
sa.getScope().getScope+() = this.getAnImproperSuperType().getPyClass() exists(SsaVariable v |
| v.getAUse() = sub.getObject().getAFlowNode() and
name = sa.getName() v.getDefinition().(DefinitionNode).getValue() = self_dict.getAFlowNode()
) )
} ) and
a.getATarget() = sub and
exists(FunctionObject meth |
meth = this.lookupAttribute(_) and a.getScope() = meth.getFunction()
)
)
}
private predicate selfDictAssigns() { pragma[nomagic]
exists(Assign a, SelfAttributeRead self_dict, Subscript sub | private predicate monkeyPatched(string name) {
self_dict.getName() = "__dict__" and exists(Attribute a |
( a.getCtx() instanceof Store and
self_dict = sub.getObject() PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and
or a.getName() = name
/* Indirect assignment via temporary variable */ )
exists(SsaVariable v | }
v.getAUse() = sub.getObject().getAFlowNode() and
v.getDefinition().(DefinitionNode).getValue() = self_dict.getAFlowNode()
)
) and
a.getATarget() = sub and
exists(FunctionObject meth |
meth = this.lookupAttribute(_) and a.getScope() = meth.getFunction()
)
)
}
pragma[nomagic] private predicate selfSetattr() {
private predicate monkeyPatched(string name) { exists(Call c, Name setattr, Name self, Function method |
exists(Attribute a | (
a.getCtx() instanceof Store and method.getScope() = this.getPyClass() or
PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and method.getScope() = this.getASuperType().getPyClass()
a.getName() = name ) and
) c.getScope() = method and
} c.getFunc() = setattr and
setattr.getId() = "setattr" and
c.getArg(0) = self and
self.getId() = "self"
)
}
private predicate selfSetattr() { predicate interestingUndefined(SelfAttributeRead a) {
exists(Call c, Name setattr, Name self, Function method | exists(string name | name = a.getName() |
( interestingContext(a, name) and
method.getScope() = this.getPyClass() or not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name)
method.getScope() = this.getASuperType().getPyClass() )
) and }
c.getScope() = method and
c.getFunc() = setattr and
setattr.getId() = "setattr" and
c.getArg(0) = self and
self.getId() = "self"
)
}
predicate interestingUndefined(SelfAttributeRead a) { private predicate interestingContext(SelfAttributeRead a, string name) {
exists(string name | name = a.getName() | name = a.getName() and
interestingContext(a, name) and this.ofInterest() and
not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name) this.getPyClass() = a.getScope().getScope() and
) not a.locallyDefined() and
} not a.guardedByHasattr() and
a.getScope().isPublic() and
not this.monkeyPatched(name) and
not attribute_assigned_in_method(lookupAttribute("setUp"), name)
}
private predicate interestingContext(SelfAttributeRead a, string name) { private predicate probablyAbstract() {
name = a.getName() and this.getName().matches("Abstract%")
this.ofInterest() and or
this.getPyClass() = a.getScope().getScope() and this.isAbstract()
not a.locallyDefined() and }
not a.guardedByHasattr() and
a.getScope().isPublic() and
not this.monkeyPatched(name) and
not attribute_assigned_in_method(lookupAttribute("setUp"), name)
}
private predicate probablyAbstract() { pragma[nomagic]
this.getName().matches("Abstract%") private predicate definitionInBlock(BasicBlock b, string name) {
or exists(SelfAttributeStore sa |
this.isAbstract() sa.getAFlowNode().getBasicBlock() = b and
} sa.getName() = name and
sa.getClass() = this.getPyClass()
)
or
exists(FunctionObject method | this.lookupAttribute(_) = method |
attribute_assigned_in_method(method, name) and
b = method.getACall().getBasicBlock()
)
}
pragma[nomagic] pragma[nomagic]
private predicate definitionInBlock(BasicBlock b, string name) { private predicate definedInBlock(BasicBlock b, string name) {
exists(SelfAttributeStore sa | // manual specialisation: this is only called from interestingUndefined,
sa.getAFlowNode().getBasicBlock() = b and // so we can push the context in from there, which must apply to a
sa.getName() = name and // SelfAttributeRead in the same scope
sa.getClass() = this.getPyClass() exists(SelfAttributeRead a | a.getScope() = b.getScope() and name = a.getName() |
) interestingContext(a, name)
or ) and
exists(FunctionObject method | this.lookupAttribute(_) = method | this.definitionInBlock(b, name)
attribute_assigned_in_method(method, name) and or
b = method.getACall().getBasicBlock() exists(BasicBlock prev | this.definedInBlock(prev, name) and prev.getASuccessor() = b)
) }
}
pragma[nomagic]
private predicate definedInBlock(BasicBlock b, string name) {
// manual specialisation: this is only called from interestingUndefined,
// so we can push the context in from there, which must apply to a
// SelfAttributeRead in the same scope
exists(SelfAttributeRead a | a.getScope() = b.getScope() and name = a.getName() |
interestingContext(a, name)
) and
this.definitionInBlock(b, name)
or
exists(BasicBlock prev | this.definedInBlock(prev, name) and prev.getASuccessor() = b)
}
} }
private Object object_getattribute() { private Object object_getattribute() {
result.asBuiltin() = theObjectType().asBuiltin().getMember("__getattribute__") result.asBuiltin() = theObjectType().asBuiltin().getMember("__getattribute__")
} }
private predicate auto_name(string name) { name = "__class__" or name = "__dict__" } private predicate auto_name(string name) { name = "__class__" or name = "__dict__" }

View File

@@ -14,48 +14,48 @@
import python import python
predicate does_nothing(PyFunctionObject f) { predicate does_nothing(PyFunctionObject f) {
not exists(Stmt s | s.getScope() = f.getFunction() | not exists(Stmt s | s.getScope() = f.getFunction() |
not s instanceof Pass and not s.(ExprStmt).getValue() = f.getFunction().getDocString() not s instanceof Pass and not s.(ExprStmt).getValue() = f.getFunction().getDocString()
) )
} }
/* If a method performs a super() call then it is OK as the 'overridden' method will get called */ /* If a method performs a super() call then it is OK as the 'overridden' method will get called */
predicate calls_super(FunctionObject f) { predicate calls_super(FunctionObject f) {
exists(Call sup, Call meth, Attribute attr, GlobalVariable v | exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
meth.getScope() = f.getFunction() and meth.getScope() = f.getFunction() and
meth.getFunc() = attr and meth.getFunc() = attr and
attr.getObject() = sup and attr.getObject() = sup and
attr.getName() = f.getName() and attr.getName() = f.getName() and
sup.getFunc() = v.getAnAccess() and sup.getFunc() = v.getAnAccess() and
v.getId() = "super" v.getId() = "super"
) )
} }
/** Holds if the given name is allowed for some reason */ /** Holds if the given name is allowed for some reason */
predicate allowed(string name) { predicate allowed(string name) {
/* /*
* The standard library specifically recommends this :( * The standard library specifically recommends this :(
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins * See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins
*/ */
name = "process_request" name = "process_request"
} }
from from
ClassObject c, ClassObject b1, ClassObject b2, string name, int i1, int i2, Object o1, Object o2 ClassObject c, ClassObject b1, ClassObject b2, string name, int i1, int i2, Object o1, Object o2
where where
c.getBaseType(i1) = b1 and c.getBaseType(i1) = b1 and
c.getBaseType(i2) = b2 and c.getBaseType(i2) = b2 and
i1 < i2 and i1 < i2 and
o1 != o2 and o1 != o2 and
o1 = b1.lookupAttribute(name) and o1 = b1.lookupAttribute(name) and
o2 = b2.lookupAttribute(name) and o2 = b2.lookupAttribute(name) and
not name.matches("\\_\\_%\\_\\_") and not name.matches("\\_\\_%\\_\\_") and
not calls_super(o1) and not calls_super(o1) and
not does_nothing(o2) and not does_nothing(o2) and
not allowed(name) and not allowed(name) and
not o1.overrides(o2) and not o1.overrides(o2) and
not o2.overrides(o1) and not o2.overrides(o1) and
not c.declaresAttribute(name) not c.declaresAttribute(name)
select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1, select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1,
o1.toString(), o2, o2.toString() o1.toString(), o2, o2.toString()

View File

@@ -15,21 +15,21 @@ import semmle.python.SelfAttribute
import Equality import Equality
predicate class_stores_to_attribute(ClassValue cls, SelfAttributeStore store, string name) { predicate class_stores_to_attribute(ClassValue cls, SelfAttributeStore store, string name) {
exists(FunctionValue f | exists(FunctionValue f |
f = cls.declaredAttribute(_) and store.getScope() = f.getScope() and store.getName() = name f = cls.declaredAttribute(_) and store.getScope() = f.getScope() and store.getName() = name
) and ) and
/* Exclude classes used as metaclasses */ /* Exclude classes used as metaclasses */
not cls.getASuperType() = ClassValue::type() not cls.getASuperType() = ClassValue::type()
} }
predicate should_override_eq(ClassValue cls, Value base_eq) { predicate should_override_eq(ClassValue cls, Value base_eq) {
not cls.declaresAttribute("__eq__") and not cls.declaresAttribute("__eq__") and
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq | exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
not exists(GenericEqMethod eq | eq.getScope() = sup.getScope()) and not exists(GenericEqMethod eq | eq.getScope() = sup.getScope()) and
not exists(IdentityEqMethod eq | eq.getScope() = sup.getScope()) and not exists(IdentityEqMethod eq | eq.getScope() = sup.getScope()) and
not base_eq.(FunctionValue).getScope() instanceof IdentityEqMethod and not base_eq.(FunctionValue).getScope() instanceof IdentityEqMethod and
not base_eq = ClassValue::object().declaredAttribute("__eq__") not base_eq = ClassValue::object().declaredAttribute("__eq__")
) )
} }
/** /**
@@ -37,21 +37,21 @@ predicate should_override_eq(ClassValue cls, Value base_eq) {
* which implies that the __eq__ method does not need to be overridden. * which implies that the __eq__ method does not need to be overridden.
*/ */
predicate superclassEqExpectsAttribute(ClassValue cls, FunctionValue base_eq, string attrname) { predicate superclassEqExpectsAttribute(ClassValue cls, FunctionValue base_eq, string attrname) {
not cls.declaresAttribute("__eq__") and not cls.declaresAttribute("__eq__") and
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq | exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
exists(SelfAttributeRead store | store.getName() = attrname | exists(SelfAttributeRead store | store.getName() = attrname |
store.getScope() = base_eq.getScope() store.getScope() = base_eq.getScope()
)
) )
)
} }
from ClassValue cls, SelfAttributeStore store, Value base_eq from ClassValue cls, SelfAttributeStore store, Value base_eq
where where
class_stores_to_attribute(cls, store, _) and class_stores_to_attribute(cls, store, _) and
should_override_eq(cls, base_eq) and should_override_eq(cls, base_eq) and
/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */ /* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */
not cls.getASuperType().getName() = "TestCase" and not cls.getASuperType().getName() = "TestCase" and
not superclassEqExpectsAttribute(cls, base_eq, store.getName()) not superclassEqExpectsAttribute(cls, base_eq, store.getName())
select cls, select cls,
"The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq, "The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq,
"'__eq__'", store, store.getName() "'__eq__'", store, store.getName()

View File

@@ -1,14 +1,14 @@
import python import python
private Attribute dictAccess(LocalVariable var) { private Attribute dictAccess(LocalVariable var) {
result.getName() = "__dict__" and result.getName() = "__dict__" and
result.getObject() = var.getAnAccess() result.getObject() = var.getAnAccess()
} }
private Call getattr(LocalVariable obj, LocalVariable attr) { private Call getattr(LocalVariable obj, LocalVariable attr) {
result.getFunc().(Name).getId() = "getattr" and result.getFunc().(Name).getId() = "getattr" and
result.getArg(0) = obj.getAnAccess() and result.getArg(0) = obj.getAnAccess() and
result.getArg(1) = attr.getAnAccess() result.getArg(1) = attr.getAnAccess()
} }
/** /**
@@ -16,59 +16,59 @@ private Call getattr(LocalVariable obj, LocalVariable attr) {
* or compares attributes using `getattr`. * or compares attributes using `getattr`.
*/ */
class GenericEqMethod extends Function { class GenericEqMethod extends Function {
GenericEqMethod() { GenericEqMethod() {
this.getName() = "__eq__" and this.getName() = "__eq__" and
exists(LocalVariable self, LocalVariable other | exists(LocalVariable self, LocalVariable other |
self.getAnAccess() = this.getArg(0) and self.getAnAccess() = this.getArg(0) and
self.getId() = "self" and self.getId() = "self" and
other.getAnAccess() = this.getArg(1) and other.getAnAccess() = this.getArg(1) and
exists(Compare eq | exists(Compare eq |
eq.getOp(0) instanceof Eq or eq.getOp(0) instanceof Eq or
eq.getOp(0) instanceof NotEq eq.getOp(0) instanceof NotEq
| |
// `self.__dict__ == other.__dict__` // `self.__dict__ == other.__dict__`
eq.getAChildNode() = dictAccess(self) and eq.getAChildNode() = dictAccess(self) and
eq.getAChildNode() = dictAccess(other) eq.getAChildNode() = dictAccess(other)
or or
// `getattr(self, var) == getattr(other, var)` // `getattr(self, var) == getattr(other, var)`
exists(Variable var | exists(Variable var |
eq.getAChildNode() = getattr(self, var) and eq.getAChildNode() = getattr(self, var) and
eq.getAChildNode() = getattr(other, var) eq.getAChildNode() = getattr(other, var)
)
)
) )
} )
)
}
} }
/** An `__eq__` method that just does `self is other` */ /** An `__eq__` method that just does `self is other` */
class IdentityEqMethod extends Function { class IdentityEqMethod extends Function {
IdentityEqMethod() { IdentityEqMethod() {
this.getName() = "__eq__" and this.getName() = "__eq__" and
exists(LocalVariable self, LocalVariable other | exists(LocalVariable self, LocalVariable other |
self.getAnAccess() = this.getArg(0) and self.getAnAccess() = this.getArg(0) and
self.getId() = "self" and self.getId() = "self" and
other.getAnAccess() = this.getArg(1) and other.getAnAccess() = this.getArg(1) and
exists(Compare eq | eq.getOp(0) instanceof Is | exists(Compare eq | eq.getOp(0) instanceof Is |
eq.getAChildNode() = self.getAnAccess() and eq.getAChildNode() = self.getAnAccess() and
eq.getAChildNode() = other.getAnAccess() eq.getAChildNode() = other.getAnAccess()
) )
) )
} }
} }
/** An (in)equality method that delegates to its complement */ /** An (in)equality method that delegates to its complement */
class DelegatingEqualityMethod extends Function { class DelegatingEqualityMethod extends Function {
DelegatingEqualityMethod() { DelegatingEqualityMethod() {
exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 | exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 |
ret.getScope() = this and ret.getScope() = this and
ret.getValue() = not_ and ret.getValue() = not_ and
not_.getOp() instanceof Not and not_.getOp() instanceof Not and
not_.getOperand() = comp and not_.getOperand() = comp and
comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess()) comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess())
| |
this.getName() = "__eq__" and op instanceof NotEq this.getName() = "__eq__" and op instanceof NotEq
or or
this.getName() = "__ne__" and op instanceof Eq this.getName() = "__ne__" and op instanceof Eq
) )
} }
} }

View File

@@ -14,49 +14,49 @@
import python import python
CallableValue defines_equality(ClassValue c, string name) { CallableValue defines_equality(ClassValue c, string name) {
( (
name = "__eq__" name = "__eq__"
or or
major_version() = 2 and name = "__cmp__" major_version() = 2 and name = "__cmp__"
) and ) and
result = c.declaredAttribute(name) result = c.declaredAttribute(name)
} }
CallableValue implemented_method(ClassValue c, string name) { CallableValue implemented_method(ClassValue c, string name) {
result = defines_equality(c, name) result = defines_equality(c, name)
or or
result = c.declaredAttribute("__hash__") and name = "__hash__" result = c.declaredAttribute("__hash__") and name = "__hash__"
} }
string unimplemented_method(ClassValue c) { string unimplemented_method(ClassValue c) {
not exists(defines_equality(c, _)) and not exists(defines_equality(c, _)) and
( (
result = "__eq__" and major_version() = 3 result = "__eq__" and major_version() = 3
or
major_version() = 2 and result = "__eq__ or __cmp__"
)
or or
/* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */ major_version() = 2 and result = "__eq__ or __cmp__"
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2 )
or
/* Python 3 automatically makes classes unhashable if __eq__ is defined, but __hash__ is not */
not c.declaresAttribute(result) and result = "__hash__" and major_version() = 2
} }
/** Holds if this class is unhashable */ /** Holds if this class is unhashable */
predicate unhashable(ClassValue cls) { predicate unhashable(ClassValue cls) {
cls.lookup("__hash__") = Value::named("None") cls.lookup("__hash__") = Value::named("None")
or or
cls.lookup("__hash__").(CallableValue).neverReturns() cls.lookup("__hash__").(CallableValue).neverReturns()
} }
predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) { predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) {
not unhashable(c) and not unhashable(c) and
missing = unimplemented_method(c) and missing = unimplemented_method(c) and
method = implemented_method(c, present) and method = implemented_method(c, present) and
not c.failedInference(_) not c.failedInference(_)
} }
from ClassValue c, string present, string missing, CallableValue method from ClassValue c, string present, string missing, CallableValue method
where where
violates_hash_contract(c, present, missing, method) and violates_hash_contract(c, present, missing, method) and
exists(c.getScope()) // Suppress results that aren't from source exists(c.getScope()) // Suppress results that aren't from source
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, select method, "Class $@ implements " + present + " but does not define " + missing + ".", c,
c.getName() c.getName()

View File

@@ -16,33 +16,33 @@ import Equality
string equals_or_ne() { result = "__eq__" or result = "__ne__" } string equals_or_ne() { result = "__eq__" or result = "__ne__" }
predicate total_ordering(Class cls) { predicate total_ordering(Class cls) {
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering") exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
or or
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering") exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
} }
CallableValue implemented_method(ClassValue c, string name) { CallableValue implemented_method(ClassValue c, string name) {
result = c.declaredAttribute(name) and name = equals_or_ne() result = c.declaredAttribute(name) and name = equals_or_ne()
} }
string unimplemented_method(ClassValue c) { string unimplemented_method(ClassValue c) {
not c.declaresAttribute(result) and result = equals_or_ne() not c.declaresAttribute(result) and result = equals_or_ne()
} }
predicate violates_equality_contract( predicate violates_equality_contract(
ClassValue c, string present, string missing, CallableValue method ClassValue c, string present, string missing, CallableValue method
) { ) {
missing = unimplemented_method(c) and missing = unimplemented_method(c) and
method = implemented_method(c, present) and method = implemented_method(c, present) and
not c.failedInference(_) and not c.failedInference(_) and
not total_ordering(c.getScope()) and not total_ordering(c.getScope()) and
/* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */ /* Python 3 automatically implements __ne__ if __eq__ is defined, but not vice-versa */
not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and not (major_version() = 3 and present = "__eq__" and missing = "__ne__") and
not method.getScope() instanceof DelegatingEqualityMethod and not method.getScope() instanceof DelegatingEqualityMethod and
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
} }
from ClassValue c, string present, string missing, CallableValue method from ClassValue c, string present, string missing, CallableValue method
where violates_equality_contract(c, present, missing, method) where violates_equality_contract(c, present, missing, method)
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c, select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c,
c.getName() c.getName()

View File

@@ -13,61 +13,61 @@
import python import python
predicate total_ordering(Class cls) { predicate total_ordering(Class cls) {
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering") exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
or or
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering") exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
} }
string ordering_name(int n) { string ordering_name(int n) {
result = "__lt__" and n = 1 result = "__lt__" and n = 1
or or
result = "__le__" and n = 2 result = "__le__" and n = 2
or or
result = "__gt__" and n = 3 result = "__gt__" and n = 3
or or
result = "__ge__" and n = 4 result = "__ge__" and n = 4
} }
predicate overrides_ordering_method(ClassValue c, string name) { predicate overrides_ordering_method(ClassValue c, string name) {
name = ordering_name(_) and name = ordering_name(_) and
( (
c.declaresAttribute(name) c.declaresAttribute(name)
or or
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") | exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
sup.declaresAttribute(name) sup.declaresAttribute(name)
)
) )
)
} }
string unimplemented_ordering(ClassValue c, int n) { string unimplemented_ordering(ClassValue c, int n) {
not c = Value::named("object") and not c = Value::named("object") and
not overrides_ordering_method(c, result) and not overrides_ordering_method(c, result) and
result = ordering_name(n) result = ordering_name(n)
} }
string unimplemented_ordering_methods(ClassValue c, int n) { string unimplemented_ordering_methods(ClassValue c, int n) {
n = 0 and result = "" and exists(unimplemented_ordering(c, _)) n = 0 and result = "" and exists(unimplemented_ordering(c, _))
or
exists(string prefix, int nm1 | n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) |
prefix = "" and result = unimplemented_ordering(c, n)
or or
exists(string prefix, int nm1 | n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) | result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5
prefix = "" and result = unimplemented_ordering(c, n) or
or prefix != "" and result = prefix + " or " + unimplemented_ordering(c, n)
result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5 )
or
prefix != "" and result = prefix + " or " + unimplemented_ordering(c, n)
)
} }
Value ordering_method(ClassValue c, string name) { Value ordering_method(ClassValue c, string name) {
/* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */ /* If class doesn't declare a method then don't blame this class (the superclass will be blamed). */
name = ordering_name(_) and result = c.declaredAttribute(name) name = ordering_name(_) and result = c.declaredAttribute(name)
} }
from ClassValue c, Value ordering, string name from ClassValue c, Value ordering, string name
where where
not c.failedInference(_) and not c.failedInference(_) and
not total_ordering(c.getScope()) and not total_ordering(c.getScope()) and
ordering = ordering_method(c, name) and ordering = ordering_method(c, name) and
exists(unimplemented_ordering(c, _)) exists(unimplemented_ordering(c, _))
select c, select c,
"Class " + c.getName() + " implements $@, but does not implement " + "Class " + c.getName() + " implements $@, but does not implement " +
unimplemented_ordering_methods(c, 4) + ".", ordering, name unimplemented_ordering_methods(c, 4) + ".", ordering, name

View File

@@ -13,18 +13,18 @@
import python import python
ClassObject left_base(ClassObject type, ClassObject base) { ClassObject left_base(ClassObject type, ClassObject base) {
exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1)) exists(int i | i > 0 and type.getBaseType(i) = base and result = type.getBaseType(i - 1))
} }
predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) { predicate invalid_mro(ClassObject t, ClassObject left, ClassObject right) {
t.isNewStyle() and t.isNewStyle() and
left = left_base(t, right) and left = left_base(t, right) and
left = right.getAnImproperSuperType() left = right.getAnImproperSuperType()
} }
from ClassObject t, ClassObject left, ClassObject right from ClassObject t, ClassObject left, ClassObject right
where invalid_mro(t, left, right) where invalid_mro(t, left, right)
select t, select t,
"Construction of class " + t.getName() + "Construction of class " + t.getName() +
" can fail due to invalid method resolution order(MRO) for bases $@ and $@.", left, " can fail due to invalid method resolution order(MRO) for bases $@ and $@.", left,
left.getName(), right, right.getName() left.getName(), right, right.getName()

View File

@@ -14,17 +14,17 @@
import python import python
from from
ClassObject supercls, string method, Call call, FunctionObject overriding, ClassObject supercls, string method, Call call, FunctionObject overriding,
FunctionObject overridden FunctionObject overridden
where where
exists(FunctionObject init, SelfAttribute sa | exists(FunctionObject init, SelfAttribute sa |
supercls.declaredAttribute("__init__") = init and supercls.declaredAttribute("__init__") = init and
call.getScope() = init.getFunction() and call.getScope() = init.getFunction() and
call.getFunc() = sa call.getFunc() = sa
| |
sa.getName() = method and sa.getName() = method and
overridden = supercls.declaredAttribute(method) and overridden = supercls.declaredAttribute(method) and
overriding.overrides(overridden) overriding.overrides(overridden)
) )
select call, "Call to self.$@ in __init__ method, which is overridden by $@.", overridden, method, select call, "Call to self.$@ in __init__ method, which is overridden by $@.", overridden, method,
overriding, overriding.descriptiveString() overriding, overriding.descriptiveString()

View File

@@ -15,30 +15,30 @@ import semmle.python.SelfAttribute
import ClassAttributes import ClassAttributes
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) { predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
c.sometimesDefines(a.getName()) and c.sometimesDefines(a.getName()) and
exists(SelfAttributeRead guard, If i | exists(SelfAttributeRead guard, If i |
i.contains(a) and i.contains(a) and
c.assignedInInit(guard.getName()) c.assignedInInit(guard.getName())
| |
i.getTest() = guard i.getTest() = guard
or or
i.getTest().contains(guard) i.getTest().contains(guard)
) )
} }
predicate maybe_undefined_class_attribute(SelfAttributeRead a, CheckClass c) { predicate maybe_undefined_class_attribute(SelfAttributeRead a, CheckClass c) {
c.sometimesDefines(a.getName()) and c.sometimesDefines(a.getName()) and
not c.alwaysDefines(a.getName()) and not c.alwaysDefines(a.getName()) and
c.interestingUndefined(a) and c.interestingUndefined(a) and
not guarded_by_other_attribute(a, c) not guarded_by_other_attribute(a, c)
} }
from Attribute a, ClassObject c, SelfAttributeStore sa from Attribute a, ClassObject c, SelfAttributeStore sa
where where
maybe_undefined_class_attribute(a, c) and maybe_undefined_class_attribute(a, c) and
sa.getClass() = c.getPyClass() and sa.getClass() = c.getPyClass() and
sa.getName() = a.getName() sa.getName() = a.getName()
select a, select a,
"Attribute '" + a.getName() + "Attribute '" + a.getName() +
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa, "' is not defined in the class body nor in the __init__() method, but it is defined $@", sa,
"here" "here"

View File

@@ -3,73 +3,73 @@ import python
// Helper predicates for multiple call to __init__/__del__ queries. // Helper predicates for multiple call to __init__/__del__ queries.
pragma[noinline] pragma[noinline]
private predicate multiple_invocation_paths_helper( private predicate multiple_invocation_paths_helper(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
) { ) {
i1 != i2 and i1 != i2 and
i1 = top.getACallee+() and i1 = top.getACallee+() and
i2 = top.getACallee+() and i2 = top.getACallee+() and
i1.getFunction() = multi i1.getFunction() = multi
} }
pragma[noinline] pragma[noinline]
private predicate multiple_invocation_paths( private predicate multiple_invocation_paths(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
) { ) {
multiple_invocation_paths_helper(top, i1, i2, multi) and multiple_invocation_paths_helper(top, i1, i2, multi) and
i2.getFunction() = multi i2.getFunction() = multi
} }
/** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */ /** Holds if `self.name` calls `multi` by multiple paths, and thus calls it more than once. */
predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) { predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject multi, string name) {
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 | exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
multiple_invocation_paths(top, i1, i2, multi) and multiple_invocation_paths(top, i1, i2, multi) and
top.runtime(self.declaredAttribute(name)) and top.runtime(self.declaredAttribute(name)) and
self.getASuperType().declaredAttribute(name) = multi self.getASuperType().declaredAttribute(name) = multi
| |
// Only called twice if called from different functions, // Only called twice if called from different functions,
// or if one call-site can reach the other. // or if one call-site can reach the other.
i1.getCall().getScope() != i2.getCall().getScope() i1.getCall().getScope() != i2.getCall().getScope()
or or
i1.getCall().strictlyReaches(i2.getCall()) i1.getCall().strictlyReaches(i2.getCall())
) )
} }
/** Holds if all attributes called `name` can be inferred to be methods. */ /** Holds if all attributes called `name` can be inferred to be methods. */
private predicate named_attributes_not_method(ClassObject cls, string name) { private predicate named_attributes_not_method(ClassObject cls, string name) {
cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject cls.declaresAttribute(name) and not cls.declaredAttribute(name) instanceof FunctionObject
} }
/** Holds if `f` actually does something. */ /** Holds if `f` actually does something. */
private predicate does_something(FunctionObject f) { private predicate does_something(FunctionObject f) {
f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__") f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__")
or or
exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass) exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass)
} }
/** Holds if `meth` looks like it should have a call to `name`, but does not */ /** Holds if `meth` looks like it should have a call to `name`, but does not */
private predicate missing_call(FunctionObject meth, string name) { private predicate missing_call(FunctionObject meth, string name) {
exists(CallNode call, AttrNode attr | exists(CallNode call, AttrNode attr |
call.getScope() = meth.getFunction() and call.getScope() = meth.getFunction() and
call.getFunction() = attr and call.getFunction() = attr and
attr.getName() = name and attr.getName() = name and
not exists(FunctionObject f | f.getACall() = call) not exists(FunctionObject f | f.getACall() = call)
) )
} }
/** Holds if `self.name` does not call `missing`, even though it is expected to. */ /** Holds if `self.name` does not call `missing`, even though it is expected to. */
predicate missing_call_to_superclass_method( predicate missing_call_to_superclass_method(
ClassObject self, FunctionObject top, FunctionObject missing, string name ClassObject self, FunctionObject top, FunctionObject missing, string name
) { ) {
missing = self.getASuperType().declaredAttribute(name) and missing = self.getASuperType().declaredAttribute(name) and
top = self.lookupAttribute(name) and top = self.lookupAttribute(name) and
/* There is no call to missing originating from top */ /* There is no call to missing originating from top */
not top.getACallee*() = missing and not top.getACallee*() = missing and
/* Make sure that all named 'methods' are objects that we can understand. */ /* Make sure that all named 'methods' are objects that we can understand. */
not exists(ClassObject sup | not exists(ClassObject sup |
sup = self.getAnImproperSuperType() and sup = self.getAnImproperSuperType() and
named_attributes_not_method(sup, name) named_attributes_not_method(sup, name)
) and ) and
not self.isAbstract() and not self.isAbstract() and
does_something(missing) and does_something(missing) and
not missing_call(top, name) not missing_call(top, name)
} }

View File

@@ -15,10 +15,10 @@ import MethodCallOrder
from ClassObject self, FunctionObject missing from ClassObject self, FunctionObject missing
where where
missing_call_to_superclass_method(self, _, missing, "__del__") and missing_call_to_superclass_method(self, _, missing, "__del__") and
not missing.neverReturns() and not missing.neverReturns() and
not self.failedInference() and not self.failedInference() and
not missing.isBuiltin() not missing.isBuiltin()
select self, select self,
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.", "Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
missing, missing.descriptiveString() missing, missing.descriptiveString()

View File

@@ -15,14 +15,14 @@ import MethodCallOrder
from ClassObject self, FunctionObject initializer, FunctionObject missing from ClassObject self, FunctionObject initializer, FunctionObject missing
where where
self.lookupAttribute("__init__") = initializer and self.lookupAttribute("__init__") = initializer and
missing_call_to_superclass_method(self, initializer, missing, "__init__") and missing_call_to_superclass_method(self, initializer, missing, "__init__") and
// If a superclass is incorrect, don't flag this class as well. // If a superclass is incorrect, don't flag this class as well.
not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and not missing_call_to_superclass_method(self.getASuperType(), _, missing, "__init__") and
not missing.neverReturns() and not missing.neverReturns() and
not self.failedInference() and not self.failedInference() and
not missing.isBuiltin() and not missing.isBuiltin() and
not self.isAbstract() not self.isAbstract()
select self, select self,
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.", "Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
missing, missing.descriptiveString(), initializer, "__init__ method" missing, missing.descriptiveString(), initializer, "__init__ method"

View File

@@ -13,20 +13,20 @@
import python import python
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) { predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
cls.isDescriptorType() and cls.isDescriptorType() and
exists(PyFunctionObject f, PyFunctionObject get_set | exists(PyFunctionObject f, PyFunctionObject get_set |
exists(string name | cls.lookupAttribute(name) = get_set | exists(string name | cls.lookupAttribute(name) = get_set |
name = "__get__" or name = "__set__" or name = "__delete__" name = "__get__" or name = "__set__" or name = "__delete__"
) and ) and
cls.lookupAttribute(_) = f and cls.lookupAttribute(_) = f and
get_set.getACallee*() = f and get_set.getACallee*() = f and
not f.getName() = "__init__" and not f.getName() = "__init__" and
s.getScope() = f.getFunction() s.getScope() = f.getFunction()
) )
} }
from ClassObject cls, SelfAttributeStore s from ClassObject cls, SelfAttributeStore s
where mutates_descriptor(cls, s) where mutates_descriptor(cls, s)
select s, select s,
"Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.", "Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.",
cls, cls.getName() cls, cls.getName()

View File

@@ -14,79 +14,79 @@
import python import python
class InitCallStmt extends ExprStmt { class InitCallStmt extends ExprStmt {
InitCallStmt() { InitCallStmt() {
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() | exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
attr.getName() = "__init__" attr.getName() = "__init__"
) )
} }
} }
predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) { predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
write_attr.getScope() = subinit and write_attr.getScope() = subinit and
self_write_stmt(write_attr, _) and self_write_stmt(write_attr, _) and
exists(Stmt top | top.contains(write_attr) or top = write_attr | exists(Stmt top | top.contains(write_attr) or top = write_attr |
( (
exists(int i, int j, InitCallStmt call | call.getScope() = subinit | exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass" i > j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "superclass"
) )
or or
exists(int i, int j, InitCallStmt call | call.getScope() = subinit | exists(int i, int j, InitCallStmt call | call.getScope() = subinit |
i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass" i < j and top = subinit.getStmt(i) and call = subinit.getStmt(j) and which = "subclass"
) )
)
) )
)
} }
predicate self_write_stmt(Stmt s, string attr) { predicate self_write_stmt(Stmt s, string attr) {
exists(Attribute a, Name self | exists(Attribute a, Name self |
self = a.getObject() and self = a.getObject() and
s.contains(a) and s.contains(a) and
self.getId() = "self" and self.getId() = "self" and
a.getCtx() instanceof Store and a.getCtx() instanceof Store and
a.getName() = attr a.getName() = attr
) )
} }
predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) { predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) {
exists(string name | exists(string name |
s1.getScope() = f1 and s1.getScope() = f1 and
s2.getScope() = f2 and s2.getScope() = f2 and
self_write_stmt(s1, name) and self_write_stmt(s1, name) and
self_write_stmt(s2, name) self_write_stmt(s2, name)
) )
} }
predicate attribute_overwritten( predicate attribute_overwritten(
AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname
) { ) {
exists( exists(
FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass, FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass,
AssignStmt subattr, AssignStmt superattr AssignStmt subattr, AssignStmt superattr
| |
( (
classtype = "superclass" and classtype = "superclass" and
classname = superclass.getName() and classname = superclass.getName() and
overwrites = subattr and overwrites = subattr and
overwritten = superattr overwritten = superattr
or or
classtype = "subclass" and classtype = "subclass" and
classname = subclass.getName() and classname = subclass.getName() and
overwrites = superattr and overwrites = superattr and
overwritten = subattr overwritten = subattr
) and ) and
/* OK if overwritten in subclass and is a class attribute */ /* OK if overwritten in subclass and is a class attribute */
(not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and (not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and
superclass.declaredAttribute("__init__") = superinit and superclass.declaredAttribute("__init__") = superinit and
subclass.declaredAttribute("__init__") = subinit and subclass.declaredAttribute("__init__") = subinit and
superclass = subclass.getASuperType() and superclass = subclass.getASuperType() and
overwrites_which(subinit.getFunction(), subattr, classtype) and overwrites_which(subinit.getFunction(), subattr, classtype) and
both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and
self_write_stmt(superattr, name) self_write_stmt(superattr, name)
) )
} }
from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname
where attribute_overwritten(overwrites, overwritten, name, classtype, classname) where attribute_overwritten(overwrites, overwritten, name, classtype, classname)
select overwrites, select overwrites,
"Assignment overwrites attribute " + name + ", which was previously defined in " + classtype + "Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
" $@.", overwritten, classname " $@.", overwritten, classname

View File

@@ -15,5 +15,5 @@ import python
from PropertyObject prop, ClassObject cls from PropertyObject prop, ClassObject cls
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle() where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
select prop, select prop,
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() + "Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
" is an old-style class." " is an old-style class."

View File

@@ -17,5 +17,5 @@ import python
from ClassValue c from ClassValue c
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__")) where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
select c, select c,
"Class " + c.getName() + "Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager." " implements __del__ (presumably to release some resource). Consider making it a context manager."

View File

@@ -20,27 +20,27 @@
import python import python
predicate shadowed_by_super_class( predicate shadowed_by_super_class(
ClassObject c, ClassObject supercls, Assign assign, FunctionObject f ClassObject c, ClassObject supercls, Assign assign, FunctionObject f
) { ) {
c.getASuperType() = supercls and c.getASuperType() = supercls and
c.declaredAttribute(_) = f and c.declaredAttribute(_) = f and
exists(FunctionObject init, Attribute attr | exists(FunctionObject init, Attribute attr |
supercls.declaredAttribute("__init__") = init and supercls.declaredAttribute("__init__") = init and
attr = assign.getATarget() and attr = assign.getATarget() and
attr.getObject().(Name).getId() = "self" and attr.getObject().(Name).getId() = "self" and
attr.getName() = f.getName() and attr.getName() = f.getName() and
assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope() assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope()
) and ) and
/* /*
* It's OK if the super class defines the method as well. * It's OK if the super class defines the method as well.
* We assume that the original method must have been defined for a reason. * We assume that the original method must have been defined for a reason.
*/ */
not supercls.hasAttribute(f.getName()) not supercls.hasAttribute(f.getName())
} }
from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed
where shadowed_by_super_class(c, supercls, assign, shadowed) where shadowed_by_super_class(c, supercls, assign, shadowed)
select shadowed.getOrigin(), select shadowed.getOrigin(),
"Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.", "Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.",
assign, "an attribute" assign, "an attribute"

View File

@@ -13,13 +13,13 @@
import python import python
predicate uses_of_super_in_old_style_class(Call s) { predicate uses_of_super_in_old_style_class(Call s) {
exists(Function f, ClassObject c | exists(Function f, ClassObject c |
s.getScope() = f and s.getScope() = f and
f.getScope() = c.getPyClass() and f.getScope() = c.getPyClass() and
not c.failedInference() and not c.failedInference() and
not c.isNewStyle() and not c.isNewStyle() and
s.getFunc().(Name).getId() = "super" s.getFunc().(Name).getId() = "super"
) )
} }
from Call c from Call c

View File

@@ -15,14 +15,14 @@ import MethodCallOrder
from ClassObject self, FunctionObject multi from ClassObject self, FunctionObject multi
where where
multiple_calls_to_superclass_method(self, multi, "__del__") and multiple_calls_to_superclass_method(self, multi, "__del__") and
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
not exists(FunctionObject better | not exists(FunctionObject better |
multiple_calls_to_superclass_method(self, better, "__del__") and multiple_calls_to_superclass_method(self, better, "__del__") and
better.overrides(multi) better.overrides(multi)
) and ) and
not self.failedInference() not self.failedInference()
select self, select self,
"Class " + self.getName() + "Class " + self.getName() +
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi, " may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
multi.descriptiveString() multi.descriptiveString()

View File

@@ -15,15 +15,15 @@ import MethodCallOrder
from ClassObject self, FunctionObject multi from ClassObject self, FunctionObject multi
where where
multi != theObjectType().lookupAttribute("__init__") and multi != theObjectType().lookupAttribute("__init__") and
multiple_calls_to_superclass_method(self, multi, "__init__") and multiple_calls_to_superclass_method(self, multi, "__init__") and
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
not exists(FunctionObject better | not exists(FunctionObject better |
multiple_calls_to_superclass_method(self, better, "__init__") and multiple_calls_to_superclass_method(self, better, "__init__") and
better.overrides(multi) better.overrides(multi)
) and ) and
not self.failedInference() not self.failedInference()
select self, select self,
"Class " + self.getName() + "Class " + self.getName() +
" may not be initialized properly as $@ may be called multiple times during initialization.", " may not be initialized properly as $@ may be called multiple times during initialization.",
multi, multi.descriptiveString() multi, multi.descriptiveString()

View File

@@ -15,18 +15,18 @@ import semmle.python.SelfAttribute
import ClassAttributes import ClassAttributes
predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) { predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) {
name = a.getName() and name = a.getName() and
not c.sometimesDefines(name) and not c.sometimesDefines(name) and
c.interestingUndefined(a) and c.interestingUndefined(a) and
line = a.getLocation().getStartLine() and line = a.getLocation().getStartLine() and
not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name) not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name)
} }
predicate report_undefined_class_attribute(Attribute a, ClassObject c, string name) { predicate report_undefined_class_attribute(Attribute a, ClassObject c, string name) {
exists(int line | exists(int line |
undefined_class_attribute(a, c, line, name) and undefined_class_attribute(a, c, line, name) and
line = min(int x | undefined_class_attribute(_, c, x, name)) line = min(int x | undefined_class_attribute(_, c, x, name))
) )
} }
from Attribute a, ClassObject c, string name from Attribute a, ClassObject c, string name

View File

@@ -13,76 +13,76 @@
import python import python
predicate fewer_than_two_public_methods(Class cls, int methods) { predicate fewer_than_two_public_methods(Class cls, int methods) {
(methods = 0 or methods = 1) and (methods = 0 or methods = 1) and
methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod()) methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod())
} }
predicate does_not_define_special_method(Class cls) { predicate does_not_define_special_method(Class cls) {
not exists(Function f | f = cls.getAMethod() and f.isSpecialMethod()) not exists(Function f | f = cls.getAMethod() and f.isSpecialMethod())
} }
predicate no_inheritance(Class c) { predicate no_inheritance(Class c) {
not exists(ClassValue cls, ClassValue other | not exists(ClassValue cls, ClassValue other |
cls.getScope() = c and cls.getScope() = c and
other != ClassValue::object() other != ClassValue::object()
| |
other.getABaseType() = cls or other.getABaseType() = cls or
cls.getABaseType() = other cls.getABaseType() = other
) and ) and
not exists(Expr base | base = c.getABase() | not exists(Expr base | base = c.getABase() |
not base instanceof Name or base.(Name).getId() != "object" not base instanceof Name or base.(Name).getId() != "object"
) )
} }
predicate is_decorated(Class c) { exists(c.getADecorator()) } predicate is_decorated(Class c) { exists(c.getADecorator()) }
predicate is_stateful(Class c) { predicate is_stateful(Class c) {
exists(Function method, ExprContext ctx | exists(Function method, ExprContext ctx |
method.getScope() = c and method.getScope() = c and
(ctx instanceof Store or ctx instanceof AugStore) (ctx instanceof Store or ctx instanceof AugStore)
| |
exists(Subscript s | s.getScope() = method and s.getCtx() = ctx) exists(Subscript s | s.getScope() = method and s.getCtx() = ctx)
or
exists(Attribute a | a.getScope() = method and a.getCtx() = ctx)
)
or or
exists(Function method, Call call, Attribute a, string name | exists(Attribute a | a.getScope() = method and a.getCtx() = ctx)
method.getScope() = c and )
call.getScope() = method and or
call.getFunc() = a and exists(Function method, Call call, Attribute a, string name |
a.getName() = name method.getScope() = c and
| call.getScope() = method and
name = "pop" or call.getFunc() = a and
name = "remove" or a.getName() = name
name = "discard" or |
name = "extend" or name = "pop" or
name = "append" name = "remove" or
) name = "discard" or
name = "extend" or
name = "append"
)
} }
predicate useless_class(Class c, int methods) { predicate useless_class(Class c, int methods) {
c.isTopLevel() and c.isTopLevel() and
c.isPublic() and c.isPublic() and
no_inheritance(c) and no_inheritance(c) and
fewer_than_two_public_methods(c, methods) and fewer_than_two_public_methods(c, methods) and
does_not_define_special_method(c) and does_not_define_special_method(c) and
not c.isProbableMixin() and not c.isProbableMixin() and
not is_decorated(c) and not is_decorated(c) and
not is_stateful(c) not is_stateful(c)
} }
from Class c, int methods, string msg from Class c, int methods, string msg
where where
useless_class(c, methods) and useless_class(c, methods) and
( (
methods = 1 and methods = 1 and
msg = msg =
"Class " + c.getName() + "Class " + c.getName() +
" defines only one public method, which should be replaced by a function." " defines only one public method, which should be replaced by a function."
or or
methods = 0 and methods = 0 and
msg = msg =
"Class " + c.getName() + "Class " + c.getName() +
" defines no public methods and could be replaced with a namedtuple or dictionary." " defines no public methods and could be replaced with a namedtuple or dictionary."
) )
select c, msg select c, msg

View File

@@ -18,7 +18,7 @@ import Expressions.CallArgs
from Call call, ClassValue cls, string name, FunctionValue init from Call call, ClassValue cls, string name, FunctionValue init
where where
illegally_named_parameter(call, cls, name) and illegally_named_parameter(call, cls, name) and
init = get_function_or_initializer(cls) init = get_function_or_initializer(cls)
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
init.getQualifiedName() init.getQualifiedName()

View File

@@ -17,15 +17,15 @@ import Expressions.CallArgs
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init
where where
( (
too_many_args(call, cls, limit) and too_many_args(call, cls, limit) and
too = "too many arguments" and too = "too many arguments" and
should = "no more than " should = "no more than "
or or
too_few_args(call, cls, limit) and too_few_args(call, cls, limit) and
too = "too few arguments" and too = "too few arguments" and
should = "no fewer than " should = "no fewer than "
) and ) and
init = get_function_or_initializer(cls) init = get_function_or_initializer(cls)
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
init.getQualifiedName() init.getQualifiedName()

View File

@@ -17,13 +17,13 @@ import python
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() } predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
predicate catches_base_exception(ExceptStmt ex) { predicate catches_base_exception(ExceptStmt ex) {
ex.getType().pointsTo(ClassValue::baseException()) ex.getType().pointsTo(ClassValue::baseException())
or or
not exists(ex.getType()) not exists(ex.getType())
} }
from ExceptStmt ex from ExceptStmt ex
where where
catches_base_exception(ex) and catches_base_exception(ex) and
doesnt_reraise(ex) doesnt_reraise(ex)
select ex, "Except block directly handles BaseException." select ex, "Except block directly handles BaseException."

View File

@@ -14,88 +14,88 @@
import python import python
predicate empty_except(ExceptStmt ex) { predicate empty_except(ExceptStmt ex) {
not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass) not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass)
} }
predicate no_else(ExceptStmt ex) { not exists(ex.getTry().getOrelse()) } predicate no_else(ExceptStmt ex) { not exists(ex.getTry().getOrelse()) }
predicate no_comment(ExceptStmt ex) { predicate no_comment(ExceptStmt ex) {
not exists(Comment c | not exists(Comment c |
c.getLocation().getFile() = ex.getLocation().getFile() and c.getLocation().getFile() = ex.getLocation().getFile() and
c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and
c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine() c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine()
) )
} }
predicate non_local_control_flow(ExceptStmt ex) { predicate non_local_control_flow(ExceptStmt ex) {
ex.getType().pointsTo(ClassValue::stopIteration()) ex.getType().pointsTo(ClassValue::stopIteration())
} }
predicate try_has_normal_exit(Try try) { predicate try_has_normal_exit(Try try) {
exists(ControlFlowNode pred, ControlFlowNode succ | exists(ControlFlowNode pred, ControlFlowNode succ |
/* Exists a non-exception predecessor, successor pair */ /* Exists a non-exception predecessor, successor pair */
pred.getASuccessor() = succ and pred.getASuccessor() = succ and
not pred.getAnExceptionalSuccessor() = succ not pred.getAnExceptionalSuccessor() = succ
| |
/* Successor is either a normal flow node or a fall-through exit */ /* Successor is either a normal flow node or a fall-through exit */
not exists(Scope s | s.getReturnNode() = succ) and not exists(Scope s | s.getReturnNode() = succ) and
/* Predecessor is in try body and successor is not */ /* Predecessor is in try body and successor is not */
pred.getNode().getParentNode*() = try.getAStmt() and pred.getNode().getParentNode*() = try.getAStmt() and
not succ.getNode().getParentNode*() = try.getAStmt() not succ.getNode().getParentNode*() = try.getAStmt()
) )
} }
predicate attribute_access(Stmt s) { predicate attribute_access(Stmt s) {
s.(ExprStmt).getValue() instanceof Attribute s.(ExprStmt).getValue() instanceof Attribute
or or
exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name | exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
name = "getattr" or name = "setattr" or name = "delattr" name = "getattr" or name = "setattr" or name = "delattr"
) )
or or
s.(Delete).getATarget() instanceof Attribute s.(Delete).getATarget() instanceof Attribute
} }
predicate subscript(Stmt s) { predicate subscript(Stmt s) {
s.(ExprStmt).getValue() instanceof Subscript s.(ExprStmt).getValue() instanceof Subscript
or or
s.(Delete).getATarget() instanceof Subscript s.(Delete).getATarget() instanceof Subscript
} }
predicate encode_decode(Call ex, ClassValue type) { predicate encode_decode(Call ex, ClassValue type) {
exists(string name | ex.getFunc().(Attribute).getName() = name | exists(string name | ex.getFunc().(Attribute).getName() = name |
name = "encode" and type = ClassValue::unicodeEncodeError() name = "encode" and type = ClassValue::unicodeEncodeError()
or or
name = "decode" and type = ClassValue::unicodeDecodeError() name = "decode" and type = ClassValue::unicodeDecodeError()
) )
} }
predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) { predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
not exists(ex.getTry().getStmt(1)) and not exists(ex.getTry().getStmt(1)) and
s = ex.getTry().getStmt(0) and s = ex.getTry().getStmt(0) and
ex.getType().pointsTo(type) ex.getType().pointsTo(type)
} }
predicate focussed_handler(ExceptStmt ex) { predicate focussed_handler(ExceptStmt ex) {
exists(Stmt s, ClassValue type | small_handler(ex, s, type) | exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
subscript(s) and type.getASuperType() = ClassValue::lookupError() subscript(s) and type.getASuperType() = ClassValue::lookupError()
or or
attribute_access(s) and type = ClassValue::attributeError() attribute_access(s) and type = ClassValue::attributeError()
or or
s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError() s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError()
or or
encode_decode(s.(ExprStmt).getValue(), type) encode_decode(s.(ExprStmt).getValue(), type)
) )
} }
Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return } Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return }
from ExceptStmt ex from ExceptStmt ex
where where
empty_except(ex) and empty_except(ex) and
no_else(ex) and no_else(ex) and
no_comment(ex) and no_comment(ex) and
not non_local_control_flow(ex) and not non_local_control_flow(ex) and
not ex.getTry() = try_return() and not ex.getTry() = try_return() and
try_has_normal_exit(ex.getTry()) and try_has_normal_exit(ex.getTry()) and
not focussed_handler(ex) not focussed_handler(ex)
select ex, "'except' clause does nothing but pass and there is no explanatory comment." select ex, "'except' clause does nothing but pass and there is no explanatory comment."

View File

@@ -15,16 +15,16 @@ import python
from ExceptFlowNode ex, Value t, ClassValue c, ControlFlowNode origin, string what from ExceptFlowNode ex, Value t, ClassValue c, ControlFlowNode origin, string what
where where
ex.handledException(t, c, origin) and ex.handledException(t, c, origin) and
( (
exists(ClassValue x | x = t | exists(ClassValue x | x = t |
not x.isLegalExceptionType() and not x.isLegalExceptionType() and
not x.failedInference(_) and not x.failedInference(_) and
what = "class '" + x.getName() + "'" what = "class '" + x.getName() + "'"
)
or
not t instanceof ClassValue and
what = "instance of '" + c.getName() + "'"
) )
or
not t instanceof ClassValue and
what = "instance of '" + c.getName() + "'"
)
select ex.getNode(), select ex.getNode(),
"Non-exception $@ in exception handler which will never match raised exception.", origin, what "Non-exception $@ in exception handler which will never match raised exception.", origin, what

View File

@@ -17,9 +17,9 @@ import Exceptions.NotImplemented
from Raise r, ClassValue t from Raise r, ClassValue t
where where
type_or_typeof(r, t, _) and type_or_typeof(r, t, _) and
not t.isLegalExceptionType() and not t.isLegalExceptionType() and
not t.failedInference(_) and not t.failedInference(_) and
not use_of_not_implemented_in_raise(r, _) not use_of_not_implemented_in_raise(r, _)
select r, select r,
"Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead."

View File

@@ -15,14 +15,14 @@
import python import python
predicate incorrect_except_order(ExceptStmt ex1, ClassValue cls1, ExceptStmt ex2, ClassValue cls2) { predicate incorrect_except_order(ExceptStmt ex1, ClassValue cls1, ExceptStmt ex2, ClassValue cls2) {
exists(int i, int j, Try t | exists(int i, int j, Try t |
ex1 = t.getHandler(i) and ex1 = t.getHandler(i) and
ex2 = t.getHandler(j) and ex2 = t.getHandler(j) and
i < j and i < j and
cls1 = except_class(ex1) and cls1 = except_class(ex1) and
cls2 = except_class(ex2) and cls2 = except_class(ex2) and
cls1 = cls2.getASuperType() cls1 = cls2.getASuperType()
) )
} }
ClassValue except_class(ExceptStmt ex) { ex.getType().pointsTo(result) } ClassValue except_class(ExceptStmt ex) { ex.getType().pointsTo(result) }
@@ -30,5 +30,5 @@ ClassValue except_class(ExceptStmt ex) { ex.getType().pointsTo(result) }
from ExceptStmt ex1, ClassValue cls1, ExceptStmt ex2, ClassValue cls2 from ExceptStmt ex1, ClassValue cls1, ExceptStmt ex2, ClassValue cls2
where incorrect_except_order(ex1, cls1, ex2, cls2) where incorrect_except_order(ex1, cls1, ex2, cls2)
select ex2, select ex2,
"Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.", "Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.",
cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName() cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName()

View File

@@ -2,9 +2,9 @@ import python
/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ /** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */
predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) {
notimpl.pointsTo(Value::named("NotImplemented")) and notimpl.pointsTo(Value::named("NotImplemented")) and
( (
notimpl = raise.getException() or notimpl = raise.getException() or
notimpl = raise.getException().(Call).getFunc() notimpl = raise.getException().(Call).getFunc()
) )
} }

View File

@@ -2,11 +2,11 @@ import python
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */ /** Whether the raise statement 'r' raises 'type' from origin 'orig' */
predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) { predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) {
exists(Expr exception | exception = r.getRaised() | exists(Expr exception | exception = r.getRaised() |
exception.pointsTo(type, orig) exception.pointsTo(type, orig)
or or
not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and
not type = ClassValue::type() and // First value is an unknown exception type not type = ClassValue::type() and // First value is an unknown exception type
exists(Value val | exception.pointsTo(val, orig) | val.getClass() = type) exists(Value val | exception.pointsTo(val, orig) | val.getClass() = type)
) )
} }

View File

@@ -13,10 +13,10 @@ import python
from Raise r, Value v, AstNode origin from Raise r, Value v, AstNode origin
where where
r.getException().pointsTo(v, origin) and r.getException().pointsTo(v, origin) and
v.getClass() = ClassValue::tuple() and v.getClass() = ClassValue::tuple() and
major_version() = 2 major_version() = 2
/* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */ /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
select r, select r,
"Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.",
origin, "a tuple" origin, "a tuple"

View File

@@ -17,45 +17,45 @@ FunctionValue iter() { result = Value::named("iter") }
BuiltinFunctionValue next() { result = Value::named("next") } BuiltinFunctionValue next() { result = Value::named("next") }
predicate call_to_iter(CallNode call, EssaVariable sequence) { predicate call_to_iter(CallNode call, EssaVariable sequence) {
sequence.getAUse() = iter().getArgumentForCall(call, 0) sequence.getAUse() = iter().getArgumentForCall(call, 0)
} }
predicate call_to_next(CallNode call, ControlFlowNode iter) { predicate call_to_next(CallNode call, ControlFlowNode iter) {
iter = next().getArgumentForCall(call, 0) iter = next().getArgumentForCall(call, 0)
} }
predicate call_to_next_has_default(CallNode call) { predicate call_to_next_has_default(CallNode call) {
exists(call.getArg(1)) or exists(call.getArgByName("default")) exists(call.getArg(1)) or exists(call.getArgByName("default"))
} }
predicate guarded_not_empty_sequence(EssaVariable sequence) { predicate guarded_not_empty_sequence(EssaVariable sequence) {
sequence.getDefinition() instanceof EssaEdgeRefinement sequence.getDefinition() instanceof EssaEdgeRefinement
} }
/** The pattern `next(iter(x))` is often used where `x` is known not be empty. Check for that. */ /** The pattern `next(iter(x))` is often used where `x` is known not be empty. Check for that. */
predicate iter_not_exhausted(EssaVariable iterator) { predicate iter_not_exhausted(EssaVariable iterator) {
exists(EssaVariable sequence | exists(EssaVariable sequence |
call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and
guarded_not_empty_sequence(sequence) guarded_not_empty_sequence(sequence)
) )
} }
predicate stop_iteration_handled(CallNode call) { predicate stop_iteration_handled(CallNode call) {
exists(Try t | exists(Try t |
t.containsInScope(call.getNode()) and t.containsInScope(call.getNode()) and
t.getAHandler().getType().pointsTo(ClassValue::stopIteration()) t.getAHandler().getType().pointsTo(ClassValue::stopIteration())
) )
} }
from CallNode call from CallNode call
where where
call_to_next(call, _) and call_to_next(call, _) and
not call_to_next_has_default(call) and not call_to_next_has_default(call) and
not exists(EssaVariable iterator | not exists(EssaVariable iterator |
call_to_next(call, iterator.getAUse()) and call_to_next(call, iterator.getAUse()) and
iter_not_exhausted(iterator) iter_not_exhausted(iterator)
) and ) and
call.getNode().getScope().(Function).isGenerator() and call.getNode().getScope().(Function).isGenerator() and
not exists(Comp comp | comp.contains(call.getNode())) and not exists(Comp comp | comp.contains(call.getNode())) and
not stop_iteration_handled(call) not stop_iteration_handled(call)
select call, "Call to next() in a generator" select call, "Call to next() in a generator"

View File

@@ -1,36 +1,36 @@
/** INTERNAL - Methods used by queries that test whether functions are invoked correctly. */ /** INTERNAL - Methods used by queries that test whether functions are invoked correctly. */
import python import python
import Testing.Mox import Testing.Mox
private int varargs_length_objectapi(Call call) { private int varargs_length_objectapi(Call call) {
not exists(call.getStarargs()) and result = 0 not exists(call.getStarargs()) and result = 0
or or
exists(TupleObject t | call.getStarargs().refersTo(t) | result = t.getLength()) exists(TupleObject t | call.getStarargs().refersTo(t) | result = t.getLength())
or or
result = count(call.getStarargs().(List).getAnElt()) result = count(call.getStarargs().(List).getAnElt())
} }
private int varargs_length(Call call) { private int varargs_length(Call call) {
not exists(call.getStarargs()) and result = 0 not exists(call.getStarargs()) and result = 0
or or
exists(TupleValue t | call.getStarargs().pointsTo(t) | result = t.length()) exists(TupleValue t | call.getStarargs().pointsTo(t) | result = t.length())
or or
result = count(call.getStarargs().(List).getAnElt()) result = count(call.getStarargs().(List).getAnElt())
} }
/** Gets a keyword argument that is not a keyword-only parameter. */ /** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
func.getACall().getNode() = call and func.getACall().getNode() = call and
result = call.getAKeyword() and result = call.getAKeyword() and
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg() not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
} }
/** Gets a keyword argument that is not a keyword-only parameter. */ /** Gets a keyword argument that is not a keyword-only parameter. */
private Keyword not_keyword_only_arg(Call call, FunctionValue func) { private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
func.getACall().getNode() = call and func.getACall().getNode() = call and
result = call.getAKeyword() and result = call.getAKeyword() and
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg() not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
} }
/** /**
@@ -40,17 +40,17 @@ private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/ */
private int positional_arg_count_for_call_objectapi(Call call, Object callable) { private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
call = get_a_call_objectapi(callable).getNode() and call = get_a_call_objectapi(callable).getNode() and
exists(int positional_keywords | exists(int positional_keywords |
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) | exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
not func.getFunction().hasKwArg() and not func.getFunction().hasKwArg() and
positional_keywords = count(not_keyword_only_arg_objectapi(call, func)) positional_keywords = count(not_keyword_only_arg_objectapi(call, func))
or or
func.getFunction().hasKwArg() and positional_keywords = 0 func.getFunction().hasKwArg() and positional_keywords = 0
)
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
) )
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
)
} }
/** /**
@@ -60,174 +60,174 @@ private int positional_arg_count_for_call_objectapi(Call call, Object callable)
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/ */
private int positional_arg_count_for_call(Call call, Value callable) { private int positional_arg_count_for_call(Call call, Value callable) {
call = get_a_call(callable).getNode() and call = get_a_call(callable).getNode() and
exists(int positional_keywords | exists(int positional_keywords |
exists(FunctionValue func | func = get_function_or_initializer(callable) | exists(FunctionValue func | func = get_function_or_initializer(callable) |
not func.getScope().hasKwArg() and not func.getScope().hasKwArg() and
positional_keywords = count(not_keyword_only_arg(call, func)) positional_keywords = count(not_keyword_only_arg(call, func))
or or
func.getScope().hasKwArg() and positional_keywords = 0 func.getScope().hasKwArg() and positional_keywords = 0
)
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
) )
|
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
)
} }
/** Gets the number of arguments in `call`. */ /** Gets the number of arguments in `call`. */
int arg_count_objectapi(Call call) { int arg_count_objectapi(Call call) {
result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword()) result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword())
} }
/** Gets the number of arguments in `call`. */ /** Gets the number of arguments in `call`. */
int arg_count(Call call) { int arg_count(Call call) {
result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword()) result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword())
} }
/** Gets a call corresponding to the given class or function. */ /** Gets a call corresponding to the given class or function. */
private ControlFlowNode get_a_call_objectapi(Object callable) { private ControlFlowNode get_a_call_objectapi(Object callable) {
result = callable.(ClassObject).getACall() result = callable.(ClassObject).getACall()
or or
result = callable.(FunctionObject).getACall() result = callable.(FunctionObject).getACall()
} }
/** Gets a call corresponding to the given class or function. */ /** Gets a call corresponding to the given class or function. */
private ControlFlowNode get_a_call(Value callable) { private ControlFlowNode get_a_call(Value callable) {
result = callable.(ClassValue).getACall() result = callable.(ClassValue).getACall()
or or
result = callable.(FunctionValue).getACall() result = callable.(FunctionValue).getACall()
} }
/** Gets the function object corresponding to the given class or function. */ /** Gets the function object corresponding to the given class or function. */
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
result = func_or_cls.(FunctionObject) result = func_or_cls.(FunctionObject)
or or
result = func_or_cls.(ClassObject).declaredAttribute("__init__") result = func_or_cls.(ClassObject).declaredAttribute("__init__")
} }
/** Gets the function object corresponding to the given class or function. */ /** Gets the function object corresponding to the given class or function. */
FunctionValue get_function_or_initializer(Value func_or_cls) { FunctionValue get_function_or_initializer(Value func_or_cls) {
result = func_or_cls.(FunctionValue) result = func_or_cls.(FunctionValue)
or or
result = func_or_cls.(ClassValue).declaredAttribute("__init__") result = func_or_cls.(ClassValue).declaredAttribute("__init__")
} }
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */ /**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter_objectapi(Call call, Object func, string name) { predicate illegally_named_parameter_objectapi(Call call, Object func, string name) {
not func.isC() and not func.isC() and
name = call.getANamedArgumentName() and name = call.getANamedArgumentName() and
call.getAFlowNode() = get_a_call_objectapi(func) and call.getAFlowNode() = get_a_call_objectapi(func) and
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name) not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
} }
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */ /**Whether there is an illegally named parameter called `name` in the `call` to `func` */
predicate illegally_named_parameter(Call call, Value func, string name) { predicate illegally_named_parameter(Call call, Value func, string name) {
not func.isBuiltin() and not func.isBuiltin() and
name = call.getANamedArgumentName() and name = call.getANamedArgumentName() and
call.getAFlowNode() = get_a_call(func) and call.getAFlowNode() = get_a_call(func) and
not get_function_or_initializer(func).isLegalArgumentName(name) not get_function_or_initializer(func).isLegalArgumentName(name)
} }
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ /**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args_objectapi(Call call, Object callable, int limit) { predicate too_few_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter_objectapi(call, callable, _) and not illegally_named_parameter_objectapi(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getStarargs()) and
not exists(call.getKwargs()) and not exists(call.getKwargs()) and
arg_count_objectapi(call) < limit and arg_count_objectapi(call) < limit and
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) | exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
call = func.getAFunctionCall().getNode() and call = func.getAFunctionCall().getNode() and
limit = func.minParameters() and limit = func.minParameters() and
// The combination of misuse of `mox.Mox().StubOutWithMock()` // The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to // and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function. // pass 1 too few arguments to the mocked function.
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod()) not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or or
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or or
callable instanceof ClassObject and callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and call.getAFlowNode() = get_a_call_objectapi(callable) and
limit = func.minParameters() - 1 limit = func.minParameters() - 1
) )
} }
/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ /**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */
predicate too_few_args(Call call, Value callable, int limit) { predicate too_few_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and not illegally_named_parameter(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getStarargs()) and
not exists(call.getKwargs()) and not exists(call.getKwargs()) and
arg_count(call) < limit and arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) | exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getAFunctionCall().getNode() and call = func.getAFunctionCall().getNode() and
limit = func.minParameters() and limit = func.minParameters() and
/* /*
* The combination of misuse of `mox.Mox().StubOutWithMock()` * The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to * and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function. * pass 1 too few arguments to the mocked function.
*/ */
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod()) not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or or
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or or
callable instanceof ClassValue and callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and call.getAFlowNode() = get_a_call(callable) and
limit = func.minParameters() - 1 limit = func.minParameters() - 1
) )
} }
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ /**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args_objectapi(Call call, Object callable, int limit) { predicate too_many_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter_objectapi(call, callable, _) and not illegally_named_parameter_objectapi(call, callable, _) and
exists(FunctionObject func | exists(FunctionObject func |
func = get_function_or_initializer_objectapi(callable) and func = get_function_or_initializer_objectapi(callable) and
not func.getFunction().hasVarArg() and not func.getFunction().hasVarArg() and
limit >= 0 limit >= 0
| |
call = func.getAFunctionCall().getNode() and limit = func.maxParameters() call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
or or
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or or
callable instanceof ClassObject and callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and call.getAFlowNode() = get_a_call_objectapi(callable) and
limit = func.maxParameters() - 1 limit = func.maxParameters() - 1
) and ) and
positional_arg_count_for_call_objectapi(call, callable) > limit positional_arg_count_for_call_objectapi(call, callable) > limit
} }
/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ /**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */
predicate too_many_args(Call call, Value callable, int limit) { predicate too_many_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and not illegally_named_parameter(call, callable, _) and
exists(FunctionValue func | exists(FunctionValue func |
func = get_function_or_initializer(callable) and func = get_function_or_initializer(callable) and
not func.getScope().hasVarArg() and not func.getScope().hasVarArg() and
limit >= 0 limit >= 0
| |
call = func.getAFunctionCall().getNode() and limit = func.maxParameters() call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
or or
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or or
callable instanceof ClassValue and callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and call.getAFlowNode() = get_a_call(callable) and
limit = func.maxParameters() - 1 limit = func.maxParameters() - 1
) and ) and
positional_arg_count_for_call(call, callable) > limit positional_arg_count_for_call(call, callable) > limit
} }
/** Holds if `call` has too many or too few arguments for `func` */ /** Holds if `call` has too many or too few arguments for `func` */
predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) { predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) {
too_few_args_objectapi(call, func, limit) and too = "too few" too_few_args_objectapi(call, func, limit) and too = "too few"
or or
too_many_args_objectapi(call, func, limit) and too = "too many" too_many_args_objectapi(call, func, limit) and too = "too many"
} }
/** Holds if `call` has too many or too few arguments for `func` */ /** Holds if `call` has too many or too few arguments for `func` */
predicate wrong_args(Call call, FunctionValue func, int limit, string too) { predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
too_few_args(call, func, limit) and too = "too few" too_few_args(call, func, limit) and too = "too few"
or or
too_many_args(call, func, limit) and too = "too many" too_many_args(call, func, limit) and too = "too many"
} }
/** /**
@@ -236,8 +236,8 @@ predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
*/ */
bindingset[call, func] bindingset[call, func]
predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject func) { predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject func) {
arg_count_objectapi(call) + 1 >= func.minParameters() and arg_count_objectapi(call) + 1 >= func.minParameters() and
arg_count_objectapi(call) < func.maxParameters() arg_count_objectapi(call) < func.maxParameters()
} }
/** /**
@@ -246,23 +246,23 @@ predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject f
*/ */
bindingset[call, func] bindingset[call, func]
predicate correct_args_if_called_as_method(Call call, FunctionValue func) { predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
arg_count(call) + 1 >= func.minParameters() and arg_count(call) + 1 >= func.minParameters() and
arg_count(call) < func.maxParameters() arg_count(call) < func.maxParameters()
} }
/** Holds if `call` is a call to `overriding`, which overrides `func`. */ /** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) { predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
overriding.overrides(func) and overriding.overrides(func) and
overriding.getACall().getNode() = call overriding.getACall().getNode() = call
} }
/** Holds if `call` is a call to `overriding`, which overrides `func`. */ /** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) { predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
overriding.overrides(func) and overriding.overrides(func) and
overriding.getACall().getNode() = call overriding.getACall().getNode() = call
} }
/** Holds if `func` will raise a `NotImplemented` error. */ /** Holds if `func` will raise a `NotImplemented` error. */
predicate isAbstract(FunctionValue func) { predicate isAbstract(FunctionValue func) {
func.getARaisedType() = ClassValue::notImplementedError() func.getARaisedType() = ClassValue::notImplementedError()
} }

View File

@@ -16,14 +16,14 @@ import python
from CallNode call_to_super, string name from CallNode call_to_super, string name
where where
exists(GlobalVariable gv, ControlFlowNode cn | exists(GlobalVariable gv, ControlFlowNode cn |
call_to_super = ClassValue::super_().getACall() and call_to_super = ClassValue::super_().getACall() and
gv.getId() = "super" and gv.getId() = "super" and
cn = call_to_super.getArg(0) and cn = call_to_super.getArg(0) and
name = call_to_super.getScope().getScope().(Class).getName() and name = call_to_super.getScope().getScope().(Class).getName() and
exists(ClassValue other | exists(ClassValue other |
cn.pointsTo(other) and cn.pointsTo(other) and
not other.getScope().getName() = name not other.getScope().getName() = name
)
) )
)
select call_to_super.getNode(), "First argument to super() should be " + name + "." select call_to_super.getNode(), "First argument to super() should be " + name + "."

View File

@@ -16,8 +16,8 @@ import python
from Compare comparison, Expr left, Expr right from Compare comparison, Expr left, Expr right
where where
comparison.compares(left, _, right) and comparison.compares(left, _, right) and
left.isConstant() and left.isConstant() and
right.isConstant() and right.isConstant() and
not exists(Assert a | a.getTest() = comparison) not exists(Assert a | a.getTest() = comparison)
select comparison, "Comparison of constants; use 'True' or 'False' instead." select comparison, "Comparison of constants; use 'True' or 'False' instead."

View File

@@ -21,9 +21,9 @@ import semmle.python.Comparisons
*/ */
private predicate is_complex(Expr comp) { private predicate is_complex(Expr comp) {
exists(comp.(Compare).getOp(1)) exists(comp.(Compare).getOp(1))
or or
is_complex(comp.(UnaryExpr).getOperand()) is_complex(comp.(UnaryExpr).getOperand())
} }
/** /**
@@ -31,21 +31,21 @@ private predicate is_complex(Expr comp) {
* strict and also controls that block. * strict and also controls that block.
*/ */
private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) { private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) {
controls.impliesThat(comp.getBasicBlock(), comp, isTrue) and controls.impliesThat(comp.getBasicBlock(), comp, isTrue) and
/* Exclude complex comparisons of form `a < x < y`, as we do not (yet) have perfect flow control for those */ /* Exclude complex comparisons of form `a < x < y`, as we do not (yet) have perfect flow control for those */
not is_complex(controls.getTest().getNode()) not is_complex(controls.getTest().getNode())
} }
private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) { private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) {
forex(Comparison compnode, ConditionBlock block | forex(Comparison compnode, ConditionBlock block |
compnode.getNode() = comp and compnode.getNode() = comp and
block.getLastNode().getNode() = previous block.getLastNode().getNode() = previous
| |
useless_test(compnode, block, isTrue) useless_test(compnode, block, isTrue)
) )
} }
from Expr test, Expr other, boolean isTrue from Expr test, Expr other, boolean isTrue
where where
useless_test_ast(test, other, isTrue) and not useless_test_ast(test.getAChildNode+(), other, _) useless_test_ast(test, other, isTrue) and not useless_test_ast(test.getAChildNode+(), other, _)
select test, "Test is always " + isTrue + ", because of $@", other, "this condition" select test, "Test is always " + isTrue + ", because of $@", other, "this condition"

View File

@@ -14,21 +14,21 @@ import python
import semmle.python.pointsto.PointsTo import semmle.python.pointsto.PointsTo
predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) { predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) {
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() | exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() |
op instanceof In or op instanceof NotIn op instanceof In or op instanceof NotIn
) )
} }
from ControlFlowNode non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin from ControlFlowNode non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin
where where
rhs_in_expr(non_seq, cmp) and rhs_in_expr(non_seq, cmp) and
non_seq.pointsTo(_, v, origin) and non_seq.pointsTo(_, v, origin) and
v.getClass() = cls and v.getClass() = cls and
not Types::failedInference(cls, _) and not Types::failedInference(cls, _) and
not cls.hasAttribute("__contains__") and not cls.hasAttribute("__contains__") and
not cls.hasAttribute("__iter__") and not cls.hasAttribute("__iter__") and
not cls.hasAttribute("__getitem__") and not cls.hasAttribute("__getitem__") and
not cls = ClassValue::nonetype() and not cls = ClassValue::nonetype() and
not cls = Value::named("types.MappingProxyType") not cls = Value::named("types.MappingProxyType")
select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin, select cmp, "This test may raise an Exception as the $@ may be of non-container class $@.", origin,
"target", cls, cls.getName() "target", cls, cls.getName()

View File

@@ -15,31 +15,31 @@ import python
import semmle.python.strings import semmle.python.strings
predicate dict_key(Dict d, Expr k, string s) { predicate dict_key(Dict d, Expr k, string s) {
k = d.getAKey() and k = d.getAKey() and
( (
s = k.(Num).getN() s = k.(Num).getN()
or or
// We use <20> to mark unrepresentable characters // We use <20> to mark unrepresentable characters
// so two instances of <20> may represent different strings in the source code // so two instances of <20> may represent different strings in the source code
not "<22>" = s.charAt(_) and not "<22>" = s.charAt(_) and
exists(StrConst c | c = k | exists(StrConst c | c = k |
s = "u\"" + c.getText() + "\"" and c.isUnicode() s = "u\"" + c.getText() + "\"" and c.isUnicode()
or or
s = "b\"" + c.getText() + "\"" and not c.isUnicode() s = "b\"" + c.getText() + "\"" and not c.isUnicode()
)
) )
)
} }
from Dict d, Expr k1, Expr k2 from Dict d, Expr k1, Expr k2
where where
exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and exists(string s | dict_key(d, k1, s) and dict_key(d, k2, s) and k1 != k2) and
( (
exists(BasicBlock b, int i1, int i2 | exists(BasicBlock b, int i1, int i2 |
k1.getAFlowNode() = b.getNode(i1) and k1.getAFlowNode() = b.getNode(i1) and
k2.getAFlowNode() = b.getNode(i2) and k2.getAFlowNode() = b.getNode(i2) and
i1 < i2 i1 < i2
)
or
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
) )
or
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
)
select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten" select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten"

View File

@@ -15,12 +15,12 @@ import semmle.python.strings
from Expr e, ClassValue t from Expr e, ClassValue t
where where
exists(BinaryExpr b | exists(BinaryExpr b |
b.getOp() instanceof Mod and b.getOp() instanceof Mod and
format_string(b.getLeft()) and format_string(b.getLeft()) and
e = b.getRight() and e = b.getRight() and
mapping_format(b.getLeft()) and mapping_format(b.getLeft()) and
e.pointsTo().getClass() = t and e.pointsTo().getClass() = t and
not t.isMapping() not t.isMapping()
) )
select e, "Right hand side of a % operator must be a mapping, not class $@.", t, t.getName() select e, "Right hand side of a % operator must be a mapping, not class $@.", t, t.getName()

View File

@@ -13,20 +13,20 @@
import python import python
class DelCall extends Call { class DelCall extends Call {
DelCall() { this.getFunc().(Attribute).getName() = "__del__" } DelCall() { this.getFunc().(Attribute).getName() = "__del__" }
predicate isSuperCall() { predicate isSuperCall() {
exists(Function f | f = this.getScope() and f.getName() = "__del__" | exists(Function f | f = this.getScope() and f.getName() = "__del__" |
// We pass in `self` as the first argument... // We pass in `self` as the first argument...
f.getArg(0).asName().getVariable() = this.getArg(0).(Name).getVariable() f.getArg(0).asName().getVariable() = this.getArg(0).(Name).getVariable()
or or
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent // ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
// Python 3: `super().__del__()`. // Python 3: `super().__del__()`.
exists(Call superCall | superCall = this.getFunc().(Attribute).getObject() | exists(Call superCall | superCall = this.getFunc().(Attribute).getObject() |
superCall.getFunc().(Name).getId() = "super" superCall.getFunc().(Name).getId() = "super"
) )
) )
} }
} }
from DelCall del from DelCall del

View File

@@ -2,125 +2,125 @@ import python
/** A string constant that looks like it may be used in string formatting operations. */ /** A string constant that looks like it may be used in string formatting operations. */
library class PossibleAdvancedFormatString extends StrConst { library class PossibleAdvancedFormatString extends StrConst {
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") } PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
private predicate field(int start, int end) { private predicate field(int start, int end) {
brace_pair(this, start, end) and brace_pair(this, start, end) and
this.getText().substring(start, end) != "{{}}" this.getText().substring(start, end) != "{{}}"
} }
/** Gets the number of the formatting field at [start, end) */ /** Gets the number of the formatting field at [start, end) */
int getFieldNumber(int start, int end) { int getFieldNumber(int start, int end) {
result = this.fieldId(start, end).toInt() result = this.fieldId(start, end).toInt()
or or
this.implicitlyNumberedField(start, end) and this.implicitlyNumberedField(start, end) and
result = count(int s | this.implicitlyNumberedField(s, _) and s < start) result = count(int s | this.implicitlyNumberedField(s, _) and s < start)
} }
/** Gets the text of the formatting field at [start, end) */ /** Gets the text of the formatting field at [start, end) */
string getField(int start, int end) { string getField(int start, int end) {
this.field(start, end) and this.field(start, end) and
result = this.getText().substring(start, end) result = this.getText().substring(start, end)
} }
private string fieldId(int start, int end) { private string fieldId(int start, int end) {
this.field(start, end) and this.field(start, end) and
( (
result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1) result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1)
or or
result = this.getText().substring(start + 1, end - 1) and result.regexpMatch("[^!:.\\[]+") result = this.getText().substring(start + 1, end - 1) and result.regexpMatch("[^!:.\\[]+")
) )
} }
/** Gets the name of the formatting field at [start, end) */ /** Gets the name of the formatting field at [start, end) */
string getFieldName(int start, int end) { string getFieldName(int start, int end) {
result = this.fieldId(start, end) and result = this.fieldId(start, end) and
not exists(this.getFieldNumber(start, end)) not exists(this.getFieldNumber(start, end))
} }
private predicate implicitlyNumberedField(int start, int end) { private predicate implicitlyNumberedField(int start, int end) {
this.field(start, end) and this.field(start, end) and
exists(string c | start + 1 = this.getText().indexOf(c) | exists(string c | start + 1 = this.getText().indexOf(c) |
c = "}" or c = ":" or c = "!" or c = "." c = "}" or c = ":" or c = "!" or c = "."
) )
} }
/** Whether this format string has implicitly numbered fields */ /** Whether this format string has implicitly numbered fields */
predicate isImplicitlyNumbered() { this.implicitlyNumberedField(_, _) } predicate isImplicitlyNumbered() { this.implicitlyNumberedField(_, _) }
/** Whether this format string has explicitly numbered fields */ /** Whether this format string has explicitly numbered fields */
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) } predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
} }
/** Holds if the formatting string `fmt` contains a sequence of braces `{` of length `len`, beginning at index `index`. */ /** Holds if the formatting string `fmt` contains a sequence of braces `{` of length `len`, beginning at index `index`. */
predicate brace_sequence(PossibleAdvancedFormatString fmt, int index, int len) { predicate brace_sequence(PossibleAdvancedFormatString fmt, int index, int len) {
exists(string text | text = fmt.getText() | exists(string text | text = fmt.getText() |
text.charAt(index) = "{" and not text.charAt(index - 1) = "{" and len = 1 text.charAt(index) = "{" and not text.charAt(index - 1) = "{" and len = 1
or or
text.charAt(index) = "{" and text.charAt(index) = "{" and
text.charAt(index - 1) = "{" and text.charAt(index - 1) = "{" and
brace_sequence(fmt, index - 1, len - 1) brace_sequence(fmt, index - 1, len - 1)
) )
} }
/** Holds if index `index` in the format string `fmt` contains an escaped brace `{`. */ /** Holds if index `index` in the format string `fmt` contains an escaped brace `{`. */
predicate escaped_brace(PossibleAdvancedFormatString fmt, int index) { predicate escaped_brace(PossibleAdvancedFormatString fmt, int index) {
exists(int len | brace_sequence(fmt, index, len) | len % 2 = 0) exists(int len | brace_sequence(fmt, index, len) | len % 2 = 0)
} }
/** Holds if index `index` in the format string `fmt` contains a left brace `{` that acts as an escape character. */ /** Holds if index `index` in the format string `fmt` contains a left brace `{` that acts as an escape character. */
predicate escaping_brace(PossibleAdvancedFormatString fmt, int index) { predicate escaping_brace(PossibleAdvancedFormatString fmt, int index) {
escaped_brace(fmt, index + 1) escaped_brace(fmt, index + 1)
} }
private predicate inner_brace_pair(PossibleAdvancedFormatString fmt, int start, int end) { private predicate inner_brace_pair(PossibleAdvancedFormatString fmt, int start, int end) {
not escaping_brace(fmt, start) and not escaping_brace(fmt, start) and
not escaped_brace(fmt, start) and not escaped_brace(fmt, start) and
fmt.getText().charAt(start) = "{" and fmt.getText().charAt(start) = "{" and
exists(string pair | exists(string pair |
pair = fmt.getText().suffix(start).regexpCapture("(?s)(\\{([^{}]|\\{\\{)*+\\}).*", 1) pair = fmt.getText().suffix(start).regexpCapture("(?s)(\\{([^{}]|\\{\\{)*+\\}).*", 1)
| |
end = start + pair.length() end = start + pair.length()
) )
} }
private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int end) { private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int end) {
inner_brace_pair(fmt, start, end) inner_brace_pair(fmt, start, end)
or or
not escaping_brace(fmt, start) and not escaping_brace(fmt, start) and
not escaped_brace(fmt, start) and not escaped_brace(fmt, start) and
exists(string prefix, string postfix, int innerstart, int innerend | exists(string prefix, string postfix, int innerstart, int innerend |
brace_pair(fmt, innerstart, innerend) and brace_pair(fmt, innerstart, innerend) and
prefix = fmt.getText().regexpFind("\\{([^{}]|\\{\\{)+\\{", _, start) and prefix = fmt.getText().regexpFind("\\{([^{}]|\\{\\{)+\\{", _, start) and
innerstart = start + prefix.length() - 1 and innerstart = start + prefix.length() - 1 and
postfix = fmt.getText().regexpFind("\\}([^{}]|\\}\\})*\\}", _, innerend - 1) and postfix = fmt.getText().regexpFind("\\}([^{}]|\\}\\})*\\}", _, innerend - 1) and
end = innerend + postfix.length() - 1 end = innerend + postfix.length() - 1
) )
} }
private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) {
exists(CallNode call | call = format_expr.getAFlowNode() | exists(CallNode call | call = format_expr.getAFlowNode() |
call.getFunction().pointsTo(Value::named("format")) and call.getFunction().pointsTo(Value::named("format")) and
call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg()) - 1 args = count(format_expr.getAnArg()) - 1
or or
call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and
args = count(format_expr.getAnArg()) args = count(format_expr.getAnArg())
) )
} }
/** A string constant that has the `format` method applied to it. */ /** A string constant that has the `format` method applied to it. */
class AdvancedFormatString extends PossibleAdvancedFormatString { class AdvancedFormatString extends PossibleAdvancedFormatString {
AdvancedFormatString() { advanced_format_call(_, this, _) } AdvancedFormatString() { advanced_format_call(_, this, _) }
} }
/** A string formatting operation that uses the `format` method. */ /** A string formatting operation that uses the `format` method. */
class AdvancedFormattingCall extends Call { class AdvancedFormattingCall extends Call {
AdvancedFormattingCall() { advanced_format_call(this, _, _) } AdvancedFormattingCall() { advanced_format_call(this, _, _) }
/** Count of the arguments actually provided */ /** Count of the arguments actually provided */
int providedArgCount() { advanced_format_call(this, _, result) } int providedArgCount() { advanced_format_call(this, _, result) }
/** Gets a formatting string for this call. */ /** Gets a formatting string for this call. */
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) } AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
} }

View File

@@ -18,11 +18,11 @@ int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _
from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field
where where
arg_count = call.providedArgCount() and arg_count = call.providedArgCount() and
max_field = field_count(fmt) and max_field = field_count(fmt) and
call.getAFormat() = fmt and call.getAFormat() = fmt and
not exists(call.getStarargs()) and not exists(call.getStarargs()) and
forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count) forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count)
select call, select call,
"Too many arguments for string format. Format $@ requires only " + max_field + ", but " + "Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\"" arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""

View File

@@ -15,17 +15,17 @@ import AdvancedFormatting
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr
where where
call.getAFormat() = fmt and call.getAFormat() = fmt and
name = call.getAKeyword().getArg() and name = call.getAKeyword().getArg() and
forall(AdvancedFormatString format | format = call.getAFormat() | forall(AdvancedFormatString format | format = call.getAFormat() |
not format.getFieldName(_, _) = name not format.getFieldName(_, _) = name
) and ) and
not exists(call.getKwargs()) and not exists(call.getKwargs()) and
( (
strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\"" strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\""
or or
strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used." strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used."
) )
select call, select call,
"Surplus named argument for string format. An argument named '" + name + "Surplus named argument for string format. An argument named '" + name +
"' is provided, but it is not required by $@.", fmt, fmt_repr "' is provided, but it is not required by $@.", fmt, fmt_repr

View File

@@ -16,10 +16,10 @@ import AdvancedFormatting
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name from AdvancedFormattingCall call, AdvancedFormatString fmt, string name
where where
call.getAFormat() = fmt and call.getAFormat() = fmt and
not name = call.getAKeyword().getArg() and not name = call.getAKeyword().getArg() and
fmt.getFieldName(_, _) = name and fmt.getFieldName(_, _) = name and
not exists(call.getKwargs()) not exists(call.getKwargs())
select call, select call,
"Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.", "Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
fmt, "\"" + fmt.getText() + "\"" fmt, "\"" + fmt.getText() + "\""

View File

@@ -15,15 +15,15 @@ import python
import AdvancedFormatting import AdvancedFormatting
from from
AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field, AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field,
string provided string provided
where where
arg_count = call.providedArgCount() and arg_count = call.providedArgCount() and
max_field = max(fmt.getFieldNumber(_, _)) and max_field = max(fmt.getFieldNumber(_, _)) and
call.getAFormat() = fmt and call.getAFormat() = fmt and
not exists(call.getStarargs()) and not exists(call.getStarargs()) and
arg_count <= max_field and arg_count <= max_field and
(if arg_count = 1 then provided = " is provided." else provided = " are provided.") (if arg_count = 1 then provided = " is provided." else provided = " are provided.")
select call, select call,
"Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " + "Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " +
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\"" arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""

View File

@@ -19,39 +19,39 @@ import python
*/ */
predicate numpy_array_type(ClassValue na) { predicate numpy_array_type(ClassValue na) {
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" | exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
na.getASuperType() = np.attr("ndarray") na.getASuperType() = np.attr("ndarray")
) )
} }
predicate has_custom_getitem(Value v) { predicate has_custom_getitem(Value v) {
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
or or
numpy_array_type(v.getClass()) numpy_array_type(v.getClass())
} }
predicate explicitly_hashed(ControlFlowNode f) { predicate explicitly_hashed(ControlFlowNode f) {
exists(CallNode c, GlobalVariable hash | exists(CallNode c, GlobalVariable hash |
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash" c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
) )
} }
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) { predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
is_unhashable(f, c, origin) and is_unhashable(f, c, origin) and
exists(SubscriptNode sub | sub.getIndex() = f | exists(SubscriptNode sub | sub.getIndex() = f |
exists(Value custom_getitem | exists(Value custom_getitem |
sub.getObject().pointsTo(custom_getitem) and sub.getObject().pointsTo(custom_getitem) and
not has_custom_getitem(custom_getitem) not has_custom_getitem(custom_getitem)
)
) )
)
} }
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) { predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls | exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls |
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle() not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
or or
cls.lookup("__hash__") = Value::named("None") cls.lookup("__hash__") = Value::named("None")
) )
} }
/** /**
@@ -68,18 +68,18 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
* it. * it.
*/ */
predicate typeerror_is_caught(ControlFlowNode f) { predicate typeerror_is_caught(ControlFlowNode f) {
exists(Try try | exists(Try try |
try.getBody().contains(f.getNode()) and try.getBody().contains(f.getNode()) and
try.getAHandler().getType().pointsTo(ClassValue::typeError()) try.getAHandler().getType().pointsTo(ClassValue::typeError())
) )
} }
from ControlFlowNode f, ClassValue c, ControlFlowNode origin from ControlFlowNode f, ClassValue c, ControlFlowNode origin
where where
not typeerror_is_caught(f) and not typeerror_is_caught(f) and
( (
explicitly_hashed(f) and is_unhashable(f, c, origin) explicitly_hashed(f) and is_unhashable(f, c, origin)
or or
unhashable_subscript(f, c, origin) unhashable_subscript(f, c, origin)
) )
select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName() select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName()

View File

@@ -15,13 +15,13 @@ import IsComparisons
from Compare comp, Cmpop op, ClassValue c, string alt from Compare comp, Cmpop op, ClassValue c, string alt
where where
invalid_portable_is_comparison(comp, op, c) and invalid_portable_is_comparison(comp, op, c) and
not cpython_interned_constant(comp.getASubExpression()) and not cpython_interned_constant(comp.getASubExpression()) and
( (
op instanceof Is and alt = "==" op instanceof Is and alt = "=="
or or
op instanceof IsNot and alt = "!=" op instanceof IsNot and alt = "!="
) )
select comp, select comp,
"Values compared using '" + op.getSymbol() + "Values compared using '" + op.getSymbol() +
"' when equivalence is not the same as identity. Use '" + alt + "' instead." "' when equivalence is not the same as identity. Use '" + alt + "' instead."

View File

@@ -4,60 +4,60 @@ import python
/** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */ /** Holds if the comparison `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) { predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() | exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
fcomp.operands(left, op, right) and fcomp.operands(left, op, right) and
(op instanceof Is or op instanceof IsNot) (op instanceof Is or op instanceof IsNot)
) )
} }
/** Holds if the class `c` overrides the default notion of equality or comparison. */ /** Holds if the class `c` overrides the default notion of equality or comparison. */
predicate overrides_eq_or_cmp(ClassValue c) { predicate overrides_eq_or_cmp(ClassValue c) {
major_version() = 2 and c.hasAttribute("__eq__") major_version() = 2 and c.hasAttribute("__eq__")
or or
c.declaresAttribute("__eq__") and not c = Value::named("object") c.declaresAttribute("__eq__") and not c = Value::named("object")
or or
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") | exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
sup.declaresAttribute("__eq__") sup.declaresAttribute("__eq__")
) )
or or
major_version() = 2 and c.hasAttribute("__cmp__") major_version() = 2 and c.hasAttribute("__cmp__")
} }
/** Holds if the class `cls` is likely to only have a single instance throughout the program. */ /** Holds if the class `cls` is likely to only have a single instance throughout the program. */
predicate probablySingleton(ClassValue cls) { predicate probablySingleton(ClassValue cls) {
strictcount(Value inst | inst.getClass() = cls) = 1 strictcount(Value inst | inst.getClass() = cls) = 1
or or
cls = Value::named("None").getClass() cls = Value::named("None").getClass()
} }
/** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */ /** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */
predicate invalid_to_use_is_portably(ClassValue c) { predicate invalid_to_use_is_portably(ClassValue c) {
overrides_eq_or_cmp(c) and overrides_eq_or_cmp(c) and
// Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ // Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__
not c = Value::named("type") and not c = Value::named("type") and
not c = ClassValue::builtinFunction() and not c = ClassValue::builtinFunction() and
not c = Value::named("bool") and not c = Value::named("bool") and
// OK to compare with 'is' if a singleton // OK to compare with 'is' if a singleton
not probablySingleton(c) not probablySingleton(c)
} }
/** Holds if the control flow node `f` points to either `True`, `False`, or `None`. */ /** Holds if the control flow node `f` points to either `True`, `False`, or `None`. */
predicate simple_constant(ControlFlowNode f) { predicate simple_constant(ControlFlowNode f) {
exists(Value val | f.pointsTo(val) | exists(Value val | f.pointsTo(val) |
val = Value::named("True") or val = Value::named("False") or val = Value::named("None") val = Value::named("True") or val = Value::named("False") or val = Value::named("None")
) )
} }
private predicate cpython_interned_value(Expr e) { private predicate cpython_interned_value(Expr e) {
exists(string text | text = e.(StrConst).getText() | exists(string text | text = e.(StrConst).getText() |
text.length() = 0 text.length() = 0
or
text.length() = 1 and text.regexpMatch("[U+0000-U+00ff]")
)
or or
exists(int i | i = e.(IntegerLiteral).getN().toInt() | -5 <= i and i <= 256) text.length() = 1 and text.regexpMatch("[U+0000-U+00ff]")
or )
exists(Tuple t | t = e and not exists(t.getAnElt())) or
exists(int i | i = e.(IntegerLiteral).getN().toInt() | -5 <= i and i <= 256)
or
exists(Tuple t | t = e and not exists(t.getAnElt()))
} }
/** /**
@@ -66,83 +66,83 @@ private predicate cpython_interned_value(Expr e) {
* follow CPython, but it varies, so this is a best guess. * follow CPython, but it varies, so this is a best guess.
*/ */
private predicate universally_interned_value(Expr e) { private predicate universally_interned_value(Expr e) {
e.(IntegerLiteral).getN().toInt() = 0 e.(IntegerLiteral).getN().toInt() = 0
or or
exists(Tuple t | t = e and not exists(t.getAnElt())) exists(Tuple t | t = e and not exists(t.getAnElt()))
or or
e.(StrConst).getText() = "" e.(StrConst).getText() = ""
} }
/** Holds if the expression `e` points to an interned constant in CPython. */ /** Holds if the expression `e` points to an interned constant in CPython. */
predicate cpython_interned_constant(Expr e) { predicate cpython_interned_constant(Expr e) {
exists(Expr const | e.pointsTo(_, const) | cpython_interned_value(const)) exists(Expr const | e.pointsTo(_, const) | cpython_interned_value(const))
} }
/** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */ /** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */
predicate universally_interned_constant(Expr e) { predicate universally_interned_constant(Expr e) {
exists(Expr const | e.pointsTo(_, const) | universally_interned_value(const)) exists(Expr const | e.pointsTo(_, const) | universally_interned_value(const))
} }
private predicate comparison_both_types(Compare comp, Cmpop op, ClassValue cls1, ClassValue cls2) { private predicate comparison_both_types(Compare comp, Cmpop op, ClassValue cls1, ClassValue cls2) {
exists(ControlFlowNode op1, ControlFlowNode op2 | exists(ControlFlowNode op1, ControlFlowNode op2 |
comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1) comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1)
| |
op1.inferredValue().getClass() = cls1 and op1.inferredValue().getClass() = cls1 and
op2.inferredValue().getClass() = cls2 op2.inferredValue().getClass() = cls2
) )
} }
private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) { private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
not comparison_both_types(comp, _, _, _) and not comparison_both_types(comp, _, _, _) and
exists(ControlFlowNode operand | exists(ControlFlowNode operand |
comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand) comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand)
| |
operand.inferredValue().getClass() = cls operand.inferredValue().getClass() = cls
) )
} }
/** /**
* Holds if using `is` or `is not` as the operator `op` in the comparison `comp` would be invalid when applied to the class `cls`. * Holds if using `is` or `is not` as the operator `op` in the comparison `comp` would be invalid when applied to the class `cls`.
*/ */
predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) { predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) {
// OK to use 'is' when defining '__eq__' // OK to use 'is' when defining '__eq__'
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" | not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" |
eq = comp.getScope().getScope*() eq = comp.getScope().getScope*()
) and ) and
( (
comparison_one_type(comp, op, cls) and invalid_to_use_is_portably(cls) comparison_one_type(comp, op, cls) and invalid_to_use_is_portably(cls)
or or
exists(ClassValue other | comparison_both_types(comp, op, cls, other) | exists(ClassValue other | comparison_both_types(comp, op, cls, other) |
invalid_to_use_is_portably(cls) and invalid_to_use_is_portably(cls) and
invalid_to_use_is_portably(other) invalid_to_use_is_portably(other)
)
) and
// OK to use 'is' when comparing items from a known set of objects
not exists(Expr left, Expr right, Value val |
comp.compares(left, op, right) and
exists(ImmutableLiteral il | il.getLiteralValue() = val)
|
left.pointsTo(val) and right.pointsTo(val)
or
// Simple constant in module, probably some sort of sentinel
exists(AstNode origin |
not left.pointsTo(_) and
right.pointsTo(val, origin) and
origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule()
)
) and
// OK to use 'is' when comparing with a member of an enum
not exists(Expr left, Expr right, AstNode origin |
comp.compares(left, op, right) and
enum_member(origin)
|
left.pointsTo(_, origin) or right.pointsTo(_, origin)
) )
) and
// OK to use 'is' when comparing items from a known set of objects
not exists(Expr left, Expr right, Value val |
comp.compares(left, op, right) and
exists(ImmutableLiteral il | il.getLiteralValue() = val)
|
left.pointsTo(val) and right.pointsTo(val)
or
// Simple constant in module, probably some sort of sentinel
exists(AstNode origin |
not left.pointsTo(_) and
right.pointsTo(val, origin) and
origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule()
)
) and
// OK to use 'is' when comparing with a member of an enum
not exists(Expr left, Expr right, AstNode origin |
comp.compares(left, op, right) and
enum_member(origin)
|
left.pointsTo(_, origin) or right.pointsTo(_, origin)
)
} }
private predicate enum_member(AstNode obj) { private predicate enum_member(AstNode obj) {
exists(ClassValue cls, AssignStmt asgn | cls.getASuperType().getName() = "Enum" | exists(ClassValue cls, AssignStmt asgn | cls.getASuperType().getName() = "Enum" |
cls.getScope() = asgn.getScope() and cls.getScope() = asgn.getScope() and
asgn.getValue() = obj asgn.getValue() = obj
) )
} }

View File

@@ -16,12 +16,12 @@ import Exceptions.NotImplemented
from Call c, Value v, ClassValue t, Expr f, AstNode origin from Call c, Value v, ClassValue t, Expr f, AstNode origin
where where
f = c.getFunc() and f = c.getFunc() and
f.pointsTo(v, origin) and f.pointsTo(v, origin) and
t = v.getClass() and t = v.getClass() and
not t.isCallable() and not t.isCallable() and
not t.failedInference(_) and not t.failedInference(_) and
not t.hasAttribute("__get__") and not t.hasAttribute("__get__") and
not v = Value::named("None") and not v = Value::named("None") and
not use_of_not_implemented_in_raise(_, f) not use_of_not_implemented_in_raise(_, f)
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString() select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()

View File

@@ -15,11 +15,11 @@ import IsComparisons
from Compare comp, Cmpop op, ClassValue c from Compare comp, Cmpop op, ClassValue c
where where
invalid_portable_is_comparison(comp, op, c) and invalid_portable_is_comparison(comp, op, c) and
exists(Expr sub | sub = comp.getASubExpression() | exists(Expr sub | sub = comp.getASubExpression() |
cpython_interned_constant(sub) and cpython_interned_constant(sub) and
not universally_interned_constant(sub) not universally_interned_constant(sub)
) )
select comp, select comp,
"The result of this comparison with '" + op.getSymbol() + "The result of this comparison with '" + op.getSymbol() +
"' may differ between implementations of Python." "' may differ between implementations of Python."

View File

@@ -4,53 +4,54 @@ import python
/** A comparison where the left and right hand sides appear to be identical. */ /** A comparison where the left and right hand sides appear to be identical. */
class RedundantComparison extends Compare { class RedundantComparison extends Compare {
RedundantComparison() { RedundantComparison() {
exists(Expr left, Expr right | exists(Expr left, Expr right |
this.compares(left, _, right) and this.compares(left, _, right) and
same_variable(left, right) same_variable(left, right)
) )
} }
/** Holds if this comparison could be redundant due to a missing `self.`, for example /**
* ```python * Holds if this comparison could be redundant due to a missing `self.`, for example
* foo == foo * ```python
* ``` * foo == foo
* instead of * ```
* ```python * instead of
* self.foo == foo * ```python
* ``` * self.foo == foo
*/ * ```
predicate maybeMissingSelf() { */
exists(Name left | predicate maybeMissingSelf() {
this.compares(left, _, _) and exists(Name left |
not this.isConstant() and this.compares(left, _, _) and
exists(Class cls | left.getScope().getScope() = cls | not this.isConstant() and
exists(SelfAttribute sa | sa.getName() = left.getId() | sa.getClass() = cls) exists(Class cls | left.getScope().getScope() = cls |
) exists(SelfAttribute sa | sa.getName() = left.getId() | sa.getClass() = cls)
) )
} )
}
} }
private predicate same_variable(Expr left, Expr right) { private predicate same_variable(Expr left, Expr right) {
same_name(left, right) same_name(left, right)
or or
same_attribute(left, right) same_attribute(left, right)
} }
private predicate name_in_comparison(Compare comp, Name n, Variable v) { private predicate name_in_comparison(Compare comp, Name n, Variable v) {
comp.contains(n) and v = n.getVariable() comp.contains(n) and v = n.getVariable()
} }
private predicate same_name(Name n1, Name n2) { private predicate same_name(Name n1, Name n2) {
n1 != n2 and n1 != n2 and
exists(Compare comp, Variable v | exists(Compare comp, Variable v |
name_in_comparison(comp, n1, v) and name_in_comparison(comp, n2, v) name_in_comparison(comp, n1, v) and name_in_comparison(comp, n2, v)
) )
} }
private predicate same_attribute(Attribute a1, Attribute a2) { private predicate same_attribute(Attribute a1, Attribute a2) {
a1 != a2 and a1 != a2 and
exists(Compare comp | comp.contains(a1) and comp.contains(a2)) and exists(Compare comp | comp.contains(a1) and comp.contains(a2)) and
a1.getName() = a2.getName() and a1.getName() = a2.getName() and
same_name(a1.getObject(), a2.getObject()) same_name(a1.getObject(), a2.getObject())
} }

View File

@@ -15,7 +15,7 @@ import semmle.python.regex
from Regex r, int offset from Regex r, int offset
where where
r.escapingChar(offset) and r.escapingChar(offset) and
r.getChar(offset + 1) = "b" and r.getChar(offset + 1) = "b" and
exists(int start, int end | start < offset and end > offset | r.charSet(start, end)) exists(int start, int end | start < offset and end > offset | r.charSet(start, end))
select r, "Backspace escape in regular expression at offset " + offset + "." select r, "Backspace escape in regular expression at offset " + offset + "."

View File

@@ -14,29 +14,29 @@ import python
import semmle.python.regex import semmle.python.regex
predicate duplicate_char_in_class(Regex r, string char) { predicate duplicate_char_in_class(Regex r, string char) {
exists(int i, int j, int x, int y, int start, int end | exists(int i, int j, int x, int y, int start, int end |
i != x and i != x and
j != y and j != y and
start < i and start < i and
j < end and j < end and
start < x and start < x and
y < end and y < end and
r.character(i, j) and r.character(i, j) and
char = r.getText().substring(i, j) and char = r.getText().substring(i, j) and
r.character(x, y) and r.character(x, y) and
char = r.getText().substring(x, y) and char = r.getText().substring(x, y) and
r.charSet(start, end) r.charSet(start, end)
) and ) and
/* Exclude <20> as we use it for any unencodable character */ /* Exclude <20> as we use it for any unencodable character */
char != "<22>" and char != "<22>" and
//Ignore whitespace in verbose mode //Ignore whitespace in verbose mode
not ( not (
r.getAMode() = "VERBOSE" and r.getAMode() = "VERBOSE" and
(char = " " or char = "\t" or char = "\r" or char = "\n") (char = " " or char = "\t" or char = "\r" or char = "\n")
) )
} }
from Regex r, string char from Regex r, string char
where duplicate_char_in_class(r, char) where duplicate_char_in_class(r, char)
select r, select r,
"This regular expression includes duplicate character '" + char + "' in a set of characters." "This regular expression includes duplicate character '" + char + "' in a set of characters."

View File

@@ -14,13 +14,13 @@ import python
import semmle.python.regex import semmle.python.regex
predicate unmatchable_caret(Regex r, int start) { predicate unmatchable_caret(Regex r, int start) {
not r.getAMode() = "MULTILINE" and not r.getAMode() = "MULTILINE" and
not r.getAMode() = "VERBOSE" and not r.getAMode() = "VERBOSE" and
r.specialCharacter(start, start + 1, "^") and r.specialCharacter(start, start + 1, "^") and
not r.firstItem(start, start + 1) not r.firstItem(start, start + 1)
} }
from Regex r, int offset from Regex r, int offset
where unmatchable_caret(r, offset) where unmatchable_caret(r, offset)
select r, select r,
"This regular expression includes an unmatchable caret at offset " + offset.toString() + "." "This regular expression includes an unmatchable caret at offset " + offset.toString() + "."

View File

@@ -14,13 +14,13 @@ import python
import semmle.python.regex import semmle.python.regex
predicate unmatchable_dollar(Regex r, int start) { predicate unmatchable_dollar(Regex r, int start) {
not r.getAMode() = "MULTILINE" and not r.getAMode() = "MULTILINE" and
not r.getAMode() = "VERBOSE" and not r.getAMode() = "VERBOSE" and
r.specialCharacter(start, start + 1, "$") and r.specialCharacter(start, start + 1, "$") and
not r.lastItem(start, start + 1) not r.lastItem(start, start + 1)
} }
from Regex r, int offset from Regex r, int offset
where unmatchable_dollar(r, offset) where unmatchable_dollar(r, offset)
select r, select r,
"This regular expression includes an unmatchable dollar at offset " + offset.toString() + "." "This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."

View File

@@ -15,23 +15,23 @@ import python
from BinaryExpr div, ControlFlowNode left, ControlFlowNode right from BinaryExpr div, ControlFlowNode left, ControlFlowNode right
where where
// Only relevant for Python 2, as all later versions implement true division // Only relevant for Python 2, as all later versions implement true division
major_version() = 2 and major_version() = 2 and
exists(BinaryExprNode bin, Value lval, Value rval | exists(BinaryExprNode bin, Value lval, Value rval |
bin = div.getAFlowNode() and bin = div.getAFlowNode() and
bin.getNode().getOp() instanceof Div and bin.getNode().getOp() instanceof Div and
bin.getLeft().pointsTo(lval, left) and bin.getLeft().pointsTo(lval, left) and
lval.getClass() = ClassValue::int_() and lval.getClass() = ClassValue::int_() and
bin.getRight().pointsTo(rval, right) and bin.getRight().pointsTo(rval, right) and
rval.getClass() = ClassValue::int_() and rval.getClass() = ClassValue::int_() and
// Ignore instances where integer division leaves no remainder // Ignore instances where integer division leaves no remainder
not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and
not bin.getNode().getEnclosingModule().hasFromFuture("division") and not bin.getNode().getEnclosingModule().hasFromFuture("division") and
// Filter out results wrapped in `int(...)` // Filter out results wrapped in `int(...)`
not exists(CallNode c | not exists(CallNode c |
c = ClassValue::int_().getACall() and c = ClassValue::int_().getACall() and
c.getAnArg() = bin c.getAnArg() = bin
)
) )
)
select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.", select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.",
left.getLocation(), "left", right.getLocation(), "right" left.getLocation(), "left", right.getLocation(), "right"

View File

@@ -15,20 +15,20 @@
import python import python
predicate string_const(Expr s) { predicate string_const(Expr s) {
s instanceof StrConst s instanceof StrConst
or or
string_const(s.(BinaryExpr).getLeft()) and string_const(s.(BinaryExpr).getRight()) string_const(s.(BinaryExpr).getLeft()) and string_const(s.(BinaryExpr).getRight())
} }
from StrConst s from StrConst s
where where
// Implicitly concatenated string is in a list and that list contains at least one other string. // Implicitly concatenated string is in a list and that list contains at least one other string.
exists(List l, Expr other | exists(List l, Expr other |
not s = other and not s = other and
l.getAnElt() = s and l.getAnElt() = s and
l.getAnElt() = other and l.getAnElt() = other and
string_const(other) string_const(other)
) and ) and
exists(s.getAnImplicitlyConcatenatedPart()) and exists(s.getAnImplicitlyConcatenatedPart()) and
not s.isParenthesized() not s.isParenthesized()
select s, "Implicit string concatenation. Maybe missing a comma?" select s, "Implicit string concatenation. Maybe missing a comma?"

View File

@@ -14,48 +14,48 @@ import python
/* f consists of a single return statement, whose value is a call. The arguments of the call are exactly the parameters of f */ /* f consists of a single return statement, whose value is a call. The arguments of the call are exactly the parameters of f */
predicate simple_wrapper(Lambda l, Expr wrapped) { predicate simple_wrapper(Lambda l, Expr wrapped) {
exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() | exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() |
wrapped = c.getFunc() and wrapped = c.getFunc() and
count(f.getAnArg()) = count(c.getAnArg()) and count(f.getAnArg()) = count(c.getAnArg()) and
forall(int arg | exists(f.getArg(arg)) | f.getArgName(arg) = c.getArg(arg).(Name).getId()) and forall(int arg | exists(f.getArg(arg)) | f.getArgName(arg) = c.getArg(arg).(Name).getId()) and
/* Either no **kwargs or they must match */ /* Either no **kwargs or they must match */
( (
not exists(f.getKwarg()) and not exists(c.getKwargs()) not exists(f.getKwarg()) and not exists(c.getKwargs())
or or
f.getKwarg().(Name).getId() = c.getKwargs().(Name).getId() f.getKwarg().(Name).getId() = c.getKwargs().(Name).getId()
) and
/* Either no *args or they must match */
(
not exists(f.getVararg()) and not exists(c.getStarargs())
or
f.getVararg().(Name).getId() = c.getStarargs().(Name).getId()
) and
/* No named parameters in call */
not exists(c.getAKeyword())
) and ) and
// f is not necessarily a drop-in replacement for the lambda if there are default argument values /* Either no *args or they must match */
not exists(l.getArgs().getADefault()) (
not exists(f.getVararg()) and not exists(c.getStarargs())
or
f.getVararg().(Name).getId() = c.getStarargs().(Name).getId()
) and
/* No named parameters in call */
not exists(c.getAKeyword())
) and
// f is not necessarily a drop-in replacement for the lambda if there are default argument values
not exists(l.getArgs().getADefault())
} }
/* The expression called will refer to the same object if evaluated when the lambda is created or when the lambda is executed. */ /* The expression called will refer to the same object if evaluated when the lambda is created or when the lambda is executed. */
predicate unnecessary_lambda(Lambda l, Expr e) { predicate unnecessary_lambda(Lambda l, Expr e) {
simple_wrapper(l, e) and simple_wrapper(l, e) and
( (
/* plain class */ /* plain class */
exists(ClassValue c | e.pointsTo(c)) exists(ClassValue c | e.pointsTo(c))
or or
/* plain function */ /* plain function */
exists(FunctionValue f | e.pointsTo(f)) exists(FunctionValue f | e.pointsTo(f))
or or
/* bound-method of enclosing instance */ /* bound-method of enclosing instance */
exists(ClassValue cls, Attribute a | cls.getScope() = l.getScope().getScope() and a = e | exists(ClassValue cls, Attribute a | cls.getScope() = l.getScope().getScope() and a = e |
a.getObject().(Name).getId() = "self" and a.getObject().(Name).getId() = "self" and
cls.hasAttribute(a.getName()) cls.hasAttribute(a.getName())
)
) )
)
} }
from Lambda l, Expr e from Lambda l, Expr e
where unnecessary_lambda(l, e) where unnecessary_lambda(l, e)
select l, select l,
"This 'lambda' is just a simple wrapper around a callable object. Use that object directly." "This 'lambda' is just a simple wrapper around a callable object. Use that object directly."

View File

@@ -14,8 +14,8 @@ import python
from CallNode call, Context context, ControlFlowNode func from CallNode call, Context context, ControlFlowNode func
where where
context.getAVersion().includes(2, _) and context.getAVersion().includes(2, _) and
call.getFunction() = func and call.getFunction() = func and
func.pointsTo(context, Value::named("input"), _) and func.pointsTo(context, Value::named("input"), _) and
not func.pointsTo(context, Value::named("raw_input"), _) not func.pointsTo(context, Value::named("raw_input"), _)
select call, "The unsafe built-in function 'input' is used in Python 2." select call, "The unsafe built-in function 'input' is used in Python 2."

View File

@@ -18,10 +18,10 @@ import Expressions.CallArgs
from Call call, FunctionObject func, string name from Call call, FunctionObject func, string name
where where
illegally_named_parameter_objectapi(call, func, name) and illegally_named_parameter_objectapi(call, func, name) and
not func.isAbstract() and not func.isAbstract() and
not exists(FunctionObject overridden | not exists(FunctionObject overridden |
func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name
) )
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func, select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func,
func.descriptiveString() func.descriptiveString()

View File

@@ -16,32 +16,32 @@ import python
import semmle.python.strings import semmle.python.strings
predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) { predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) {
operation.getOp() instanceof Mod and operation.getOp() instanceof Mod and
exists(Value fmt, Context ctx | exists(Value fmt, Context ctx |
operation.getLeft().pointsTo(ctx, fmt, str) and operation.getLeft().pointsTo(ctx, fmt, str) and
operation.getRight().pointsTo(ctx, args, origin) operation.getRight().pointsTo(ctx, args, origin)
) )
} }
int sequence_length(Value args) { int sequence_length(Value args) {
/* Guess length of sequence */ /* Guess length of sequence */
exists(Tuple seq, AstNode origin | seq.pointsTo(args, origin) | exists(Tuple seq, AstNode origin | seq.pointsTo(args, origin) |
result = strictcount(seq.getAnElt()) and result = strictcount(seq.getAnElt()) and
not seq.getAnElt() instanceof Starred not seq.getAnElt() instanceof Starred
) )
or or
exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1) exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1)
} }
from from
BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin, BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin,
string provided string provided
where where
string_format(operation, fmt, args, origin) and string_format(operation, fmt, args, origin) and
slen = sequence_length(args) and slen = sequence_length(args) and
alen = format_items(fmt) and alen = format_items(fmt) and
slen != alen and slen != alen and
(if slen = 1 then provided = " is provided." else provided = " are provided.") (if slen = 1 then provided = " is provided." else provided = " are provided.")
select operation, select operation,
"Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " +
slen.toString() + provided, origin, "arguments", fmt, fmt.getText() slen.toString() + provided, origin, "arguments", fmt, fmt.getText()

View File

@@ -16,15 +16,16 @@ import CallArgs
from Call call, FunctionValue func, string too, string should, int limit from Call call, FunctionValue func, string too, string should, int limit
where where
( (
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than " too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
or or
too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than " too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than "
) and ) and
not isAbstract(func) and not isAbstract(func) and
not exists(FunctionValue overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden)) not exists(FunctionValue overridden |
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */ func.overrides(overridden) and correct_args_if_called_as_method(call, overridden)
and not func.getName() = "__new__" ) and
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString() not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
func.descriptiveString()

View File

@@ -11,9 +11,9 @@ import semmle.python.filters.GeneratedCode
import semmle.python.filters.Tests import semmle.python.filters.Tests
predicate classify(File f, string tag) { predicate classify(File f, string tag) {
f instanceof GeneratedFile and tag = "generated" f instanceof GeneratedFile and tag = "generated"
or or
exists(TestScope t | t.getLocation().getFile() = f) and tag = "test" exists(TestScope t | t.getLocation().getFile() = f) and tag = "test"
} }
from File f, string tag from File f, string tag

View File

@@ -13,21 +13,21 @@
import python import python
predicate explicitly_returns_non_none(Function func) { predicate explicitly_returns_non_none(Function func) {
exists(Return return | exists(Return return |
return.getScope() = func and return.getScope() = func and
exists(Expr val | val = return.getValue() | not val instanceof None) exists(Expr val | val = return.getValue() | not val instanceof None)
) )
} }
predicate has_implicit_return(Function func) { predicate has_implicit_return(Function func) {
exists(ControlFlowNode fallthru | exists(ControlFlowNode fallthru |
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable() fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
) )
or or
exists(Return return | return.getScope() = func and not exists(return.getValue())) exists(Return return | return.getScope() = func and not exists(return.getValue()))
} }
from Function func from Function func
where explicitly_returns_non_none(func) and has_implicit_return(func) where explicitly_returns_non_none(func) and has_implicit_return(func)
select func, select func,
"Mixing implicit and explicit returns may indicate an error as implicit returns always return None." "Mixing implicit and explicit returns may indicate an error as implicit returns always return None."

View File

@@ -12,13 +12,13 @@
import python import python
predicate slice_method_name(string name) { predicate slice_method_name(string name) {
name = "__getslice__" or name = "__setslice__" or name = "__delslice__" name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
} }
from PythonFunctionValue f, string meth from PythonFunctionValue f, string meth
where where
f.getScope().isMethod() and f.getScope().isMethod() and
not f.isOverridingMethod() and not f.isOverridingMethod() and
slice_method_name(meth) and slice_method_name(meth) and
f.getName() = meth f.getName() = meth
select f, meth + " method has been deprecated since Python 2.0" select f, meth + " method has been deprecated since Python 2.0"

View File

@@ -14,10 +14,10 @@ import python
from Return r, Expr rv from Return r, Expr rv
where where
exists(Function init | init.isInitMethod() and r.getScope() = init) and exists(Function init | init.isInitMethod() and r.getScope() = init) and
r.getValue() = rv and r.getValue() = rv and
not rv.pointsTo(Value::none_()) and not rv.pointsTo(Value::none_()) and
not exists(FunctionValue f | f.getACall() = rv.getAFlowNode() | f.neverReturns()) and not exists(FunctionValue f | f.getACall() = rv.getAFlowNode() | f.neverReturns()) and
// to avoid double reporting, don't trigger if returning result from other __init__ function // to avoid double reporting, don't trigger if returning result from other __init__ function
not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__") not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__")
select r, "Explicit return in __init__ method." select r, "Explicit return in __init__ method."

View File

@@ -14,142 +14,142 @@
import python import python
private predicate attribute_method(string name) { private predicate attribute_method(string name) {
name = "__getattribute__" or name = "__getattr__" or name = "__setattr__" name = "__getattribute__" or name = "__getattr__" or name = "__setattr__"
} }
private predicate indexing_method(string name) { private predicate indexing_method(string name) {
name = "__getitem__" or name = "__setitem__" or name = "__delitem__" name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
} }
private predicate arithmetic_method(string name) { private predicate arithmetic_method(string name) {
name = "__add__" or name = "__add__" or
name = "__sub__" or name = "__sub__" or
name = "__div__" or name = "__div__" or
name = "__pos__" or name = "__pos__" or
name = "__abs__" or name = "__abs__" or
name = "__floordiv__" or name = "__floordiv__" or
name = "__div__" or name = "__div__" or
name = "__divmod__" or name = "__divmod__" or
name = "__lshift__" or name = "__lshift__" or
name = "__and__" or name = "__and__" or
name = "__or__" or name = "__or__" or
name = "__xor__" or name = "__xor__" or
name = "__rshift__" or name = "__rshift__" or
name = "__pow__" or name = "__pow__" or
name = "__mul__" or name = "__mul__" or
name = "__neg__" or name = "__neg__" or
name = "__radd__" or name = "__radd__" or
name = "__rsub__" or name = "__rsub__" or
name = "__rdiv__" or name = "__rdiv__" or
name = "__rfloordiv__" or name = "__rfloordiv__" or
name = "__rdiv__" or name = "__rdiv__" or
name = "__rlshift__" or name = "__rlshift__" or
name = "__rand__" or name = "__rand__" or
name = "__ror__" or name = "__ror__" or
name = "__rxor__" or name = "__rxor__" or
name = "__rrshift__" or name = "__rrshift__" or
name = "__rpow__" or name = "__rpow__" or
name = "__rmul__" or name = "__rmul__" or
name = "__truediv__" or name = "__truediv__" or
name = "__rtruediv__" or name = "__rtruediv__" or
name = "__iadd__" or name = "__iadd__" or
name = "__isub__" or name = "__isub__" or
name = "__idiv__" or name = "__idiv__" or
name = "__ifloordiv__" or name = "__ifloordiv__" or
name = "__idiv__" or name = "__idiv__" or
name = "__ilshift__" or name = "__ilshift__" or
name = "__iand__" or name = "__iand__" or
name = "__ior__" or name = "__ior__" or
name = "__ixor__" or name = "__ixor__" or
name = "__irshift__" or name = "__irshift__" or
name = "__ipow__" or name = "__ipow__" or
name = "__imul__" or name = "__imul__" or
name = "__itruediv__" name = "__itruediv__"
} }
private predicate ordering_method(string name) { private predicate ordering_method(string name) {
name = "__lt__" name = "__lt__"
or or
name = "__le__" name = "__le__"
or or
name = "__gt__" name = "__gt__"
or or
name = "__ge__" name = "__ge__"
or or
name = "__cmp__" and major_version() = 2 name = "__cmp__" and major_version() = 2
} }
private predicate cast_method(string name) { private predicate cast_method(string name) {
name = "__nonzero__" and major_version() = 2 name = "__nonzero__" and major_version() = 2
or or
name = "__int__" name = "__int__"
or or
name = "__float__" name = "__float__"
or or
name = "__long__" name = "__long__"
or or
name = "__trunc__" name = "__trunc__"
or or
name = "__complex__" name = "__complex__"
} }
predicate correct_raise(string name, ClassObject ex) { predicate correct_raise(string name, ClassObject ex) {
ex.getAnImproperSuperType() = theTypeErrorType() and ex.getAnImproperSuperType() = theTypeErrorType() and
( (
name = "__copy__" or name = "__copy__" or
name = "__deepcopy__" or name = "__deepcopy__" or
name = "__call__" or name = "__call__" or
indexing_method(name) or indexing_method(name) or
attribute_method(name) attribute_method(name)
) )
or or
preferred_raise(name, ex) preferred_raise(name, ex)
or or
preferred_raise(name, ex.getASuperType()) preferred_raise(name, ex.getASuperType())
} }
predicate preferred_raise(string name, ClassObject ex) { predicate preferred_raise(string name, ClassObject ex) {
attribute_method(name) and ex = theAttributeErrorType() attribute_method(name) and ex = theAttributeErrorType()
or or
indexing_method(name) and ex = Object::builtin("LookupError") indexing_method(name) and ex = Object::builtin("LookupError")
or or
ordering_method(name) and ex = theTypeErrorType() ordering_method(name) and ex = theTypeErrorType()
or or
arithmetic_method(name) and ex = Object::builtin("ArithmeticError") arithmetic_method(name) and ex = Object::builtin("ArithmeticError")
or or
name = "__bool__" and ex = theTypeErrorType() name = "__bool__" and ex = theTypeErrorType()
} }
predicate no_need_to_raise(string name, string message) { predicate no_need_to_raise(string name, string message) {
name = "__hash__" and message = "use __hash__ = None instead" name = "__hash__" and message = "use __hash__ = None instead"
or or
cast_method(name) and message = "there is no need to implement the method at all." cast_method(name) and message = "there is no need to implement the method at all."
} }
predicate is_abstract(FunctionObject func) { predicate is_abstract(FunctionObject func) {
func.getFunction().getADecorator().(Name).getId().matches("%abstract%") func.getFunction().getADecorator().(Name).getId().matches("%abstract%")
} }
predicate always_raises(FunctionObject f, ClassObject ex) { predicate always_raises(FunctionObject f, ClassObject ex) {
ex = f.getARaisedType() and ex = f.getARaisedType() and
strictcount(f.getARaisedType()) = 1 and strictcount(f.getARaisedType()) = 1 and
not exists(f.getFunction().getANormalExit()) and not exists(f.getFunction().getANormalExit()) and
/* raising StopIteration is equivalent to a return in a generator */ /* raising StopIteration is equivalent to a return in a generator */
not ex = theStopIterationType() not ex = theStopIterationType()
} }
from FunctionObject f, ClassObject cls, string message from FunctionObject f, ClassObject cls, string message
where where
f.getFunction().isSpecialMethod() and f.getFunction().isSpecialMethod() and
not is_abstract(f) and not is_abstract(f) and
always_raises(f, cls) and always_raises(f, cls) and
( (
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError" no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
or or
not correct_raise(f.getName(), cls) and not correct_raise(f.getName(), cls) and
not cls.getName() = "NotImplementedError" and not cls.getName() = "NotImplementedError" and
exists(ClassObject preferred | preferred_raise(f.getName(), preferred) | exists(ClassObject preferred | preferred_raise(f.getName(), preferred) |
message = "raise " + preferred.getName() + " instead" message = "raise " + preferred.getName() + " instead"
)
) )
)
select f, "Function always raises $@; " + message, cls, cls.toString() select f, "Function always raises $@; " + message, cls, cls.toString()

View File

@@ -14,18 +14,18 @@ import Expressions.CallArgs
from Call call, FunctionValue func, FunctionValue overridden, string problem from Call call, FunctionValue func, FunctionValue overridden, string problem
where where
func.overrides(overridden) and func.overrides(overridden) and
( (
wrong_args(call, func, _, problem) and wrong_args(call, func, _, problem) and
correct_args_if_called_as_method(call, overridden) correct_args_if_called_as_method(call, overridden)
or or
exists(string name | exists(string name |
illegally_named_parameter(call, func, name) and illegally_named_parameter(call, func, name) and
problem = "an argument named '" + name + "'" and problem = "an argument named '" + name + "'" and
overridden.getScope().getAnArg().(Name).getId() = name overridden.getScope().getAnArg().(Name).getId() = name
)
) )
)
select func, select func,
"Overriding method signature does not match $@, where it is passed " + problem + "Overriding method signature does not match $@, where it is passed " + problem +
". Overridden method $@ is correctly specified.", call, "here", overridden, ". Overridden method $@ is correctly specified.", call, "here", overridden,
overridden.descriptiveString() overridden.descriptiveString()

View File

@@ -15,23 +15,23 @@ import Expressions.CallArgs
from Call call, FunctionValue func, FunctionValue overriding, string problem from Call call, FunctionValue func, FunctionValue overriding, string problem
where where
not func.getName() = "__init__" and not func.getName() = "__init__" and
overriding.overrides(func) and overriding.overrides(func) and
call = overriding.getAMethodCall().getNode() and call = overriding.getAMethodCall().getNode() and
correct_args_if_called_as_method(call, overriding) and correct_args_if_called_as_method(call, overriding) and
( (
arg_count(call) + 1 < func.minParameters() and problem = "too few arguments" arg_count(call) + 1 < func.minParameters() and problem = "too few arguments"
or or
arg_count(call) >= func.maxParameters() and problem = "too many arguments" arg_count(call) >= func.maxParameters() and problem = "too many arguments"
or or
exists(string name | exists(string name |
call.getAKeyword().getArg() = name and call.getAKeyword().getArg() = name and
overriding.getScope().getAnArg().(Name).getId() = name and overriding.getScope().getAnArg().(Name).getId() = name and
not func.getScope().getAnArg().(Name).getId() = name and not func.getScope().getAnArg().(Name).getId() = name and
problem = "an argument named '" + name + "'" problem = "an argument named '" + name + "'"
)
) )
)
select func, select func,
"Overridden method signature does not match $@, where it is passed " + problem + "Overridden method signature does not match $@, where it is passed " + problem +
". Overriding method $@ matches the call.", call, "call", overriding, ". Overriding method $@ matches the call.", call, "call", overriding,
overriding.descriptiveString() overriding.descriptiveString()

View File

@@ -14,6 +14,6 @@ import python
from Function f from Function f
where where
f.isInitMethod() and f.isInitMethod() and
(exists(Yield y | y.getScope() = f) or exists(YieldFrom y | y.getScope() = f)) (exists(Yield y | y.getScope() = f) or exists(YieldFrom y | y.getScope() = f))
select f, "__init__ method is a generator." select f, "__init__ method is a generator."

View File

@@ -14,10 +14,10 @@ import python
from ClassValue iterable, FunctionValue iter, ClassValue iterator from ClassValue iterable, FunctionValue iter, ClassValue iterator
where where
iter = iterable.lookup("__iter__") and iter = iterable.lookup("__iter__") and
iterator = iter.getAnInferredReturnType() and iterator = iter.getAnInferredReturnType() and
not iterator.isIterator() not iterator.isIterator()
select iterator, select iterator,
"Class " + iterator.getName() + "Class " + iterator.getName() +
" is returned as an iterator (by $@) but does not fully implement the iterator interface.", " is returned as an iterator (by $@) but does not fully implement the iterator interface.",
iter, iter.getName() iter, iter.getName()

View File

@@ -17,14 +17,14 @@ Function iter_method(ClassValue t) { result = t.lookup("__iter__").(FunctionValu
predicate is_self(Name value, Function f) { value.getVariable() = f.getArg(0).(Name).getVariable() } predicate is_self(Name value, Function f) { value.getVariable() = f.getArg(0).(Name).getVariable() }
predicate returns_non_self(Function f) { predicate returns_non_self(Function f) {
exists(f.getFallthroughNode()) exists(f.getFallthroughNode())
or or
exists(Return r | r.getScope() = f and not is_self(r.getValue(), f)) exists(Return r | r.getScope() = f and not is_self(r.getValue(), f))
or or
exists(Return r | r.getScope() = f and not exists(r.getValue())) exists(Return r | r.getScope() = f and not exists(r.getValue()))
} }
from ClassValue t, Function iter from ClassValue t, Function iter
where t.isIterator() and iter = iter_method(t) and returns_non_self(iter) where t.isIterator() and iter = iter_method(t) and returns_non_self(iter)
select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.", select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.",
iter, iter.getName() iter, iter.getName()

View File

@@ -15,85 +15,85 @@ import python
import semmle.python.security.Paths import semmle.python.security.Paths
predicate safe_method(string name) { predicate safe_method(string name) {
name = "count" or name = "count" or
name = "index" or name = "index" or
name = "copy" or name = "copy" or
name = "get" or name = "get" or
name = "has_key" or name = "has_key" or
name = "items" or name = "items" or
name = "keys" or name = "keys" or
name = "values" or name = "values" or
name = "iteritems" or name = "iteritems" or
name = "iterkeys" or name = "iterkeys" or
name = "itervalues" or name = "itervalues" or
name = "__contains__" or name = "__contains__" or
name = "__getitem__" or name = "__getitem__" or
name = "__getattribute__" name = "__getattribute__"
} }
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */ /** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
private boolean mutableDefaultValue(Parameter p) { private boolean mutableDefaultValue(Parameter p) {
exists(Dict d | p.getDefault() = d | exists(Dict d | p.getDefault() = d |
exists(d.getAKey()) and result = true exists(d.getAKey()) and result = true
or
not exists(d.getAKey()) and result = false
)
or or
exists(List l | p.getDefault() = l | not exists(d.getAKey()) and result = false
exists(l.getAnElt()) and result = true )
or or
not exists(l.getAnElt()) and result = false exists(List l | p.getDefault() = l |
) exists(l.getAnElt()) and result = true
or
not exists(l.getAnElt()) and result = false
)
} }
class NonEmptyMutableValue extends TaintKind { class NonEmptyMutableValue extends TaintKind {
NonEmptyMutableValue() { this = "non-empty mutable value" } NonEmptyMutableValue() { this = "non-empty mutable value" }
} }
class EmptyMutableValue extends TaintKind { class EmptyMutableValue extends TaintKind {
EmptyMutableValue() { this = "empty mutable value" } EmptyMutableValue() { this = "empty mutable value" }
override boolean booleanValue() { result = false } override boolean booleanValue() { result = false }
} }
class MutableDefaultValue extends TaintSource { class MutableDefaultValue extends TaintSource {
boolean nonEmpty; boolean nonEmpty;
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.(NameNode).getNode()) } MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.(NameNode).getNode()) }
override string toString() { result = "mutable default value" } override string toString() { result = "mutable default value" }
override predicate isSourceOf(TaintKind kind) { override predicate isSourceOf(TaintKind kind) {
nonEmpty = false and kind instanceof EmptyMutableValue nonEmpty = false and kind instanceof EmptyMutableValue
or or
nonEmpty = true and kind instanceof NonEmptyMutableValue nonEmpty = true and kind instanceof NonEmptyMutableValue
} }
} }
private ClassValue mutable_class() { private ClassValue mutable_class() {
result = Value::named("list") or result = Value::named("list") or
result = Value::named("dict") result = Value::named("dict")
} }
class Mutation extends TaintSink { class Mutation extends TaintSink {
Mutation() { Mutation() {
exists(AugAssign a | a.getTarget().getAFlowNode() = this) exists(AugAssign a | a.getTarget().getAFlowNode() = this)
or or
exists(Call c, Attribute a | c.getFunc() = a | exists(Call c, Attribute a | c.getFunc() = a |
a.getObject().getAFlowNode() = this and a.getObject().getAFlowNode() = this and
not safe_method(a.getName()) and not safe_method(a.getName()) and
this.(ControlFlowNode).pointsTo().getClass() = mutable_class() this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
) )
} }
override predicate sinks(TaintKind kind) { override predicate sinks(TaintKind kind) {
kind instanceof EmptyMutableValue kind instanceof EmptyMutableValue
or or
kind instanceof NonEmptyMutableValue kind instanceof NonEmptyMutableValue
} }
} }
from TaintedPathSource src, TaintedPathSink sink from TaintedPathSource src, TaintedPathSink sink
where src.flowsTo(sink) where src.flowsTo(sink)
select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(), select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(),
"Default value" "Default value"

View File

@@ -15,36 +15,36 @@
import python import python
predicate first_arg_cls(Function f) { predicate first_arg_cls(Function f) {
exists(string argname | argname = f.getArgName(0) | exists(string argname | argname = f.getArgName(0) |
argname = "cls" argname = "cls"
or or
/* Not PEP8, but relatively common */ /* Not PEP8, but relatively common */
argname = "mcls" argname = "mcls"
) )
} }
predicate is_type_method(Function f) { predicate is_type_method(Function f) {
exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type()) exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type())
} }
predicate classmethod_decorators_only(Function f) { predicate classmethod_decorators_only(Function f) {
forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod") forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod")
} }
from Function f, string message from Function f, string message
where where
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and (f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
not first_arg_cls(f) and not first_arg_cls(f) and
classmethod_decorators_only(f) and classmethod_decorators_only(f) and
not f.getName() = "__new__" and not f.getName() = "__new__" and
( (
if exists(f.getArgName(0)) if exists(f.getArgName(0))
then then
message = message =
"Class methods or methods of a type deriving from type should have 'cls', rather than '" + "Class methods or methods of a type deriving from type should have 'cls', rather than '" +
f.getArgName(0) + "', as their first parameter." f.getArgName(0) + "', as their first parameter."
else else
message = message =
"Class methods or methods of a type deriving from type should have 'cls' as their first parameter." "Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
) )
select f, message select f, message

View File

@@ -17,42 +17,42 @@ import python
import semmle.python.libraries.Zope import semmle.python.libraries.Zope
predicate is_type_method(FunctionValue fv) { predicate is_type_method(FunctionValue fv) {
exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type()) exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type())
} }
predicate used_in_defining_scope(FunctionValue fv) { predicate used_in_defining_scope(FunctionValue fv) {
exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv)) exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv))
} }
from Function f, FunctionValue fv, string message from Function f, FunctionValue fv, string message
where where
exists(ClassValue cls, string name | exists(ClassValue cls, string name |
cls.declaredAttribute(name) = fv and cls.declaredAttribute(name) = fv and
cls.isNewStyle() and cls.isNewStyle() and
not name = "__new__" and not name = "__new__" and
not name = "__metaclass__" and not name = "__metaclass__" and
not name = "__init_subclass__" and not name = "__init_subclass__" and
not name = "__class_getitem__" and not name = "__class_getitem__" and
/* declared in scope */ /* declared in scope */
f.getScope() = cls.getScope() f.getScope() = cls.getScope()
) and ) and
not f.getArgName(0) = "self" and not f.getArgName(0) = "self" and
not is_type_method(fv) and not is_type_method(fv) and
fv.getScope() = f and fv.getScope() = f and
not f.getName() = "lambda" and not f.getName() = "lambda" and
not used_in_defining_scope(fv) and not used_in_defining_scope(fv) and
(
( (
( if exists(f.getArgName(0))
if exists(f.getArgName(0)) then
then message =
message = "Normal methods should have 'self', rather than '" + f.getArgName(0) +
"Normal methods should have 'self', rather than '" + f.getArgName(0) + "', as their first parameter."
"', as their first parameter." else
else message =
message = "Normal methods should have at least one parameter (the first of which should be 'self')."
"Normal methods should have at least one parameter (the first of which should be 'self')."
) and
not f.hasVarArg()
) and ) and
not fv instanceof ZopeInterfaceMethodValue not f.hasVarArg()
) and
not fv instanceof ZopeInterfaceMethodValue
select f, message select f, message

View File

@@ -17,8 +17,8 @@ import python
from FunctionValue method from FunctionValue method
where where
exists(ClassValue c | exists(ClassValue c |
c.declaredAttribute("__del__") = method and c.declaredAttribute("__del__") = method and
method.getScope().getMetrics().getCyclomaticComplexity() > 3 method.getScope().getMetrics().getCyclomaticComplexity() > 3
) )
select method, "Overly complex '__del__' method." select method, "Overly complex '__del__' method."

View File

@@ -13,18 +13,18 @@
import python import python
predicate returns_tuple_of_size(Function func, int size, AstNode origin) { predicate returns_tuple_of_size(Function func, int size, AstNode origin) {
exists(Return return, TupleValue val | exists(Return return, TupleValue val |
return.getScope() = func and return.getScope() = func and
return.getValue().pointsTo(val, origin) return.getValue().pointsTo(val, origin)
| |
size = val.length() size = val.length()
) )
} }
from Function func, int s1, int s2, AstNode t1, AstNode t2 from Function func, int s1, int s2, AstNode t1, AstNode t2
where where
returns_tuple_of_size(func, s1, t1) and returns_tuple_of_size(func, s1, t1) and
returns_tuple_of_size(func, s2, t2) and returns_tuple_of_size(func, s2, t2) and
s1 < s2 s1 < s2
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2, select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
"tuple of size " + s2 "tuple of size " + s2

View File

@@ -18,66 +18,66 @@ import python
import semmle.python.objects.Callables import semmle.python.objects.Callables
predicate meaningful_return_value(Expr val) { predicate meaningful_return_value(Expr val) {
val instanceof Name val instanceof Name
or or
val instanceof BooleanLiteral val instanceof BooleanLiteral
or or
exists(FunctionValue callee | exists(FunctionValue callee |
val = callee.getACall().getNode() and returns_meaningful_value(callee) val = callee.getACall().getNode() and returns_meaningful_value(callee)
) )
or or
not exists(FunctionValue callee | val = callee.getACall().getNode()) and not val instanceof Name not exists(FunctionValue callee | val = callee.getACall().getNode()) and not val instanceof Name
} }
/* Value is used before returning, and thus its value is not lost if ignored */ /* Value is used before returning, and thus its value is not lost if ignored */
predicate used_value(Expr val) { predicate used_value(Expr val) {
exists(LocalVariable var, Expr other | exists(LocalVariable var, Expr other |
var.getAnAccess() = val and other = var.getAnAccess() and not other = val var.getAnAccess() = val and other = var.getAnAccess() and not other = val
) )
} }
predicate returns_meaningful_value(FunctionValue f) { predicate returns_meaningful_value(FunctionValue f) {
not exists(f.getScope().getFallthroughNode()) and not exists(f.getScope().getFallthroughNode()) and
( (
exists(Return ret, Expr val | ret.getScope() = f.getScope() and val = ret.getValue() | exists(Return ret, Expr val | ret.getScope() = f.getScope() and val = ret.getValue() |
meaningful_return_value(val) and meaningful_return_value(val) and
not used_value(val) not used_value(val)
)
or
/*
* Is f a builtin function that returns something other than None?
* Ignore __import__ as it is often called purely for side effects
*/
f.isBuiltin() and
f.getAnInferredReturnType() != ClassValue::nonetype() and
not f.getName() = "__import__"
) )
or
/*
* Is f a builtin function that returns something other than None?
* Ignore __import__ as it is often called purely for side effects
*/
f.isBuiltin() and
f.getAnInferredReturnType() != ClassValue::nonetype() and
not f.getName() = "__import__"
)
} }
/* If a call is wrapped tightly in a try-except then we assume it is being executed for the exception. */ /* If a call is wrapped tightly in a try-except then we assume it is being executed for the exception. */
predicate wrapped_in_try_except(ExprStmt call) { predicate wrapped_in_try_except(ExprStmt call) {
exists(Try t | exists(Try t |
exists(t.getAHandler()) and exists(t.getAHandler()) and
strictcount(Call c | t.getBody().contains(c)) = 1 and strictcount(Call c | t.getBody().contains(c)) = 1 and
call = t.getAStmt() call = t.getAStmt()
) )
} }
from ExprStmt call, FunctionValue callee, float percentage_used, int total from ExprStmt call, FunctionValue callee, float percentage_used, int total
where where
call.getValue() = callee.getACall().getNode() and call.getValue() = callee.getACall().getNode() and
returns_meaningful_value(callee) and returns_meaningful_value(callee) and
not wrapped_in_try_except(call) and not wrapped_in_try_except(call) and
exists(int unused | exists(int unused |
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
total = count(callee.getACall()) total = count(callee.getACall())
| |
percentage_used = (100.0 * (total - unused) / total).floor() percentage_used = (100.0 * (total - unused) / total).floor()
) and ) and
/* Report an alert if we see at least 5 calls and the return value is used in at least 3/4 of those calls. */ /* Report an alert if we see at least 5 calls and the return value is used in at least 3/4 of those calls. */
percentage_used >= 75 and percentage_used >= 75 and
total >= 5 total >= 5
select call, select call,
"Call discards return value of function $@. The result is used in " + percentage_used.toString() + "Call discards return value of function $@. The result is used in " + percentage_used.toString() +
"% of calls.", callee, callee.getName() "% of calls.", callee, callee.getName()

View File

@@ -16,20 +16,20 @@ import Expressions.CallArgs
from FunctionValue base, PythonFunctionValue derived from FunctionValue base, PythonFunctionValue derived
where where
not exists(base.getACall()) and not exists(base.getACall()) and
not exists(FunctionValue a_derived | not exists(FunctionValue a_derived |
a_derived.overrides(base) and a_derived.overrides(base) and
exists(a_derived.getACall()) exists(a_derived.getACall())
) and ) and
not derived.getScope().isSpecialMethod() and not derived.getScope().isSpecialMethod() and
derived.getName() != "__init__" and derived.getName() != "__init__" and
derived.isNormalMethod() and derived.isNormalMethod() and
not derived.getScope().isSpecialMethod() and not derived.getScope().isSpecialMethod() and
// call to overrides distributed for efficiency // call to overrides distributed for efficiency
( (
derived.overrides(base) and derived.minParameters() > base.maxParameters() derived.overrides(base) and derived.minParameters() > base.maxParameters()
or or
derived.overrides(base) and derived.maxParameters() < base.minParameters() derived.overrides(base) and derived.maxParameters() < base.minParameters()
) )
select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.", select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.",
base, "overridden method" base, "overridden method"

View File

@@ -13,200 +13,200 @@
import python import python
predicate is_unary_op(string name) { predicate is_unary_op(string name) {
name = "__del__" or name = "__del__" or
name = "__repr__" or name = "__repr__" or
name = "__str__" or name = "__str__" or
name = "__hash__" or name = "__hash__" or
name = "__bool__" or name = "__bool__" or
name = "__nonzero__" or name = "__nonzero__" or
name = "__unicode__" or name = "__unicode__" or
name = "__len__" or name = "__len__" or
name = "__iter__" or name = "__iter__" or
name = "__reversed__" or name = "__reversed__" or
name = "__neg__" or name = "__neg__" or
name = "__pos__" or name = "__pos__" or
name = "__abs__" or name = "__abs__" or
name = "__invert__" or name = "__invert__" or
name = "__complex__" or name = "__complex__" or
name = "__int__" or name = "__int__" or
name = "__float__" or name = "__float__" or
name = "__long__" or name = "__long__" or
name = "__oct__" or name = "__oct__" or
name = "__hex__" or name = "__hex__" or
name = "__index__" or name = "__index__" or
name = "__enter__" name = "__enter__"
} }
predicate is_binary_op(string name) { predicate is_binary_op(string name) {
name = "__lt__" or name = "__lt__" or
name = "__le__" or name = "__le__" or
name = "__eq__" or name = "__eq__" or
name = "__ne__" or name = "__ne__" or
name = "__gt__" or name = "__gt__" or
name = "__ge__" or name = "__ge__" or
name = "__cmp__" or name = "__cmp__" or
name = "__rcmp__" or name = "__rcmp__" or
name = "__getattr___" or name = "__getattr___" or
name = "__getattribute___" or name = "__getattribute___" or
name = "__delattr__" or name = "__delattr__" or
name = "__delete__" or name = "__delete__" or
name = "__instancecheck__" or name = "__instancecheck__" or
name = "__subclasscheck__" or name = "__subclasscheck__" or
name = "__getitem__" or name = "__getitem__" or
name = "__delitem__" or name = "__delitem__" or
name = "__contains__" or name = "__contains__" or
name = "__add__" or name = "__add__" or
name = "__sub__" or name = "__sub__" or
name = "__mul__" or name = "__mul__" or
name = "__floordiv__" or name = "__floordiv__" or
name = "__div__" or name = "__div__" or
name = "__truediv__" or name = "__truediv__" or
name = "__mod__" or name = "__mod__" or
name = "__divmod__" or name = "__divmod__" or
name = "__lshift__" or name = "__lshift__" or
name = "__rshift__" or name = "__rshift__" or
name = "__and__" or name = "__and__" or
name = "__xor__" or name = "__xor__" or
name = "__or__" or name = "__or__" or
name = "__radd__" or name = "__radd__" or
name = "__rsub__" or name = "__rsub__" or
name = "__rmul__" or name = "__rmul__" or
name = "__rfloordiv__" or name = "__rfloordiv__" or
name = "__rdiv__" or name = "__rdiv__" or
name = "__rtruediv__" or name = "__rtruediv__" or
name = "__rmod__" or name = "__rmod__" or
name = "__rdivmod__" or name = "__rdivmod__" or
name = "__rpow__" or name = "__rpow__" or
name = "__rlshift__" or name = "__rlshift__" or
name = "__rrshift__" or name = "__rrshift__" or
name = "__rand__" or name = "__rand__" or
name = "__rxor__" or name = "__rxor__" or
name = "__ror__" or name = "__ror__" or
name = "__iadd__" or name = "__iadd__" or
name = "__isub__" or name = "__isub__" or
name = "__imul__" or name = "__imul__" or
name = "__ifloordiv__" or name = "__ifloordiv__" or
name = "__idiv__" or name = "__idiv__" or
name = "__itruediv__" or name = "__itruediv__" or
name = "__imod__" or name = "__imod__" or
name = "__idivmod__" or name = "__idivmod__" or
name = "__ipow__" or name = "__ipow__" or
name = "__ilshift__" or name = "__ilshift__" or
name = "__irshift__" or name = "__irshift__" or
name = "__iand__" or name = "__iand__" or
name = "__ixor__" or name = "__ixor__" or
name = "__ior__" or name = "__ior__" or
name = "__coerce__" name = "__coerce__"
} }
predicate is_ternary_op(string name) { predicate is_ternary_op(string name) {
name = "__setattr__" or name = "__setattr__" or
name = "__set__" or name = "__set__" or
name = "__setitem__" or name = "__setitem__" or
name = "__getslice__" or name = "__getslice__" or
name = "__delslice__" name = "__delslice__"
} }
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" } predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
int argument_count(PythonFunctionValue f, string name, ClassValue cls) { int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
cls.declaredAttribute(name) = f and cls.declaredAttribute(name) = f and
( (
is_unary_op(name) and result = 1 is_unary_op(name) and result = 1
or or
is_binary_op(name) and result = 2 is_binary_op(name) and result = 2
or or
is_ternary_op(name) and result = 3 is_ternary_op(name) and result = 3
or or
is_quad_op(name) and result = 4 is_quad_op(name) and result = 4
) )
} }
predicate incorrect_special_method_defn( predicate incorrect_special_method_defn(
PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner
) { ) {
exists(int required | required = argument_count(func, name, owner) | exists(int required | required = argument_count(func, name, owner) |
/* actual_non_default <= actual */ /* actual_non_default <= actual */
if required > func.maxParameters() if required > func.maxParameters()
then message = "Too few parameters" and show_counts = true then message = "Too few parameters" and show_counts = true
else else
if required < func.minParameters() if required < func.minParameters()
then message = "Too many parameters" and show_counts = true then message = "Too many parameters" and show_counts = true
else else
if func.minParameters() < required and not func.getScope().hasVarArg() if func.minParameters() < required and not func.getScope().hasVarArg()
then then
message = (required - func.minParameters()) + " default values(s) will never be used" and message = (required - func.minParameters()) + " default values(s) will never be used" and
show_counts = false show_counts = false
else none() else none()
) )
} }
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) { predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__pow__") = func and owner.declaredAttribute("__pow__") = func and
( (
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
or or
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
or or
func.minParameters() < 2 and func.minParameters() < 2 and
message = (2 - func.minParameters()) + " default value(s) will never be used" and message = (2 - func.minParameters()) + " default value(s) will never be used" and
show_counts = false show_counts = false
or or
func.minParameters() = 3 and func.minParameters() = 3 and
message = "Third parameter to __pow__ should have a default value" and message = "Third parameter to __pow__ should have a default value" and
show_counts = false show_counts = false
) )
} }
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) { predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__get__") = func and owner.declaredAttribute("__get__") = func and
( (
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
or or
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
or or
func.minParameters() < 2 and func.minParameters() < 2 and
not func.getScope().hasVarArg() and not func.getScope().hasVarArg() and
message = (2 - func.minParameters()) + " default value(s) will never be used" and message = (2 - func.minParameters()) + " default value(s) will never be used" and
show_counts = false show_counts = false
) )
} }
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) { string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
exists(int i | i = argument_count(f, name, owner) | result = i.toString()) exists(int i | i = argument_count(f, name, owner) | result = i.toString())
or or
owner.declaredAttribute(name) = f and owner.declaredAttribute(name) = f and
(name = "__get__" or name = "__pow__") and (name = "__get__" or name = "__pow__") and
result = "2 or 3" result = "2 or 3"
} }
string has_parameters(PythonFunctionValue f) { string has_parameters(PythonFunctionValue f) {
exists(int i | i = f.minParameters() | exists(int i | i = f.minParameters() |
i = 0 and result = "no parameters" i = 0 and result = "no parameters"
or or
i = 1 and result = "1 parameter" i = 1 and result = "1 parameter"
or or
i > 1 and result = i.toString() + " parameters" i > 1 and result = i.toString() + " parameters"
) )
} }
from from
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name, PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
ClassValue owner ClassValue owner
where where
( (
incorrect_special_method_defn(f, message, show_counts, name, owner) incorrect_special_method_defn(f, message, show_counts, name, owner)
or or
incorrect_pow(f, message, show_counts, owner) and name = "__pow__" incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
or or
incorrect_get(f, message, show_counts, owner) and name = "__get__" incorrect_get(f, message, show_counts, owner) and name = "__get__"
) and ) and
( (
show_counts = false and sizes = "" show_counts = false and sizes = ""
or or
show_counts = true and show_counts = true and
sizes = sizes =
", which has " + has_parameters(f) + ", but should have " + ", which has " + has_parameters(f) + ", but should have " +
should_have_parameters(f, name, owner) should_have_parameters(f, name, owner)
) )
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName() select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()

View File

@@ -13,26 +13,26 @@ import python
import Testing.Mox import Testing.Mox
predicate is_used(Call c) { predicate is_used(Call c) {
exists(Expr outer | outer != c and outer.containsInScope(c) | exists(Expr outer | outer != c and outer.containsInScope(c) |
outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript
) )
or or
exists(Stmt s | exists(Stmt s |
c = s.getASubExpression() and c = s.getASubExpression() and
not s instanceof ExprStmt and not s instanceof ExprStmt and
/* Ignore if a single return, as def f(): return g() is quite common. Covers implicit return in a lambda. */ /* Ignore if a single return, as def f(): return g() is quite common. Covers implicit return in a lambda. */
not (s instanceof Return and strictcount(Return r | r.getScope() = s.getScope()) = 1) not (s instanceof Return and strictcount(Return r | r.getScope() = s.getScope()) = 1)
) )
} }
from Call c, FunctionValue func from Call c, FunctionValue func
where where
/* Call result is used, but callee is a procedure */ /* Call result is used, but callee is a procedure */
is_used(c) and is_used(c) and
c.getFunc().pointsTo(func) and c.getFunc().pointsTo(func) and
func.getScope().isProcedure() and func.getScope().isProcedure() and
/* All callees are procedures */ /* All callees are procedures */
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
/* Mox return objects have an `AndReturn` method */ /* Mox return objects have an `AndReturn` method */
not useOfMoxInModule(c.getEnclosingModule()) not useOfMoxInModule(c.getEnclosingModule())
select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName() select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName()

View File

@@ -3,84 +3,84 @@ import python
predicate is_import_time(Stmt s) { not s.getScope+() instanceof Function } predicate is_import_time(Stmt s) { not s.getScope+() instanceof Function }
ModuleValue module_imported_by(ModuleValue m) { ModuleValue module_imported_by(ModuleValue m) {
exists(Stmt imp | exists(Stmt imp |
result = stmt_imports(imp) and result = stmt_imports(imp) and
imp.getEnclosingModule() = m.getScope() and imp.getEnclosingModule() = m.getScope() and
// Import must reach exit to be part of a cycle // Import must reach exit to be part of a cycle
imp.getAnEntryNode().getBasicBlock().reachesExit() imp.getAnEntryNode().getBasicBlock().reachesExit()
) )
} }
/** Is there a circular import of 'm1' beginning with 'm2'? */ /** Is there a circular import of 'm1' beginning with 'm2'? */
predicate circular_import(ModuleValue m1, ModuleValue m2) { predicate circular_import(ModuleValue m1, ModuleValue m2) {
m1 != m2 and m1 != m2 and
m2 = module_imported_by(m1) and m2 = module_imported_by(m1) and
m1 = module_imported_by+(m2) m1 = module_imported_by+(m2)
} }
ModuleValue stmt_imports(ImportingStmt s) { ModuleValue stmt_imports(ImportingStmt s) {
exists(string name | result.importedAs(name) and not name = "__main__" | exists(string name | result.importedAs(name) and not name = "__main__" |
name = s.getAnImportedModuleName() and name = s.getAnImportedModuleName() and
s.getASubExpression().pointsTo(result) and s.getASubExpression().pointsTo(result) and
not result.isPackage() not result.isPackage()
) )
} }
predicate import_time_imported_module(ModuleValue m1, ModuleValue m2, Stmt imp) { predicate import_time_imported_module(ModuleValue m1, ModuleValue m2, Stmt imp) {
imp.getEnclosingModule() = m1.getScope() and imp.getEnclosingModule() = m1.getScope() and
is_import_time(imp) and is_import_time(imp) and
m2 = stmt_imports(imp) m2 = stmt_imports(imp)
} }
/** Is there a cyclic import of 'm1' beginning with an import 'm2' at 'imp' where all the imports are top-level? */ /** Is there a cyclic import of 'm1' beginning with an import 'm2' at 'imp' where all the imports are top-level? */
predicate import_time_circular_import(ModuleValue m1, ModuleValue m2, Stmt imp) { predicate import_time_circular_import(ModuleValue m1, ModuleValue m2, Stmt imp) {
m1 != m2 and m1 != m2 and
import_time_imported_module(m1, m2, imp) and import_time_imported_module(m1, m2, imp) and
import_time_transitive_import(m2, _, m1) import_time_transitive_import(m2, _, m1)
} }
predicate import_time_transitive_import(ModuleValue base, Stmt imp, ModuleValue last) { predicate import_time_transitive_import(ModuleValue base, Stmt imp, ModuleValue last) {
last != base and last != base and
( (
import_time_imported_module(base, last, imp) import_time_imported_module(base, last, imp)
or or
exists(ModuleValue mid | exists(ModuleValue mid |
import_time_transitive_import(base, imp, mid) and import_time_transitive_import(base, imp, mid) and
import_time_imported_module(mid, last, _) import_time_imported_module(mid, last, _)
) )
) and ) and
// Import must reach exit to be part of a cycle // Import must reach exit to be part of a cycle
imp.getAnEntryNode().getBasicBlock().reachesExit() imp.getAnEntryNode().getBasicBlock().reachesExit()
} }
/** /**
* Returns import-time usages of module 'm' in module 'enclosing' * Returns import-time usages of module 'm' in module 'enclosing'
*/ */
predicate import_time_module_use(ModuleValue m, ModuleValue enclosing, Expr use, string attr) { predicate import_time_module_use(ModuleValue m, ModuleValue enclosing, Expr use, string attr) {
exists(Expr mod | exists(Expr mod |
use.getEnclosingModule() = enclosing.getScope() and use.getEnclosingModule() = enclosing.getScope() and
not use.getScope+() instanceof Function and not use.getScope+() instanceof Function and
mod.pointsTo(m) and mod.pointsTo(m) and
not is_annotation_with_from_future_import_annotations(use) not is_annotation_with_from_future_import_annotations(use)
| |
// either 'M.foo' // either 'M.foo'
use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr
or or
// or 'from M import foo' // or 'from M import foo'
use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr
) )
} }
/** /**
* Holds if `use` appears inside an annotation. * Holds if `use` appears inside an annotation.
*/ */
predicate is_used_in_annotation(Expr use) { predicate is_used_in_annotation(Expr use) {
exists(FunctionExpr f | exists(FunctionExpr f |
f.getReturns().getASubExpression*() = use or f.getReturns().getASubExpression*() = use or
f.getArgs().getAnAnnotation().getASubExpression*() = use f.getArgs().getAnAnnotation().getASubExpression*() = use
) )
or or
exists(AnnAssign a | a.getAnnotation().getASubExpression*() = use) exists(AnnAssign a | a.getAnnotation().getASubExpression*() = use)
} }
/** /**
@@ -89,10 +89,10 @@ predicate is_used_in_annotation(Expr use) {
* See https://www.python.org/dev/peps/pep-0563/ * See https://www.python.org/dev/peps/pep-0563/
*/ */
predicate is_annotation_with_from_future_import_annotations(Expr use) { predicate is_annotation_with_from_future_import_annotations(Expr use) {
exists(ImportMember i | i.getScope() = use.getEnclosingModule() | exists(ImportMember i | i.getScope() = use.getEnclosingModule() |
i.getModule().pointsTo().getName() = "__future__" and i.getName() = "annotations" i.getModule().pointsTo().getName() = "__future__" and i.getName() = "annotations"
) and ) and
is_used_in_annotation(use) is_used_in_annotation(use)
} }
/** /**
@@ -101,18 +101,18 @@ predicate is_annotation_with_from_future_import_annotations(Expr use) {
* occur after the import 'other' in 'first'. * occur after the import 'other' in 'first'.
*/ */
predicate failing_import_due_to_cycle( predicate failing_import_due_to_cycle(
ModuleValue first, ModuleValue other, Stmt imp, ControlFlowNode defn, Expr use, string attr ModuleValue first, ModuleValue other, Stmt imp, ControlFlowNode defn, Expr use, string attr
) { ) {
import_time_imported_module(other, first, _) and import_time_imported_module(other, first, _) and
import_time_transitive_import(first, imp, other) and import_time_transitive_import(first, imp, other) and
import_time_module_use(first, other, use, attr) and import_time_module_use(first, other, use, attr) and
exists(ImportTimeScope n, SsaVariable v | exists(ImportTimeScope n, SsaVariable v |
defn = v.getDefinition() and defn = v.getDefinition() and
n = first.getScope() and n = first.getScope() and
v.getVariable().getScope() = n and v.getVariable().getScope() = n and
v.getId() = attr v.getId() = attr
| |
not defn.strictlyDominates(imp.getAnEntryNode()) not defn.strictlyDominates(imp.getAnEntryNode())
) and ) and
not exists(If i | i.isNameEqMain() and i.contains(use)) not exists(If i | i.isNameEqMain() and i.contains(use))
} }

View File

@@ -16,11 +16,11 @@ import Cyclic
from ModuleValue m1, ModuleValue m2, Stmt imp from ModuleValue m1, ModuleValue m2, Stmt imp
where where
imp.getEnclosingModule() = m1.getScope() and imp.getEnclosingModule() = m1.getScope() and
stmt_imports(imp) = m2 and stmt_imports(imp) = m2 and
circular_import(m1, m2) and circular_import(m1, m2) and
m1 != m2 and m1 != m2 and
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport // this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
not exists(If i | i.isNameEqMain() and i.contains(imp)) not exists(If i | i.isNameEqMain() and i.contains(imp))
select imp, "Import of module $@ begins an import cycle.", m2, m2.getName() select imp, "Import of module $@ begins an import cycle.", m2, m2.getName()

View File

@@ -17,69 +17,69 @@ import python
* and module `instead` should be used instead (or `instead = "no replacement"`) * and module `instead` should be used instead (or `instead = "no replacement"`)
*/ */
predicate deprecated_module(string name, string instead, int major, int minor) { predicate deprecated_module(string name, string instead, int major, int minor) {
name = "posixfile" and instead = "fcntl" and major = 1 and minor = 5 name = "posixfile" and instead = "fcntl" and major = 1 and minor = 5
or or
name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5 name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5
or or
name = "rgbimgmodule" and instead = "no replacement" and major = 2 and minor = 5 name = "rgbimgmodule" and instead = "no replacement" and major = 2 and minor = 5
or or
name = "pre" and instead = "re" and major = 1 and minor = 5 name = "pre" and instead = "re" and major = 1 and minor = 5
or or
name = "whrandom" and instead = "random" and major = 2 and minor = 1 name = "whrandom" and instead = "random" and major = 2 and minor = 1
or or
name = "rfc822" and instead = "email" and major = 2 and minor = 3 name = "rfc822" and instead = "email" and major = 2 and minor = 3
or or
name = "mimetools" and instead = "email" and major = 2 and minor = 3 name = "mimetools" and instead = "email" and major = 2 and minor = 3
or or
name = "MimeWriter" and instead = "email" and major = 2 and minor = 3 name = "MimeWriter" and instead = "email" and major = 2 and minor = 3
or or
name = "mimify" and instead = "email" and major = 2 and minor = 3 name = "mimify" and instead = "email" and major = 2 and minor = 3
or or
name = "rotor" and instead = "no replacement" and major = 2 and minor = 4 name = "rotor" and instead = "no replacement" and major = 2 and minor = 4
or or
name = "statcache" and instead = "no replacement" and major = 2 and minor = 2 name = "statcache" and instead = "no replacement" and major = 2 and minor = 2
or or
name = "mpz" and instead = "a third party" and major = 2 and minor = 2 name = "mpz" and instead = "a third party" and major = 2 and minor = 2
or or
name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3 name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3
or or
name = "multifile" and instead = "email" and major = 2 and minor = 5 name = "multifile" and instead = "email" and major = 2 and minor = 5
or or
name = "sets" and instead = "builtins" and major = 2 and minor = 6 name = "sets" and instead = "builtins" and major = 2 and minor = 6
or or
name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3 name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3
or or
name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4 name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4
or or
name = "macfs" and instead = "no replacement" and major = 2 and minor = 3 name = "macfs" and instead = "no replacement" and major = 2 and minor = 3
or or
name = "md5" and instead = "hashlib" and major = 2 and minor = 5 name = "md5" and instead = "hashlib" and major = 2 and minor = 5
or or
name = "sha" and instead = "hashlib" and major = 2 and minor = 5 name = "sha" and instead = "hashlib" and major = 2 and minor = 5
} }
string deprecation_message(string mod) { string deprecation_message(string mod) {
exists(int major, int minor | deprecated_module(mod, _, major, minor) | exists(int major, int minor | deprecated_module(mod, _, major, minor) |
result = result =
"The " + mod + " module was deprecated in version " + major.toString() + "." + "The " + mod + " module was deprecated in version " + major.toString() + "." +
minor.toString() + "." minor.toString() + "."
) )
} }
string replacement_message(string mod) { string replacement_message(string mod) {
exists(string instead | deprecated_module(mod, instead, _, _) | exists(string instead | deprecated_module(mod, instead, _, _) |
result = " Use " + instead + " module instead." and not instead = "no replacement" result = " Use " + instead + " module instead." and not instead = "no replacement"
or or
result = "" and instead = "no replacement" result = "" and instead = "no replacement"
) )
} }
from ImportExpr imp, string name, string instead from ImportExpr imp, string name, string instead
where where
name = imp.getName() and name = imp.getName() and
deprecated_module(name, instead, _, _) and deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler() | not exists(Try try, ExceptStmt except | except = try.getAHandler() |
except.getType().pointsTo(ClassValue::importError()) and except.getType().pointsTo(ClassValue::importError()) and
except.containsInScope(imp) except.containsInScope(imp)
) )
select imp, deprecation_message(name) + replacement_message(name) select imp, deprecation_message(name) + replacement_message(name)

View File

@@ -16,19 +16,19 @@ import semmle.python.filters.Tests
from ImportMember im, ModuleValue m, AttrNode store_attr, string name from ImportMember im, ModuleValue m, AttrNode store_attr, string name
where where
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and
im.getName() = name and im.getName() = name and
/* Modification must be in a function, so it can occur during lifetime of the import value */ /* Modification must be in a function, so it can occur during lifetime of the import value */
store_attr.getScope() instanceof Function and store_attr.getScope() instanceof Function and
/* variable resulting from import must have a long lifetime */ /* variable resulting from import must have a long lifetime */
not im.getScope() instanceof Function and not im.getScope() instanceof Function and
store_attr.isStore() and store_attr.isStore() and
store_attr.getObject(name).pointsTo(m) and store_attr.getObject(name).pointsTo(m) and
/* Import not in same module as modification. */ /* Import not in same module as modification. */
not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and
/* Modification is not in a test */ /* Modification is not in a test */
not store_attr.getScope().getScope*() instanceof TestScope not store_attr.getScope().getScope*() instanceof TestScope
select im, select im,
"Importing the value of '" + name + "Importing the value of '" + name +
"' from $@ means that any change made to $@ will be not be observed locally.", m, "' from $@ means that any change made to $@ will be not be observed locally.", m,
"module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName()

View File

@@ -13,11 +13,11 @@
import python import python
predicate shadowsImport(Variable l) { predicate shadowsImport(Variable l) {
exists(Import i, Name shadow | exists(Import i, Name shadow |
shadow = i.getAName().getAsname() and shadow = i.getAName().getAsname() and
shadow.getId() = l.getId() and shadow.getId() = l.getId() and
i.getScope() = l.getScope().getScope*() i.getScope() = l.getScope().getScope*()
) )
} }
from Variable l, Name defn from Variable l, Name defn

Some files were not shown because too many files have changed in this diff Show More