Merge branch 'master' into python-objectapi-to-valueapi-wrongnumberargumentsincall

This commit is contained in:
Rebecca Valentine
2020-04-03 20:25:53 -07:00
1055 changed files with 33246 additions and 29161 deletions

View File

@@ -6,7 +6,7 @@
* builtin
* object
*/
import python
from Expr e, string name

View File

@@ -5,7 +5,7 @@
* @tags call
* function
*/
import python
from Value len, CallNode call

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
* block
* statement
*/
import python
from StmtList blk

View File

@@ -1,7 +1,7 @@
/**
* @id py/examples/emptythen
* @name If statements with empty then branch
* @description Finds 'if' statements where the "then" branch
* @description Finds 'if' statements where the "then" branch
* consists entirely of Pass statements
* @tags if
* then
@@ -9,13 +9,13 @@
* conditional
* branch
*/
import python
from If i
where
not exists(Stmt s |
i.getStmt(_) = s and
i.getStmt(_) = s and
not s instanceof Pass
)
select i

View File

@@ -6,7 +6,7 @@
* test
* boolean
*/
import python
from Compare eq

View File

@@ -6,7 +6,7 @@
* equality
* expression statement
*/
import python
from ExprStmt e, Compare eq

View File

@@ -9,11 +9,11 @@
* subtype
* supertype
*/
import python
from ClassObject sub, ClassObject base
where
where
base.getName() = "MyClass" and
sub.getABaseType() = base
select sub

View File

@@ -4,7 +4,7 @@
* @description Finds files called `spam.py`
* @tags file
*/
import python
from File f

View File

@@ -10,4 +10,4 @@ import python
from Function f
where f.isGenerator()
select f
select f

View File

@@ -5,7 +5,7 @@
* @tags integer
* literal
*/
import python
from IntegerLiteral literal

View File

@@ -5,11 +5,11 @@
* @tags call
* method
*/
import python
from AstNode call, PythonFunctionValue method
where
where
method.getQualifiedName() = "MyClass.methodName" and
method.getACall().getNode() = call
select call

View File

@@ -6,7 +6,7 @@
* constructor
* new
*/
import python
from Call new, ClassValue cls

View File

@@ -5,7 +5,7 @@
* @tags method
* override
*/
import python
from FunctionObject override, FunctionObject base

View File

@@ -4,11 +4,11 @@
* @description Find print statements or calls to the builtin function 'print'
* @tags print
*/
import python
from AstNode print
where
where
/* Python 2 without `from __future__ import print_function` */
print instanceof Print
or

View File

@@ -1,19 +1,20 @@
/**
* @id py/examples/private-access
* @name Private access
* @description Find accesses to "private" attributes (those starting with an underscore)
* @description Find accesses to "private" attributes (those starting with an underscore)
* @tags access
* private
*/
import python
predicate is_private(Attribute a) {
a.getName().matches("\\_%") and
a.getName().matches("\\_%") and
not a.getName().matches("\\_\\_%\\_\\_")
}
from Attribute access
where is_private(access) and
not access.getObject().(Name).getId() = "self"
where
is_private(access) and
not access.getObject().(Name).getId() = "self"
select access

View File

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

View File

@@ -1,11 +1,11 @@
/**
* @id py/examples/raw-string
* @name Raw string literals
* @description Finds string literals with an 'r' prefix
* @description Finds string literals with an 'r' prefix
* @tags string
* raw
*/
import python
from StrConst s

View File

@@ -8,11 +8,11 @@
* collection
* add
*/
import python
from SubscriptNode store
where
where
store.isStore() and
store.getIndex().pointsTo(Value::named("None"))
select store

View File

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

View File

@@ -3,7 +3,6 @@ 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
@@ -19,7 +18,8 @@ class CheckClass extends ClassObject {
forall(ClassObject sup |
sup = this.getAnImproperSuperType() and
sup.declaresAttribute("__init__") and
not sup = theObjectType() |
not sup = theObjectType()
|
sup.declaredAttribute("__init__") instanceof PyFunctionObject
)
}
@@ -32,108 +32,111 @@ class CheckClass extends ClassObject {
}
predicate sometimesDefines(string name) {
this.alwaysDefines(name) or
exists(SelfAttributeStore sa |
sa.getScope().getScope+() = this.getAnImproperSuperType().getPyClass() |
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 |
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()
)
(
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())
exists(FunctionObject meth |
meth = this.lookupAttribute(_) and a.getScope() = meth.getFunction()
)
)
}
pragma [nomagic]
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
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"
)
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()
}
private pragma[nomagic] 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()
)
}
private pragma[nomagic] 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 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)
}
}
private Object object_getattribute() {
result.asBuiltin() = theObjectType().asBuiltin().getMember("__getattribute__")
}
private predicate auto_name(string name) {
name = "__class__" or name = "__dict__"
}
private predicate auto_name(string name) { name = "__class__" or name = "__dict__" }

View File

