mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
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:
@@ -11,6 +11,6 @@ import python
|
||||
|
||||
from ExceptStmt ex, ClassValue cls
|
||||
where
|
||||
cls.getName() = "MyExceptionClass" and
|
||||
ex.getType().pointsTo(cls)
|
||||
cls.getName() = "MyExceptionClass" and
|
||||
ex.getType().pointsTo(cls)
|
||||
select ex
|
||||
|
||||
@@ -12,7 +12,7 @@ import python
|
||||
|
||||
from IfExp e, ClassObject cls1, ClassObject cls2
|
||||
where
|
||||
e.getBody().refersTo(_, cls1, _) and
|
||||
e.getOrelse().refersTo(_, cls2, _) and
|
||||
cls1 != cls2
|
||||
e.getBody().refersTo(_, cls1, _) and
|
||||
e.getOrelse().refersTo(_, cls2, _) and
|
||||
cls1 != cls2
|
||||
select e
|
||||
|
||||
@@ -14,8 +14,8 @@ import python
|
||||
|
||||
from If i
|
||||
where
|
||||
not exists(Stmt s |
|
||||
i.getStmt(_) = s and
|
||||
not s instanceof Pass
|
||||
)
|
||||
not exists(Stmt s |
|
||||
i.getStmt(_) = s and
|
||||
not s instanceof Pass
|
||||
)
|
||||
select i
|
||||
|
||||
@@ -14,6 +14,6 @@ import python
|
||||
|
||||
from ClassObject sub, ClassObject base
|
||||
where
|
||||
base.getName() = "MyClass" and
|
||||
sub.getABaseType() = base
|
||||
base.getName() = "MyClass" and
|
||||
sub.getABaseType() = base
|
||||
select sub
|
||||
|
||||
@@ -10,6 +10,6 @@ import python
|
||||
|
||||
from AstNode call, PythonFunctionValue method
|
||||
where
|
||||
method.getQualifiedName() = "MyClass.methodName" and
|
||||
method.getACall().getNode() = call
|
||||
method.getQualifiedName() = "MyClass.methodName" and
|
||||
method.getACall().getNode() = call
|
||||
select call
|
||||
|
||||
@@ -11,6 +11,6 @@ import python
|
||||
|
||||
from Call new, ClassValue cls
|
||||
where
|
||||
cls.getName() = "MyClass" and
|
||||
new.getFunc().pointsTo(cls)
|
||||
cls.getName() = "MyClass" and
|
||||
new.getFunc().pointsTo(cls)
|
||||
select new
|
||||
|
||||
@@ -10,6 +10,6 @@ import python
|
||||
|
||||
from FunctionObject override, FunctionObject base
|
||||
where
|
||||
base.getQualifiedName() = "MyClass.methodName" and
|
||||
override.overrides(base)
|
||||
base.getQualifiedName() = "MyClass.methodName" and
|
||||
override.overrides(base)
|
||||
select override
|
||||
|
||||
@@ -9,9 +9,9 @@ import python
|
||||
|
||||
from AstNode print
|
||||
where
|
||||
/* Python 2 without `from __future__ import print_function` */
|
||||
print instanceof Print
|
||||
or
|
||||
/* Python 3 or with `from __future__ import print_function` */
|
||||
print.(Call).getFunc().pointsTo(Value::named("print"))
|
||||
/* Python 2 without `from __future__ import print_function` */
|
||||
print instanceof Print
|
||||
or
|
||||
/* Python 3 or with `from __future__ import print_function` */
|
||||
print.(Call).getFunc().pointsTo(Value::named("print"))
|
||||
select print
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
import python
|
||||
|
||||
predicate is_private(Attribute a) {
|
||||
a.getName().matches("\\_%") and
|
||||
not a.getName().matches("\\_\\_%\\_\\_")
|
||||
a.getName().matches("\\_%") and
|
||||
not a.getName().matches("\\_\\_%\\_\\_")
|
||||
}
|
||||
|
||||
from Attribute access
|
||||
where
|
||||
is_private(access) and
|
||||
not access.getObject().(Name).getId() = "self"
|
||||
is_private(access) and
|
||||
not access.getObject().(Name).getId() = "self"
|
||||
select access
|
||||
|
||||
@@ -11,6 +11,6 @@ import python
|
||||
|
||||
from Raise raise, ClassValue ex
|
||||
where
|
||||
ex.getName() = "AnException" and
|
||||
raise.getException().pointsTo(ex.getASuperType())
|
||||
ex.getName() = "AnException" and
|
||||
raise.getException().pointsTo(ex.getASuperType())
|
||||
select raise, "Don't raise instances of 'AnException'"
|
||||
|
||||
@@ -13,6 +13,6 @@ import python
|
||||
|
||||
from SubscriptNode store
|
||||
where
|
||||
store.isStore() and
|
||||
store.getIndex().pointsTo(Value::named("None"))
|
||||
store.isStore() and
|
||||
store.getIndex().pointsTo(Value::named("None"))
|
||||
select store
|
||||
|
||||
@@ -11,6 +11,6 @@ import python
|
||||
|
||||
from Try t
|
||||
where
|
||||
exists(t.getFinalbody()) and
|
||||
not exists(t.getAHandler())
|
||||
exists(t.getFinalbody()) and
|
||||
not exists(t.getAHandler())
|
||||
select t
|
||||
|
||||
@@ -3,140 +3,140 @@ private import semmle.python.pointsto.PointsTo
|
||||
|
||||
/** Helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */
|
||||
class CheckClass extends ClassObject {
|
||||
private predicate ofInterest() {
|
||||
not this.unknowableAttributes() and
|
||||
not this.getPyClass().isProbableMixin() and
|
||||
this.getPyClass().isPublic() and
|
||||
not this.getPyClass().getScope() instanceof Function and
|
||||
not this.probablyAbstract() and
|
||||
not this.declaresAttribute("__new__") and
|
||||
not this.selfDictAssigns() and
|
||||
not this.lookupAttribute("__getattribute__") != object_getattribute() and
|
||||
not this.hasAttribute("__getattr__") and
|
||||
not this.selfSetattr() and
|
||||
/* If class overrides object.__init__, but we can't resolve it to a Python function then give up */
|
||||
forall(ClassObject sup |
|
||||
sup = this.getAnImproperSuperType() and
|
||||
sup.declaresAttribute("__init__") and
|
||||
not sup = theObjectType()
|
||||
|
|
||||
sup.declaredAttribute("__init__") instanceof PyFunctionObject
|
||||
)
|
||||
}
|
||||
private predicate ofInterest() {
|
||||
not this.unknowableAttributes() and
|
||||
not this.getPyClass().isProbableMixin() and
|
||||
this.getPyClass().isPublic() and
|
||||
not this.getPyClass().getScope() instanceof Function and
|
||||
not this.probablyAbstract() and
|
||||
not this.declaresAttribute("__new__") and
|
||||
not this.selfDictAssigns() and
|
||||
not this.lookupAttribute("__getattribute__") != object_getattribute() and
|
||||
not this.hasAttribute("__getattr__") and
|
||||
not this.selfSetattr() and
|
||||
/* If class overrides object.__init__, but we can't resolve it to a Python function then give up */
|
||||
forall(ClassObject sup |
|
||||
sup = this.getAnImproperSuperType() and
|
||||
sup.declaresAttribute("__init__") and
|
||||
not sup = theObjectType()
|
||||
|
|
||||
sup.declaredAttribute("__init__") instanceof PyFunctionObject
|
||||
)
|
||||
}
|
||||
|
||||
predicate alwaysDefines(string name) {
|
||||
auto_name(name) or
|
||||
this.hasAttribute(name) or
|
||||
this.getAnImproperSuperType().assignedInInit(name) or
|
||||
this.getMetaClass().assignedInInit(name)
|
||||
}
|
||||
predicate alwaysDefines(string name) {
|
||||
auto_name(name) or
|
||||
this.hasAttribute(name) or
|
||||
this.getAnImproperSuperType().assignedInInit(name) or
|
||||
this.getMetaClass().assignedInInit(name)
|
||||
}
|
||||
|
||||
predicate sometimesDefines(string name) {
|
||||
this.alwaysDefines(name)
|
||||
predicate sometimesDefines(string 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
|
||||
exists(SelfAttributeStore sa |
|
||||
sa.getScope().getScope+() = this.getAnImproperSuperType().getPyClass()
|
||||
|
|
||||
name = sa.getName()
|
||||
/* 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()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate selfDictAssigns() {
|
||||
exists(Assign a, SelfAttributeRead self_dict, Subscript sub |
|
||||
self_dict.getName() = "__dict__" and
|
||||
(
|
||||
self_dict = sub.getObject()
|
||||
or
|
||||
/* 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 monkeyPatched(string name) {
|
||||
exists(Attribute a |
|
||||
a.getCtx() instanceof Store and
|
||||
PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and
|
||||
a.getName() = name
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate monkeyPatched(string name) {
|
||||
exists(Attribute a |
|
||||
a.getCtx() instanceof Store and
|
||||
PointsTo::points_to(a.getObject().getAFlowNode(), _, this, _, _) and
|
||||
a.getName() = name
|
||||
)
|
||||
}
|
||||
private predicate selfSetattr() {
|
||||
exists(Call c, Name setattr, Name self, Function method |
|
||||
(
|
||||
method.getScope() = this.getPyClass() or
|
||||
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"
|
||||
)
|
||||
}
|
||||
|
||||
private predicate selfSetattr() {
|
||||
exists(Call c, Name setattr, Name self, Function method |
|
||||
(
|
||||
method.getScope() = this.getPyClass() or
|
||||
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) {
|
||||
exists(string name | name = a.getName() |
|
||||
interestingContext(a, name) and
|
||||
not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name)
|
||||
)
|
||||
}
|
||||
|
||||
predicate interestingUndefined(SelfAttributeRead a) {
|
||||
exists(string name | name = a.getName() |
|
||||
interestingContext(a, name) and
|
||||
not this.definedInBlock(a.getAFlowNode().getBasicBlock(), name)
|
||||
)
|
||||
}
|
||||
private predicate interestingContext(SelfAttributeRead a, string name) {
|
||||
name = a.getName() and
|
||||
this.ofInterest() and
|
||||
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) {
|
||||
name = a.getName() and
|
||||
this.ofInterest() and
|
||||
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 probablyAbstract() {
|
||||
this.getName().matches("Abstract%")
|
||||
or
|
||||
this.isAbstract()
|
||||
}
|
||||
|
||||
private predicate probablyAbstract() {
|
||||
this.getName().matches("Abstract%")
|
||||
or
|
||||
this.isAbstract()
|
||||
}
|
||||
pragma[nomagic]
|
||||
private predicate definitionInBlock(BasicBlock b, string name) {
|
||||
exists(SelfAttributeStore sa |
|
||||
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]
|
||||
private predicate definitionInBlock(BasicBlock b, string name) {
|
||||
exists(SelfAttributeStore sa |
|
||||
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]
|
||||
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)
|
||||
}
|
||||
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() {
|
||||
result.asBuiltin() = theObjectType().asBuiltin().getMember("__getattribute__")
|
||||
result.asBuiltin() = theObjectType().asBuiltin().getMember("__getattribute__")
|
||||
}
|
||||
|
||||
private predicate auto_name(string name) { name = "__class__" or name = "__dict__" }
|
||||
|
||||
@@ -14,48 +14,48 @@
|
||||
import python
|
||||
|
||||
predicate does_nothing(PyFunctionObject f) {
|
||||
not exists(Stmt s | s.getScope() = f.getFunction() |
|
||||
not s instanceof Pass and not s.(ExprStmt).getValue() = f.getFunction().getDocString()
|
||||
)
|
||||
not exists(Stmt s | s.getScope() = f.getFunction() |
|
||||
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 */
|
||||
predicate calls_super(FunctionObject f) {
|
||||
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
|
||||
meth.getScope() = f.getFunction() and
|
||||
meth.getFunc() = attr and
|
||||
attr.getObject() = sup and
|
||||
attr.getName() = f.getName() and
|
||||
sup.getFunc() = v.getAnAccess() and
|
||||
v.getId() = "super"
|
||||
)
|
||||
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
|
||||
meth.getScope() = f.getFunction() and
|
||||
meth.getFunc() = attr and
|
||||
attr.getObject() = sup and
|
||||
attr.getName() = f.getName() and
|
||||
sup.getFunc() = v.getAnAccess() and
|
||||
v.getId() = "super"
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the given name is allowed for some reason */
|
||||
predicate allowed(string name) {
|
||||
/*
|
||||
* The standard library specifically recommends this :(
|
||||
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins
|
||||
*/
|
||||
/*
|
||||
* The standard library specifically recommends this :(
|
||||
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins
|
||||
*/
|
||||
|
||||
name = "process_request"
|
||||
name = "process_request"
|
||||
}
|
||||
|
||||
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
|
||||
c.getBaseType(i1) = b1 and
|
||||
c.getBaseType(i2) = b2 and
|
||||
i1 < i2 and
|
||||
o1 != o2 and
|
||||
o1 = b1.lookupAttribute(name) and
|
||||
o2 = b2.lookupAttribute(name) and
|
||||
not name.matches("\\_\\_%\\_\\_") and
|
||||
not calls_super(o1) and
|
||||
not does_nothing(o2) and
|
||||
not allowed(name) and
|
||||
not o1.overrides(o2) and
|
||||
not o2.overrides(o1) and
|
||||
not c.declaresAttribute(name)
|
||||
c.getBaseType(i1) = b1 and
|
||||
c.getBaseType(i2) = b2 and
|
||||
i1 < i2 and
|
||||
o1 != o2 and
|
||||
o1 = b1.lookupAttribute(name) and
|
||||
o2 = b2.lookupAttribute(name) and
|
||||
not name.matches("\\_\\_%\\_\\_") and
|
||||
not calls_super(o1) and
|
||||
not does_nothing(o2) and
|
||||
not allowed(name) and
|
||||
not o1.overrides(o2) and
|
||||
not o2.overrides(o1) and
|
||||
not c.declaresAttribute(name)
|
||||
select c, "Base classes have conflicting values for attribute '" + name + "': $@ and $@.", o1,
|
||||
o1.toString(), o2, o2.toString()
|
||||
o1.toString(), o2, o2.toString()
|
||||
|
||||
@@ -15,21 +15,21 @@ import semmle.python.SelfAttribute
|
||||
import Equality
|
||||
|
||||
predicate class_stores_to_attribute(ClassValue cls, SelfAttributeStore store, string name) {
|
||||
exists(FunctionValue f |
|
||||
f = cls.declaredAttribute(_) and store.getScope() = f.getScope() and store.getName() = name
|
||||
) and
|
||||
/* Exclude classes used as metaclasses */
|
||||
not cls.getASuperType() = ClassValue::type()
|
||||
exists(FunctionValue f |
|
||||
f = cls.declaredAttribute(_) and store.getScope() = f.getScope() and store.getName() = name
|
||||
) and
|
||||
/* Exclude classes used as metaclasses */
|
||||
not cls.getASuperType() = ClassValue::type()
|
||||
}
|
||||
|
||||
predicate should_override_eq(ClassValue cls, Value base_eq) {
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
not exists(GenericEqMethod 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 = ClassValue::object().declaredAttribute("__eq__")
|
||||
)
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
not exists(GenericEqMethod 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 = 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.
|
||||
*/
|
||||
predicate superclassEqExpectsAttribute(ClassValue cls, FunctionValue base_eq, string attrname) {
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
exists(SelfAttributeRead store | store.getName() = attrname |
|
||||
store.getScope() = base_eq.getScope()
|
||||
)
|
||||
not cls.declaresAttribute("__eq__") and
|
||||
exists(ClassValue sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
|
||||
exists(SelfAttributeRead store | store.getName() = attrname |
|
||||
store.getScope() = base_eq.getScope()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from ClassValue cls, SelfAttributeStore store, Value base_eq
|
||||
where
|
||||
class_stores_to_attribute(cls, store, _) and
|
||||
should_override_eq(cls, base_eq) and
|
||||
/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */
|
||||
not cls.getASuperType().getName() = "TestCase" and
|
||||
not superclassEqExpectsAttribute(cls, base_eq, store.getName())
|
||||
class_stores_to_attribute(cls, store, _) and
|
||||
should_override_eq(cls, base_eq) and
|
||||
/* Don't report overridden unittest.TestCase. -- TestCase overrides __eq__, but subclasses do not really need to. */
|
||||
not cls.getASuperType().getName() = "TestCase" and
|
||||
not superclassEqExpectsAttribute(cls, base_eq, store.getName())
|
||||
select cls,
|
||||
"The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq,
|
||||
"'__eq__'", store, store.getName()
|
||||
"The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq,
|
||||
"'__eq__'", store, store.getName()
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import python
|
||||
|
||||
private Attribute dictAccess(LocalVariable var) {
|
||||
result.getName() = "__dict__" and
|
||||
result.getObject() = var.getAnAccess()
|
||||
result.getName() = "__dict__" and
|
||||
result.getObject() = var.getAnAccess()
|
||||
}
|
||||
|
||||
private Call getattr(LocalVariable obj, LocalVariable attr) {
|
||||
result.getFunc().(Name).getId() = "getattr" and
|
||||
result.getArg(0) = obj.getAnAccess() and
|
||||
result.getArg(1) = attr.getAnAccess()
|
||||
result.getFunc().(Name).getId() = "getattr" and
|
||||
result.getArg(0) = obj.getAnAccess() and
|
||||
result.getArg(1) = attr.getAnAccess()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -16,59 +16,59 @@ private Call getattr(LocalVariable obj, LocalVariable attr) {
|
||||
* or compares attributes using `getattr`.
|
||||
*/
|
||||
class GenericEqMethod extends Function {
|
||||
GenericEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and
|
||||
self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq |
|
||||
eq.getOp(0) instanceof Eq or
|
||||
eq.getOp(0) instanceof NotEq
|
||||
|
|
||||
// `self.__dict__ == other.__dict__`
|
||||
eq.getAChildNode() = dictAccess(self) and
|
||||
eq.getAChildNode() = dictAccess(other)
|
||||
or
|
||||
// `getattr(self, var) == getattr(other, var)`
|
||||
exists(Variable var |
|
||||
eq.getAChildNode() = getattr(self, var) and
|
||||
eq.getAChildNode() = getattr(other, var)
|
||||
)
|
||||
)
|
||||
GenericEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and
|
||||
self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq |
|
||||
eq.getOp(0) instanceof Eq or
|
||||
eq.getOp(0) instanceof NotEq
|
||||
|
|
||||
// `self.__dict__ == other.__dict__`
|
||||
eq.getAChildNode() = dictAccess(self) and
|
||||
eq.getAChildNode() = dictAccess(other)
|
||||
or
|
||||
// `getattr(self, var) == getattr(other, var)`
|
||||
exists(Variable var |
|
||||
eq.getAChildNode() = getattr(self, var) and
|
||||
eq.getAChildNode() = getattr(other, var)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An `__eq__` method that just does `self is other` */
|
||||
class IdentityEqMethod extends Function {
|
||||
IdentityEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and
|
||||
self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq | eq.getOp(0) instanceof Is |
|
||||
eq.getAChildNode() = self.getAnAccess() and
|
||||
eq.getAChildNode() = other.getAnAccess()
|
||||
)
|
||||
)
|
||||
}
|
||||
IdentityEqMethod() {
|
||||
this.getName() = "__eq__" and
|
||||
exists(LocalVariable self, LocalVariable other |
|
||||
self.getAnAccess() = this.getArg(0) and
|
||||
self.getId() = "self" and
|
||||
other.getAnAccess() = this.getArg(1) and
|
||||
exists(Compare eq | eq.getOp(0) instanceof Is |
|
||||
eq.getAChildNode() = self.getAnAccess() and
|
||||
eq.getAChildNode() = other.getAnAccess()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An (in)equality method that delegates to its complement */
|
||||
class DelegatingEqualityMethod extends Function {
|
||||
DelegatingEqualityMethod() {
|
||||
exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 |
|
||||
ret.getScope() = this and
|
||||
ret.getValue() = not_ and
|
||||
not_.getOp() instanceof Not and
|
||||
not_.getOperand() = comp and
|
||||
comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess())
|
||||
|
|
||||
this.getName() = "__eq__" and op instanceof NotEq
|
||||
or
|
||||
this.getName() = "__ne__" and op instanceof Eq
|
||||
)
|
||||
}
|
||||
DelegatingEqualityMethod() {
|
||||
exists(Return ret, UnaryExpr not_, Compare comp, Cmpop op, Parameter p0, Parameter p1 |
|
||||
ret.getScope() = this and
|
||||
ret.getValue() = not_ and
|
||||
not_.getOp() instanceof Not and
|
||||
not_.getOperand() = comp and
|
||||
comp.compares(p0.getVariable().getAnAccess(), op, p1.getVariable().getAnAccess())
|
||||
|
|
||||
this.getName() = "__eq__" and op instanceof NotEq
|
||||
or
|
||||
this.getName() = "__ne__" and op instanceof Eq
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,49 +14,49 @@
|
||||
import python
|
||||
|
||||
CallableValue defines_equality(ClassValue c, string name) {
|
||||
(
|
||||
name = "__eq__"
|
||||
or
|
||||
major_version() = 2 and name = "__cmp__"
|
||||
) and
|
||||
result = c.declaredAttribute(name)
|
||||
(
|
||||
name = "__eq__"
|
||||
or
|
||||
major_version() = 2 and name = "__cmp__"
|
||||
) and
|
||||
result = c.declaredAttribute(name)
|
||||
}
|
||||
|
||||
CallableValue implemented_method(ClassValue c, string name) {
|
||||
result = defines_equality(c, name)
|
||||
or
|
||||
result = c.declaredAttribute("__hash__") and name = "__hash__"
|
||||
result = defines_equality(c, name)
|
||||
or
|
||||
result = c.declaredAttribute("__hash__") and name = "__hash__"
|
||||
}
|
||||
|
||||
string unimplemented_method(ClassValue c) {
|
||||
not exists(defines_equality(c, _)) and
|
||||
(
|
||||
result = "__eq__" and major_version() = 3
|
||||
or
|
||||
major_version() = 2 and result = "__eq__ or __cmp__"
|
||||
)
|
||||
not exists(defines_equality(c, _)) and
|
||||
(
|
||||
result = "__eq__" and major_version() = 3
|
||||
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
|
||||
major_version() = 2 and result = "__eq__ or __cmp__"
|
||||
)
|
||||
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 */
|
||||
predicate unhashable(ClassValue cls) {
|
||||
cls.lookup("__hash__") = Value::named("None")
|
||||
or
|
||||
cls.lookup("__hash__").(CallableValue).neverReturns()
|
||||
cls.lookup("__hash__") = Value::named("None")
|
||||
or
|
||||
cls.lookup("__hash__").(CallableValue).neverReturns()
|
||||
}
|
||||
|
||||
predicate violates_hash_contract(ClassValue c, string present, string missing, Value method) {
|
||||
not unhashable(c) and
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_)
|
||||
not unhashable(c) and
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_)
|
||||
}
|
||||
|
||||
from ClassValue c, string present, string missing, CallableValue method
|
||||
where
|
||||
violates_hash_contract(c, present, missing, method) and
|
||||
exists(c.getScope()) // Suppress results that aren't from source
|
||||
violates_hash_contract(c, present, missing, method) and
|
||||
exists(c.getScope()) // Suppress results that aren't from source
|
||||
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c,
|
||||
c.getName()
|
||||
c.getName()
|
||||
|
||||
@@ -16,33 +16,33 @@ import Equality
|
||||
string equals_or_ne() { result = "__eq__" or result = "__ne__" }
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
}
|
||||
|
||||
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) {
|
||||
not c.declaresAttribute(result) and result = equals_or_ne()
|
||||
not c.declaresAttribute(result) and result = equals_or_ne()
|
||||
}
|
||||
|
||||
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
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
/* 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 method.getScope() instanceof DelegatingEqualityMethod and
|
||||
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
|
||||
missing = unimplemented_method(c) and
|
||||
method = implemented_method(c, present) and
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
/* 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 method.getScope() instanceof DelegatingEqualityMethod and
|
||||
not c.lookup(missing).(CallableValue).getScope() instanceof DelegatingEqualityMethod
|
||||
}
|
||||
|
||||
from ClassValue c, string present, string missing, CallableValue method
|
||||
where violates_equality_contract(c, present, missing, method)
|
||||
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c,
|
||||
c.getName()
|
||||
c.getName()
|
||||
|
||||
@@ -13,61 +13,61 @@
|
||||
import python
|
||||
|
||||
predicate total_ordering(Class cls) {
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
|
||||
or
|
||||
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
|
||||
}
|
||||
|
||||
string ordering_name(int n) {
|
||||
result = "__lt__" and n = 1
|
||||
or
|
||||
result = "__le__" and n = 2
|
||||
or
|
||||
result = "__gt__" and n = 3
|
||||
or
|
||||
result = "__ge__" and n = 4
|
||||
result = "__lt__" and n = 1
|
||||
or
|
||||
result = "__le__" and n = 2
|
||||
or
|
||||
result = "__gt__" and n = 3
|
||||
or
|
||||
result = "__ge__" and n = 4
|
||||
}
|
||||
|
||||
predicate overrides_ordering_method(ClassValue c, string name) {
|
||||
name = ordering_name(_) and
|
||||
(
|
||||
c.declaresAttribute(name)
|
||||
or
|
||||
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
sup.declaresAttribute(name)
|
||||
)
|
||||
name = ordering_name(_) and
|
||||
(
|
||||
c.declaresAttribute(name)
|
||||
or
|
||||
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
sup.declaresAttribute(name)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
string unimplemented_ordering(ClassValue c, int n) {
|
||||
not c = Value::named("object") and
|
||||
not overrides_ordering_method(c, result) and
|
||||
result = ordering_name(n)
|
||||
not c = Value::named("object") and
|
||||
not overrides_ordering_method(c, result) and
|
||||
result = ordering_name(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
|
||||
exists(string prefix, int nm1 | n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) |
|
||||
prefix = "" and result = unimplemented_ordering(c, n)
|
||||
or
|
||||
result = prefix and not exists(unimplemented_ordering(c, n)) and n < 5
|
||||
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) {
|
||||
/* 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)
|
||||
/* 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)
|
||||
}
|
||||
|
||||
from ClassValue c, Value ordering, string name
|
||||
where
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
ordering = ordering_method(c, name) and
|
||||
exists(unimplemented_ordering(c, _))
|
||||
not c.failedInference(_) and
|
||||
not total_ordering(c.getScope()) and
|
||||
ordering = ordering_method(c, name) and
|
||||
exists(unimplemented_ordering(c, _))
|
||||
select c,
|
||||
"Class " + c.getName() + " implements $@, but does not implement " +
|
||||
unimplemented_ordering_methods(c, 4) + ".", ordering, name
|
||||
"Class " + c.getName() + " implements $@, but does not implement " +
|
||||
unimplemented_ordering_methods(c, 4) + ".", ordering, name
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
import python
|
||||
|
||||
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) {
|
||||
t.isNewStyle() and
|
||||
left = left_base(t, right) and
|
||||
left = right.getAnImproperSuperType()
|
||||
t.isNewStyle() and
|
||||
left = left_base(t, right) and
|
||||
left = right.getAnImproperSuperType()
|
||||
}
|
||||
|
||||
from ClassObject t, ClassObject left, ClassObject right
|
||||
where invalid_mro(t, left, right)
|
||||
select t,
|
||||
"Construction of class " + t.getName() +
|
||||
" can fail due to invalid method resolution order(MRO) for bases $@ and $@.", left,
|
||||
left.getName(), right, right.getName()
|
||||
"Construction of class " + t.getName() +
|
||||
" can fail due to invalid method resolution order(MRO) for bases $@ and $@.", left,
|
||||
left.getName(), right, right.getName()
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
import python
|
||||
|
||||
from
|
||||
ClassObject supercls, string method, Call call, FunctionObject overriding,
|
||||
FunctionObject overridden
|
||||
ClassObject supercls, string method, Call call, FunctionObject overriding,
|
||||
FunctionObject overridden
|
||||
where
|
||||
exists(FunctionObject init, SelfAttribute sa |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
call.getScope() = init.getFunction() and
|
||||
call.getFunc() = sa
|
||||
|
|
||||
sa.getName() = method and
|
||||
overridden = supercls.declaredAttribute(method) and
|
||||
overriding.overrides(overridden)
|
||||
)
|
||||
exists(FunctionObject init, SelfAttribute sa |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
call.getScope() = init.getFunction() and
|
||||
call.getFunc() = sa
|
||||
|
|
||||
sa.getName() = method and
|
||||
overridden = supercls.declaredAttribute(method) and
|
||||
overriding.overrides(overridden)
|
||||
)
|
||||
select call, "Call to self.$@ in __init__ method, which is overridden by $@.", overridden, method,
|
||||
overriding, overriding.descriptiveString()
|
||||
overriding, overriding.descriptiveString()
|
||||
|
||||
@@ -15,30 +15,30 @@ import semmle.python.SelfAttribute
|
||||
import ClassAttributes
|
||||
|
||||
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
c.sometimesDefines(a.getName()) and
|
||||
exists(SelfAttributeRead guard, If i |
|
||||
i.contains(a) and
|
||||
c.assignedInInit(guard.getName())
|
||||
|
|
||||
i.getTest() = guard
|
||||
or
|
||||
i.getTest().contains(guard)
|
||||
)
|
||||
c.sometimesDefines(a.getName()) and
|
||||
exists(SelfAttributeRead guard, If i |
|
||||
i.contains(a) and
|
||||
c.assignedInInit(guard.getName())
|
||||
|
|
||||
i.getTest() = guard
|
||||
or
|
||||
i.getTest().contains(guard)
|
||||
)
|
||||
}
|
||||
|
||||
predicate maybe_undefined_class_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
c.sometimesDefines(a.getName()) and
|
||||
not c.alwaysDefines(a.getName()) and
|
||||
c.interestingUndefined(a) and
|
||||
not guarded_by_other_attribute(a, c)
|
||||
c.sometimesDefines(a.getName()) and
|
||||
not c.alwaysDefines(a.getName()) and
|
||||
c.interestingUndefined(a) and
|
||||
not guarded_by_other_attribute(a, c)
|
||||
}
|
||||
|
||||
from Attribute a, ClassObject c, SelfAttributeStore sa
|
||||
where
|
||||
maybe_undefined_class_attribute(a, c) and
|
||||
sa.getClass() = c.getPyClass() and
|
||||
sa.getName() = a.getName()
|
||||
maybe_undefined_class_attribute(a, c) and
|
||||
sa.getClass() = c.getPyClass() and
|
||||
sa.getName() = a.getName()
|
||||
select a,
|
||||
"Attribute '" + a.getName() +
|
||||
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa,
|
||||
"here"
|
||||
"Attribute '" + a.getName() +
|
||||
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa,
|
||||
"here"
|
||||
|
||||
@@ -3,73 +3,73 @@ import python
|
||||
// Helper predicates for multiple call to __init__/__del__ queries.
|
||||
pragma[noinline]
|
||||
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 = top.getACallee+() and
|
||||
i2 = top.getACallee+() and
|
||||
i1.getFunction() = multi
|
||||
i1 != i2 and
|
||||
i1 = top.getACallee+() and
|
||||
i2 = top.getACallee+() and
|
||||
i1.getFunction() = multi
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
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
|
||||
i2.getFunction() = multi
|
||||
multiple_invocation_paths_helper(top, i1, i2, multi) and
|
||||
i2.getFunction() = multi
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
|
||||
multiple_invocation_paths(top, i1, i2, multi) and
|
||||
top.runtime(self.declaredAttribute(name)) and
|
||||
self.getASuperType().declaredAttribute(name) = multi
|
||||
|
|
||||
// Only called twice if called from different functions,
|
||||
// or if one call-site can reach the other.
|
||||
i1.getCall().getScope() != i2.getCall().getScope()
|
||||
or
|
||||
i1.getCall().strictlyReaches(i2.getCall())
|
||||
)
|
||||
exists(FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2 |
|
||||
multiple_invocation_paths(top, i1, i2, multi) and
|
||||
top.runtime(self.declaredAttribute(name)) and
|
||||
self.getASuperType().declaredAttribute(name) = multi
|
||||
|
|
||||
// Only called twice if called from different functions,
|
||||
// or if one call-site can reach the other.
|
||||
i1.getCall().getScope() != i2.getCall().getScope()
|
||||
or
|
||||
i1.getCall().strictlyReaches(i2.getCall())
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if all attributes called `name` can be inferred to be methods. */
|
||||
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. */
|
||||
private predicate does_something(FunctionObject f) {
|
||||
f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__")
|
||||
or
|
||||
exists(Stmt s | s = f.getFunction().getAStmt() and not s instanceof Pass)
|
||||
f.isBuiltin() and not f = theObjectType().lookupAttribute("__init__")
|
||||
or
|
||||
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 */
|
||||
private predicate missing_call(FunctionObject meth, string name) {
|
||||
exists(CallNode call, AttrNode attr |
|
||||
call.getScope() = meth.getFunction() and
|
||||
call.getFunction() = attr and
|
||||
attr.getName() = name and
|
||||
not exists(FunctionObject f | f.getACall() = call)
|
||||
)
|
||||
exists(CallNode call, AttrNode attr |
|
||||
call.getScope() = meth.getFunction() and
|
||||
call.getFunction() = attr and
|
||||
attr.getName() = name and
|
||||
not exists(FunctionObject f | f.getACall() = call)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `self.name` does not call `missing`, even though it is expected to. */
|
||||
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
|
||||
top = self.lookupAttribute(name) and
|
||||
/* There is no call to missing originating from top */
|
||||
not top.getACallee*() = missing and
|
||||
/* Make sure that all named 'methods' are objects that we can understand. */
|
||||
not exists(ClassObject sup |
|
||||
sup = self.getAnImproperSuperType() and
|
||||
named_attributes_not_method(sup, name)
|
||||
) and
|
||||
not self.isAbstract() and
|
||||
does_something(missing) and
|
||||
not missing_call(top, name)
|
||||
missing = self.getASuperType().declaredAttribute(name) and
|
||||
top = self.lookupAttribute(name) and
|
||||
/* There is no call to missing originating from top */
|
||||
not top.getACallee*() = missing and
|
||||
/* Make sure that all named 'methods' are objects that we can understand. */
|
||||
not exists(ClassObject sup |
|
||||
sup = self.getAnImproperSuperType() and
|
||||
named_attributes_not_method(sup, name)
|
||||
) and
|
||||
not self.isAbstract() and
|
||||
does_something(missing) and
|
||||
not missing_call(top, name)
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject missing
|
||||
where
|
||||
missing_call_to_superclass_method(self, _, missing, "__del__") and
|
||||
not missing.neverReturns() and
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin()
|
||||
missing_call_to_superclass_method(self, _, missing, "__del__") and
|
||||
not missing.neverReturns() and
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin()
|
||||
select self,
|
||||
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
|
||||
missing, missing.descriptiveString()
|
||||
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
|
||||
missing, missing.descriptiveString()
|
||||
|
||||
@@ -15,14 +15,14 @@ import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject initializer, FunctionObject missing
|
||||
where
|
||||
self.lookupAttribute("__init__") = initializer and
|
||||
missing_call_to_superclass_method(self, initializer, missing, "__init__") and
|
||||
// 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.neverReturns() and
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin() and
|
||||
not self.isAbstract()
|
||||
self.lookupAttribute("__init__") = initializer and
|
||||
missing_call_to_superclass_method(self, initializer, missing, "__init__") and
|
||||
// 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.neverReturns() and
|
||||
not self.failedInference() and
|
||||
not missing.isBuiltin() and
|
||||
not self.isAbstract()
|
||||
select self,
|
||||
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
|
||||
missing, missing.descriptiveString(), initializer, "__init__ method"
|
||||
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
|
||||
missing, missing.descriptiveString(), initializer, "__init__ method"
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
import python
|
||||
|
||||
predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
|
||||
cls.isDescriptorType() and
|
||||
exists(PyFunctionObject f, PyFunctionObject get_set |
|
||||
exists(string name | cls.lookupAttribute(name) = get_set |
|
||||
name = "__get__" or name = "__set__" or name = "__delete__"
|
||||
) and
|
||||
cls.lookupAttribute(_) = f and
|
||||
get_set.getACallee*() = f and
|
||||
not f.getName() = "__init__" and
|
||||
s.getScope() = f.getFunction()
|
||||
)
|
||||
cls.isDescriptorType() and
|
||||
exists(PyFunctionObject f, PyFunctionObject get_set |
|
||||
exists(string name | cls.lookupAttribute(name) = get_set |
|
||||
name = "__get__" or name = "__set__" or name = "__delete__"
|
||||
) and
|
||||
cls.lookupAttribute(_) = f and
|
||||
get_set.getACallee*() = f and
|
||||
not f.getName() = "__init__" and
|
||||
s.getScope() = f.getFunction()
|
||||
)
|
||||
}
|
||||
|
||||
from ClassObject cls, SelfAttributeStore s
|
||||
where mutates_descriptor(cls, s)
|
||||
select s,
|
||||
"Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.",
|
||||
cls, cls.getName()
|
||||
"Mutation of descriptor $@ object may lead to action-at-a-distance effects or race conditions for properties.",
|
||||
cls, cls.getName()
|
||||
|
||||
@@ -14,79 +14,79 @@
|
||||
import python
|
||||
|
||||
class InitCallStmt extends ExprStmt {
|
||||
InitCallStmt() {
|
||||
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
|
||||
attr.getName() = "__init__"
|
||||
)
|
||||
}
|
||||
InitCallStmt() {
|
||||
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
|
||||
attr.getName() = "__init__"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
|
||||
write_attr.getScope() = subinit and
|
||||
self_write_stmt(write_attr, _) and
|
||||
exists(Stmt top | top.contains(write_attr) or top = write_attr |
|
||||
(
|
||||
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"
|
||||
)
|
||||
or
|
||||
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"
|
||||
)
|
||||
)
|
||||
write_attr.getScope() = subinit and
|
||||
self_write_stmt(write_attr, _) and
|
||||
exists(Stmt top | top.contains(write_attr) or top = write_attr |
|
||||
(
|
||||
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"
|
||||
)
|
||||
or
|
||||
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"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate self_write_stmt(Stmt s, string attr) {
|
||||
exists(Attribute a, Name self |
|
||||
self = a.getObject() and
|
||||
s.contains(a) and
|
||||
self.getId() = "self" and
|
||||
a.getCtx() instanceof Store and
|
||||
a.getName() = attr
|
||||
)
|
||||
exists(Attribute a, Name self |
|
||||
self = a.getObject() and
|
||||
s.contains(a) and
|
||||
self.getId() = "self" and
|
||||
a.getCtx() instanceof Store and
|
||||
a.getName() = attr
|
||||
)
|
||||
}
|
||||
|
||||
predicate both_assign_attribute(Stmt s1, Stmt s2, Function f1, Function f2) {
|
||||
exists(string name |
|
||||
s1.getScope() = f1 and
|
||||
s2.getScope() = f2 and
|
||||
self_write_stmt(s1, name) and
|
||||
self_write_stmt(s2, name)
|
||||
)
|
||||
exists(string name |
|
||||
s1.getScope() = f1 and
|
||||
s2.getScope() = f2 and
|
||||
self_write_stmt(s1, name) and
|
||||
self_write_stmt(s2, name)
|
||||
)
|
||||
}
|
||||
|
||||
predicate attribute_overwritten(
|
||||
AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname
|
||||
AssignStmt overwrites, AssignStmt overwritten, string name, string classtype, string classname
|
||||
) {
|
||||
exists(
|
||||
FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass,
|
||||
AssignStmt subattr, AssignStmt superattr
|
||||
|
|
||||
(
|
||||
classtype = "superclass" and
|
||||
classname = superclass.getName() and
|
||||
overwrites = subattr and
|
||||
overwritten = superattr
|
||||
or
|
||||
classtype = "subclass" and
|
||||
classname = subclass.getName() and
|
||||
overwrites = superattr and
|
||||
overwritten = subattr
|
||||
) and
|
||||
/* OK if overwritten in subclass and is a class attribute */
|
||||
(not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and
|
||||
superclass.declaredAttribute("__init__") = superinit and
|
||||
subclass.declaredAttribute("__init__") = subinit and
|
||||
superclass = subclass.getASuperType() and
|
||||
overwrites_which(subinit.getFunction(), subattr, classtype) and
|
||||
both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and
|
||||
self_write_stmt(superattr, name)
|
||||
)
|
||||
exists(
|
||||
FunctionObject superinit, FunctionObject subinit, ClassObject superclass, ClassObject subclass,
|
||||
AssignStmt subattr, AssignStmt superattr
|
||||
|
|
||||
(
|
||||
classtype = "superclass" and
|
||||
classname = superclass.getName() and
|
||||
overwrites = subattr and
|
||||
overwritten = superattr
|
||||
or
|
||||
classtype = "subclass" and
|
||||
classname = subclass.getName() and
|
||||
overwrites = superattr and
|
||||
overwritten = subattr
|
||||
) and
|
||||
/* OK if overwritten in subclass and is a class attribute */
|
||||
(not exists(superclass.declaredAttribute(name)) or classtype = "subclass") and
|
||||
superclass.declaredAttribute("__init__") = superinit and
|
||||
subclass.declaredAttribute("__init__") = subinit and
|
||||
superclass = subclass.getASuperType() and
|
||||
overwrites_which(subinit.getFunction(), subattr, classtype) and
|
||||
both_assign_attribute(subattr, superattr, subinit.getFunction(), superinit.getFunction()) and
|
||||
self_write_stmt(superattr, name)
|
||||
)
|
||||
}
|
||||
|
||||
from string classtype, AssignStmt overwrites, AssignStmt overwritten, string name, string classname
|
||||
where attribute_overwritten(overwrites, overwritten, name, classtype, classname)
|
||||
select overwrites,
|
||||
"Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
|
||||
" $@.", overwritten, classname
|
||||
"Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
|
||||
" $@.", overwritten, classname
|
||||
|
||||
@@ -15,5 +15,5 @@ import python
|
||||
from PropertyObject prop, ClassObject cls
|
||||
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
|
||||
select prop,
|
||||
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
||||
" is an old-style class."
|
||||
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
|
||||
" is an old-style class."
|
||||
|
||||
@@ -17,5 +17,5 @@ import python
|
||||
from ClassValue c
|
||||
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
|
||||
select c,
|
||||
"Class " + c.getName() +
|
||||
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||
"Class " + c.getName() +
|
||||
" implements __del__ (presumably to release some resource). Consider making it a context manager."
|
||||
|
||||
@@ -20,27 +20,27 @@
|
||||
import python
|
||||
|
||||
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.declaredAttribute(_) = f and
|
||||
exists(FunctionObject init, Attribute attr |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
attr = assign.getATarget() and
|
||||
attr.getObject().(Name).getId() = "self" and
|
||||
attr.getName() = f.getName() and
|
||||
assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope()
|
||||
) and
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
c.getASuperType() = supercls and
|
||||
c.declaredAttribute(_) = f and
|
||||
exists(FunctionObject init, Attribute attr |
|
||||
supercls.declaredAttribute("__init__") = init and
|
||||
attr = assign.getATarget() and
|
||||
attr.getObject().(Name).getId() = "self" and
|
||||
attr.getName() = f.getName() and
|
||||
assign.getScope() = init.getOrigin().(FunctionExpr).getInnerScope()
|
||||
) and
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
not supercls.hasAttribute(f.getName())
|
||||
not supercls.hasAttribute(f.getName())
|
||||
}
|
||||
|
||||
from ClassObject c, ClassObject supercls, Assign assign, FunctionObject shadowed
|
||||
where shadowed_by_super_class(c, supercls, assign, shadowed)
|
||||
select shadowed.getOrigin(),
|
||||
"Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.",
|
||||
assign, "an attribute"
|
||||
"Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.",
|
||||
assign, "an attribute"
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
import python
|
||||
|
||||
predicate uses_of_super_in_old_style_class(Call s) {
|
||||
exists(Function f, ClassObject c |
|
||||
s.getScope() = f and
|
||||
f.getScope() = c.getPyClass() and
|
||||
not c.failedInference() and
|
||||
not c.isNewStyle() and
|
||||
s.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
exists(Function f, ClassObject c |
|
||||
s.getScope() = f and
|
||||
f.getScope() = c.getPyClass() and
|
||||
not c.failedInference() and
|
||||
not c.isNewStyle() and
|
||||
s.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
}
|
||||
|
||||
from Call c
|
||||
|
||||
@@ -15,14 +15,14 @@ import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject multi
|
||||
where
|
||||
multiple_calls_to_superclass_method(self, multi, "__del__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__del__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
multiple_calls_to_superclass_method(self, multi, "__del__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__del__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__del__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self,
|
||||
"Class " + self.getName() +
|
||||
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
|
||||
multi.descriptiveString()
|
||||
"Class " + self.getName() +
|
||||
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
|
||||
multi.descriptiveString()
|
||||
|
||||
@@ -15,15 +15,15 @@ import MethodCallOrder
|
||||
|
||||
from ClassObject self, FunctionObject multi
|
||||
where
|
||||
multi != theObjectType().lookupAttribute("__init__") and
|
||||
multiple_calls_to_superclass_method(self, multi, "__init__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__init__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
multi != theObjectType().lookupAttribute("__init__") and
|
||||
multiple_calls_to_superclass_method(self, multi, "__init__") and
|
||||
not multiple_calls_to_superclass_method(self.getABaseType(), multi, "__init__") and
|
||||
not exists(FunctionObject better |
|
||||
multiple_calls_to_superclass_method(self, better, "__init__") and
|
||||
better.overrides(multi)
|
||||
) and
|
||||
not self.failedInference()
|
||||
select self,
|
||||
"Class " + self.getName() +
|
||||
" may not be initialized properly as $@ may be called multiple times during initialization.",
|
||||
multi, multi.descriptiveString()
|
||||
"Class " + self.getName() +
|
||||
" may not be initialized properly as $@ may be called multiple times during initialization.",
|
||||
multi, multi.descriptiveString()
|
||||
|
||||
@@ -15,18 +15,18 @@ import semmle.python.SelfAttribute
|
||||
import ClassAttributes
|
||||
|
||||
predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) {
|
||||
name = a.getName() and
|
||||
not c.sometimesDefines(name) and
|
||||
c.interestingUndefined(a) and
|
||||
line = a.getLocation().getStartLine() and
|
||||
not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name)
|
||||
name = a.getName() and
|
||||
not c.sometimesDefines(name) and
|
||||
c.interestingUndefined(a) and
|
||||
line = a.getLocation().getStartLine() and
|
||||
not attribute_assigned_in_method(c.getAMethodCalledFromInit(), name)
|
||||
}
|
||||
|
||||
predicate report_undefined_class_attribute(Attribute a, ClassObject c, string name) {
|
||||
exists(int line |
|
||||
undefined_class_attribute(a, c, line, name) and
|
||||
line = min(int x | undefined_class_attribute(_, c, x, name))
|
||||
)
|
||||
exists(int line |
|
||||
undefined_class_attribute(a, c, line, name) and
|
||||
line = min(int x | undefined_class_attribute(_, c, x, name))
|
||||
)
|
||||
}
|
||||
|
||||
from Attribute a, ClassObject c, string name
|
||||
|
||||
@@ -13,76 +13,76 @@
|
||||
import python
|
||||
|
||||
predicate fewer_than_two_public_methods(Class cls, int methods) {
|
||||
(methods = 0 or methods = 1) and
|
||||
methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod())
|
||||
(methods = 0 or methods = 1) and
|
||||
methods = count(Function f | f = cls.getAMethod() and not f = cls.getInitMethod())
|
||||
}
|
||||
|
||||
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) {
|
||||
not exists(ClassValue cls, ClassValue other |
|
||||
cls.getScope() = c and
|
||||
other != ClassValue::object()
|
||||
|
|
||||
other.getABaseType() = cls or
|
||||
cls.getABaseType() = other
|
||||
) and
|
||||
not exists(Expr base | base = c.getABase() |
|
||||
not base instanceof Name or base.(Name).getId() != "object"
|
||||
)
|
||||
not exists(ClassValue cls, ClassValue other |
|
||||
cls.getScope() = c and
|
||||
other != ClassValue::object()
|
||||
|
|
||||
other.getABaseType() = cls or
|
||||
cls.getABaseType() = other
|
||||
) and
|
||||
not exists(Expr base | base = c.getABase() |
|
||||
not base instanceof Name or base.(Name).getId() != "object"
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_decorated(Class c) { exists(c.getADecorator()) }
|
||||
|
||||
predicate is_stateful(Class c) {
|
||||
exists(Function method, ExprContext ctx |
|
||||
method.getScope() = c and
|
||||
(ctx instanceof Store or ctx instanceof AugStore)
|
||||
|
|
||||
exists(Subscript s | s.getScope() = method and s.getCtx() = ctx)
|
||||
or
|
||||
exists(Attribute a | a.getScope() = method and a.getCtx() = ctx)
|
||||
)
|
||||
exists(Function method, ExprContext ctx |
|
||||
method.getScope() = c and
|
||||
(ctx instanceof Store or ctx instanceof AugStore)
|
||||
|
|
||||
exists(Subscript s | s.getScope() = method and s.getCtx() = ctx)
|
||||
or
|
||||
exists(Function method, Call call, Attribute a, string name |
|
||||
method.getScope() = c and
|
||||
call.getScope() = method and
|
||||
call.getFunc() = a and
|
||||
a.getName() = name
|
||||
|
|
||||
name = "pop" or
|
||||
name = "remove" or
|
||||
name = "discard" or
|
||||
name = "extend" or
|
||||
name = "append"
|
||||
)
|
||||
exists(Attribute a | a.getScope() = method and a.getCtx() = ctx)
|
||||
)
|
||||
or
|
||||
exists(Function method, Call call, Attribute a, string name |
|
||||
method.getScope() = c and
|
||||
call.getScope() = method and
|
||||
call.getFunc() = a and
|
||||
a.getName() = name
|
||||
|
|
||||
name = "pop" or
|
||||
name = "remove" or
|
||||
name = "discard" or
|
||||
name = "extend" or
|
||||
name = "append"
|
||||
)
|
||||
}
|
||||
|
||||
predicate useless_class(Class c, int methods) {
|
||||
c.isTopLevel() and
|
||||
c.isPublic() and
|
||||
no_inheritance(c) and
|
||||
fewer_than_two_public_methods(c, methods) and
|
||||
does_not_define_special_method(c) and
|
||||
not c.isProbableMixin() and
|
||||
not is_decorated(c) and
|
||||
not is_stateful(c)
|
||||
c.isTopLevel() and
|
||||
c.isPublic() and
|
||||
no_inheritance(c) and
|
||||
fewer_than_two_public_methods(c, methods) and
|
||||
does_not_define_special_method(c) and
|
||||
not c.isProbableMixin() and
|
||||
not is_decorated(c) and
|
||||
not is_stateful(c)
|
||||
}
|
||||
|
||||
from Class c, int methods, string msg
|
||||
where
|
||||
useless_class(c, methods) and
|
||||
(
|
||||
methods = 1 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines only one public method, which should be replaced by a function."
|
||||
or
|
||||
methods = 0 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines no public methods and could be replaced with a namedtuple or dictionary."
|
||||
)
|
||||
useless_class(c, methods) and
|
||||
(
|
||||
methods = 1 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines only one public method, which should be replaced by a function."
|
||||
or
|
||||
methods = 0 and
|
||||
msg =
|
||||
"Class " + c.getName() +
|
||||
" defines no public methods and could be replaced with a namedtuple or dictionary."
|
||||
)
|
||||
select c, msg
|
||||
|
||||
@@ -18,7 +18,7 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, ClassValue cls, string name, FunctionValue init
|
||||
where
|
||||
illegally_named_parameter(call, cls, name) and
|
||||
init = get_function_or_initializer(cls)
|
||||
illegally_named_parameter(call, cls, name) and
|
||||
init = get_function_or_initializer(cls)
|
||||
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
|
||||
init.getQualifiedName()
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -17,15 +17,15 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, ClassValue cls, string too, string should, int limit, FunctionValue init
|
||||
where
|
||||
(
|
||||
too_many_args(call, cls, limit) and
|
||||
too = "too many arguments" and
|
||||
should = "no more than "
|
||||
or
|
||||
too_few_args(call, cls, limit) and
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
init = get_function_or_initializer(cls)
|
||||
(
|
||||
too_many_args(call, cls, limit) and
|
||||
too = "too many arguments" and
|
||||
should = "no more than "
|
||||
or
|
||||
too_few_args(call, cls, limit) and
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
init = get_function_or_initializer(cls)
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
|
||||
init.getQualifiedName()
|
||||
init.getQualifiedName()
|
||||
|
||||
@@ -17,13 +17,13 @@ import python
|
||||
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
|
||||
|
||||
predicate catches_base_exception(ExceptStmt ex) {
|
||||
ex.getType().pointsTo(ClassValue::baseException())
|
||||
or
|
||||
not exists(ex.getType())
|
||||
ex.getType().pointsTo(ClassValue::baseException())
|
||||
or
|
||||
not exists(ex.getType())
|
||||
}
|
||||
|
||||
from ExceptStmt ex
|
||||
where
|
||||
catches_base_exception(ex) and
|
||||
doesnt_reraise(ex)
|
||||
catches_base_exception(ex) and
|
||||
doesnt_reraise(ex)
|
||||
select ex, "Except block directly handles BaseException."
|
||||
|
||||
@@ -14,88 +14,88 @@
|
||||
import python
|
||||
|
||||
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_comment(ExceptStmt ex) {
|
||||
not exists(Comment c |
|
||||
c.getLocation().getFile() = ex.getLocation().getFile() and
|
||||
c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and
|
||||
c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine()
|
||||
)
|
||||
not exists(Comment c |
|
||||
c.getLocation().getFile() = ex.getLocation().getFile() and
|
||||
c.getLocation().getStartLine() >= ex.getLocation().getStartLine() and
|
||||
c.getLocation().getEndLine() <= ex.getBody().getLastItem().getLocation().getEndLine()
|
||||
)
|
||||
}
|
||||
|
||||
predicate non_local_control_flow(ExceptStmt ex) {
|
||||
ex.getType().pointsTo(ClassValue::stopIteration())
|
||||
ex.getType().pointsTo(ClassValue::stopIteration())
|
||||
}
|
||||
|
||||
predicate try_has_normal_exit(Try try) {
|
||||
exists(ControlFlowNode pred, ControlFlowNode succ |
|
||||
/* Exists a non-exception predecessor, successor pair */
|
||||
pred.getASuccessor() = succ and
|
||||
not pred.getAnExceptionalSuccessor() = succ
|
||||
|
|
||||
/* Successor is either a normal flow node or a fall-through exit */
|
||||
not exists(Scope s | s.getReturnNode() = succ) and
|
||||
/* Predecessor is in try body and successor is not */
|
||||
pred.getNode().getParentNode*() = try.getAStmt() and
|
||||
not succ.getNode().getParentNode*() = try.getAStmt()
|
||||
)
|
||||
exists(ControlFlowNode pred, ControlFlowNode succ |
|
||||
/* Exists a non-exception predecessor, successor pair */
|
||||
pred.getASuccessor() = succ and
|
||||
not pred.getAnExceptionalSuccessor() = succ
|
||||
|
|
||||
/* Successor is either a normal flow node or a fall-through exit */
|
||||
not exists(Scope s | s.getReturnNode() = succ) and
|
||||
/* Predecessor is in try body and successor is not */
|
||||
pred.getNode().getParentNode*() = try.getAStmt() and
|
||||
not succ.getNode().getParentNode*() = try.getAStmt()
|
||||
)
|
||||
}
|
||||
|
||||
predicate attribute_access(Stmt s) {
|
||||
s.(ExprStmt).getValue() instanceof Attribute
|
||||
or
|
||||
exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
|
||||
name = "getattr" or name = "setattr" or name = "delattr"
|
||||
)
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Attribute
|
||||
s.(ExprStmt).getValue() instanceof Attribute
|
||||
or
|
||||
exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
|
||||
name = "getattr" or name = "setattr" or name = "delattr"
|
||||
)
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Attribute
|
||||
}
|
||||
|
||||
predicate subscript(Stmt s) {
|
||||
s.(ExprStmt).getValue() instanceof Subscript
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Subscript
|
||||
s.(ExprStmt).getValue() instanceof Subscript
|
||||
or
|
||||
s.(Delete).getATarget() instanceof Subscript
|
||||
}
|
||||
|
||||
predicate encode_decode(Call ex, ClassValue type) {
|
||||
exists(string name | ex.getFunc().(Attribute).getName() = name |
|
||||
name = "encode" and type = ClassValue::unicodeEncodeError()
|
||||
or
|
||||
name = "decode" and type = ClassValue::unicodeDecodeError()
|
||||
)
|
||||
exists(string name | ex.getFunc().(Attribute).getName() = name |
|
||||
name = "encode" and type = ClassValue::unicodeEncodeError()
|
||||
or
|
||||
name = "decode" and type = ClassValue::unicodeDecodeError()
|
||||
)
|
||||
}
|
||||
|
||||
predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
|
||||
not exists(ex.getTry().getStmt(1)) and
|
||||
s = ex.getTry().getStmt(0) and
|
||||
ex.getType().pointsTo(type)
|
||||
not exists(ex.getTry().getStmt(1)) and
|
||||
s = ex.getTry().getStmt(0) and
|
||||
ex.getType().pointsTo(type)
|
||||
}
|
||||
|
||||
predicate focussed_handler(ExceptStmt ex) {
|
||||
exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
|
||||
subscript(s) and type.getASuperType() = ClassValue::lookupError()
|
||||
or
|
||||
attribute_access(s) and type = ClassValue::attributeError()
|
||||
or
|
||||
s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError()
|
||||
or
|
||||
encode_decode(s.(ExprStmt).getValue(), type)
|
||||
)
|
||||
exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
|
||||
subscript(s) and type.getASuperType() = ClassValue::lookupError()
|
||||
or
|
||||
attribute_access(s) and type = ClassValue::attributeError()
|
||||
or
|
||||
s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError()
|
||||
or
|
||||
encode_decode(s.(ExprStmt).getValue(), type)
|
||||
)
|
||||
}
|
||||
|
||||
Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return }
|
||||
|
||||
from ExceptStmt ex
|
||||
where
|
||||
empty_except(ex) and
|
||||
no_else(ex) and
|
||||
no_comment(ex) and
|
||||
not non_local_control_flow(ex) and
|
||||
not ex.getTry() = try_return() and
|
||||
try_has_normal_exit(ex.getTry()) and
|
||||
not focussed_handler(ex)
|
||||
empty_except(ex) and
|
||||
no_else(ex) and
|
||||
no_comment(ex) and
|
||||
not non_local_control_flow(ex) and
|
||||
not ex.getTry() = try_return() and
|
||||
try_has_normal_exit(ex.getTry()) and
|
||||
not focussed_handler(ex)
|
||||
select ex, "'except' clause does nothing but pass and there is no explanatory comment."
|
||||
|
||||
@@ -15,16 +15,16 @@ import python
|
||||
|
||||
from ExceptFlowNode ex, Value t, ClassValue c, ControlFlowNode origin, string what
|
||||
where
|
||||
ex.handledException(t, c, origin) and
|
||||
(
|
||||
exists(ClassValue x | x = t |
|
||||
not x.isLegalExceptionType() and
|
||||
not x.failedInference(_) and
|
||||
what = "class '" + x.getName() + "'"
|
||||
)
|
||||
or
|
||||
not t instanceof ClassValue and
|
||||
what = "instance of '" + c.getName() + "'"
|
||||
ex.handledException(t, c, origin) and
|
||||
(
|
||||
exists(ClassValue x | x = t |
|
||||
not x.isLegalExceptionType() and
|
||||
not x.failedInference(_) and
|
||||
what = "class '" + x.getName() + "'"
|
||||
)
|
||||
or
|
||||
not t instanceof ClassValue and
|
||||
what = "instance of '" + c.getName() + "'"
|
||||
)
|
||||
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
|
||||
|
||||
@@ -17,9 +17,9 @@ import Exceptions.NotImplemented
|
||||
|
||||
from Raise r, ClassValue t
|
||||
where
|
||||
type_or_typeof(r, t, _) and
|
||||
not t.isLegalExceptionType() and
|
||||
not t.failedInference(_) and
|
||||
not use_of_not_implemented_in_raise(r, _)
|
||||
type_or_typeof(r, t, _) and
|
||||
not t.isLegalExceptionType() and
|
||||
not t.failedInference(_) and
|
||||
not use_of_not_implemented_in_raise(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."
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
import python
|
||||
|
||||
predicate incorrect_except_order(ExceptStmt ex1, ClassValue cls1, ExceptStmt ex2, ClassValue cls2) {
|
||||
exists(int i, int j, Try t |
|
||||
ex1 = t.getHandler(i) and
|
||||
ex2 = t.getHandler(j) and
|
||||
i < j and
|
||||
cls1 = except_class(ex1) and
|
||||
cls2 = except_class(ex2) and
|
||||
cls1 = cls2.getASuperType()
|
||||
)
|
||||
exists(int i, int j, Try t |
|
||||
ex1 = t.getHandler(i) and
|
||||
ex2 = t.getHandler(j) and
|
||||
i < j and
|
||||
cls1 = except_class(ex1) and
|
||||
cls2 = except_class(ex2) and
|
||||
cls1 = cls2.getASuperType()
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
where incorrect_except_order(ex1, cls1, ex2, cls2)
|
||||
select ex2,
|
||||
"Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.",
|
||||
cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName()
|
||||
"Except block for $@ is unreachable; the more general $@ for $@ will always be executed in preference.",
|
||||
cls2, cls2.getName(), ex1, "except block", cls1, cls1.getName()
|
||||
|
||||
@@ -2,9 +2,9 @@ import python
|
||||
|
||||
/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */
|
||||
predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) {
|
||||
notimpl.pointsTo(Value::named("NotImplemented")) and
|
||||
(
|
||||
notimpl = raise.getException() or
|
||||
notimpl = raise.getException().(Call).getFunc()
|
||||
)
|
||||
notimpl.pointsTo(Value::named("NotImplemented")) and
|
||||
(
|
||||
notimpl = raise.getException() or
|
||||
notimpl = raise.getException().(Call).getFunc()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import python
|
||||
|
||||
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */
|
||||
predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) {
|
||||
exists(Expr exception | exception = r.getRaised() |
|
||||
exception.pointsTo(type, orig)
|
||||
or
|
||||
not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and
|
||||
not type = ClassValue::type() and // First value is an unknown exception type
|
||||
exists(Value val | exception.pointsTo(val, orig) | val.getClass() = type)
|
||||
)
|
||||
exists(Expr exception | exception = r.getRaised() |
|
||||
exception.pointsTo(type, orig)
|
||||
or
|
||||
not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and
|
||||
not type = ClassValue::type() and // First value is an unknown exception type
|
||||
exists(Value val | exception.pointsTo(val, orig) | val.getClass() = type)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ import python
|
||||
|
||||
from Raise r, Value v, AstNode origin
|
||||
where
|
||||
r.getException().pointsTo(v, origin) and
|
||||
v.getClass() = ClassValue::tuple() and
|
||||
major_version() = 2
|
||||
r.getException().pointsTo(v, origin) and
|
||||
v.getClass() = ClassValue::tuple() and
|
||||
major_version() = 2
|
||||
/* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
|
||||
select r,
|
||||
"Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.",
|
||||
origin, "a tuple"
|
||||
"Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.",
|
||||
origin, "a tuple"
|
||||
|
||||
@@ -17,45 +17,45 @@ FunctionValue iter() { result = Value::named("iter") }
|
||||
BuiltinFunctionValue next() { result = Value::named("next") }
|
||||
|
||||
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) {
|
||||
iter = next().getArgumentForCall(call, 0)
|
||||
iter = next().getArgumentForCall(call, 0)
|
||||
}
|
||||
|
||||
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) {
|
||||
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. */
|
||||
predicate iter_not_exhausted(EssaVariable iterator) {
|
||||
exists(EssaVariable sequence |
|
||||
call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and
|
||||
guarded_not_empty_sequence(sequence)
|
||||
)
|
||||
exists(EssaVariable sequence |
|
||||
call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and
|
||||
guarded_not_empty_sequence(sequence)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stop_iteration_handled(CallNode call) {
|
||||
exists(Try t |
|
||||
t.containsInScope(call.getNode()) and
|
||||
t.getAHandler().getType().pointsTo(ClassValue::stopIteration())
|
||||
)
|
||||
exists(Try t |
|
||||
t.containsInScope(call.getNode()) and
|
||||
t.getAHandler().getType().pointsTo(ClassValue::stopIteration())
|
||||
)
|
||||
}
|
||||
|
||||
from CallNode call
|
||||
where
|
||||
call_to_next(call, _) and
|
||||
not call_to_next_has_default(call) and
|
||||
not exists(EssaVariable iterator |
|
||||
call_to_next(call, iterator.getAUse()) and
|
||||
iter_not_exhausted(iterator)
|
||||
) and
|
||||
call.getNode().getScope().(Function).isGenerator() and
|
||||
not exists(Comp comp | comp.contains(call.getNode())) and
|
||||
not stop_iteration_handled(call)
|
||||
call_to_next(call, _) and
|
||||
not call_to_next_has_default(call) and
|
||||
not exists(EssaVariable iterator |
|
||||
call_to_next(call, iterator.getAUse()) and
|
||||
iter_not_exhausted(iterator)
|
||||
) and
|
||||
call.getNode().getScope().(Function).isGenerator() and
|
||||
not exists(Comp comp | comp.contains(call.getNode())) and
|
||||
not stop_iteration_handled(call)
|
||||
select call, "Call to next() in a generator"
|
||||
|
||||
@@ -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 Testing.Mox
|
||||
|
||||
private int varargs_length_objectapi(Call call) {
|
||||
not exists(call.getStarargs()) and result = 0
|
||||
or
|
||||
exists(TupleObject t | call.getStarargs().refersTo(t) | result = t.getLength())
|
||||
or
|
||||
result = count(call.getStarargs().(List).getAnElt())
|
||||
not exists(call.getStarargs()) and result = 0
|
||||
or
|
||||
exists(TupleObject t | call.getStarargs().refersTo(t) | result = t.getLength())
|
||||
or
|
||||
result = count(call.getStarargs().(List).getAnElt())
|
||||
}
|
||||
|
||||
private int varargs_length(Call call) {
|
||||
not exists(call.getStarargs()) and result = 0
|
||||
or
|
||||
exists(TupleValue t | call.getStarargs().pointsTo(t) | result = t.length())
|
||||
or
|
||||
result = count(call.getStarargs().(List).getAnElt())
|
||||
not exists(call.getStarargs()) and result = 0
|
||||
or
|
||||
exists(TupleValue t | call.getStarargs().pointsTo(t) | result = t.length())
|
||||
or
|
||||
result = count(call.getStarargs().(List).getAnElt())
|
||||
}
|
||||
|
||||
/** Gets a keyword argument that is not a keyword-only parameter. */
|
||||
private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) {
|
||||
func.getACall().getNode() = call and
|
||||
result = call.getAKeyword() and
|
||||
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
|
||||
func.getACall().getNode() = call and
|
||||
result = call.getAKeyword() and
|
||||
not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg()
|
||||
}
|
||||
|
||||
/** Gets a keyword argument that is not a keyword-only parameter. */
|
||||
private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
|
||||
func.getACall().getNode() = call and
|
||||
result = call.getAKeyword() and
|
||||
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
|
||||
func.getACall().getNode() = call and
|
||||
result = call.getAKeyword() and
|
||||
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).
|
||||
*/
|
||||
private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
|
||||
call = get_a_call_objectapi(callable).getNode() and
|
||||
exists(int positional_keywords |
|
||||
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
|
||||
not func.getFunction().hasKwArg() and
|
||||
positional_keywords = count(not_keyword_only_arg_objectapi(call, func))
|
||||
or
|
||||
func.getFunction().hasKwArg() and positional_keywords = 0
|
||||
)
|
||||
|
|
||||
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
|
||||
call = get_a_call_objectapi(callable).getNode() and
|
||||
exists(int positional_keywords |
|
||||
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
|
||||
not func.getFunction().hasKwArg() and
|
||||
positional_keywords = count(not_keyword_only_arg_objectapi(call, func))
|
||||
or
|
||||
func.getFunction().hasKwArg() and positional_keywords = 0
|
||||
)
|
||||
|
|
||||
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).
|
||||
*/
|
||||
private int positional_arg_count_for_call(Call call, Value callable) {
|
||||
call = get_a_call(callable).getNode() and
|
||||
exists(int positional_keywords |
|
||||
exists(FunctionValue func | func = get_function_or_initializer(callable) |
|
||||
not func.getScope().hasKwArg() and
|
||||
positional_keywords = count(not_keyword_only_arg(call, func))
|
||||
or
|
||||
func.getScope().hasKwArg() and positional_keywords = 0
|
||||
)
|
||||
|
|
||||
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
|
||||
call = get_a_call(callable).getNode() and
|
||||
exists(int positional_keywords |
|
||||
exists(FunctionValue func | func = get_function_or_initializer(callable) |
|
||||
not func.getScope().hasKwArg() and
|
||||
positional_keywords = count(not_keyword_only_arg(call, func))
|
||||
or
|
||||
func.getScope().hasKwArg() and positional_keywords = 0
|
||||
)
|
||||
|
|
||||
result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the number of arguments in `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`. */
|
||||
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. */
|
||||
private ControlFlowNode get_a_call_objectapi(Object callable) {
|
||||
result = callable.(ClassObject).getACall()
|
||||
or
|
||||
result = callable.(FunctionObject).getACall()
|
||||
result = callable.(ClassObject).getACall()
|
||||
or
|
||||
result = callable.(FunctionObject).getACall()
|
||||
}
|
||||
|
||||
/** Gets a call corresponding to the given class or function. */
|
||||
private ControlFlowNode get_a_call(Value callable) {
|
||||
result = callable.(ClassValue).getACall()
|
||||
or
|
||||
result = callable.(FunctionValue).getACall()
|
||||
result = callable.(ClassValue).getACall()
|
||||
or
|
||||
result = callable.(FunctionValue).getACall()
|
||||
}
|
||||
|
||||
/** Gets the function object corresponding to the given class or function. */
|
||||
FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) {
|
||||
result = func_or_cls.(FunctionObject)
|
||||
or
|
||||
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
|
||||
result = func_or_cls.(FunctionObject)
|
||||
or
|
||||
result = func_or_cls.(ClassObject).declaredAttribute("__init__")
|
||||
}
|
||||
|
||||
/** Gets the function object corresponding to the given class or function. */
|
||||
FunctionValue get_function_or_initializer(Value func_or_cls) {
|
||||
result = func_or_cls.(FunctionValue)
|
||||
or
|
||||
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
|
||||
result = func_or_cls.(FunctionValue)
|
||||
or
|
||||
result = func_or_cls.(ClassValue).declaredAttribute("__init__")
|
||||
}
|
||||
|
||||
/**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) {
|
||||
not func.isC() and
|
||||
name = call.getANamedArgumentName() and
|
||||
call.getAFlowNode() = get_a_call_objectapi(func) and
|
||||
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
|
||||
not func.isC() and
|
||||
name = call.getANamedArgumentName() and
|
||||
call.getAFlowNode() = get_a_call_objectapi(func) and
|
||||
not get_function_or_initializer_objectapi(func).isLegalArgumentName(name)
|
||||
}
|
||||
|
||||
/**Whether there is an illegally named parameter called `name` in the `call` to `func` */
|
||||
predicate illegally_named_parameter(Call call, Value func, string name) {
|
||||
not func.isBuiltin() and
|
||||
name = call.getANamedArgumentName() and
|
||||
call.getAFlowNode() = get_a_call(func) and
|
||||
not get_function_or_initializer(func).isLegalArgumentName(name)
|
||||
not func.isBuiltin() and
|
||||
name = call.getANamedArgumentName() and
|
||||
call.getAFlowNode() = get_a_call(func) and
|
||||
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 */
|
||||
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'
|
||||
not illegally_named_parameter_objectapi(call, callable, _) and
|
||||
not exists(call.getStarargs()) and
|
||||
not exists(call.getKwargs()) and
|
||||
arg_count_objectapi(call) < limit and
|
||||
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
|
||||
call = func.getAFunctionCall().getNode() and
|
||||
limit = func.minParameters() and
|
||||
// The combination of misuse of `mox.Mox().StubOutWithMock()`
|
||||
// and a bug in mox's implementation of methods results in having to
|
||||
// pass 1 too few arguments to the mocked function.
|
||||
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
|
||||
or
|
||||
callable instanceof ClassObject and
|
||||
call.getAFlowNode() = get_a_call_objectapi(callable) and
|
||||
limit = func.minParameters() - 1
|
||||
)
|
||||
// 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 exists(call.getStarargs()) and
|
||||
not exists(call.getKwargs()) and
|
||||
arg_count_objectapi(call) < limit and
|
||||
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
|
||||
call = func.getAFunctionCall().getNode() and
|
||||
limit = func.minParameters() and
|
||||
// The combination of misuse of `mox.Mox().StubOutWithMock()`
|
||||
// and a bug in mox's implementation of methods results in having to
|
||||
// pass 1 too few arguments to the mocked function.
|
||||
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
|
||||
or
|
||||
callable instanceof ClassObject and
|
||||
call.getAFlowNode() = get_a_call_objectapi(callable) and
|
||||
limit = func.minParameters() - 1
|
||||
)
|
||||
}
|
||||
|
||||
/**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) {
|
||||
// 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 exists(call.getStarargs()) and
|
||||
not exists(call.getKwargs()) and
|
||||
arg_count(call) < limit and
|
||||
exists(FunctionValue func | func = get_function_or_initializer(callable) |
|
||||
call = func.getAFunctionCall().getNode() and
|
||||
limit = func.minParameters() and
|
||||
/*
|
||||
* The combination of misuse of `mox.Mox().StubOutWithMock()`
|
||||
* and a bug in mox's implementation of methods results in having to
|
||||
* pass 1 too few arguments to the mocked function.
|
||||
*/
|
||||
// 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 exists(call.getStarargs()) and
|
||||
not exists(call.getKwargs()) and
|
||||
arg_count(call) < limit and
|
||||
exists(FunctionValue func | func = get_function_or_initializer(callable) |
|
||||
call = func.getAFunctionCall().getNode() and
|
||||
limit = func.minParameters() and
|
||||
/*
|
||||
* The combination of misuse of `mox.Mox().StubOutWithMock()`
|
||||
* and a bug in mox's implementation of methods results in having to
|
||||
* pass 1 too few arguments to the mocked function.
|
||||
*/
|
||||
|
||||
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
|
||||
or
|
||||
callable instanceof ClassValue and
|
||||
call.getAFlowNode() = get_a_call(callable) and
|
||||
limit = func.minParameters() - 1
|
||||
)
|
||||
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
|
||||
or
|
||||
callable instanceof ClassValue and
|
||||
call.getAFlowNode() = get_a_call(callable) and
|
||||
limit = func.minParameters() - 1
|
||||
)
|
||||
}
|
||||
|
||||
/**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) {
|
||||
// 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
|
||||
exists(FunctionObject func |
|
||||
func = get_function_or_initializer_objectapi(callable) and
|
||||
not func.getFunction().hasVarArg() and
|
||||
limit >= 0
|
||||
|
|
||||
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
|
||||
or
|
||||
callable instanceof ClassObject and
|
||||
call.getAFlowNode() = get_a_call_objectapi(callable) and
|
||||
limit = func.maxParameters() - 1
|
||||
) and
|
||||
positional_arg_count_for_call_objectapi(call, callable) > limit
|
||||
// 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
|
||||
exists(FunctionObject func |
|
||||
func = get_function_or_initializer_objectapi(callable) and
|
||||
not func.getFunction().hasVarArg() and
|
||||
limit >= 0
|
||||
|
|
||||
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
|
||||
or
|
||||
callable instanceof ClassObject and
|
||||
call.getAFlowNode() = get_a_call_objectapi(callable) and
|
||||
limit = func.maxParameters() - 1
|
||||
) and
|
||||
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 */
|
||||
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'
|
||||
not illegally_named_parameter(call, callable, _) and
|
||||
exists(FunctionValue func |
|
||||
func = get_function_or_initializer(callable) and
|
||||
not func.getScope().hasVarArg() and
|
||||
limit >= 0
|
||||
|
|
||||
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
|
||||
or
|
||||
callable instanceof ClassValue and
|
||||
call.getAFlowNode() = get_a_call(callable) and
|
||||
limit = func.maxParameters() - 1
|
||||
) and
|
||||
positional_arg_count_for_call(call, callable) > limit
|
||||
// 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
|
||||
exists(FunctionValue func |
|
||||
func = get_function_or_initializer(callable) and
|
||||
not func.getScope().hasVarArg() and
|
||||
limit >= 0
|
||||
|
|
||||
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
|
||||
or
|
||||
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
|
||||
or
|
||||
callable instanceof ClassValue and
|
||||
call.getAFlowNode() = get_a_call(callable) and
|
||||
limit = func.maxParameters() - 1
|
||||
) and
|
||||
positional_arg_count_for_call(call, callable) > limit
|
||||
}
|
||||
|
||||
/** Holds if `call` has too many or too few arguments for `func` */
|
||||
predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) {
|
||||
too_few_args_objectapi(call, func, limit) and too = "too few"
|
||||
or
|
||||
too_many_args_objectapi(call, func, limit) and too = "too many"
|
||||
too_few_args_objectapi(call, func, limit) and too = "too few"
|
||||
or
|
||||
too_many_args_objectapi(call, func, limit) and too = "too many"
|
||||
}
|
||||
|
||||
/** Holds if `call` has too many or too few arguments for `func` */
|
||||
predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
|
||||
too_few_args(call, func, limit) and too = "too few"
|
||||
or
|
||||
too_many_args(call, func, limit) and too = "too many"
|
||||
too_few_args(call, func, limit) and too = "too few"
|
||||
or
|
||||
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]
|
||||
predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject func) {
|
||||
arg_count_objectapi(call) + 1 >= func.minParameters() and
|
||||
arg_count_objectapi(call) < func.maxParameters()
|
||||
arg_count_objectapi(call) + 1 >= func.minParameters() and
|
||||
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]
|
||||
predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
|
||||
arg_count(call) + 1 >= func.minParameters() and
|
||||
arg_count(call) < func.maxParameters()
|
||||
arg_count(call) + 1 >= func.minParameters() and
|
||||
arg_count(call) < func.maxParameters()
|
||||
}
|
||||
|
||||
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
|
||||
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
|
||||
overriding.overrides(func) and
|
||||
overriding.getACall().getNode() = call
|
||||
overriding.overrides(func) and
|
||||
overriding.getACall().getNode() = call
|
||||
}
|
||||
|
||||
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
|
||||
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
|
||||
overriding.overrides(func) and
|
||||
overriding.getACall().getNode() = call
|
||||
overriding.overrides(func) and
|
||||
overriding.getACall().getNode() = call
|
||||
}
|
||||
|
||||
/** Holds if `func` will raise a `NotImplemented` error. */
|
||||
predicate isAbstract(FunctionValue func) {
|
||||
func.getARaisedType() = ClassValue::notImplementedError()
|
||||
func.getARaisedType() = ClassValue::notImplementedError()
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ import python
|
||||
|
||||
from CallNode call_to_super, string name
|
||||
where
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
)
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
)
|
||||
)
|
||||
select call_to_super.getNode(), "First argument to super() should be " + name + "."
|
||||
|
||||
@@ -16,8 +16,8 @@ import python
|
||||
|
||||
from Compare comparison, Expr left, Expr right
|
||||
where
|
||||
comparison.compares(left, _, right) and
|
||||
left.isConstant() and
|
||||
right.isConstant() and
|
||||
not exists(Assert a | a.getTest() = comparison)
|
||||
comparison.compares(left, _, right) and
|
||||
left.isConstant() and
|
||||
right.isConstant() and
|
||||
not exists(Assert a | a.getTest() = comparison)
|
||||
select comparison, "Comparison of constants; use 'True' or 'False' instead."
|
||||
|
||||
@@ -21,9 +21,9 @@ import semmle.python.Comparisons
|
||||
*/
|
||||
|
||||
private predicate is_complex(Expr comp) {
|
||||
exists(comp.(Compare).getOp(1))
|
||||
or
|
||||
is_complex(comp.(UnaryExpr).getOperand())
|
||||
exists(comp.(Compare).getOp(1))
|
||||
or
|
||||
is_complex(comp.(UnaryExpr).getOperand())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,21 +31,21 @@ private predicate is_complex(Expr comp) {
|
||||
* strict and also controls that block.
|
||||
*/
|
||||
private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) {
|
||||
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 */
|
||||
not is_complex(controls.getTest().getNode())
|
||||
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 */
|
||||
not is_complex(controls.getTest().getNode())
|
||||
}
|
||||
|
||||
private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) {
|
||||
forex(Comparison compnode, ConditionBlock block |
|
||||
compnode.getNode() = comp and
|
||||
block.getLastNode().getNode() = previous
|
||||
|
|
||||
useless_test(compnode, block, isTrue)
|
||||
)
|
||||
forex(Comparison compnode, ConditionBlock block |
|
||||
compnode.getNode() = comp and
|
||||
block.getLastNode().getNode() = previous
|
||||
|
|
||||
useless_test(compnode, block, isTrue)
|
||||
)
|
||||
}
|
||||
|
||||
from Expr test, Expr other, boolean isTrue
|
||||
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"
|
||||
|
||||
@@ -14,21 +14,21 @@ import python
|
||||
import semmle.python.pointsto.PointsTo
|
||||
|
||||
predicate rhs_in_expr(ControlFlowNode rhs, Compare cmp) {
|
||||
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() |
|
||||
op instanceof In or op instanceof NotIn
|
||||
)
|
||||
exists(Cmpop op, int i | cmp.getOp(i) = op and cmp.getComparator(i) = rhs.getNode() |
|
||||
op instanceof In or op instanceof NotIn
|
||||
)
|
||||
}
|
||||
|
||||
from ControlFlowNode non_seq, Compare cmp, Value v, ClassValue cls, ControlFlowNode origin
|
||||
where
|
||||
rhs_in_expr(non_seq, cmp) and
|
||||
non_seq.pointsTo(_, v, origin) and
|
||||
v.getClass() = cls and
|
||||
not Types::failedInference(cls, _) and
|
||||
not cls.hasAttribute("__contains__") and
|
||||
not cls.hasAttribute("__iter__") and
|
||||
not cls.hasAttribute("__getitem__") and
|
||||
not cls = ClassValue::nonetype() and
|
||||
not cls = Value::named("types.MappingProxyType")
|
||||
rhs_in_expr(non_seq, cmp) and
|
||||
non_seq.pointsTo(_, v, origin) and
|
||||
v.getClass() = cls and
|
||||
not Types::failedInference(cls, _) and
|
||||
not cls.hasAttribute("__contains__") and
|
||||
not cls.hasAttribute("__iter__") and
|
||||
not cls.hasAttribute("__getitem__") and
|
||||
not cls = ClassValue::nonetype() and
|
||||
not cls = Value::named("types.MappingProxyType")
|
||||
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()
|
||||
|
||||
@@ -15,31 +15,31 @@ import python
|
||||
import semmle.python.strings
|
||||
|
||||
predicate dict_key(Dict d, Expr k, string s) {
|
||||
k = d.getAKey() and
|
||||
(
|
||||
s = k.(Num).getN()
|
||||
or
|
||||
// We use <20> to mark unrepresentable characters
|
||||
// so two instances of <20> may represent different strings in the source code
|
||||
not "<22>" = s.charAt(_) and
|
||||
exists(StrConst c | c = k |
|
||||
s = "u\"" + c.getText() + "\"" and c.isUnicode()
|
||||
or
|
||||
s = "b\"" + c.getText() + "\"" and not c.isUnicode()
|
||||
)
|
||||
k = d.getAKey() and
|
||||
(
|
||||
s = k.(Num).getN()
|
||||
or
|
||||
// We use <20> to mark unrepresentable characters
|
||||
// so two instances of <20> may represent different strings in the source code
|
||||
not "<22>" = s.charAt(_) and
|
||||
exists(StrConst c | c = k |
|
||||
s = "u\"" + c.getText() + "\"" and c.isUnicode()
|
||||
or
|
||||
s = "b\"" + c.getText() + "\"" and not c.isUnicode()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from Dict d, Expr k1, Expr k2
|
||||
where
|
||||
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 |
|
||||
k1.getAFlowNode() = b.getNode(i1) and
|
||||
k2.getAFlowNode() = b.getNode(i2) and
|
||||
i1 < i2
|
||||
)
|
||||
or
|
||||
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
|
||||
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 |
|
||||
k1.getAFlowNode() = b.getNode(i1) and
|
||||
k2.getAFlowNode() = b.getNode(i2) and
|
||||
i1 < i2
|
||||
)
|
||||
or
|
||||
k1.getAFlowNode().getBasicBlock().strictlyDominates(k2.getAFlowNode().getBasicBlock())
|
||||
)
|
||||
select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten"
|
||||
|
||||
@@ -15,12 +15,12 @@ import semmle.python.strings
|
||||
|
||||
from Expr e, ClassValue t
|
||||
where
|
||||
exists(BinaryExpr b |
|
||||
b.getOp() instanceof Mod and
|
||||
format_string(b.getLeft()) and
|
||||
e = b.getRight() and
|
||||
mapping_format(b.getLeft()) and
|
||||
e.pointsTo().getClass() = t and
|
||||
not t.isMapping()
|
||||
)
|
||||
exists(BinaryExpr b |
|
||||
b.getOp() instanceof Mod and
|
||||
format_string(b.getLeft()) and
|
||||
e = b.getRight() and
|
||||
mapping_format(b.getLeft()) and
|
||||
e.pointsTo().getClass() = t and
|
||||
not t.isMapping()
|
||||
)
|
||||
select e, "Right hand side of a % operator must be a mapping, not class $@.", t, t.getName()
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
import python
|
||||
|
||||
class DelCall extends Call {
|
||||
DelCall() { this.getFunc().(Attribute).getName() = "__del__" }
|
||||
DelCall() { this.getFunc().(Attribute).getName() = "__del__" }
|
||||
|
||||
predicate isSuperCall() {
|
||||
exists(Function f | f = this.getScope() and f.getName() = "__del__" |
|
||||
// We pass in `self` as the first argument...
|
||||
f.getArg(0).asName().getVariable() = this.getArg(0).(Name).getVariable()
|
||||
or
|
||||
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
|
||||
// Python 3: `super().__del__()`.
|
||||
exists(Call superCall | superCall = this.getFunc().(Attribute).getObject() |
|
||||
superCall.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
)
|
||||
}
|
||||
predicate isSuperCall() {
|
||||
exists(Function f | f = this.getScope() and f.getName() = "__del__" |
|
||||
// We pass in `self` as the first argument...
|
||||
f.getArg(0).asName().getVariable() = this.getArg(0).(Name).getVariable()
|
||||
or
|
||||
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
|
||||
// Python 3: `super().__del__()`.
|
||||
exists(Call superCall | superCall = this.getFunc().(Attribute).getObject() |
|
||||
superCall.getFunc().(Name).getId() = "super"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DelCall del
|
||||
|
||||
@@ -2,125 +2,125 @@ import python
|
||||
|
||||
/** A string constant that looks like it may be used in string formatting operations. */
|
||||
library class PossibleAdvancedFormatString extends StrConst {
|
||||
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
|
||||
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
|
||||
|
||||
private predicate field(int start, int end) {
|
||||
brace_pair(this, start, end) and
|
||||
this.getText().substring(start, end) != "{{}}"
|
||||
}
|
||||
private predicate field(int start, int end) {
|
||||
brace_pair(this, start, end) and
|
||||
this.getText().substring(start, end) != "{{}}"
|
||||
}
|
||||
|
||||
/** Gets the number of the formatting field at [start, end) */
|
||||
int getFieldNumber(int start, int end) {
|
||||
result = this.fieldId(start, end).toInt()
|
||||
or
|
||||
this.implicitlyNumberedField(start, end) and
|
||||
result = count(int s | this.implicitlyNumberedField(s, _) and s < start)
|
||||
}
|
||||
/** Gets the number of the formatting field at [start, end) */
|
||||
int getFieldNumber(int start, int end) {
|
||||
result = this.fieldId(start, end).toInt()
|
||||
or
|
||||
this.implicitlyNumberedField(start, end) and
|
||||
result = count(int s | this.implicitlyNumberedField(s, _) and s < start)
|
||||
}
|
||||
|
||||
/** Gets the text of the formatting field at [start, end) */
|
||||
string getField(int start, int end) {
|
||||
this.field(start, end) and
|
||||
result = this.getText().substring(start, end)
|
||||
}
|
||||
/** Gets the text of the formatting field at [start, end) */
|
||||
string getField(int start, int end) {
|
||||
this.field(start, end) and
|
||||
result = this.getText().substring(start, end)
|
||||
}
|
||||
|
||||
private string fieldId(int start, int end) {
|
||||
this.field(start, end) and
|
||||
(
|
||||
result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1)
|
||||
or
|
||||
result = this.getText().substring(start + 1, end - 1) and result.regexpMatch("[^!:.\\[]+")
|
||||
)
|
||||
}
|
||||
private string fieldId(int start, int end) {
|
||||
this.field(start, end) and
|
||||
(
|
||||
result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1)
|
||||
or
|
||||
result = this.getText().substring(start + 1, end - 1) and result.regexpMatch("[^!:.\\[]+")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the name of the formatting field at [start, end) */
|
||||
string getFieldName(int start, int end) {
|
||||
result = this.fieldId(start, end) and
|
||||
not exists(this.getFieldNumber(start, end))
|
||||
}
|
||||
/** Gets the name of the formatting field at [start, end) */
|
||||
string getFieldName(int start, int end) {
|
||||
result = this.fieldId(start, end) and
|
||||
not exists(this.getFieldNumber(start, end))
|
||||
}
|
||||
|
||||
private predicate implicitlyNumberedField(int start, int end) {
|
||||
this.field(start, end) and
|
||||
exists(string c | start + 1 = this.getText().indexOf(c) |
|
||||
c = "}" or c = ":" or c = "!" or c = "."
|
||||
)
|
||||
}
|
||||
private predicate implicitlyNumberedField(int start, int end) {
|
||||
this.field(start, end) and
|
||||
exists(string c | start + 1 = this.getText().indexOf(c) |
|
||||
c = "}" or c = ":" or c = "!" or c = "."
|
||||
)
|
||||
}
|
||||
|
||||
/** Whether this format string has implicitly numbered fields */
|
||||
predicate isImplicitlyNumbered() { this.implicitlyNumberedField(_, _) }
|
||||
/** Whether this format string has implicitly numbered fields */
|
||||
predicate isImplicitlyNumbered() { this.implicitlyNumberedField(_, _) }
|
||||
|
||||
/** Whether this format string has explicitly numbered fields */
|
||||
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
|
||||
/** Whether this format string has explicitly numbered fields */
|
||||
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
exists(string text | text = fmt.getText() |
|
||||
text.charAt(index) = "{" and not text.charAt(index - 1) = "{" and len = 1
|
||||
or
|
||||
text.charAt(index) = "{" and
|
||||
text.charAt(index - 1) = "{" and
|
||||
brace_sequence(fmt, index - 1, len - 1)
|
||||
)
|
||||
exists(string text | text = fmt.getText() |
|
||||
text.charAt(index) = "{" and not text.charAt(index - 1) = "{" and len = 1
|
||||
or
|
||||
text.charAt(index) = "{" and
|
||||
text.charAt(index - 1) = "{" and
|
||||
brace_sequence(fmt, index - 1, len - 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if index `index` in the format string `fmt` contains an escaped brace `{`. */
|
||||
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. */
|
||||
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) {
|
||||
not escaping_brace(fmt, start) and
|
||||
not escaped_brace(fmt, start) and
|
||||
fmt.getText().charAt(start) = "{" and
|
||||
exists(string pair |
|
||||
pair = fmt.getText().suffix(start).regexpCapture("(?s)(\\{([^{}]|\\{\\{)*+\\}).*", 1)
|
||||
|
|
||||
end = start + pair.length()
|
||||
)
|
||||
not escaping_brace(fmt, start) and
|
||||
not escaped_brace(fmt, start) and
|
||||
fmt.getText().charAt(start) = "{" and
|
||||
exists(string pair |
|
||||
pair = fmt.getText().suffix(start).regexpCapture("(?s)(\\{([^{}]|\\{\\{)*+\\}).*", 1)
|
||||
|
|
||||
end = start + pair.length()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int end) {
|
||||
inner_brace_pair(fmt, start, end)
|
||||
or
|
||||
not escaping_brace(fmt, start) and
|
||||
not escaped_brace(fmt, start) and
|
||||
exists(string prefix, string postfix, int innerstart, int innerend |
|
||||
brace_pair(fmt, innerstart, innerend) and
|
||||
prefix = fmt.getText().regexpFind("\\{([^{}]|\\{\\{)+\\{", _, start) and
|
||||
innerstart = start + prefix.length() - 1 and
|
||||
postfix = fmt.getText().regexpFind("\\}([^{}]|\\}\\})*\\}", _, innerend - 1) and
|
||||
end = innerend + postfix.length() - 1
|
||||
)
|
||||
inner_brace_pair(fmt, start, end)
|
||||
or
|
||||
not escaping_brace(fmt, start) and
|
||||
not escaped_brace(fmt, start) and
|
||||
exists(string prefix, string postfix, int innerstart, int innerend |
|
||||
brace_pair(fmt, innerstart, innerend) and
|
||||
prefix = fmt.getText().regexpFind("\\{([^{}]|\\{\\{)+\\{", _, start) and
|
||||
innerstart = start + prefix.length() - 1 and
|
||||
postfix = fmt.getText().regexpFind("\\}([^{}]|\\}\\})*\\}", _, innerend - 1) and
|
||||
end = innerend + postfix.length() - 1
|
||||
)
|
||||
}
|
||||
|
||||
private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) {
|
||||
exists(CallNode call | call = format_expr.getAFlowNode() |
|
||||
call.getFunction().pointsTo(Value::named("format")) and
|
||||
call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and
|
||||
args = count(format_expr.getAnArg()) - 1
|
||||
or
|
||||
call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and
|
||||
args = count(format_expr.getAnArg())
|
||||
)
|
||||
exists(CallNode call | call = format_expr.getAFlowNode() |
|
||||
call.getFunction().pointsTo(Value::named("format")) and
|
||||
call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and
|
||||
args = count(format_expr.getAnArg()) - 1
|
||||
or
|
||||
call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and
|
||||
args = count(format_expr.getAnArg())
|
||||
)
|
||||
}
|
||||
|
||||
/** A string constant that has the `format` method applied to it. */
|
||||
class AdvancedFormatString extends PossibleAdvancedFormatString {
|
||||
AdvancedFormatString() { advanced_format_call(_, this, _) }
|
||||
AdvancedFormatString() { advanced_format_call(_, this, _) }
|
||||
}
|
||||
|
||||
/** A string formatting operation that uses the `format` method. */
|
||||
class AdvancedFormattingCall extends Call {
|
||||
AdvancedFormattingCall() { advanced_format_call(this, _, _) }
|
||||
AdvancedFormattingCall() { advanced_format_call(this, _, _) }
|
||||
|
||||
/** Count of the arguments actually provided */
|
||||
int providedArgCount() { advanced_format_call(this, _, result) }
|
||||
/** Count of the arguments actually provided */
|
||||
int providedArgCount() { advanced_format_call(this, _, result) }
|
||||
|
||||
/** Gets a formatting string for this call. */
|
||||
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
|
||||
/** Gets a formatting string for this call. */
|
||||
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@ int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field
|
||||
where
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = field_count(fmt) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count)
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = field_count(fmt) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count)
|
||||
select call,
|
||||
"Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
|
||||
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""
|
||||
"Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
|
||||
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -15,17 +15,17 @@ import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr
|
||||
where
|
||||
call.getAFormat() = fmt and
|
||||
name = call.getAKeyword().getArg() and
|
||||
forall(AdvancedFormatString format | format = call.getAFormat() |
|
||||
not format.getFieldName(_, _) = name
|
||||
) and
|
||||
not exists(call.getKwargs()) and
|
||||
(
|
||||
strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\""
|
||||
or
|
||||
strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used."
|
||||
)
|
||||
call.getAFormat() = fmt and
|
||||
name = call.getAKeyword().getArg() and
|
||||
forall(AdvancedFormatString format | format = call.getAFormat() |
|
||||
not format.getFieldName(_, _) = name
|
||||
) and
|
||||
not exists(call.getKwargs()) and
|
||||
(
|
||||
strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\""
|
||||
or
|
||||
strictcount(call.getAFormat()) != 1 and fmt_repr = "any format used."
|
||||
)
|
||||
select call,
|
||||
"Surplus named argument for string format. An argument named '" + name +
|
||||
"' is provided, but it is not required by $@.", fmt, fmt_repr
|
||||
"Surplus named argument for string format. An argument named '" + name +
|
||||
"' is provided, but it is not required by $@.", fmt, fmt_repr
|
||||
|
||||
@@ -16,10 +16,10 @@ import AdvancedFormatting
|
||||
|
||||
from AdvancedFormattingCall call, AdvancedFormatString fmt, string name
|
||||
where
|
||||
call.getAFormat() = fmt and
|
||||
not name = call.getAKeyword().getArg() and
|
||||
fmt.getFieldName(_, _) = name and
|
||||
not exists(call.getKwargs())
|
||||
call.getAFormat() = fmt and
|
||||
not name = call.getAKeyword().getArg() and
|
||||
fmt.getFieldName(_, _) = name and
|
||||
not exists(call.getKwargs())
|
||||
select call,
|
||||
"Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
|
||||
fmt, "\"" + fmt.getText() + "\""
|
||||
"Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
|
||||
fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -15,15 +15,15 @@ import python
|
||||
import AdvancedFormatting
|
||||
|
||||
from
|
||||
AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field,
|
||||
string provided
|
||||
AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field,
|
||||
string provided
|
||||
where
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = max(fmt.getFieldNumber(_, _)) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
arg_count <= max_field and
|
||||
(if arg_count = 1 then provided = " is provided." else provided = " are provided.")
|
||||
arg_count = call.providedArgCount() and
|
||||
max_field = max(fmt.getFieldNumber(_, _)) and
|
||||
call.getAFormat() = fmt and
|
||||
not exists(call.getStarargs()) and
|
||||
arg_count <= max_field and
|
||||
(if arg_count = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select call,
|
||||
"Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " +
|
||||
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""
|
||||
"Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " +
|
||||
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""
|
||||
|
||||
@@ -19,39 +19,39 @@ import python
|
||||
*/
|
||||
|
||||
predicate numpy_array_type(ClassValue na) {
|
||||
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
|
||||
na.getASuperType() = np.attr("ndarray")
|
||||
)
|
||||
exists(ModuleValue np | np.getName() = "numpy" or np.getName() = "numpy.core" |
|
||||
na.getASuperType() = np.attr("ndarray")
|
||||
)
|
||||
}
|
||||
|
||||
predicate has_custom_getitem(Value v) {
|
||||
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
|
||||
or
|
||||
numpy_array_type(v.getClass())
|
||||
v.getClass().lookup("__getitem__") instanceof PythonFunctionValue
|
||||
or
|
||||
numpy_array_type(v.getClass())
|
||||
}
|
||||
|
||||
predicate explicitly_hashed(ControlFlowNode f) {
|
||||
exists(CallNode c, GlobalVariable hash |
|
||||
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
|
||||
)
|
||||
exists(CallNode c, GlobalVariable hash |
|
||||
c.getArg(0) = f and c.getFunction().(NameNode).uses(hash) and hash.getId() = "hash"
|
||||
)
|
||||
}
|
||||
|
||||
predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode origin) {
|
||||
is_unhashable(f, c, origin) and
|
||||
exists(SubscriptNode sub | sub.getIndex() = f |
|
||||
exists(Value custom_getitem |
|
||||
sub.getObject().pointsTo(custom_getitem) and
|
||||
not has_custom_getitem(custom_getitem)
|
||||
)
|
||||
is_unhashable(f, c, origin) and
|
||||
exists(SubscriptNode sub | sub.getIndex() = f |
|
||||
exists(Value custom_getitem |
|
||||
sub.getObject().pointsTo(custom_getitem) and
|
||||
not has_custom_getitem(custom_getitem)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
|
||||
exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls |
|
||||
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
|
||||
or
|
||||
cls.lookup("__hash__") = Value::named("None")
|
||||
)
|
||||
exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls |
|
||||
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
|
||||
or
|
||||
cls.lookup("__hash__") = Value::named("None")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,18 +68,18 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
|
||||
* it.
|
||||
*/
|
||||
predicate typeerror_is_caught(ControlFlowNode f) {
|
||||
exists(Try try |
|
||||
try.getBody().contains(f.getNode()) and
|
||||
try.getAHandler().getType().pointsTo(ClassValue::typeError())
|
||||
)
|
||||
exists(Try try |
|
||||
try.getBody().contains(f.getNode()) and
|
||||
try.getAHandler().getType().pointsTo(ClassValue::typeError())
|
||||
)
|
||||
}
|
||||
|
||||
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
|
||||
where
|
||||
not typeerror_is_caught(f) and
|
||||
(
|
||||
explicitly_hashed(f) and is_unhashable(f, c, origin)
|
||||
or
|
||||
unhashable_subscript(f, c, origin)
|
||||
)
|
||||
not typeerror_is_caught(f) and
|
||||
(
|
||||
explicitly_hashed(f) and is_unhashable(f, c, origin)
|
||||
or
|
||||
unhashable_subscript(f, c, origin)
|
||||
)
|
||||
select f.getNode(), "This $@ of $@ is unhashable.", origin, "instance", c, c.getQualifiedName()
|
||||
|
||||
@@ -15,13 +15,13 @@ import IsComparisons
|
||||
|
||||
from Compare comp, Cmpop op, ClassValue c, string alt
|
||||
where
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
not cpython_interned_constant(comp.getASubExpression()) and
|
||||
(
|
||||
op instanceof Is and alt = "=="
|
||||
or
|
||||
op instanceof IsNot and alt = "!="
|
||||
)
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
not cpython_interned_constant(comp.getASubExpression()) and
|
||||
(
|
||||
op instanceof Is and alt = "=="
|
||||
or
|
||||
op instanceof IsNot and alt = "!="
|
||||
)
|
||||
select comp,
|
||||
"Values compared using '" + op.getSymbol() +
|
||||
"' when equivalence is not the same as identity. Use '" + alt + "' instead."
|
||||
"Values compared using '" + op.getSymbol() +
|
||||
"' when equivalence is not the same as identity. Use '" + alt + "' instead."
|
||||
|
||||
@@ -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. */
|
||||
predicate comparison_using_is(Compare comp, ControlFlowNode left, Cmpop op, ControlFlowNode right) {
|
||||
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
|
||||
fcomp.operands(left, op, right) and
|
||||
(op instanceof Is or op instanceof IsNot)
|
||||
)
|
||||
exists(CompareNode fcomp | fcomp = comp.getAFlowNode() |
|
||||
fcomp.operands(left, op, right) and
|
||||
(op instanceof Is or op instanceof IsNot)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the class `c` overrides the default notion of equality or comparison. */
|
||||
predicate overrides_eq_or_cmp(ClassValue c) {
|
||||
major_version() = 2 and c.hasAttribute("__eq__")
|
||||
or
|
||||
c.declaresAttribute("__eq__") and not c = Value::named("object")
|
||||
or
|
||||
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
sup.declaresAttribute("__eq__")
|
||||
)
|
||||
or
|
||||
major_version() = 2 and c.hasAttribute("__cmp__")
|
||||
major_version() = 2 and c.hasAttribute("__eq__")
|
||||
or
|
||||
c.declaresAttribute("__eq__") and not c = Value::named("object")
|
||||
or
|
||||
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
|
||||
sup.declaresAttribute("__eq__")
|
||||
)
|
||||
or
|
||||
major_version() = 2 and c.hasAttribute("__cmp__")
|
||||
}
|
||||
|
||||
/** Holds if the class `cls` is likely to only have a single instance throughout the program. */
|
||||
predicate probablySingleton(ClassValue cls) {
|
||||
strictcount(Value inst | inst.getClass() = cls) = 1
|
||||
or
|
||||
cls = Value::named("None").getClass()
|
||||
strictcount(Value inst | inst.getClass() = cls) = 1
|
||||
or
|
||||
cls = Value::named("None").getClass()
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
overrides_eq_or_cmp(c) and
|
||||
// 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 = ClassValue::builtinFunction() and
|
||||
not c = Value::named("bool") and
|
||||
// OK to compare with 'is' if a singleton
|
||||
not probablySingleton(c)
|
||||
overrides_eq_or_cmp(c) and
|
||||
// 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 = ClassValue::builtinFunction() and
|
||||
not c = Value::named("bool") and
|
||||
// OK to compare with 'is' if a singleton
|
||||
not probablySingleton(c)
|
||||
}
|
||||
|
||||
/** Holds if the control flow node `f` points to either `True`, `False`, or `None`. */
|
||||
predicate simple_constant(ControlFlowNode f) {
|
||||
exists(Value val | f.pointsTo(val) |
|
||||
val = Value::named("True") or val = Value::named("False") or val = Value::named("None")
|
||||
)
|
||||
exists(Value val | f.pointsTo(val) |
|
||||
val = Value::named("True") or val = Value::named("False") or val = Value::named("None")
|
||||
)
|
||||
}
|
||||
|
||||
private predicate cpython_interned_value(Expr e) {
|
||||
exists(string text | text = e.(StrConst).getText() |
|
||||
text.length() = 0
|
||||
or
|
||||
text.length() = 1 and text.regexpMatch("[U+0000-U+00ff]")
|
||||
)
|
||||
exists(string text | text = e.(StrConst).getText() |
|
||||
text.length() = 0
|
||||
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()))
|
||||
text.length() = 1 and text.regexpMatch("[U+0000-U+00ff]")
|
||||
)
|
||||
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.
|
||||
*/
|
||||
private predicate universally_interned_value(Expr e) {
|
||||
e.(IntegerLiteral).getN().toInt() = 0
|
||||
or
|
||||
exists(Tuple t | t = e and not exists(t.getAnElt()))
|
||||
or
|
||||
e.(StrConst).getText() = ""
|
||||
e.(IntegerLiteral).getN().toInt() = 0
|
||||
or
|
||||
exists(Tuple t | t = e and not exists(t.getAnElt()))
|
||||
or
|
||||
e.(StrConst).getText() = ""
|
||||
}
|
||||
|
||||
/** Holds if the expression `e` points to an interned constant in CPython. */
|
||||
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. */
|
||||
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) {
|
||||
exists(ControlFlowNode op1, ControlFlowNode op2 |
|
||||
comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1)
|
||||
|
|
||||
op1.inferredValue().getClass() = cls1 and
|
||||
op2.inferredValue().getClass() = cls2
|
||||
)
|
||||
exists(ControlFlowNode op1, ControlFlowNode op2 |
|
||||
comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1)
|
||||
|
|
||||
op1.inferredValue().getClass() = cls1 and
|
||||
op2.inferredValue().getClass() = cls2
|
||||
)
|
||||
}
|
||||
|
||||
private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
|
||||
not comparison_both_types(comp, _, _, _) and
|
||||
exists(ControlFlowNode operand |
|
||||
comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand)
|
||||
|
|
||||
operand.inferredValue().getClass() = cls
|
||||
)
|
||||
not comparison_both_types(comp, _, _, _) and
|
||||
exists(ControlFlowNode operand |
|
||||
comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand)
|
||||
|
|
||||
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) {
|
||||
// OK to use 'is' when defining '__eq__'
|
||||
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" |
|
||||
eq = comp.getScope().getScope*()
|
||||
) and
|
||||
(
|
||||
comparison_one_type(comp, op, cls) and invalid_to_use_is_portably(cls)
|
||||
or
|
||||
exists(ClassValue other | comparison_both_types(comp, op, cls, other) |
|
||||
invalid_to_use_is_portably(cls) and
|
||||
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)
|
||||
// OK to use 'is' when defining '__eq__'
|
||||
not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" |
|
||||
eq = comp.getScope().getScope*()
|
||||
) and
|
||||
(
|
||||
comparison_one_type(comp, op, cls) and invalid_to_use_is_portably(cls)
|
||||
or
|
||||
exists(ClassValue other | comparison_both_types(comp, op, cls, other) |
|
||||
invalid_to_use_is_portably(cls) and
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate enum_member(AstNode obj) {
|
||||
exists(ClassValue cls, AssignStmt asgn | cls.getASuperType().getName() = "Enum" |
|
||||
cls.getScope() = asgn.getScope() and
|
||||
asgn.getValue() = obj
|
||||
)
|
||||
exists(ClassValue cls, AssignStmt asgn | cls.getASuperType().getName() = "Enum" |
|
||||
cls.getScope() = asgn.getScope() and
|
||||
asgn.getValue() = obj
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ import Exceptions.NotImplemented
|
||||
|
||||
from Call c, Value v, ClassValue t, Expr f, AstNode origin
|
||||
where
|
||||
f = c.getFunc() and
|
||||
f.pointsTo(v, origin) and
|
||||
t = v.getClass() and
|
||||
not t.isCallable() and
|
||||
not t.failedInference(_) and
|
||||
not t.hasAttribute("__get__") and
|
||||
not v = Value::named("None") and
|
||||
not use_of_not_implemented_in_raise(_, f)
|
||||
f = c.getFunc() and
|
||||
f.pointsTo(v, origin) and
|
||||
t = v.getClass() and
|
||||
not t.isCallable() and
|
||||
not t.failedInference(_) and
|
||||
not t.hasAttribute("__get__") and
|
||||
not v = Value::named("None") and
|
||||
not use_of_not_implemented_in_raise(_, f)
|
||||
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()
|
||||
|
||||
@@ -15,11 +15,11 @@ import IsComparisons
|
||||
|
||||
from Compare comp, Cmpop op, ClassValue c
|
||||
where
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
exists(Expr sub | sub = comp.getASubExpression() |
|
||||
cpython_interned_constant(sub) and
|
||||
not universally_interned_constant(sub)
|
||||
)
|
||||
invalid_portable_is_comparison(comp, op, c) and
|
||||
exists(Expr sub | sub = comp.getASubExpression() |
|
||||
cpython_interned_constant(sub) and
|
||||
not universally_interned_constant(sub)
|
||||
)
|
||||
select comp,
|
||||
"The result of this comparison with '" + op.getSymbol() +
|
||||
"' may differ between implementations of Python."
|
||||
"The result of this comparison with '" + op.getSymbol() +
|
||||
"' may differ between implementations of Python."
|
||||
|
||||
@@ -4,53 +4,54 @@ import python
|
||||
|
||||
/** A comparison where the left and right hand sides appear to be identical. */
|
||||
class RedundantComparison extends Compare {
|
||||
RedundantComparison() {
|
||||
exists(Expr left, Expr right |
|
||||
this.compares(left, _, right) and
|
||||
same_variable(left, right)
|
||||
)
|
||||
}
|
||||
RedundantComparison() {
|
||||
exists(Expr left, Expr right |
|
||||
this.compares(left, _, right) and
|
||||
same_variable(left, right)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this comparison could be redundant due to a missing `self.`, for example
|
||||
* ```python
|
||||
* foo == foo
|
||||
* ```
|
||||
* instead of
|
||||
* ```python
|
||||
* self.foo == foo
|
||||
* ```
|
||||
*/
|
||||
predicate maybeMissingSelf() {
|
||||
exists(Name left |
|
||||
this.compares(left, _, _) and
|
||||
not this.isConstant() and
|
||||
exists(Class cls | left.getScope().getScope() = cls |
|
||||
exists(SelfAttribute sa | sa.getName() = left.getId() | sa.getClass() = cls)
|
||||
)
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Holds if this comparison could be redundant due to a missing `self.`, for example
|
||||
* ```python
|
||||
* foo == foo
|
||||
* ```
|
||||
* instead of
|
||||
* ```python
|
||||
* self.foo == foo
|
||||
* ```
|
||||
*/
|
||||
predicate maybeMissingSelf() {
|
||||
exists(Name left |
|
||||
this.compares(left, _, _) and
|
||||
not this.isConstant() and
|
||||
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) {
|
||||
same_name(left, right)
|
||||
or
|
||||
same_attribute(left, right)
|
||||
same_name(left, right)
|
||||
or
|
||||
same_attribute(left, right)
|
||||
}
|
||||
|
||||
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) {
|
||||
n1 != n2 and
|
||||
exists(Compare comp, Variable v |
|
||||
name_in_comparison(comp, n1, v) and name_in_comparison(comp, n2, v)
|
||||
)
|
||||
n1 != n2 and
|
||||
exists(Compare comp, Variable v |
|
||||
name_in_comparison(comp, n1, v) and name_in_comparison(comp, n2, v)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate same_attribute(Attribute a1, Attribute a2) {
|
||||
a1 != a2 and
|
||||
exists(Compare comp | comp.contains(a1) and comp.contains(a2)) and
|
||||
a1.getName() = a2.getName() and
|
||||
same_name(a1.getObject(), a2.getObject())
|
||||
a1 != a2 and
|
||||
exists(Compare comp | comp.contains(a1) and comp.contains(a2)) and
|
||||
a1.getName() = a2.getName() and
|
||||
same_name(a1.getObject(), a2.getObject())
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import semmle.python.regex
|
||||
|
||||
from Regex r, int offset
|
||||
where
|
||||
r.escapingChar(offset) and
|
||||
r.getChar(offset + 1) = "b" and
|
||||
exists(int start, int end | start < offset and end > offset | r.charSet(start, end))
|
||||
r.escapingChar(offset) and
|
||||
r.getChar(offset + 1) = "b" and
|
||||
exists(int start, int end | start < offset and end > offset | r.charSet(start, end))
|
||||
select r, "Backspace escape in regular expression at offset " + offset + "."
|
||||
|
||||
@@ -14,29 +14,29 @@ import python
|
||||
import semmle.python.regex
|
||||
|
||||
predicate duplicate_char_in_class(Regex r, string char) {
|
||||
exists(int i, int j, int x, int y, int start, int end |
|
||||
i != x and
|
||||
j != y and
|
||||
start < i and
|
||||
j < end and
|
||||
start < x and
|
||||
y < end and
|
||||
r.character(i, j) and
|
||||
char = r.getText().substring(i, j) and
|
||||
r.character(x, y) and
|
||||
char = r.getText().substring(x, y) and
|
||||
r.charSet(start, end)
|
||||
) and
|
||||
/* Exclude <20> as we use it for any unencodable character */
|
||||
char != "<22>" and
|
||||
//Ignore whitespace in verbose mode
|
||||
not (
|
||||
r.getAMode() = "VERBOSE" and
|
||||
(char = " " or char = "\t" or char = "\r" or char = "\n")
|
||||
)
|
||||
exists(int i, int j, int x, int y, int start, int end |
|
||||
i != x and
|
||||
j != y and
|
||||
start < i and
|
||||
j < end and
|
||||
start < x and
|
||||
y < end and
|
||||
r.character(i, j) and
|
||||
char = r.getText().substring(i, j) and
|
||||
r.character(x, y) and
|
||||
char = r.getText().substring(x, y) and
|
||||
r.charSet(start, end)
|
||||
) and
|
||||
/* Exclude <20> as we use it for any unencodable character */
|
||||
char != "<22>" and
|
||||
//Ignore whitespace in verbose mode
|
||||
not (
|
||||
r.getAMode() = "VERBOSE" and
|
||||
(char = " " or char = "\t" or char = "\r" or char = "\n")
|
||||
)
|
||||
}
|
||||
|
||||
from Regex r, string char
|
||||
where duplicate_char_in_class(r, char)
|
||||
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."
|
||||
|
||||
@@ -14,13 +14,13 @@ import python
|
||||
import semmle.python.regex
|
||||
|
||||
predicate unmatchable_caret(Regex r, int start) {
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start + 1, "^") and
|
||||
not r.firstItem(start, start + 1)
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start + 1, "^") and
|
||||
not r.firstItem(start, start + 1)
|
||||
}
|
||||
|
||||
from Regex r, int offset
|
||||
where unmatchable_caret(r, offset)
|
||||
select r,
|
||||
"This regular expression includes an unmatchable caret at offset " + offset.toString() + "."
|
||||
"This regular expression includes an unmatchable caret at offset " + offset.toString() + "."
|
||||
|
||||
@@ -14,13 +14,13 @@ import python
|
||||
import semmle.python.regex
|
||||
|
||||
predicate unmatchable_dollar(Regex r, int start) {
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start + 1, "$") and
|
||||
not r.lastItem(start, start + 1)
|
||||
not r.getAMode() = "MULTILINE" and
|
||||
not r.getAMode() = "VERBOSE" and
|
||||
r.specialCharacter(start, start + 1, "$") and
|
||||
not r.lastItem(start, start + 1)
|
||||
}
|
||||
|
||||
from Regex r, int offset
|
||||
where unmatchable_dollar(r, offset)
|
||||
select r,
|
||||
"This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."
|
||||
"This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."
|
||||
|
||||
@@ -15,23 +15,23 @@ import python
|
||||
|
||||
from BinaryExpr div, ControlFlowNode left, ControlFlowNode right
|
||||
where
|
||||
// Only relevant for Python 2, as all later versions implement true division
|
||||
major_version() = 2 and
|
||||
exists(BinaryExprNode bin, Value lval, Value rval |
|
||||
bin = div.getAFlowNode() and
|
||||
bin.getNode().getOp() instanceof Div and
|
||||
bin.getLeft().pointsTo(lval, left) and
|
||||
lval.getClass() = ClassValue::int_() and
|
||||
bin.getRight().pointsTo(rval, right) and
|
||||
rval.getClass() = ClassValue::int_() and
|
||||
// Ignore instances where integer division leaves no remainder
|
||||
not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and
|
||||
not bin.getNode().getEnclosingModule().hasFromFuture("division") and
|
||||
// Filter out results wrapped in `int(...)`
|
||||
not exists(CallNode c |
|
||||
c = ClassValue::int_().getACall() and
|
||||
c.getAnArg() = bin
|
||||
)
|
||||
// Only relevant for Python 2, as all later versions implement true division
|
||||
major_version() = 2 and
|
||||
exists(BinaryExprNode bin, Value lval, Value rval |
|
||||
bin = div.getAFlowNode() and
|
||||
bin.getNode().getOp() instanceof Div and
|
||||
bin.getLeft().pointsTo(lval, left) and
|
||||
lval.getClass() = ClassValue::int_() and
|
||||
bin.getRight().pointsTo(rval, right) and
|
||||
rval.getClass() = ClassValue::int_() and
|
||||
// Ignore instances where integer division leaves no remainder
|
||||
not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and
|
||||
not bin.getNode().getEnclosingModule().hasFromFuture("division") and
|
||||
// Filter out results wrapped in `int(...)`
|
||||
not exists(CallNode c |
|
||||
c = ClassValue::int_().getACall() and
|
||||
c.getAnArg() = bin
|
||||
)
|
||||
)
|
||||
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"
|
||||
|
||||
@@ -15,20 +15,20 @@
|
||||
import python
|
||||
|
||||
predicate string_const(Expr s) {
|
||||
s instanceof StrConst
|
||||
or
|
||||
string_const(s.(BinaryExpr).getLeft()) and string_const(s.(BinaryExpr).getRight())
|
||||
s instanceof StrConst
|
||||
or
|
||||
string_const(s.(BinaryExpr).getLeft()) and string_const(s.(BinaryExpr).getRight())
|
||||
}
|
||||
|
||||
from StrConst s
|
||||
where
|
||||
// Implicitly concatenated string is in a list and that list contains at least one other string.
|
||||
exists(List l, Expr other |
|
||||
not s = other and
|
||||
l.getAnElt() = s and
|
||||
l.getAnElt() = other and
|
||||
string_const(other)
|
||||
) and
|
||||
exists(s.getAnImplicitlyConcatenatedPart()) and
|
||||
not s.isParenthesized()
|
||||
// Implicitly concatenated string is in a list and that list contains at least one other string.
|
||||
exists(List l, Expr other |
|
||||
not s = other and
|
||||
l.getAnElt() = s and
|
||||
l.getAnElt() = other and
|
||||
string_const(other)
|
||||
) and
|
||||
exists(s.getAnImplicitlyConcatenatedPart()) and
|
||||
not s.isParenthesized()
|
||||
select s, "Implicit string concatenation. Maybe missing a comma?"
|
||||
|
||||
@@ -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 */
|
||||
predicate simple_wrapper(Lambda l, Expr wrapped) {
|
||||
exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() |
|
||||
wrapped = c.getFunc() and
|
||||
count(f.getAnArg()) = count(c.getAnArg()) and
|
||||
forall(int arg | exists(f.getArg(arg)) | f.getArgName(arg) = c.getArg(arg).(Name).getId()) and
|
||||
/* Either no **kwargs or they must match */
|
||||
(
|
||||
not exists(f.getKwarg()) and not exists(c.getKwargs())
|
||||
or
|
||||
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())
|
||||
exists(Function f, Call c | f = l.getInnerScope() and c = l.getExpression() |
|
||||
wrapped = c.getFunc() and
|
||||
count(f.getAnArg()) = count(c.getAnArg()) and
|
||||
forall(int arg | exists(f.getArg(arg)) | f.getArgName(arg) = c.getArg(arg).(Name).getId()) and
|
||||
/* Either no **kwargs or they must match */
|
||||
(
|
||||
not exists(f.getKwarg()) and not exists(c.getKwargs())
|
||||
or
|
||||
f.getKwarg().(Name).getId() = c.getKwargs().(Name).getId()
|
||||
) and
|
||||
// f is not necessarily a drop-in replacement for the lambda if there are default argument values
|
||||
not exists(l.getArgs().getADefault())
|
||||
/* 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
|
||||
// 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. */
|
||||
predicate unnecessary_lambda(Lambda l, Expr e) {
|
||||
simple_wrapper(l, e) and
|
||||
(
|
||||
/* plain class */
|
||||
exists(ClassValue c | e.pointsTo(c))
|
||||
or
|
||||
/* plain function */
|
||||
exists(FunctionValue f | e.pointsTo(f))
|
||||
or
|
||||
/* bound-method of enclosing instance */
|
||||
exists(ClassValue cls, Attribute a | cls.getScope() = l.getScope().getScope() and a = e |
|
||||
a.getObject().(Name).getId() = "self" and
|
||||
cls.hasAttribute(a.getName())
|
||||
)
|
||||
simple_wrapper(l, e) and
|
||||
(
|
||||
/* plain class */
|
||||
exists(ClassValue c | e.pointsTo(c))
|
||||
or
|
||||
/* plain function */
|
||||
exists(FunctionValue f | e.pointsTo(f))
|
||||
or
|
||||
/* bound-method of enclosing instance */
|
||||
exists(ClassValue cls, Attribute a | cls.getScope() = l.getScope().getScope() and a = e |
|
||||
a.getObject().(Name).getId() = "self" and
|
||||
cls.hasAttribute(a.getName())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from Lambda l, Expr e
|
||||
where unnecessary_lambda(l, e)
|
||||
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."
|
||||
|
||||
@@ -14,8 +14,8 @@ import python
|
||||
|
||||
from CallNode call, Context context, ControlFlowNode func
|
||||
where
|
||||
context.getAVersion().includes(2, _) and
|
||||
call.getFunction() = func and
|
||||
func.pointsTo(context, Value::named("input"), _) and
|
||||
not func.pointsTo(context, Value::named("raw_input"), _)
|
||||
context.getAVersion().includes(2, _) and
|
||||
call.getFunction() = func and
|
||||
func.pointsTo(context, Value::named("input"), _) and
|
||||
not func.pointsTo(context, Value::named("raw_input"), _)
|
||||
select call, "The unsafe built-in function 'input' is used in Python 2."
|
||||
|
||||
@@ -18,10 +18,10 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, FunctionObject func, string name
|
||||
where
|
||||
illegally_named_parameter_objectapi(call, func, name) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden |
|
||||
func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name
|
||||
)
|
||||
illegally_named_parameter_objectapi(call, func, name) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden |
|
||||
func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name
|
||||
)
|
||||
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func,
|
||||
func.descriptiveString()
|
||||
func.descriptiveString()
|
||||
|
||||
@@ -16,32 +16,32 @@ import python
|
||||
import semmle.python.strings
|
||||
|
||||
predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) {
|
||||
operation.getOp() instanceof Mod and
|
||||
exists(Value fmt, Context ctx |
|
||||
operation.getLeft().pointsTo(ctx, fmt, str) and
|
||||
operation.getRight().pointsTo(ctx, args, origin)
|
||||
)
|
||||
operation.getOp() instanceof Mod and
|
||||
exists(Value fmt, Context ctx |
|
||||
operation.getLeft().pointsTo(ctx, fmt, str) and
|
||||
operation.getRight().pointsTo(ctx, args, origin)
|
||||
)
|
||||
}
|
||||
|
||||
int sequence_length(Value args) {
|
||||
/* Guess length of sequence */
|
||||
exists(Tuple seq, AstNode origin | seq.pointsTo(args, origin) |
|
||||
result = strictcount(seq.getAnElt()) and
|
||||
not seq.getAnElt() instanceof Starred
|
||||
)
|
||||
or
|
||||
exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1)
|
||||
/* Guess length of sequence */
|
||||
exists(Tuple seq, AstNode origin | seq.pointsTo(args, origin) |
|
||||
result = strictcount(seq.getAnElt()) and
|
||||
not seq.getAnElt() instanceof Starred
|
||||
)
|
||||
or
|
||||
exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1)
|
||||
}
|
||||
|
||||
from
|
||||
BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin,
|
||||
string provided
|
||||
BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin,
|
||||
string provided
|
||||
where
|
||||
string_format(operation, fmt, args, origin) and
|
||||
slen = sequence_length(args) and
|
||||
alen = format_items(fmt) and
|
||||
slen != alen and
|
||||
(if slen = 1 then provided = " is provided." else provided = " are provided.")
|
||||
string_format(operation, fmt, args, origin) and
|
||||
slen = sequence_length(args) and
|
||||
alen = format_items(fmt) and
|
||||
slen != alen and
|
||||
(if slen = 1 then provided = " is provided." else provided = " are provided.")
|
||||
select operation,
|
||||
"Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " +
|
||||
slen.toString() + provided, origin, "arguments", fmt, fmt.getText()
|
||||
"Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " +
|
||||
slen.toString() + provided, origin, "arguments", fmt, fmt.getText()
|
||||
|
||||
@@ -16,15 +16,16 @@ import CallArgs
|
||||
|
||||
from Call call, FunctionValue func, string too, string should, int limit
|
||||
where
|
||||
(
|
||||
(
|
||||
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
|
||||
or
|
||||
too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than "
|
||||
) and
|
||||
not isAbstract(func) and
|
||||
not exists(FunctionValue overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden))
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
and not func.getName() = "__new__"
|
||||
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()
|
||||
|
||||
) and
|
||||
not isAbstract(func) and
|
||||
not exists(FunctionValue overridden |
|
||||
func.overrides(overridden) and correct_args_if_called_as_method(call, overridden)
|
||||
) and
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
not func.getName() = "__new__"
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
|
||||
func.descriptiveString()
|
||||
|
||||
@@ -11,9 +11,9 @@ import semmle.python.filters.GeneratedCode
|
||||
import semmle.python.filters.Tests
|
||||
|
||||
predicate classify(File f, string tag) {
|
||||
f instanceof GeneratedFile and tag = "generated"
|
||||
or
|
||||
exists(TestScope t | t.getLocation().getFile() = f) and tag = "test"
|
||||
f instanceof GeneratedFile and tag = "generated"
|
||||
or
|
||||
exists(TestScope t | t.getLocation().getFile() = f) and tag = "test"
|
||||
}
|
||||
|
||||
from File f, string tag
|
||||
|
||||
@@ -13,21 +13,21 @@
|
||||
import python
|
||||
|
||||
predicate explicitly_returns_non_none(Function func) {
|
||||
exists(Return return |
|
||||
return.getScope() = func and
|
||||
exists(Expr val | val = return.getValue() | not val instanceof None)
|
||||
)
|
||||
exists(Return return |
|
||||
return.getScope() = func and
|
||||
exists(Expr val | val = return.getValue() | not val instanceof None)
|
||||
)
|
||||
}
|
||||
|
||||
predicate has_implicit_return(Function func) {
|
||||
exists(ControlFlowNode fallthru |
|
||||
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
|
||||
)
|
||||
or
|
||||
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
||||
exists(ControlFlowNode fallthru |
|
||||
fallthru = func.getFallthroughNode() and not fallthru.unlikelyReachable()
|
||||
)
|
||||
or
|
||||
exists(Return return | return.getScope() = func and not exists(return.getValue()))
|
||||
}
|
||||
|
||||
from Function func
|
||||
where explicitly_returns_non_none(func) and has_implicit_return(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."
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
import python
|
||||
|
||||
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
|
||||
where
|
||||
f.getScope().isMethod() and
|
||||
not f.isOverridingMethod() and
|
||||
slice_method_name(meth) and
|
||||
f.getName() = meth
|
||||
f.getScope().isMethod() and
|
||||
not f.isOverridingMethod() and
|
||||
slice_method_name(meth) and
|
||||
f.getName() = meth
|
||||
select f, meth + " method has been deprecated since Python 2.0"
|
||||
|
||||
@@ -14,10 +14,10 @@ import python
|
||||
|
||||
from Return r, Expr rv
|
||||
where
|
||||
exists(Function init | init.isInitMethod() and r.getScope() = init) and
|
||||
r.getValue() = rv and
|
||||
not rv.pointsTo(Value::none_()) 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
|
||||
not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__")
|
||||
exists(Function init | init.isInitMethod() and r.getScope() = init) and
|
||||
r.getValue() = rv and
|
||||
not rv.pointsTo(Value::none_()) 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
|
||||
not exists(Attribute meth | meth = rv.(Call).getFunc() | meth.getName() = "__init__")
|
||||
select r, "Explicit return in __init__ method."
|
||||
|
||||
@@ -14,142 +14,142 @@
|
||||
import python
|
||||
|
||||
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) {
|
||||
name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
|
||||
name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
|
||||
}
|
||||
|
||||
private predicate arithmetic_method(string name) {
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__div__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__and__" or
|
||||
name = "__or__" or
|
||||
name = "__xor__" or
|
||||
name = "__rshift__" or
|
||||
name = "__pow__" or
|
||||
name = "__mul__" or
|
||||
name = "__neg__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__ror__" or
|
||||
name = "__rxor__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rmul__" or
|
||||
name = "__truediv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ior__" or
|
||||
name = "__ixor__" or
|
||||
name = "__irshift__" or
|
||||
name = "__ipow__" or
|
||||
name = "__imul__" or
|
||||
name = "__itruediv__"
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__div__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__and__" or
|
||||
name = "__or__" or
|
||||
name = "__xor__" or
|
||||
name = "__rshift__" or
|
||||
name = "__pow__" or
|
||||
name = "__mul__" or
|
||||
name = "__neg__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__ror__" or
|
||||
name = "__rxor__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rmul__" or
|
||||
name = "__truediv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ior__" or
|
||||
name = "__ixor__" or
|
||||
name = "__irshift__" or
|
||||
name = "__ipow__" or
|
||||
name = "__imul__" or
|
||||
name = "__itruediv__"
|
||||
}
|
||||
|
||||
private predicate ordering_method(string name) {
|
||||
name = "__lt__"
|
||||
or
|
||||
name = "__le__"
|
||||
or
|
||||
name = "__gt__"
|
||||
or
|
||||
name = "__ge__"
|
||||
or
|
||||
name = "__cmp__" and major_version() = 2
|
||||
name = "__lt__"
|
||||
or
|
||||
name = "__le__"
|
||||
or
|
||||
name = "__gt__"
|
||||
or
|
||||
name = "__ge__"
|
||||
or
|
||||
name = "__cmp__" and major_version() = 2
|
||||
}
|
||||
|
||||
private predicate cast_method(string name) {
|
||||
name = "__nonzero__" and major_version() = 2
|
||||
or
|
||||
name = "__int__"
|
||||
or
|
||||
name = "__float__"
|
||||
or
|
||||
name = "__long__"
|
||||
or
|
||||
name = "__trunc__"
|
||||
or
|
||||
name = "__complex__"
|
||||
name = "__nonzero__" and major_version() = 2
|
||||
or
|
||||
name = "__int__"
|
||||
or
|
||||
name = "__float__"
|
||||
or
|
||||
name = "__long__"
|
||||
or
|
||||
name = "__trunc__"
|
||||
or
|
||||
name = "__complex__"
|
||||
}
|
||||
|
||||
predicate correct_raise(string name, ClassObject ex) {
|
||||
ex.getAnImproperSuperType() = theTypeErrorType() and
|
||||
(
|
||||
name = "__copy__" or
|
||||
name = "__deepcopy__" or
|
||||
name = "__call__" or
|
||||
indexing_method(name) or
|
||||
attribute_method(name)
|
||||
)
|
||||
or
|
||||
preferred_raise(name, ex)
|
||||
or
|
||||
preferred_raise(name, ex.getASuperType())
|
||||
ex.getAnImproperSuperType() = theTypeErrorType() and
|
||||
(
|
||||
name = "__copy__" or
|
||||
name = "__deepcopy__" or
|
||||
name = "__call__" or
|
||||
indexing_method(name) or
|
||||
attribute_method(name)
|
||||
)
|
||||
or
|
||||
preferred_raise(name, ex)
|
||||
or
|
||||
preferred_raise(name, ex.getASuperType())
|
||||
}
|
||||
|
||||
predicate preferred_raise(string name, ClassObject ex) {
|
||||
attribute_method(name) and ex = theAttributeErrorType()
|
||||
or
|
||||
indexing_method(name) and ex = Object::builtin("LookupError")
|
||||
or
|
||||
ordering_method(name) and ex = theTypeErrorType()
|
||||
or
|
||||
arithmetic_method(name) and ex = Object::builtin("ArithmeticError")
|
||||
or
|
||||
name = "__bool__" and ex = theTypeErrorType()
|
||||
attribute_method(name) and ex = theAttributeErrorType()
|
||||
or
|
||||
indexing_method(name) and ex = Object::builtin("LookupError")
|
||||
or
|
||||
ordering_method(name) and ex = theTypeErrorType()
|
||||
or
|
||||
arithmetic_method(name) and ex = Object::builtin("ArithmeticError")
|
||||
or
|
||||
name = "__bool__" and ex = theTypeErrorType()
|
||||
}
|
||||
|
||||
predicate no_need_to_raise(string name, string message) {
|
||||
name = "__hash__" and message = "use __hash__ = None instead"
|
||||
or
|
||||
cast_method(name) and message = "there is no need to implement the method at all."
|
||||
name = "__hash__" and message = "use __hash__ = None instead"
|
||||
or
|
||||
cast_method(name) and message = "there is no need to implement the method at all."
|
||||
}
|
||||
|
||||
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) {
|
||||
ex = f.getARaisedType() and
|
||||
strictcount(f.getARaisedType()) = 1 and
|
||||
not exists(f.getFunction().getANormalExit()) and
|
||||
/* raising StopIteration is equivalent to a return in a generator */
|
||||
not ex = theStopIterationType()
|
||||
ex = f.getARaisedType() and
|
||||
strictcount(f.getARaisedType()) = 1 and
|
||||
not exists(f.getFunction().getANormalExit()) and
|
||||
/* raising StopIteration is equivalent to a return in a generator */
|
||||
not ex = theStopIterationType()
|
||||
}
|
||||
|
||||
from FunctionObject f, ClassObject cls, string message
|
||||
where
|
||||
f.getFunction().isSpecialMethod() and
|
||||
not is_abstract(f) and
|
||||
always_raises(f, cls) and
|
||||
(
|
||||
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
|
||||
or
|
||||
not correct_raise(f.getName(), cls) and
|
||||
not cls.getName() = "NotImplementedError" and
|
||||
exists(ClassObject preferred | preferred_raise(f.getName(), preferred) |
|
||||
message = "raise " + preferred.getName() + " instead"
|
||||
)
|
||||
f.getFunction().isSpecialMethod() and
|
||||
not is_abstract(f) and
|
||||
always_raises(f, cls) and
|
||||
(
|
||||
no_need_to_raise(f.getName(), message) and not cls.getName() = "NotImplementedError"
|
||||
or
|
||||
not correct_raise(f.getName(), cls) and
|
||||
not cls.getName() = "NotImplementedError" and
|
||||
exists(ClassObject preferred | preferred_raise(f.getName(), preferred) |
|
||||
message = "raise " + preferred.getName() + " instead"
|
||||
)
|
||||
)
|
||||
select f, "Function always raises $@; " + message, cls, cls.toString()
|
||||
|
||||
@@ -14,18 +14,18 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, FunctionValue func, FunctionValue overridden, string problem
|
||||
where
|
||||
func.overrides(overridden) and
|
||||
(
|
||||
wrong_args(call, func, _, problem) and
|
||||
correct_args_if_called_as_method(call, overridden)
|
||||
or
|
||||
exists(string name |
|
||||
illegally_named_parameter(call, func, name) and
|
||||
problem = "an argument named '" + name + "'" and
|
||||
overridden.getScope().getAnArg().(Name).getId() = name
|
||||
)
|
||||
func.overrides(overridden) and
|
||||
(
|
||||
wrong_args(call, func, _, problem) and
|
||||
correct_args_if_called_as_method(call, overridden)
|
||||
or
|
||||
exists(string name |
|
||||
illegally_named_parameter(call, func, name) and
|
||||
problem = "an argument named '" + name + "'" and
|
||||
overridden.getScope().getAnArg().(Name).getId() = name
|
||||
)
|
||||
)
|
||||
select func,
|
||||
"Overriding method signature does not match $@, where it is passed " + problem +
|
||||
". Overridden method $@ is correctly specified.", call, "here", overridden,
|
||||
overridden.descriptiveString()
|
||||
"Overriding method signature does not match $@, where it is passed " + problem +
|
||||
". Overridden method $@ is correctly specified.", call, "here", overridden,
|
||||
overridden.descriptiveString()
|
||||
|
||||
@@ -15,23 +15,23 @@ import Expressions.CallArgs
|
||||
|
||||
from Call call, FunctionValue func, FunctionValue overriding, string problem
|
||||
where
|
||||
not func.getName() = "__init__" and
|
||||
overriding.overrides(func) and
|
||||
call = overriding.getAMethodCall().getNode() and
|
||||
correct_args_if_called_as_method(call, overriding) and
|
||||
(
|
||||
arg_count(call) + 1 < func.minParameters() and problem = "too few arguments"
|
||||
or
|
||||
arg_count(call) >= func.maxParameters() and problem = "too many arguments"
|
||||
or
|
||||
exists(string name |
|
||||
call.getAKeyword().getArg() = name and
|
||||
overriding.getScope().getAnArg().(Name).getId() = name and
|
||||
not func.getScope().getAnArg().(Name).getId() = name and
|
||||
problem = "an argument named '" + name + "'"
|
||||
)
|
||||
not func.getName() = "__init__" and
|
||||
overriding.overrides(func) and
|
||||
call = overriding.getAMethodCall().getNode() and
|
||||
correct_args_if_called_as_method(call, overriding) and
|
||||
(
|
||||
arg_count(call) + 1 < func.minParameters() and problem = "too few arguments"
|
||||
or
|
||||
arg_count(call) >= func.maxParameters() and problem = "too many arguments"
|
||||
or
|
||||
exists(string name |
|
||||
call.getAKeyword().getArg() = name and
|
||||
overriding.getScope().getAnArg().(Name).getId() = name and
|
||||
not func.getScope().getAnArg().(Name).getId() = name and
|
||||
problem = "an argument named '" + name + "'"
|
||||
)
|
||||
)
|
||||
select func,
|
||||
"Overridden method signature does not match $@, where it is passed " + problem +
|
||||
". Overriding method $@ matches the call.", call, "call", overriding,
|
||||
overriding.descriptiveString()
|
||||
"Overridden method signature does not match $@, where it is passed " + problem +
|
||||
". Overriding method $@ matches the call.", call, "call", overriding,
|
||||
overriding.descriptiveString()
|
||||
|
||||
@@ -14,6 +14,6 @@ import python
|
||||
|
||||
from Function f
|
||||
where
|
||||
f.isInitMethod() and
|
||||
(exists(Yield y | y.getScope() = f) or exists(YieldFrom y | y.getScope() = f))
|
||||
f.isInitMethod() and
|
||||
(exists(Yield y | y.getScope() = f) or exists(YieldFrom y | y.getScope() = f))
|
||||
select f, "__init__ method is a generator."
|
||||
|
||||
@@ -14,10 +14,10 @@ import python
|
||||
|
||||
from ClassValue iterable, FunctionValue iter, ClassValue iterator
|
||||
where
|
||||
iter = iterable.lookup("__iter__") and
|
||||
iterator = iter.getAnInferredReturnType() and
|
||||
not iterator.isIterator()
|
||||
iter = iterable.lookup("__iter__") and
|
||||
iterator = iter.getAnInferredReturnType() and
|
||||
not iterator.isIterator()
|
||||
select iterator,
|
||||
"Class " + iterator.getName() +
|
||||
" is returned as an iterator (by $@) but does not fully implement the iterator interface.",
|
||||
iter, iter.getName()
|
||||
"Class " + iterator.getName() +
|
||||
" is returned as an iterator (by $@) but does not fully implement the iterator interface.",
|
||||
iter, iter.getName()
|
||||
|
||||
@@ -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 returns_non_self(Function f) {
|
||||
exists(f.getFallthroughNode())
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not is_self(r.getValue(), f))
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not exists(r.getValue()))
|
||||
exists(f.getFallthroughNode())
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not is_self(r.getValue(), f))
|
||||
or
|
||||
exists(Return r | r.getScope() = f and not exists(r.getValue()))
|
||||
}
|
||||
|
||||
from ClassValue t, Function 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'.",
|
||||
iter, iter.getName()
|
||||
iter, iter.getName()
|
||||
|
||||
@@ -15,85 +15,85 @@ import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
predicate safe_method(string name) {
|
||||
name = "count" or
|
||||
name = "index" or
|
||||
name = "copy" or
|
||||
name = "get" or
|
||||
name = "has_key" or
|
||||
name = "items" or
|
||||
name = "keys" or
|
||||
name = "values" or
|
||||
name = "iteritems" or
|
||||
name = "iterkeys" or
|
||||
name = "itervalues" or
|
||||
name = "__contains__" or
|
||||
name = "__getitem__" or
|
||||
name = "__getattribute__"
|
||||
name = "count" or
|
||||
name = "index" or
|
||||
name = "copy" or
|
||||
name = "get" or
|
||||
name = "has_key" or
|
||||
name = "items" or
|
||||
name = "keys" or
|
||||
name = "values" or
|
||||
name = "iteritems" or
|
||||
name = "iterkeys" or
|
||||
name = "itervalues" or
|
||||
name = "__contains__" or
|
||||
name = "__getitem__" or
|
||||
name = "__getattribute__"
|
||||
}
|
||||
|
||||
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
|
||||
private boolean mutableDefaultValue(Parameter p) {
|
||||
exists(Dict d | p.getDefault() = d |
|
||||
exists(d.getAKey()) and result = true
|
||||
or
|
||||
not exists(d.getAKey()) and result = false
|
||||
)
|
||||
exists(Dict d | p.getDefault() = d |
|
||||
exists(d.getAKey()) and result = true
|
||||
or
|
||||
exists(List l | p.getDefault() = l |
|
||||
exists(l.getAnElt()) and result = true
|
||||
or
|
||||
not exists(l.getAnElt()) and result = false
|
||||
)
|
||||
not exists(d.getAKey()) and result = false
|
||||
)
|
||||
or
|
||||
exists(List l | p.getDefault() = l |
|
||||
exists(l.getAnElt()) and result = true
|
||||
or
|
||||
not exists(l.getAnElt()) and result = false
|
||||
)
|
||||
}
|
||||
|
||||
class NonEmptyMutableValue extends TaintKind {
|
||||
NonEmptyMutableValue() { this = "non-empty mutable value" }
|
||||
NonEmptyMutableValue() { this = "non-empty mutable value" }
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
nonEmpty = false and kind instanceof EmptyMutableValue
|
||||
or
|
||||
nonEmpty = true and kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
nonEmpty = false and kind instanceof EmptyMutableValue
|
||||
or
|
||||
nonEmpty = true and kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
}
|
||||
|
||||
private ClassValue mutable_class() {
|
||||
result = Value::named("list") or
|
||||
result = Value::named("dict")
|
||||
result = Value::named("list") or
|
||||
result = Value::named("dict")
|
||||
}
|
||||
|
||||
class Mutation extends TaintSink {
|
||||
Mutation() {
|
||||
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Call c, Attribute a | c.getFunc() = a |
|
||||
a.getObject().getAFlowNode() = this and
|
||||
not safe_method(a.getName()) and
|
||||
this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
|
||||
)
|
||||
}
|
||||
Mutation() {
|
||||
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Call c, Attribute a | c.getFunc() = a |
|
||||
a.getObject().getAFlowNode() = this and
|
||||
not safe_method(a.getName()) and
|
||||
this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof EmptyMutableValue
|
||||
or
|
||||
kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof EmptyMutableValue
|
||||
or
|
||||
kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
}
|
||||
|
||||
from TaintedPathSource src, TaintedPathSink sink
|
||||
where src.flowsTo(sink)
|
||||
select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(),
|
||||
"Default value"
|
||||
"Default value"
|
||||
|
||||
@@ -15,36 +15,36 @@
|
||||
import python
|
||||
|
||||
predicate first_arg_cls(Function f) {
|
||||
exists(string argname | argname = f.getArgName(0) |
|
||||
argname = "cls"
|
||||
or
|
||||
/* Not PEP8, but relatively common */
|
||||
argname = "mcls"
|
||||
)
|
||||
exists(string argname | argname = f.getArgName(0) |
|
||||
argname = "cls"
|
||||
or
|
||||
/* Not PEP8, but relatively common */
|
||||
argname = "mcls"
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
where
|
||||
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
|
||||
not first_arg_cls(f) and
|
||||
classmethod_decorators_only(f) and
|
||||
not f.getName() = "__new__" and
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls', rather than '" +
|
||||
f.getArgName(0) + "', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
|
||||
)
|
||||
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
|
||||
not first_arg_cls(f) and
|
||||
classmethod_decorators_only(f) and
|
||||
not f.getName() = "__new__" and
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls', rather than '" +
|
||||
f.getArgName(0) + "', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
|
||||
)
|
||||
select f, message
|
||||
|
||||
@@ -17,42 +17,42 @@ import python
|
||||
import semmle.python.libraries.Zope
|
||||
|
||||
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) {
|
||||
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
|
||||
where
|
||||
exists(ClassValue cls, string name |
|
||||
cls.declaredAttribute(name) = fv and
|
||||
cls.isNewStyle() and
|
||||
not name = "__new__" and
|
||||
not name = "__metaclass__" and
|
||||
not name = "__init_subclass__" and
|
||||
not name = "__class_getitem__" and
|
||||
/* declared in scope */
|
||||
f.getScope() = cls.getScope()
|
||||
) and
|
||||
not f.getArgName(0) = "self" and
|
||||
not is_type_method(fv) and
|
||||
fv.getScope() = f and
|
||||
not f.getName() = "lambda" and
|
||||
not used_in_defining_scope(fv) and
|
||||
exists(ClassValue cls, string name |
|
||||
cls.declaredAttribute(name) = fv and
|
||||
cls.isNewStyle() and
|
||||
not name = "__new__" and
|
||||
not name = "__metaclass__" and
|
||||
not name = "__init_subclass__" and
|
||||
not name = "__class_getitem__" and
|
||||
/* declared in scope */
|
||||
f.getScope() = cls.getScope()
|
||||
) and
|
||||
not f.getArgName(0) = "self" and
|
||||
not is_type_method(fv) and
|
||||
fv.getScope() = f and
|
||||
not f.getName() = "lambda" and
|
||||
not used_in_defining_scope(fv) and
|
||||
(
|
||||
(
|
||||
(
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
) and
|
||||
not f.hasVarArg()
|
||||
if exists(f.getArgName(0))
|
||||
then
|
||||
message =
|
||||
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
|
||||
"', as their first parameter."
|
||||
else
|
||||
message =
|
||||
"Normal methods should have at least one parameter (the first of which should be 'self')."
|
||||
) and
|
||||
not fv instanceof ZopeInterfaceMethodValue
|
||||
not f.hasVarArg()
|
||||
) and
|
||||
not fv instanceof ZopeInterfaceMethodValue
|
||||
select f, message
|
||||
|
||||
@@ -17,8 +17,8 @@ import python
|
||||
|
||||
from FunctionValue method
|
||||
where
|
||||
exists(ClassValue c |
|
||||
c.declaredAttribute("__del__") = method and
|
||||
method.getScope().getMetrics().getCyclomaticComplexity() > 3
|
||||
)
|
||||
exists(ClassValue c |
|
||||
c.declaredAttribute("__del__") = method and
|
||||
method.getScope().getMetrics().getCyclomaticComplexity() > 3
|
||||
)
|
||||
select method, "Overly complex '__del__' method."
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
import python
|
||||
|
||||
predicate returns_tuple_of_size(Function func, int size, AstNode origin) {
|
||||
exists(Return return, TupleValue val |
|
||||
return.getScope() = func and
|
||||
return.getValue().pointsTo(val, origin)
|
||||
|
|
||||
size = val.length()
|
||||
)
|
||||
exists(Return return, TupleValue val |
|
||||
return.getScope() = func and
|
||||
return.getValue().pointsTo(val, origin)
|
||||
|
|
||||
size = val.length()
|
||||
)
|
||||
}
|
||||
|
||||
from Function func, int s1, int s2, AstNode t1, AstNode t2
|
||||
where
|
||||
returns_tuple_of_size(func, s1, t1) and
|
||||
returns_tuple_of_size(func, s2, t2) and
|
||||
s1 < s2
|
||||
returns_tuple_of_size(func, s1, t1) and
|
||||
returns_tuple_of_size(func, s2, t2) and
|
||||
s1 < s2
|
||||
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
|
||||
"tuple of size " + s2
|
||||
"tuple of size " + s2
|
||||
|
||||
@@ -18,66 +18,66 @@ import python
|
||||
import semmle.python.objects.Callables
|
||||
|
||||
predicate meaningful_return_value(Expr val) {
|
||||
val instanceof Name
|
||||
or
|
||||
val instanceof BooleanLiteral
|
||||
or
|
||||
exists(FunctionValue callee |
|
||||
val = callee.getACall().getNode() and returns_meaningful_value(callee)
|
||||
)
|
||||
or
|
||||
not exists(FunctionValue callee | val = callee.getACall().getNode()) and not val instanceof Name
|
||||
val instanceof Name
|
||||
or
|
||||
val instanceof BooleanLiteral
|
||||
or
|
||||
exists(FunctionValue callee |
|
||||
val = callee.getACall().getNode() and returns_meaningful_value(callee)
|
||||
)
|
||||
or
|
||||
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 */
|
||||
predicate used_value(Expr val) {
|
||||
exists(LocalVariable var, Expr other |
|
||||
var.getAnAccess() = val and other = var.getAnAccess() and not other = val
|
||||
)
|
||||
exists(LocalVariable var, Expr other |
|
||||
var.getAnAccess() = val and other = var.getAnAccess() and not other = val
|
||||
)
|
||||
}
|
||||
|
||||
predicate returns_meaningful_value(FunctionValue f) {
|
||||
not exists(f.getScope().getFallthroughNode()) and
|
||||
(
|
||||
exists(Return ret, Expr val | ret.getScope() = f.getScope() and val = ret.getValue() |
|
||||
meaningful_return_value(val) and
|
||||
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__"
|
||||
not exists(f.getScope().getFallthroughNode()) and
|
||||
(
|
||||
exists(Return ret, Expr val | ret.getScope() = f.getScope() and val = ret.getValue() |
|
||||
meaningful_return_value(val) and
|
||||
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__"
|
||||
)
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
exists(Try t |
|
||||
exists(t.getAHandler()) and
|
||||
strictcount(Call c | t.getBody().contains(c)) = 1 and
|
||||
call = t.getAStmt()
|
||||
)
|
||||
exists(Try t |
|
||||
exists(t.getAHandler()) and
|
||||
strictcount(Call c | t.getBody().contains(c)) = 1 and
|
||||
call = t.getAStmt()
|
||||
)
|
||||
}
|
||||
|
||||
from ExprStmt call, FunctionValue callee, float percentage_used, int total
|
||||
where
|
||||
call.getValue() = callee.getACall().getNode() and
|
||||
returns_meaningful_value(callee) and
|
||||
not wrapped_in_try_except(call) and
|
||||
exists(int unused |
|
||||
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
|
||||
total = count(callee.getACall())
|
||||
|
|
||||
percentage_used = (100.0 * (total - unused) / total).floor()
|
||||
) 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. */
|
||||
percentage_used >= 75 and
|
||||
total >= 5
|
||||
call.getValue() = callee.getACall().getNode() and
|
||||
returns_meaningful_value(callee) and
|
||||
not wrapped_in_try_except(call) and
|
||||
exists(int unused |
|
||||
unused = count(ExprStmt e | e.getValue().getAFlowNode() = callee.getACall()) and
|
||||
total = count(callee.getACall())
|
||||
|
|
||||
percentage_used = (100.0 * (total - unused) / total).floor()
|
||||
) 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. */
|
||||
percentage_used >= 75 and
|
||||
total >= 5
|
||||
select call,
|
||||
"Call discards return value of function $@. The result is used in " + percentage_used.toString() +
|
||||
"% of calls.", callee, callee.getName()
|
||||
"Call discards return value of function $@. The result is used in " + percentage_used.toString() +
|
||||
"% of calls.", callee, callee.getName()
|
||||
|
||||
@@ -16,20 +16,20 @@ import Expressions.CallArgs
|
||||
|
||||
from FunctionValue base, PythonFunctionValue derived
|
||||
where
|
||||
not exists(base.getACall()) and
|
||||
not exists(FunctionValue a_derived |
|
||||
a_derived.overrides(base) and
|
||||
exists(a_derived.getACall())
|
||||
) and
|
||||
not derived.getScope().isSpecialMethod() and
|
||||
derived.getName() != "__init__" and
|
||||
derived.isNormalMethod() and
|
||||
not derived.getScope().isSpecialMethod() and
|
||||
// call to overrides distributed for efficiency
|
||||
(
|
||||
derived.overrides(base) and derived.minParameters() > base.maxParameters()
|
||||
or
|
||||
derived.overrides(base) and derived.maxParameters() < base.minParameters()
|
||||
)
|
||||
not exists(base.getACall()) and
|
||||
not exists(FunctionValue a_derived |
|
||||
a_derived.overrides(base) and
|
||||
exists(a_derived.getACall())
|
||||
) and
|
||||
not derived.getScope().isSpecialMethod() and
|
||||
derived.getName() != "__init__" and
|
||||
derived.isNormalMethod() and
|
||||
not derived.getScope().isSpecialMethod() and
|
||||
// call to overrides distributed for efficiency
|
||||
(
|
||||
derived.overrides(base) and derived.minParameters() > base.maxParameters()
|
||||
or
|
||||
derived.overrides(base) and derived.maxParameters() < base.minParameters()
|
||||
)
|
||||
select derived, "Overriding method '" + derived.getName() + "' has signature mismatch with $@.",
|
||||
base, "overridden method"
|
||||
base, "overridden method"
|
||||
|
||||
@@ -13,200 +13,200 @@
|
||||
import python
|
||||
|
||||
predicate is_unary_op(string name) {
|
||||
name = "__del__" or
|
||||
name = "__repr__" or
|
||||
name = "__str__" or
|
||||
name = "__hash__" or
|
||||
name = "__bool__" or
|
||||
name = "__nonzero__" or
|
||||
name = "__unicode__" or
|
||||
name = "__len__" or
|
||||
name = "__iter__" or
|
||||
name = "__reversed__" or
|
||||
name = "__neg__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__invert__" or
|
||||
name = "__complex__" or
|
||||
name = "__int__" or
|
||||
name = "__float__" or
|
||||
name = "__long__" or
|
||||
name = "__oct__" or
|
||||
name = "__hex__" or
|
||||
name = "__index__" or
|
||||
name = "__enter__"
|
||||
name = "__del__" or
|
||||
name = "__repr__" or
|
||||
name = "__str__" or
|
||||
name = "__hash__" or
|
||||
name = "__bool__" or
|
||||
name = "__nonzero__" or
|
||||
name = "__unicode__" or
|
||||
name = "__len__" or
|
||||
name = "__iter__" or
|
||||
name = "__reversed__" or
|
||||
name = "__neg__" or
|
||||
name = "__pos__" or
|
||||
name = "__abs__" or
|
||||
name = "__invert__" or
|
||||
name = "__complex__" or
|
||||
name = "__int__" or
|
||||
name = "__float__" or
|
||||
name = "__long__" or
|
||||
name = "__oct__" or
|
||||
name = "__hex__" or
|
||||
name = "__index__" or
|
||||
name = "__enter__"
|
||||
}
|
||||
|
||||
predicate is_binary_op(string name) {
|
||||
name = "__lt__" or
|
||||
name = "__le__" or
|
||||
name = "__eq__" or
|
||||
name = "__ne__" or
|
||||
name = "__gt__" or
|
||||
name = "__ge__" or
|
||||
name = "__cmp__" or
|
||||
name = "__rcmp__" or
|
||||
name = "__getattr___" or
|
||||
name = "__getattribute___" or
|
||||
name = "__delattr__" or
|
||||
name = "__delete__" or
|
||||
name = "__instancecheck__" or
|
||||
name = "__subclasscheck__" or
|
||||
name = "__getitem__" or
|
||||
name = "__delitem__" or
|
||||
name = "__contains__" or
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__mul__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__truediv__" or
|
||||
name = "__mod__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__rshift__" or
|
||||
name = "__and__" or
|
||||
name = "__xor__" or
|
||||
name = "__or__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rmul__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__rmod__" or
|
||||
name = "__rdivmod__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__rxor__" or
|
||||
name = "__ror__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__imul__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__itruediv__" or
|
||||
name = "__imod__" or
|
||||
name = "__idivmod__" or
|
||||
name = "__ipow__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__irshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ixor__" or
|
||||
name = "__ior__" or
|
||||
name = "__coerce__"
|
||||
name = "__lt__" or
|
||||
name = "__le__" or
|
||||
name = "__eq__" or
|
||||
name = "__ne__" or
|
||||
name = "__gt__" or
|
||||
name = "__ge__" or
|
||||
name = "__cmp__" or
|
||||
name = "__rcmp__" or
|
||||
name = "__getattr___" or
|
||||
name = "__getattribute___" or
|
||||
name = "__delattr__" or
|
||||
name = "__delete__" or
|
||||
name = "__instancecheck__" or
|
||||
name = "__subclasscheck__" or
|
||||
name = "__getitem__" or
|
||||
name = "__delitem__" or
|
||||
name = "__contains__" or
|
||||
name = "__add__" or
|
||||
name = "__sub__" or
|
||||
name = "__mul__" or
|
||||
name = "__floordiv__" or
|
||||
name = "__div__" or
|
||||
name = "__truediv__" or
|
||||
name = "__mod__" or
|
||||
name = "__divmod__" or
|
||||
name = "__lshift__" or
|
||||
name = "__rshift__" or
|
||||
name = "__and__" or
|
||||
name = "__xor__" or
|
||||
name = "__or__" or
|
||||
name = "__radd__" or
|
||||
name = "__rsub__" or
|
||||
name = "__rmul__" or
|
||||
name = "__rfloordiv__" or
|
||||
name = "__rdiv__" or
|
||||
name = "__rtruediv__" or
|
||||
name = "__rmod__" or
|
||||
name = "__rdivmod__" or
|
||||
name = "__rpow__" or
|
||||
name = "__rlshift__" or
|
||||
name = "__rrshift__" or
|
||||
name = "__rand__" or
|
||||
name = "__rxor__" or
|
||||
name = "__ror__" or
|
||||
name = "__iadd__" or
|
||||
name = "__isub__" or
|
||||
name = "__imul__" or
|
||||
name = "__ifloordiv__" or
|
||||
name = "__idiv__" or
|
||||
name = "__itruediv__" or
|
||||
name = "__imod__" or
|
||||
name = "__idivmod__" or
|
||||
name = "__ipow__" or
|
||||
name = "__ilshift__" or
|
||||
name = "__irshift__" or
|
||||
name = "__iand__" or
|
||||
name = "__ixor__" or
|
||||
name = "__ior__" or
|
||||
name = "__coerce__"
|
||||
}
|
||||
|
||||
predicate is_ternary_op(string name) {
|
||||
name = "__setattr__" or
|
||||
name = "__set__" or
|
||||
name = "__setitem__" or
|
||||
name = "__getslice__" or
|
||||
name = "__delslice__"
|
||||
name = "__setattr__" or
|
||||
name = "__set__" or
|
||||
name = "__setitem__" or
|
||||
name = "__getslice__" or
|
||||
name = "__delslice__"
|
||||
}
|
||||
|
||||
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
|
||||
|
||||
int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
|
||||
cls.declaredAttribute(name) = f and
|
||||
(
|
||||
is_unary_op(name) and result = 1
|
||||
or
|
||||
is_binary_op(name) and result = 2
|
||||
or
|
||||
is_ternary_op(name) and result = 3
|
||||
or
|
||||
is_quad_op(name) and result = 4
|
||||
)
|
||||
cls.declaredAttribute(name) = f and
|
||||
(
|
||||
is_unary_op(name) and result = 1
|
||||
or
|
||||
is_binary_op(name) and result = 2
|
||||
or
|
||||
is_ternary_op(name) and result = 3
|
||||
or
|
||||
is_quad_op(name) and result = 4
|
||||
)
|
||||
}
|
||||
|
||||
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) |
|
||||
/* actual_non_default <= actual */
|
||||
if required > func.maxParameters()
|
||||
then message = "Too few parameters" and show_counts = true
|
||||
else
|
||||
if required < func.minParameters()
|
||||
then message = "Too many parameters" and show_counts = true
|
||||
else
|
||||
if func.minParameters() < required and not func.getScope().hasVarArg()
|
||||
then
|
||||
message = (required - func.minParameters()) + " default values(s) will never be used" and
|
||||
show_counts = false
|
||||
else none()
|
||||
)
|
||||
exists(int required | required = argument_count(func, name, owner) |
|
||||
/* actual_non_default <= actual */
|
||||
if required > func.maxParameters()
|
||||
then message = "Too few parameters" and show_counts = true
|
||||
else
|
||||
if required < func.minParameters()
|
||||
then message = "Too many parameters" and show_counts = true
|
||||
else
|
||||
if func.minParameters() < required and not func.getScope().hasVarArg()
|
||||
then
|
||||
message = (required - func.minParameters()) + " default values(s) will never be used" and
|
||||
show_counts = false
|
||||
else none()
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__pow__") = func and
|
||||
(
|
||||
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
or
|
||||
func.minParameters() = 3 and
|
||||
message = "Third parameter to __pow__ should have a default value" and
|
||||
show_counts = false
|
||||
)
|
||||
owner.declaredAttribute("__pow__") = func and
|
||||
(
|
||||
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
or
|
||||
func.minParameters() = 3 and
|
||||
message = "Third parameter to __pow__ should have a default value" and
|
||||
show_counts = false
|
||||
)
|
||||
}
|
||||
|
||||
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
|
||||
owner.declaredAttribute("__get__") = func and
|
||||
(
|
||||
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
not func.getScope().hasVarArg() and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
)
|
||||
owner.declaredAttribute("__get__") = func and
|
||||
(
|
||||
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
|
||||
or
|
||||
func.minParameters() < 2 and
|
||||
not func.getScope().hasVarArg() and
|
||||
message = (2 - func.minParameters()) + " default value(s) will never be used" and
|
||||
show_counts = false
|
||||
)
|
||||
}
|
||||
|
||||
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
|
||||
exists(int i | i = argument_count(f, name, owner) | result = i.toString())
|
||||
or
|
||||
owner.declaredAttribute(name) = f and
|
||||
(name = "__get__" or name = "__pow__") and
|
||||
result = "2 or 3"
|
||||
exists(int i | i = argument_count(f, name, owner) | result = i.toString())
|
||||
or
|
||||
owner.declaredAttribute(name) = f and
|
||||
(name = "__get__" or name = "__pow__") and
|
||||
result = "2 or 3"
|
||||
}
|
||||
|
||||
string has_parameters(PythonFunctionValue f) {
|
||||
exists(int i | i = f.minParameters() |
|
||||
i = 0 and result = "no parameters"
|
||||
or
|
||||
i = 1 and result = "1 parameter"
|
||||
or
|
||||
i > 1 and result = i.toString() + " parameters"
|
||||
)
|
||||
exists(int i | i = f.minParameters() |
|
||||
i = 0 and result = "no parameters"
|
||||
or
|
||||
i = 1 and result = "1 parameter"
|
||||
or
|
||||
i > 1 and result = i.toString() + " parameters"
|
||||
)
|
||||
}
|
||||
|
||||
from
|
||||
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
|
||||
ClassValue owner
|
||||
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
|
||||
ClassValue owner
|
||||
where
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, owner)
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
|
||||
or
|
||||
incorrect_get(f, message, show_counts, owner) and name = "__get__"
|
||||
) and
|
||||
(
|
||||
show_counts = false and sizes = ""
|
||||
or
|
||||
show_counts = true and
|
||||
sizes =
|
||||
", which has " + has_parameters(f) + ", but should have " +
|
||||
should_have_parameters(f, name, owner)
|
||||
)
|
||||
(
|
||||
incorrect_special_method_defn(f, message, show_counts, name, owner)
|
||||
or
|
||||
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
|
||||
or
|
||||
incorrect_get(f, message, show_counts, owner) and name = "__get__"
|
||||
) and
|
||||
(
|
||||
show_counts = false and sizes = ""
|
||||
or
|
||||
show_counts = true and
|
||||
sizes =
|
||||
", which has " + has_parameters(f) + ", but should have " +
|
||||
should_have_parameters(f, name, owner)
|
||||
)
|
||||
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()
|
||||
|
||||
@@ -13,26 +13,26 @@ import python
|
||||
import Testing.Mox
|
||||
|
||||
predicate is_used(Call c) {
|
||||
exists(Expr outer | outer != c and outer.containsInScope(c) |
|
||||
outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript
|
||||
)
|
||||
or
|
||||
exists(Stmt s |
|
||||
c = s.getASubExpression() 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. */
|
||||
not (s instanceof Return and strictcount(Return r | r.getScope() = s.getScope()) = 1)
|
||||
)
|
||||
exists(Expr outer | outer != c and outer.containsInScope(c) |
|
||||
outer instanceof Call or outer instanceof Attribute or outer instanceof Subscript
|
||||
)
|
||||
or
|
||||
exists(Stmt s |
|
||||
c = s.getASubExpression() 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. */
|
||||
not (s instanceof Return and strictcount(Return r | r.getScope() = s.getScope()) = 1)
|
||||
)
|
||||
}
|
||||
|
||||
from Call c, FunctionValue func
|
||||
where
|
||||
/* Call result is used, but callee is a procedure */
|
||||
is_used(c) and
|
||||
c.getFunc().pointsTo(func) and
|
||||
func.getScope().isProcedure() and
|
||||
/* All callees are procedures */
|
||||
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
|
||||
/* Mox return objects have an `AndReturn` method */
|
||||
not useOfMoxInModule(c.getEnclosingModule())
|
||||
/* Call result is used, but callee is a procedure */
|
||||
is_used(c) and
|
||||
c.getFunc().pointsTo(func) and
|
||||
func.getScope().isProcedure() and
|
||||
/* All callees are procedures */
|
||||
forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and
|
||||
/* Mox return objects have an `AndReturn` method */
|
||||
not useOfMoxInModule(c.getEnclosingModule())
|
||||
select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName()
|
||||
|
||||
@@ -3,84 +3,84 @@ import python
|
||||
predicate is_import_time(Stmt s) { not s.getScope+() instanceof Function }
|
||||
|
||||
ModuleValue module_imported_by(ModuleValue m) {
|
||||
exists(Stmt imp |
|
||||
result = stmt_imports(imp) and
|
||||
imp.getEnclosingModule() = m.getScope() and
|
||||
// Import must reach exit to be part of a cycle
|
||||
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||
)
|
||||
exists(Stmt imp |
|
||||
result = stmt_imports(imp) and
|
||||
imp.getEnclosingModule() = m.getScope() and
|
||||
// Import must reach exit to be part of a cycle
|
||||
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||
)
|
||||
}
|
||||
|
||||
/** Is there a circular import of 'm1' beginning with 'm2'? */
|
||||
predicate circular_import(ModuleValue m1, ModuleValue m2) {
|
||||
m1 != m2 and
|
||||
m2 = module_imported_by(m1) and
|
||||
m1 = module_imported_by+(m2)
|
||||
m1 != m2 and
|
||||
m2 = module_imported_by(m1) and
|
||||
m1 = module_imported_by+(m2)
|
||||
}
|
||||
|
||||
ModuleValue stmt_imports(ImportingStmt s) {
|
||||
exists(string name | result.importedAs(name) and not name = "__main__" |
|
||||
name = s.getAnImportedModuleName() and
|
||||
s.getASubExpression().pointsTo(result) and
|
||||
not result.isPackage()
|
||||
)
|
||||
exists(string name | result.importedAs(name) and not name = "__main__" |
|
||||
name = s.getAnImportedModuleName() and
|
||||
s.getASubExpression().pointsTo(result) and
|
||||
not result.isPackage()
|
||||
)
|
||||
}
|
||||
|
||||
predicate import_time_imported_module(ModuleValue m1, ModuleValue m2, Stmt imp) {
|
||||
imp.getEnclosingModule() = m1.getScope() and
|
||||
is_import_time(imp) and
|
||||
m2 = stmt_imports(imp)
|
||||
imp.getEnclosingModule() = m1.getScope() and
|
||||
is_import_time(imp) and
|
||||
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? */
|
||||
predicate import_time_circular_import(ModuleValue m1, ModuleValue m2, Stmt imp) {
|
||||
m1 != m2 and
|
||||
import_time_imported_module(m1, m2, imp) and
|
||||
import_time_transitive_import(m2, _, m1)
|
||||
m1 != m2 and
|
||||
import_time_imported_module(m1, m2, imp) and
|
||||
import_time_transitive_import(m2, _, m1)
|
||||
}
|
||||
|
||||
predicate import_time_transitive_import(ModuleValue base, Stmt imp, ModuleValue last) {
|
||||
last != base and
|
||||
(
|
||||
import_time_imported_module(base, last, imp)
|
||||
or
|
||||
exists(ModuleValue mid |
|
||||
import_time_transitive_import(base, imp, mid) and
|
||||
import_time_imported_module(mid, last, _)
|
||||
)
|
||||
) and
|
||||
// Import must reach exit to be part of a cycle
|
||||
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||
last != base and
|
||||
(
|
||||
import_time_imported_module(base, last, imp)
|
||||
or
|
||||
exists(ModuleValue mid |
|
||||
import_time_transitive_import(base, imp, mid) and
|
||||
import_time_imported_module(mid, last, _)
|
||||
)
|
||||
) and
|
||||
// Import must reach exit to be part of a cycle
|
||||
imp.getAnEntryNode().getBasicBlock().reachesExit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns import-time usages of module 'm' in module 'enclosing'
|
||||
*/
|
||||
predicate import_time_module_use(ModuleValue m, ModuleValue enclosing, Expr use, string attr) {
|
||||
exists(Expr mod |
|
||||
use.getEnclosingModule() = enclosing.getScope() and
|
||||
not use.getScope+() instanceof Function and
|
||||
mod.pointsTo(m) and
|
||||
not is_annotation_with_from_future_import_annotations(use)
|
||||
|
|
||||
// either 'M.foo'
|
||||
use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr
|
||||
or
|
||||
// or 'from M import foo'
|
||||
use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr
|
||||
)
|
||||
exists(Expr mod |
|
||||
use.getEnclosingModule() = enclosing.getScope() and
|
||||
not use.getScope+() instanceof Function and
|
||||
mod.pointsTo(m) and
|
||||
not is_annotation_with_from_future_import_annotations(use)
|
||||
|
|
||||
// either 'M.foo'
|
||||
use.(Attribute).getObject() = mod and use.(Attribute).getName() = attr
|
||||
or
|
||||
// or 'from M import foo'
|
||||
use.(ImportMember).getModule() = mod and use.(ImportMember).getName() = attr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `use` appears inside an annotation.
|
||||
*/
|
||||
predicate is_used_in_annotation(Expr use) {
|
||||
exists(FunctionExpr f |
|
||||
f.getReturns().getASubExpression*() = use or
|
||||
f.getArgs().getAnAnnotation().getASubExpression*() = use
|
||||
)
|
||||
or
|
||||
exists(AnnAssign a | a.getAnnotation().getASubExpression*() = use)
|
||||
exists(FunctionExpr f |
|
||||
f.getReturns().getASubExpression*() = use or
|
||||
f.getArgs().getAnAnnotation().getASubExpression*() = use
|
||||
)
|
||||
or
|
||||
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/
|
||||
*/
|
||||
predicate is_annotation_with_from_future_import_annotations(Expr use) {
|
||||
exists(ImportMember i | i.getScope() = use.getEnclosingModule() |
|
||||
i.getModule().pointsTo().getName() = "__future__" and i.getName() = "annotations"
|
||||
) and
|
||||
is_used_in_annotation(use)
|
||||
exists(ImportMember i | i.getScope() = use.getEnclosingModule() |
|
||||
i.getModule().pointsTo().getName() = "__future__" and i.getName() = "annotations"
|
||||
) and
|
||||
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'.
|
||||
*/
|
||||
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_transitive_import(first, imp, other) and
|
||||
import_time_module_use(first, other, use, attr) and
|
||||
exists(ImportTimeScope n, SsaVariable v |
|
||||
defn = v.getDefinition() and
|
||||
n = first.getScope() and
|
||||
v.getVariable().getScope() = n and
|
||||
v.getId() = attr
|
||||
|
|
||||
not defn.strictlyDominates(imp.getAnEntryNode())
|
||||
) and
|
||||
not exists(If i | i.isNameEqMain() and i.contains(use))
|
||||
import_time_imported_module(other, first, _) and
|
||||
import_time_transitive_import(first, imp, other) and
|
||||
import_time_module_use(first, other, use, attr) and
|
||||
exists(ImportTimeScope n, SsaVariable v |
|
||||
defn = v.getDefinition() and
|
||||
n = first.getScope() and
|
||||
v.getVariable().getScope() = n and
|
||||
v.getId() = attr
|
||||
|
|
||||
not defn.strictlyDominates(imp.getAnEntryNode())
|
||||
) and
|
||||
not exists(If i | i.isNameEqMain() and i.contains(use))
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ import Cyclic
|
||||
|
||||
from ModuleValue m1, ModuleValue m2, Stmt imp
|
||||
where
|
||||
imp.getEnclosingModule() = m1.getScope() and
|
||||
stmt_imports(imp) = m2 and
|
||||
circular_import(m1, m2) and
|
||||
m1 != m2 and
|
||||
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
|
||||
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
|
||||
not exists(If i | i.isNameEqMain() and i.contains(imp))
|
||||
imp.getEnclosingModule() = m1.getScope() and
|
||||
stmt_imports(imp) = m2 and
|
||||
circular_import(m1, m2) and
|
||||
m1 != m2 and
|
||||
// this query finds all cyclic imports that are *not* flagged by ModuleLevelCyclicImport
|
||||
not failing_import_due_to_cycle(m2, m1, _, _, _, _) and
|
||||
not exists(If i | i.isNameEqMain() and i.contains(imp))
|
||||
select imp, "Import of module $@ begins an import cycle.", m2, m2.getName()
|
||||
|
||||
@@ -17,69 +17,69 @@ import python
|
||||
* and module `instead` should be used instead (or `instead = "no replacement"`)
|
||||
*/
|
||||
predicate deprecated_module(string name, string instead, int major, int minor) {
|
||||
name = "posixfile" and instead = "fcntl" and major = 1 and minor = 5
|
||||
or
|
||||
name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5
|
||||
or
|
||||
name = "rgbimgmodule" and instead = "no replacement" and major = 2 and minor = 5
|
||||
or
|
||||
name = "pre" and instead = "re" and major = 1 and minor = 5
|
||||
or
|
||||
name = "whrandom" and instead = "random" and major = 2 and minor = 1
|
||||
or
|
||||
name = "rfc822" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "mimetools" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "MimeWriter" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "mimify" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "rotor" and instead = "no replacement" and major = 2 and minor = 4
|
||||
or
|
||||
name = "statcache" and instead = "no replacement" and major = 2 and minor = 2
|
||||
or
|
||||
name = "mpz" and instead = "a third party" and major = 2 and minor = 2
|
||||
or
|
||||
name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "multifile" and instead = "email" and major = 2 and minor = 5
|
||||
or
|
||||
name = "sets" and instead = "builtins" and major = 2 and minor = 6
|
||||
or
|
||||
name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4
|
||||
or
|
||||
name = "macfs" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "md5" and instead = "hashlib" and major = 2 and minor = 5
|
||||
or
|
||||
name = "sha" and instead = "hashlib" and major = 2 and minor = 5
|
||||
name = "posixfile" and instead = "fcntl" and major = 1 and minor = 5
|
||||
or
|
||||
name = "gopherlib" and instead = "no replacement" and major = 2 and minor = 5
|
||||
or
|
||||
name = "rgbimgmodule" and instead = "no replacement" and major = 2 and minor = 5
|
||||
or
|
||||
name = "pre" and instead = "re" and major = 1 and minor = 5
|
||||
or
|
||||
name = "whrandom" and instead = "random" and major = 2 and minor = 1
|
||||
or
|
||||
name = "rfc822" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "mimetools" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "MimeWriter" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "mimify" and instead = "email" and major = 2 and minor = 3
|
||||
or
|
||||
name = "rotor" and instead = "no replacement" and major = 2 and minor = 4
|
||||
or
|
||||
name = "statcache" and instead = "no replacement" and major = 2 and minor = 2
|
||||
or
|
||||
name = "mpz" and instead = "a third party" and major = 2 and minor = 2
|
||||
or
|
||||
name = "xreadlines" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "multifile" and instead = "email" and major = 2 and minor = 5
|
||||
or
|
||||
name = "sets" and instead = "builtins" and major = 2 and minor = 6
|
||||
or
|
||||
name = "buildtools" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "cfmfile" and instead = "no replacement" and major = 2 and minor = 4
|
||||
or
|
||||
name = "macfs" and instead = "no replacement" and major = 2 and minor = 3
|
||||
or
|
||||
name = "md5" and instead = "hashlib" and major = 2 and minor = 5
|
||||
or
|
||||
name = "sha" and instead = "hashlib" and major = 2 and minor = 5
|
||||
}
|
||||
|
||||
string deprecation_message(string mod) {
|
||||
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
|
||||
result =
|
||||
"The " + mod + " module was deprecated in version " + major.toString() + "." +
|
||||
minor.toString() + "."
|
||||
)
|
||||
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
|
||||
result =
|
||||
"The " + mod + " module was deprecated in version " + major.toString() + "." +
|
||||
minor.toString() + "."
|
||||
)
|
||||
}
|
||||
|
||||
string replacement_message(string mod) {
|
||||
exists(string instead | deprecated_module(mod, instead, _, _) |
|
||||
result = " Use " + instead + " module instead." and not instead = "no replacement"
|
||||
or
|
||||
result = "" and instead = "no replacement"
|
||||
)
|
||||
exists(string instead | deprecated_module(mod, instead, _, _) |
|
||||
result = " Use " + instead + " module instead." and not instead = "no replacement"
|
||||
or
|
||||
result = "" and instead = "no replacement"
|
||||
)
|
||||
}
|
||||
|
||||
from ImportExpr imp, string name, string instead
|
||||
where
|
||||
name = imp.getName() and
|
||||
deprecated_module(name, instead, _, _) and
|
||||
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
|
||||
except.getType().pointsTo(ClassValue::importError()) and
|
||||
except.containsInScope(imp)
|
||||
)
|
||||
name = imp.getName() and
|
||||
deprecated_module(name, instead, _, _) and
|
||||
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
|
||||
except.getType().pointsTo(ClassValue::importError()) and
|
||||
except.containsInScope(imp)
|
||||
)
|
||||
select imp, deprecation_message(name) + replacement_message(name)
|
||||
|
||||
@@ -16,19 +16,19 @@ import semmle.python.filters.Tests
|
||||
|
||||
from ImportMember im, ModuleValue m, AttrNode store_attr, string name
|
||||
where
|
||||
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and
|
||||
im.getName() = name and
|
||||
/* Modification must be in a function, so it can occur during lifetime of the import value */
|
||||
store_attr.getScope() instanceof Function and
|
||||
/* variable resulting from import must have a long lifetime */
|
||||
not im.getScope() instanceof Function and
|
||||
store_attr.isStore() and
|
||||
store_attr.getObject(name).pointsTo(m) and
|
||||
/* Import not in same module as modification. */
|
||||
not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and
|
||||
/* Modification is not in a test */
|
||||
not store_attr.getScope().getScope*() instanceof TestScope
|
||||
m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and
|
||||
im.getName() = name and
|
||||
/* Modification must be in a function, so it can occur during lifetime of the import value */
|
||||
store_attr.getScope() instanceof Function and
|
||||
/* variable resulting from import must have a long lifetime */
|
||||
not im.getScope() instanceof Function and
|
||||
store_attr.isStore() and
|
||||
store_attr.getObject(name).pointsTo(m) and
|
||||
/* Import not in same module as modification. */
|
||||
not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and
|
||||
/* Modification is not in a test */
|
||||
not store_attr.getScope().getScope*() instanceof TestScope
|
||||
select im,
|
||||
"Importing the value of '" + name +
|
||||
"' from $@ means that any change made to $@ will be not be observed locally.", m,
|
||||
"module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName()
|
||||
"Importing the value of '" + name +
|
||||
"' from $@ means that any change made to $@ will be not be observed locally.", m,
|
||||
"module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName()
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
import python
|
||||
|
||||
predicate shadowsImport(Variable l) {
|
||||
exists(Import i, Name shadow |
|
||||
shadow = i.getAName().getAsname() and
|
||||
shadow.getId() = l.getId() and
|
||||
i.getScope() = l.getScope().getScope*()
|
||||
)
|
||||
exists(Import i, Name shadow |
|
||||
shadow = i.getAName().getAsname() and
|
||||
shadow.getId() = l.getId() and
|
||||
i.getScope() = l.getScope().getScope*()
|
||||
)
|
||||
}
|
||||
|
||||
from Variable l, Name defn
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user