@@ -15,13 +15,13 @@ import python
predicate does_nothing(PyFunctionObject f) {
not exists(Stmt s | s.getScope() = f.getFunction() |
not s instanceof Pass and not ((ExprStmt)s).getValue() = f.getFunction().getDocString()
not s instanceof Pass and not s.(ExprStmt).getValue() = f.getFunction().getDocString()
)
}
/* If a method performs a super() call then it is OK as the 'overridden' method will get called */
predicate calls_super(FunctionObject f) {
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
exists(Call sup, Call meth, Attribute attr, GlobalVariable v |
meth.getScope() = f.getFunction() and
meth.getFunc() = attr and
attr.getObject() = sup and
@@ -33,25 +33,29 @@ predicate calls_super(FunctionObject f) {
/** Holds if the given name is white-listed for some reason */
predicate whitelisted(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"
}
from 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 whitelisted(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()
from
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 whitelisted(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()

View File

@@ -15,7 +15,9 @@ import semmle.python.SelfAttribute
import Equality
predicate class_stores_to_attribute(ClassObject cls, SelfAttributeStore store, string name) {
exists(FunctionObject f | f = cls.declaredAttribute(_) and store.getScope() = f.getFunction() and store.getName() = name) and
exists(FunctionObject f |
f = cls.declaredAttribute(_) and store.getScope() = f.getFunction() and store.getName() = name
) and
/* Exclude classes used as metaclasses */
not cls.getASuperType() = theTypeType()
}
@@ -30,23 +32,26 @@ predicate should_override_eq(ClassObject cls, Object base_eq) {
)
}
/** Does the non-overridden __eq__ method access the attribute,
/**
* Does the non-overridden __eq__ method access the attribute,
* which implies that the __eq__ method does not need to be overridden.
*/
predicate superclassEqExpectsAttribute(ClassObject cls, PyFunctionObject base_eq, string attrname) {
not cls.declaresAttribute("__eq__") and
exists(ClassObject sup | sup = cls.getABaseType() and sup.declaredAttribute("__eq__") = base_eq |
exists(SelfAttributeRead store |
store.getName() = attrname |
exists(SelfAttributeRead store | store.getName() = attrname |
store.getScope() = base_eq.getFunction()
)
)
}
from ClassObject cls, SelfAttributeStore store, Object 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())
select cls, "The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq, "'__eq__'", store, store.getName()
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())
select cls,
"The class '" + cls.getName() + "' does not override $@, but adds the new attribute $@.", base_eq,
"'__eq__'", store, store.getName()

View File

@@ -1,6 +1,5 @@
import python
private Attribute dictAccess(LocalVariable var) {
result.getName() = "__dict__" and
result.getObject() = var.getAnAccess()
@@ -12,18 +11,21 @@ private Call getattr(LocalVariable obj, LocalVariable attr) {
result.getArg(1) = attr.getAnAccess()
}
/** A generic equality method that compares all attributes in its dict,
* or compares attributes using `getattr`. */
/**
* A generic equality method that compares all attributes in its dict,
* 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
self.getAnAccess() = this.getArg(0) and
self.getId() = "self" and
other.getAnAccess() = this.getArg(1) and
exists(Compare eq |
exists(Compare eq |
eq.getOp(0) instanceof Eq or
eq.getOp(0) instanceof NotEq |
eq.getOp(0) instanceof NotEq
|
// `self.__dict__ == other.__dict__`
eq.getAChildNode() = dictAccess(self) and
eq.getAChildNode() = dictAccess(other)
@@ -40,11 +42,11 @@ class GenericEqMethod extends Function {
/** 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
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
@@ -52,19 +54,20 @@ class IdentityEqMethod extends Function {
)
)
}
}
/** 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
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
)
}

View File

@@ -14,8 +14,11 @@
import python
CallableValue defines_equality(ClassValue c, string name) {
(name = "__eq__" or major_version() = 2 and name = "__cmp__")
and
(
name = "__eq__"
or
major_version() = 2 and name = "__cmp__"
) and
result = c.declaredAttribute(name)
}
@@ -26,8 +29,12 @@ CallableValue implemented_method(ClassValue c, string name) {
}
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
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
@@ -41,13 +48,15 @@ predicate unhashable(ClassValue cls) {
}
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
select method, "Class $@ implements " + present + " but does not define " + missing + ".", c, c.getName()
where
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()

View File

@@ -13,16 +13,12 @@
import python
import Equality
string equals_or_ne() {
result = "__eq__" or result = "__ne__"
}
string equals_or_ne() { result = "__eq__" or result = "__ne__" }
predicate total_ordering(Class cls) {
exists(Attribute a | a = cls.getADecorator() |
a.getName() = "total_ordering")
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
or
exists(Name n | n = cls.getADecorator() |
n.getId() = "total_ordering")
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
}
CallableValue implemented_method(ClassValue c, string name) {
@@ -33,18 +29,20 @@ string unimplemented_method(ClassValue c) {
not c.declaresAttribute(result) and result = equals_or_ne()
}
predicate violates_equality_contract(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
predicate violates_equality_contract(
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
}
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()
select method, "Class $@ implements " + present + " but does not implement " + missing + ".", c,
c.getName()

View File

@@ -13,17 +13,18 @@
import python
predicate total_ordering(Class cls) {
exists(Attribute a | a = cls.getADecorator() |
a.getName() = "total_ordering")
exists(Attribute a | a = cls.getADecorator() | a.getName() = "total_ordering")
or
exists(Name n | n = cls.getADecorator() |
n.getId() = "total_ordering")
exists(Name n | n = cls.getADecorator() | n.getId() = "total_ordering")
}
string ordering_name(int n) {
result = "__lt__" and n = 1 or
result = "__le__" and n = 2 or
result = "__gt__" and n = 3 or
result = "__lt__" and n = 1
or
result = "__le__" and n = 2
or
result = "__gt__" and n = 3
or
result = "__ge__" and n = 4
}
@@ -32,8 +33,7 @@ predicate overrides_ordering_method(ClassValue c, string name) {
(
c.declaresAttribute(name)
or
exists(ClassValue sup |
sup = c.getASuperType() and not sup = Value::named("object") |
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
sup.declaresAttribute(name)
)
)
@@ -41,15 +41,14 @@ predicate overrides_ordering_method(ClassValue c, string name) {
string unimplemented_ordering(ClassValue c, int n) {
not c = Value::named("object") and
not overrides_ordering_method(c, result) 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, _))
or
exists(string prefix, int nm1 |
n = nm1 + 1 and prefix = unimplemented_ordering_methods(c, nm1) |
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
@@ -60,16 +59,15 @@ string unimplemented_ordering_methods(ClassValue c, int 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)
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, _))
select c, "Class " + c.getName() + " implements $@, but does not implement " + unimplemented_ordering_methods(c, 4) + ".",
ordering, name
where
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

View File

@@ -13,15 +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()
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()
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()

View File

@@ -13,23 +13,18 @@
import python
from ClassObject supercls, string method, Call call,
FunctionObject overriding, FunctionObject overridden
from
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)
)
select call, "Call to self.$@ in __init__ method, which is overridden by $@.",
overridden, method,
overriding, overriding.descriptiveString()
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()

View File

@@ -16,26 +16,29 @@ import ClassAttributes
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
c.sometimesDefines(a.getName()) and
exists(SelfAttributeRead guard, If i |
exists(SelfAttributeRead guard, If i |
i.contains(a) and
c.assignedInInit(guard.getName()) |
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
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()
select a, "Attribute '" + a.getName() +
"' is not defined in the class body nor in the __init__() method, but it is defined $@", sa, "here"
where
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"

View File

@@ -1,17 +1,20 @@
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) {
pragma[noinline]
private predicate multiple_invocation_paths_helper(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject 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) {
pragma[noinline]
private predicate multiple_invocation_paths(
FunctionInvocation top, FunctionInvocation i1, FunctionInvocation i2, FunctionObject multi
) {
multiple_invocation_paths_helper(top, i1, i2, multi) and
i2.getFunction() = multi
}
@@ -21,9 +24,10 @@ predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject m
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 */
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())
@@ -53,7 +57,9 @@ private predicate missing_call(FunctionObject meth, string name) {
}
/** 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) {
predicate missing_call_to_superclass_method(
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 */
@@ -63,10 +69,7 @@ predicate missing_call_to_superclass_method(ClassObject self, FunctionObject top
sup = self.getAnImproperSuperType() and
named_attributes_not_method(sup, name)
) and
not self.isAbstract()
and
does_something(missing)
and
not self.isAbstract() and
does_something(missing) and
not missing_call(top, name)
}

View File

@@ -13,14 +13,12 @@
import python
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()
select self, "Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
missing, missing.descriptiveString()
select self,
"Class " + self.getName() + " may not be cleaned up properly as $@ is not called during deletion.",
missing, missing.descriptiveString()

View File

@@ -14,7 +14,6 @@ import python
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
@@ -24,5 +23,6 @@ where
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"
select self,
"Class " + self.getName() + " may not be initialized properly as $@ is not called from its $@.",
missing, missing.descriptiveString(), initializer, "__init__ method"

View File

@@ -15,8 +15,7 @@ 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 |
exists(string name | cls.lookupAttribute(name) = get_set |
name = "__get__" or name = "__set__" or name = "__delete__"
) and
cls.lookupAttribute(_) = f and
@@ -27,7 +26,7 @@ predicate mutates_descriptor(ClassObject cls, SelfAttributeStore s) {
}
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()
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()

View File

@@ -13,59 +13,80 @@
import python
class InitCallStmt extends ExprStmt {
InitCallStmt() {
exists(Call call, Attribute attr | call = this.getValue() and attr = call.getFunc() |
attr.getName() = "__init__")
attr.getName() = "__init__"
)
}
}
predicate overwrites_which(Function subinit, AssignStmt write_attr, string which) {
write_attr.getScope() = subinit and self_write_stmt(write_attr, _) and
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")
)
)
(
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)
{
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)
)
predicate attribute_overwritten(
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)
)
}
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
select overwrites,
"Assignment overwrites attribute " + name + ", which was previously defined in " + classtype +
" $@.", overwritten, classname

View File

@@ -14,4 +14,6 @@ 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."
select prop,
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
" is an old-style class."

View File

@@ -16,4 +16,6 @@ import python
from ClassObject c
where not c.isC() 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."
select c,
"Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager."

View File

@@ -11,30 +11,36 @@
* @id py/attribute-shadows-method
*/
/* Determine if a class defines a method that is shadowed by an attribute
defined in a super-class
*/
/*
* Determine if a class defines a method that is shadowed by an attribute
* defined in a super-class
*/
/* Need to find attributes defined in superclass (only in __init__?) */
import python
predicate shadowed_by_super_class(ClassObject c, ClassObject supercls, Assign assign, FunctionObject f)
{
c.getASuperType() = supercls and c.declaredAttribute(_) = f and
predicate shadowed_by_super_class(
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
((Name)attr.getObject()).getId() = "self" and
attr.getObject().(Name).getId() = "self" and
attr.getName() = f.getName() and
assign.getScope() = ((FunctionExpr)init.getOrigin()).getInnerScope()
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. */
/*
* 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())
}
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"
select shadowed.getOrigin(),
"Method " + shadowed.getName() + " is shadowed by $@ in super class '" + supercls.getName() + "'.",
assign, "an attribute"

View File

@@ -13,10 +13,15 @@
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 ((Name)s.getFunc()).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
where uses_of_super_in_old_style_class(c)
select c, "super() will not work in old-style classes"
select c, "super() will not work in old-style classes"

View File

@@ -13,15 +13,16 @@
import python
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()
select self, "Class " + self.getName() + " may not be cleaned up properly as $@ may be called multiple times during destruction.",
multi, multi.descriptiveString()
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()
select self,
"Class " + self.getName() +
" may not be cleaned up properly as $@ may be called multiple times during destruction.", multi,
multi.descriptiveString()

View File

@@ -14,13 +14,16 @@ import python
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()
select self, "Class " + self.getName() + " may not be initialized properly as $@ may be called multiple times during initialization.",
multi, multi.descriptiveString()
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()
select self,
"Class " + self.getName() +
" may not be initialized properly as $@ may be called multiple times during initialization.",
multi, multi.descriptiveString()

View File

@@ -32,4 +32,3 @@ predicate report_undefined_class_attribute(Attribute a, ClassObject c, string na
from Attribute a, ClassObject c, string name
where report_undefined_class_attribute(a, c, name)
select a, "Attribute '" + name + "' is not defined in either the class body or in any method"

View File

@@ -21,63 +21,68 @@ predicate does_not_define_special_method(Class cls) {
not exists(Function f | f = cls.getAMethod() and f.isSpecialMethod())
}
predicate no_inheritance(Class c) {
not exists(ClassObject cls, ClassObject other |
cls.getPyClass() = c and
other != theObjectType() |
cls.getPyClass() = c and
other != theObjectType()
|
other.getABaseType() = cls or
cls.getABaseType() = other
)
and
) and
not exists(Expr base | base = c.getABase() |
not base instanceof Name or ((Name)base).getId() != "object"
not base instanceof Name or base.(Name).getId() != "object"
)
}
predicate is_decorated(Class c) {
exists(c.getADecorator())
}
predicate is_decorated(Class c) { exists(c.getADecorator()) }
predicate is_stateful(Class c) {
exists(Function method, ExprContext ctx |
method.getScope() = c and (ctx instanceof Store or ctx instanceof AugStore) |
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)
)
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(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
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."
)
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."
)
select c, msg

View File

@@ -14,14 +14,11 @@
*/
import python
import Expressions.CallArgs
from Call call, ClassObject cls, string name, FunctionObject init
where
illegally_named_parameter_objectapi(call, cls, name)
and init = get_function_or_initializer_objectapi(cls)
select
call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, init.getQualifiedName()
illegally_named_parameter_objectapi(call, cls, name) and
init = get_function_or_initializer_objectapi(cls)
select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init,
init.getQualifiedName()

View File

@@ -17,9 +17,15 @@ import Expressions.CallArgs
from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init
where
(
too_many_args_objectapi(call, cls, limit) and too = "too many arguments" and should = "no more than "
or
too_few_args_objectapi(call, cls, limit) and too = "too few arguments" and should = "no fewer than "
) and init = get_function_or_initializer_objectapi(cls)
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, init.getQualifiedName()
(
too_many_args_objectapi(call, cls, limit) and
too = "too many arguments" and
should = "no more than "
or
too_few_args_objectapi(call, cls, limit) and
too = "too few arguments" and
should = "no fewer than "
) and
init = get_function_or_initializer_objectapi(cls)
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init,
init.getQualifiedName()

View File

@@ -14,17 +14,16 @@
import python
predicate doesnt_reraise(ExceptStmt ex) {
ex.getAFlowNode().getBasicBlock().reachesExit()
}
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
predicate catches_base_exception(ExceptStmt ex) {
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)
where
catches_base_exception(ex) and
doesnt_reraise(ex)
select ex, "Except block directly handles BaseException."

View File

@@ -13,21 +13,18 @@
import python
predicate
empty_except(ExceptStmt ex) {
predicate empty_except(ExceptStmt ex) {
not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass)
}
predicate no_else(ExceptStmt ex) {
not exists(ex.getTry().getOrelse())
}
predicate no_else(ExceptStmt ex) { not exists(ex.getTry().getOrelse()) }
predicate no_comment(ExceptStmt ex) {
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) {
@@ -38,7 +35,8 @@ 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 |
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 */
@@ -50,8 +48,7 @@ predicate try_has_normal_exit(Try try) {
predicate attribute_access(Stmt s) {
s.(ExprStmt).getValue() instanceof Attribute
or
exists(string name |
s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
exists(string name | s.(ExprStmt).getValue().(Call).getFunc().(Name).getId() = name |
name = "getattr" or name = "setattr" or name = "delattr"
)
or
@@ -65,8 +62,7 @@ predicate subscript(Stmt s) {
}
predicate encode_decode(Call ex, ClassValue type) {
exists(string name |
ex.getFunc().(Attribute).getName() = name |
exists(string name | ex.getFunc().(Attribute).getName() = name |
name = "encode" and type = ClassValue::unicodeEncodeError()
or
name = "decode" and type = ClassValue::unicodeDecodeError()
@@ -80,8 +76,7 @@ predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
}
predicate focussed_handler(ExceptStmt ex) {
exists(Stmt s, ClassValue type |
small_handler(ex, s, type) |
exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
subscript(s) and type.getASuperType() = ClassValue::lookupError()
or
attribute_access(s) and type = ClassValue::attributeError()
@@ -92,12 +87,15 @@ predicate focussed_handler(ExceptStmt ex) {
)
}
Try try_return() {
not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return
}
Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return }
from ExceptStmt ex
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)
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)
select ex, "'except' clause does nothing but pass and there is no explanatory comment."

View File

@@ -16,6 +16,10 @@ import Raising
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, _)
select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead."
where
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."

View File

@@ -14,21 +14,21 @@
import python
predicate incorrect_except_order(ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject 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()
)
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()
)
}
ClassObject except_class(ExceptStmt ex) {
ex.getType().refersTo(result)
}
ClassValue except_class(ExceptStmt ex) { ex.getType().pointsTo(result) }
from ExceptStmt ex1, ClassObject cls1, ExceptStmt ex2, ClassObject cls2
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()
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()

View File

@@ -1,4 +1,3 @@
import python
/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */

View File

@@ -15,5 +15,4 @@ import Exceptions.NotImplemented
from Expr notimpl
where use_of_not_implemented_in_raise(_, notimpl)
select notimpl, "NotImplemented is not an Exception. Did you mean NotImplementedError?"

View File

@@ -1,16 +1,12 @@
import python
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */
/** Whether the raise statement 'r' raises 'type' from origin 'orig' */
predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) {
exists(Expr exception |
exception = r.getRaised() |
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(Value val | exception.pointsTo(val, orig) | val.getClass() = type)
)
}

View File

@@ -12,8 +12,11 @@
import python
from Raise r, Value v, AstNode origin
where 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"
where
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"

View File

@@ -12,13 +12,9 @@
import python
FunctionObject iter() {
result = Object::builtin("iter")
}
FunctionValue iter() { result = Value::named("iter") }
BuiltinFunctionObject next() {
result = Object::builtin("next")
}
BuiltinFunctionValue next() { result = Value::named("next") }
predicate call_to_iter(CallNode call, EssaVariable sequence) {
sequence.getAUse() = iter().getArgumentForCall(call, 0)
@@ -28,6 +24,10 @@ predicate call_to_next(CallNode call, ControlFlowNode iter) {
iter = next().getArgumentForCall(call, 0)
}
predicate call_to_next_has_default(CallNode call) {
exists(call.getArg(1)) or exists(call.getArgByName("default"))
}
predicate guarded_not_empty_sequence(EssaVariable sequence) {
sequence.getDefinition() instanceof EssaEdgeRefinement
}
@@ -43,19 +43,19 @@ predicate iter_not_exhausted(EssaVariable iterator) {
predicate stop_iteration_handled(CallNode call) {
exists(Try t |
t.containsInScope(call.getNode()) and
t.getAHandler().getType().refersTo(theStopIterationType())
t.getAHandler().getType().pointsTo(ClassValue::stopIteration())
)
}
from CallNode call
where call_to_next(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)
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)
select call, "Call to next() in a generator"

View File

@@ -1,14 +1,10 @@
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()
)
exists(TupleObject t | call.getStarargs().refersTo(t) | result = t.getLength())
or
result = count(call.getStarargs().(List).getAnElt())
}
@@ -16,10 +12,7 @@ private int varargs_length_objectapi(Call call) {
private int varargs_length(Call call) {
not exists(call.getStarargs()) and result = 0
or
exists(TupleValue t |
call.getStarargs().pointsTo(t) |
result = t.length()
)
exists(TupleValue t | call.getStarargs().pointsTo(t) | result = t.length())
or
result = count(call.getStarargs().(List).getAnElt())
}
@@ -38,43 +31,43 @@ private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
not func.getScope().getAKeywordOnlyArg().getId() = result.getArg()
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
/**
* Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* 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
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
)
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
/**
* Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* 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
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
)
}
@@ -88,33 +81,32 @@ int arg_count(Call call) {
/* 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
@@ -135,20 +127,22 @@ predicate illegally_named_parameter(Call call, Value func, string name) {
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
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
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
)
}
@@ -156,7 +150,8 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) {
predicate too_few_args(Call call, Value callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
not illegally_named_parameter(call, callable, _) and
not exists(call.getStarargs()) and not exists(call.getKwargs()) 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
@@ -177,16 +172,18 @@ predicate too_few_args(Call call, Value callable, int limit) {
predicate too_many_args_objectapi(Call call, Object callable, int limit) {
// Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call'
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
|
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
or
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
or
callable instanceof ClassObject and
call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1
call.getAFlowNode() = get_a_call_objectapi(callable) and
limit = func.maxParameters() - 1
) and
positional_arg_count_for_call_objectapi(call, callable) > limit
}
@@ -204,7 +201,8 @@ predicate too_many_args(Call call, Value callable, int limit) {
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
call.getAFlowNode() = get_a_call(callable) and
limit = func.maxParameters() - 1
) and
positional_arg_count_for_call(call, callable) > limit
}
@@ -223,34 +221,34 @@ predicate wrong_args(Call call, FunctionValue func, int limit, string too) {
too_many_args(call, func, limit) and too = "too many"
}
/** Holds if `call` has correct number of arguments for `func`.
/**
* Holds if `call` has correct number of arguments for `func`.
* Implies nothing about whether `call` could call `func`.
*/
bindingset[call, func]
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) + 1 >= func.minParameters() and
arg_count_objectapi(call) < func.maxParameters()
}
/** Holds if `call` has correct number of arguments for `func`.
/**
* Holds if `call` has correct number of arguments for `func`.
* Implies nothing about whether `call` could call `func`.
*/
bindingset[call, func]
bindingset[call, func]
predicate correct_args_if_called_as_method(Call call, FunctionValue func) {
arg_count(call)+1 >= func.minParameters()
and
arg_count(call) + 1 >= func.minParameters() and
arg_count(call) < func.maxParameters()
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `call` is a call to `overriding`, which overrides `func`. */
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) {
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
@@ -258,4 +256,4 @@ predicate overridden_call(FunctionValue func, FunctionValue overriding, Call cal
/** Holds if `func` will raise a `NotImplemented` error. */
predicate isAbstract(FunctionValue func) {
func.getARaisedType() = ClassValue::notImplementedError()
}
}

View File

@@ -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 + "."

View File

@@ -16,6 +16,8 @@ import python
from Compare comparison, Expr left, Expr right
where
comparison.compares(left, _, right) and left.isConstant() and right.isConstant() and
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."

View File

@@ -16,6 +16,5 @@ import python
import Expressions.RedundantComparison
from RedundantComparison comparison
where
comparison.maybeMissingSelf()
where comparison.maybeMissingSelf()
select comparison, "Comparison of identical values; may be missing 'self'."

View File

@@ -15,16 +15,19 @@
import python
import semmle.python.Comparisons
/* Holds if the comparison `comp` is of the complex form `a op b op c` and not of
/*
* Holds if the comparison `comp` is of the complex form `a op b op c` and not of
* the simple form `a op b`.
*/
private predicate is_complex(Expr comp) {
exists(comp.(Compare).getOp(1))
or
is_complex(comp.(UnaryExpr).getOperand())
}
/** A test is useless if for every block that it controls there is another test that is at least as
/**
* A test is useless if for every block that it controls there is another test that is at least as
* strict and also controls that block.
*/
private predicate useless_test(Comparison comp, ComparisonControlBlock controls, boolean isTrue) {
@@ -34,17 +37,15 @@ private predicate useless_test(Comparison comp, ComparisonControlBlock controls,
}
private predicate useless_test_ast(AstNode comp, AstNode previous, boolean isTrue) {
forex(Comparison compnode, ConditionBlock block|
forex(Comparison compnode, ConditionBlock block |
compnode.getNode() = comp and
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, _)
where
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"

View File

@@ -17,13 +17,12 @@ import semmle.python.strings
predicate dict_key(Dict d, Expr k, string s) {
k = d.getAKey() and
(
s = ((Num)k).getN()
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 |
exists(StrConst c | c = k |
s = "u\"" + c.getText() + "\"" and c.isUnicode()
or
s = "b\"" + c.getText() + "\"" and not c.isUnicode()
@@ -32,13 +31,15 @@ predicate dict_key(Dict d, Expr k, string s) {
}
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())
)
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())
)
select k1, "Dictionary key " + repr(k1) + " is subsequently $@.", k2, "overwritten"

View File

@@ -13,23 +13,22 @@
import python
class DelCall extends Call {
DelCall() {
((Attribute)this.getFunc()).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() = ((Name)this.getArg(0)).getVariable() or
// ... or the call is of the form `super(Type, self).__del__()`, or the equivalent
// Python 3: `super().__del__()`.
exists(Call superCall | superCall = ((Attribute)this.getFunc()).getObject() |
((Name)superCall.getFunc()).getId() = "super"
)
)
}
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"
)
)
}
}
from DelCall del
where not del.isSuperCall()
select del, "The __del__ special method is called explicitly."
select del, "The __del__ special method is called explicitly."

View File

@@ -1,11 +1,7 @@
import python
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
@@ -31,83 +27,77 @@ library class PossibleAdvancedFormatString extends StrConst {
(
result = this.getText().substring(start, end).regexpCapture("\\{([^!:.\\[]+)[!:.\\[].*", 1)
or
result = this.getText().substring(start+1, end-1) and result.regexpMatch("[^!:.\\[]+")
result = this.getText().substring(start + 1, end - 1) and result.regexpMatch("[^!:.\\[]+")
)
}
/** Gets the name of the formatting field at [start, end) */
string getFieldName(int start, int end) {
result = this.fieldId(start, end)
and not exists(this.getFieldNumber(start, 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) |
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(_, _)
}
predicate isImplicitlyNumbered() { this.implicitlyNumberedField(_, _) }
/** Whether this format string has explicitly numbered fields */
predicate isExplicitlyNumbered() {
exists(this.fieldId(_, _).toInt())
}
predicate isExplicitlyNumbered() { exists(this.fieldId(_, _).toInt()) }
}
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
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)
text.charAt(index) = "{" and
text.charAt(index - 1) = "{" and
brace_sequence(fmt, index - 1, len - 1)
)
}
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)
}
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) |
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
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
@@ -116,26 +106,14 @@ private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatS
}
class AdvancedFormatString extends PossibleAdvancedFormatString {
AdvancedFormatString() {
advanced_format_call(_, this, _)
}
AdvancedFormatString() { advanced_format_call(_, this, _) }
}
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)
}
AdvancedFormatString getAFormat() {
advanced_format_call(this, result, _)
}
int providedArgCount() { advanced_format_call(this, _, result) }
AdvancedFormatString getAFormat() { advanced_format_call(this, result, _) }
}

View File

@@ -15,4 +15,4 @@ import AdvancedFormatting
from AdvancedFormattingCall call, AdvancedFormatString fmt
where call.getAFormat() = fmt and fmt.isImplicitlyNumbered() and fmt.isExplicitlyNumbered()
select fmt, "Formatting string mixes implicitly and explicitly numbered fields."
select fmt, "Formatting string mixes implicitly and explicitly numbered fields."

View File

@@ -11,16 +11,18 @@
*/
import python
import python
import AdvancedFormatting
int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 }
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)
select call, "Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""
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)
select call,
"Too many arguments for string format. Format $@ requires only " + max_field + ", but " +
arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\""

View File

@@ -14,14 +14,18 @@ import python
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."
)
select call, "Surplus named argument for string format. An argument named '" + name +
"' is provided, but it is not required by $@.", fmt, 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."
)
select call,
"Surplus named argument for string format. An argument named '" + name +
"' is provided, but it is not required by $@.", fmt, fmt_repr

View File

@@ -15,9 +15,11 @@ import python
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())
select call, "Missing named argument for string format. Format $@ requires '" + name + "', but it is omitted.",
fmt, "\"" + fmt.getText() + "\""
where
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() + "\""

View File

@@ -14,10 +14,16 @@
import python
import AdvancedFormatting
from 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.")
select call, "Too few arguments for string format. Format $@ requires at least " + (max_field+1) + ", but " +
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""
from
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.")
select call,
"Too few arguments for string format. Format $@ requires at least " + (max_field + 1) + ", but " +
arg_count.toString() + provided, fmt, "\"" + fmt.getText() + "\""

View File

@@ -12,7 +12,8 @@
import python
/* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing.
/*
* This assumes that any indexing operation where the value is not a sequence or numpy array involves hashing.
* For sequences, the index must be an int, which are hashable, so we don't need to treat them specially.
* For numpy arrays, the index may be a list, which are not hashable and needs to be treated specially.
*/
@@ -30,7 +31,9 @@ predicate has_custom_getitem(Value v) {
}
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) {
@@ -44,9 +47,7 @@ predicate unhashable_subscript(ControlFlowNode f, ClassValue c, ControlFlowNode
}
predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origin) {
exists(Value v |
f.pointsTo(v, origin) and v.getClass() = cls
|
exists(Value v | f.pointsTo(v, origin) and v.getClass() = cls |
not cls.hasAttribute("__hash__") and not cls.failedInference(_) and cls.isNewStyle()
or
cls.lookup("__hash__") = Value::named("None")
@@ -67,16 +68,18 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi
* it.
*/
predicate typeerror_is_caught(ControlFlowNode f) {
exists (Try try |
exists(Try try |
try.getBody().contains(f.getNode()) and
try.getAHandler().getType().pointsTo(ClassValue::typeError()))
try.getAHandler().getType().pointsTo(ClassValue::typeError())
)
}
from ControlFlowNode f, ClassValue c, ControlFlowNode origin
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()

View File

@@ -14,7 +14,14 @@ import python
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 = "!=")
select comp, "Values compared using '" + op.getSymbol() + "' when equivalence is not the same as identity. Use '" + alt + "' instead."
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 = "!="
)
select comp,
"Values compared using '" + op.getSymbol() +
"' when equivalence is not the same as identity. Use '" + alt + "' instead."

View File

@@ -1,9 +1,9 @@
import python
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)
fcomp.operands(left, op, right) and
(op instanceof Is or op instanceof IsNot)
)
}
@@ -12,8 +12,7 @@ predicate overrides_eq_or_cmp(ClassValue c) {
or
c.declaresAttribute("__eq__") and not c = Value::named("object")
or
exists(ClassValue sup |
sup = c.getASuperType() and not sup = Value::named("object") |
exists(ClassValue sup | sup = c.getASuperType() and not sup = Value::named("object") |
sup.declaresAttribute("__eq__")
)
or
@@ -29,58 +28,56 @@ predicate probablySingleton(ClassValue cls) {
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
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)
}
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() = 0
or
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(int i | i = e.(IntegerLiteral).getN().toInt() | -5 <= i and i <= 256)
or
exists(Tuple t | t = e and not exists(t.getAnElt()))
}
/** The set of values that can be expected to be interned across
/**
* The set of values that can be expected to be interned across
* the main implementations of Python. PyPy, Jython, etc tend to
* 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() = ""
}
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))
}
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) |
comparison_using_is(comp, op1, op, op2) or comparison_using_is(comp, op2, op, op1)
|
op1.inferredValue().getClass() = cls1 and
op2.inferredValue().getClass() = cls2
)
@@ -89,15 +86,17 @@ private predicate comparison_both_types(Compare comp, Cmpop op, ClassValue cls1,
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
comparison_using_is(comp, operand, op, _) or comparison_using_is(comp, _, op, operand)
|
operand.inferredValue().getClass() = 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
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
@@ -105,32 +104,32 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls)
invalid_to_use_is_portably(cls) and
invalid_to_use_is_portably(other)
)
)
and
) 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) |
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
not left.pointsTo(_) and
right.pointsTo(val, origin) and
origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule()
)
)
and
) 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) |
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" |
exists(ClassValue cls, AssignStmt asgn | cls.getASuperType().getName() = "Enum" |
cls.getScope() = asgn.getScope() and
asgn.getValue() = obj
)

View File

@@ -15,10 +15,13 @@ import python
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)
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)
select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString()

View File

@@ -14,10 +14,12 @@ import python
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)
)
select comp, "The result of this comparison with '" + op.getSymbol() + "' may differ between implementations of Python."
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)
)
select comp,
"The result of this comparison with '" + op.getSymbol() +
"' may differ between implementations of Python."

View File

@@ -1,27 +1,22 @@
import python
class RedundantComparison extends Compare {
RedundantComparison() {
exists(Expr left, Expr right |
this.compares(left, _, right)
and
this.compares(left, _, right) and
same_variable(left, right)
)
}
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
)
exists(SelfAttribute sa | sa.getName() = left.getId() | sa.getClass() = cls)
)
)
}
}
private predicate same_variable(Expr left, Expr right) {
@@ -36,11 +31,14 @@ private predicate name_in_comparison(Compare comp, Name n, Variable v) {
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))
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.getName() = a2.getName() and
same_name(a1.getObject(), a2.getObject())
}

View File

@@ -14,9 +14,8 @@ import python
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)
)
select r, "Backspace escape in regular expression at offset " + 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))
select r, "Backspace escape in regular expression at offset " + offset + "."

View File

@@ -15,20 +15,28 @@ 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
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"))
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."
select r,
"This regular expression includes duplicate character '" + char + "' in a set of characters."

View File

@@ -16,5 +16,3 @@ import semmle.python.regex
from Regex r, string missing, string part
where r.getText().regexpMatch(".*\\(P<\\w+>.*") and missing = "?" and part = "named group"
select r, "Regular expression is missing '" + missing + "' in " + part + "."

View File

@@ -16,10 +16,11 @@ 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)
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() + "."
select r,
"This regular expression includes an unmatchable caret at offset " + offset.toString() + "."

View File

@@ -16,11 +16,11 @@ 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)
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() + "."
select r,
"This regular expression includes an unmatchable dollar at offset " + offset.toString() + "."

View File

@@ -1,38 +1,37 @@
/**
* @name Result of integer division may be truncated
* @description The arguments to a division statement may be integers, which
* may cause the result to be truncated in Python 2.
* @kind problem
* @tags maintainability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/truncated-division
*/
/**
* @name Result of integer division may be truncated
* @description The arguments to a division statement may be integers, which
* may cause the result to be truncated in Python 2.
* @kind problem
* @tags maintainability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/truncated-division
*/
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
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_()
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
and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0
and not bin.getNode().getEnclosingModule().hasFromFuture("division")
not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and
not bin.getNode().getEnclosingModule().hasFromFuture("division") and
// Filter out results wrapped in `int(...)`
and not exists(CallNode c |
c = ClassValue::int_().getACall()
and c.getAnArg() = bin
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"

View File

@@ -22,14 +22,13 @@ predicate string_const(Expr s) {
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?"

View File

@@ -15,43 +15,47 @@ 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) = ((Name)c.getArg(arg)).getId()) and
/* Either no **kwargs or they must match */
(not exists(f.getKwarg()) and not exists(c.getKwargs()) or
((Name)f.getKwarg()).getId() = ((Name)c.getKwargs()).getId()) and
/* Either no *args or they must match */
(not exists(f.getVararg()) and not exists(c.getStarargs()) or
((Name)f.getVararg()).getId() = ((Name)c.getStarargs()).getId()) and
/* No named parameters in call */
not exists(c.getAKeyword())
)
and
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())
) 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
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 |
((Name)a.getObject()).getId() = "self" and
cls.hasAttribute(a.getName())
)
/* 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."
select l,
"This 'lambda' is just a simple wrapper around a callable object. Use that object directly."

View File

@@ -16,11 +16,12 @@
import python
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)
select
call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", func, func.descriptiveString()
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()

View File

@@ -18,29 +18,30 @@ 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.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) |
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
)
exists(ImmutableLiteral i | i.getLiteralValue() = args | result = 1)
}
from 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.")
select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided,
origin, "arguments",
fmt, fmt.getText()
from
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.")
select operation,
"Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " +
slen.toString() + provided, origin, "arguments", fmt, fmt.getText()

View File

@@ -11,8 +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

View File

@@ -4,6 +4,7 @@
* @kind problem
* @id py/not-generated-file-filter
*/
import python
import external.DefectFilter
import semmle.python.filters.GeneratedCode

View File

@@ -4,6 +4,7 @@
* @kind problem
* @id py/not-test-file-filter
*/
import python
import external.DefectFilter
import semmle.python.filters.Tests

View File

@@ -13,20 +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(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."
select func,
"Mixing implicit and explicit returns may indicate an error as implicit returns always return None."

View File

@@ -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
select f, meth + " method has been deprecated since Python 2.0"
where
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"

View File

@@ -22,44 +22,87 @@ private predicate indexing_method(string name) {
}
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 = "__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 = "__bool__" or
name = "__int__" or name = "__float__" or
name = "__long__" or
name = "__trunc__" or
name = "__nonzero__" and major_version() = 2
or
name = "__bool__"
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
ex.getAnImproperSuperType() = theTypeErrorType() and
(
name = "__copy__" or
name = "__deepcopy__" or
name = "__call__" or
indexing_method(name) or
attribute_method(name)
name = "__copy__" or
name = "__deepcopy__" or
name = "__call__" or
indexing_method(name) or
attribute_method(name)
)
or
preferred_raise(name, ex)
@@ -84,11 +127,11 @@ predicate no_need_to_raise(string name, string message) {
}
predicate is_abstract(FunctionObject func) {
((Name)func.getFunction().getADecorator()).getId().matches("%abstract%")
func.getFunction().getADecorator().(Name).getId().matches("%abstract%")
}
predicate always_raises(FunctionObject f, ClassObject ex) {
ex = f.getARaisedType() and
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 */
@@ -96,17 +139,17 @@ predicate always_raises(FunctionObject f, ClassObject ex) {
}
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"
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"
)
)
)
select f, "Function always raises $@; " + message, cls, cls.toString()

View File

@@ -14,14 +14,18 @@ import Expressions.CallArgs
from Call call, FunctionObject func, FunctionObject overridden, string problem
where
func.overrides(overridden) and (
wrong_args_objectapi(call, func, _, problem) and correct_args_if_called_as_method_objectapi(call, overridden)
or
exists(string name |
illegally_named_parameter_objectapi(call, func, name) and problem = "an argument named '" + name + "'" and
overridden.getFunction().getAnArg().(Name).getId() = name
func.overrides(overridden) and
(
wrong_args_objectapi(call, func, _, problem) and
correct_args_if_called_as_method_objectapi(call, overridden)
or
exists(string name |
illegally_named_parameter_objectapi(call, func, name) and
problem = "an argument named '" + name + "'" and
overridden.getFunction().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()
select func,
"Overriding method signature does not match $@, where it is passed " + problem +
". Overridden method $@ is correctly specified.", call, "here", overridden,
overridden.descriptiveString()

View File

@@ -15,21 +15,23 @@ import Expressions.CallArgs
from Call call, FunctionObject func, FunctionObject overriding, string problem
where
not func.getName() = "__init__" and
overriding.overrides(func) and
call = overriding.getAMethodCall().getNode() and
correct_args_if_called_as_method_objectapi(call, overriding) and
(
arg_count_objectapi(call)+1 < func.minParameters() and problem = "too few arguments"
or
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
or
exists(string name | call.getAKeyword().getArg() = name and
overriding.getFunction().getAnArg().(Name).getId() = name and
not func.getFunction().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_objectapi(call, overriding) and
(
arg_count_objectapi(call) + 1 < func.minParameters() and problem = "too few arguments"
or
arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments"
or
exists(string name |
call.getAKeyword().getArg() = name and
overriding.getFunction().getAnArg().(Name).getId() = name and
not func.getFunction().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()
select func,
"Overridden method signature does not match $@, where it is passed " + problem +
". Overriding method $@ matches the call.", call, "call", overriding,
overriding.descriptiveString()

View File

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

View File

@@ -13,21 +13,20 @@
import python
Function iter_method(ClassObject t) {
result = ((FunctionObject)t.lookupAttribute("__iter__")).getFunction()
result = t.lookupAttribute("__iter__").(FunctionObject).getFunction()
}
predicate is_self(Name value, Function f) {
value.getVariable() = ((Name)f.getArg(0)).getVariable()
}
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 ClassObject 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()
select t, "Class " + t.getName() + " is an iterator but its $@ method does not return 'self'.",
iter, iter.getName()

View File

@@ -40,9 +40,11 @@ where
(
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."
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."
message =
"Class methods or methods of a type deriving from type should have 'cls' as their first parameter."
)
select f, message

View File

@@ -45,10 +45,12 @@ where
(
if exists(f.getArgName(0))
then
message = "Normal methods should have 'self', rather than '" + f.getArgName(0) +
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')."
message =
"Normal methods should have at least one parameter (the first of which should be 'self')."
) and
not f.hasVarArg()
) and

View File

@@ -16,6 +16,9 @@
import python
from FunctionObject method
where exists(ClassObject c | c.declaredAttribute("__del__") = method and
method.getFunction().getMetrics().getCyclomaticComplexity() > 3)
where
exists(ClassObject c |
c.declaredAttribute("__del__") = method and
method.getFunction().getMetrics().getCyclomaticComplexity() > 3
)
select method, "Overly complex '__del__' method."

View File

@@ -15,15 +15,16 @@ 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) |
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
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2, "tuple of size " + s2
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
"tuple of size " + s2

View File

@@ -15,34 +15,43 @@
*/
import python
import semmle.python.objects.Callables
predicate meaningful_return_value(Expr val) {
val instanceof Name
or
val instanceof BooleanLiteral
or
exists(FunctionObject callee | val = callee.getACall().getNode() and returns_meaningful_value(callee))
exists(FunctionValue callee |
val = callee.getACall().getNode() and returns_meaningful_value(callee)
)
or
not exists(FunctionObject callee | val = callee.getACall().getNode()) and not val instanceof Name
not exists(FunctionValue callee | val = callee.getACall().getNode()) and not val instanceof Name
}
/* Value is used before returning, and thus its value is not lost if ignored */
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(FunctionObject f) {
not exists(f.getFunction().getFallthroughNode())
and
predicate returns_meaningful_value(FunctionValue f) {
not exists(f.getScope().getFallthroughNode()) and
(
exists(Return ret, Expr val | ret.getScope() = f.getFunction() 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.isC() and f.getAnInferredReturnType() != theNoneType() and not f.getName() = "__import__"
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__"
)
}
@@ -55,18 +64,20 @@ predicate wrapped_in_try_except(ExprStmt call) {
)
}
from ExprStmt call, FunctionObject 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
select call, "Call discards return value of function $@. The result is used in " + percentage_used.toString() + "% of calls.",
callee, callee.getName()
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
select call,
"Call discards return value of function $@. The result is used in " + percentage_used.toString() +
"% of calls.", callee, callee.getName()

View File

@@ -6,7 +6,6 @@
* @problem.severity warning
* @tags reliability
* correctness
* @problem.severity warning
* @sub-severity high
* @precision very-high
* @id py/inheritance/signature-mismatch
@@ -15,21 +14,22 @@
import python
import Expressions.CallArgs
from FunctionObject base, PyFunctionObject derived
from FunctionValue base, PythonFunctionValue derived
where
not exists(base.getACall()) and
not exists(FunctionObject a_derived |
a_derived.overrides(base) and
exists(a_derived.getACall())
) and
not derived.getFunction().isSpecialMethod() and
derived.getName() != "__init__" and
derived.isNormalMethod() and
not derived.getFunction().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"
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"

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