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

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

View File

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

View File

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

View File

@@ -60,7 +60,8 @@ predicate deprecated_module(string name, string instead, int major, int minor) {
string deprecation_message(string mod) {
exists(int major, int minor | deprecated_module(mod, _, major, minor) |
result = "The " + mod + " module was deprecated in version " + major.toString() + "." +
result =
"The " + mod + " module was deprecated in version " + major.toString() + "." +
minor.toString() + "."
)
}
@@ -77,8 +78,7 @@ from ImportExpr imp, string name, string instead
where
name = imp.getName() and
deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler()
|
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
except.getType().pointsTo(ClassValue::importError()) and
except.containsInScope(imp)
)

View File

@@ -13,4 +13,4 @@
import python
from EncodingError error
select error, error.getMessage()
select error, error.getMessage()

View File

@@ -13,10 +13,13 @@
import python
predicate shadowsImport(Variable l) {
exists(Import i, Name shadow | shadow = i.getAName().getAsname() and shadow.getId() = l.getId() and i.getScope() = l.getScope().getScope*())
exists(Import i, Name shadow |
shadow = i.getAName().getAsname() and
shadow.getId() = l.getId() and
i.getScope() = l.getScope().getScope*()
)
}
from Variable l, Name defn
where shadowsImport(l) and defn.defines(l) and exists(For for | defn = for.getTarget())
select defn, "Loop variable '" + l.getId() + "' shadows an import"

View File

@@ -14,4 +14,3 @@ import python
from ImportStar i
select i, "Using 'from ... import *' pollutes the namespace"

View File

@@ -12,9 +12,10 @@
import python
predicate import_and_import_from(Import i1, Import i2, Module m) {
i1.getEnclosingModule() = i2.getEnclosingModule() and
exists (ImportExpr e1, ImportExpr e2, ImportMember im |
e1 = i1.getAName().getValue() and im = i2.getAName().getValue() and e2 = im.getModule() |
i1.getEnclosingModule() = i2.getEnclosingModule() and
exists(ImportExpr e1, ImportExpr e2, ImportMember im |
e1 = i1.getAName().getValue() and im = i2.getAName().getValue() and e2 = im.getModule()
|
e1.getName() = m.getName() and e2.getName() = m.getName()
)
}

View File

@@ -11,16 +11,15 @@
* @id py/multiple-imports-on-line
*/
/* Look for imports of the form:
import modA, modB
(Imports should be one per line according PEP 8)
*/
/*
* Look for imports of the form:
* import modA, modB
* (Imports should be one per line according PEP 8)
*/
import python
predicate multiple_import(Import imp) {
count(imp.getAName()) > 1 and not imp.isFromImport()
}
predicate multiple_import(Import imp) { count(imp.getAName()) > 1 and not imp.isFromImport() }
from Import i
where multiple_import(i)

View File

@@ -14,7 +14,8 @@ import python
predicate modules_imports_itself(ImportingStmt i, ModuleValue m) {
i.getEnclosingModule() = m.getScope() and
m = max(string s, ModuleValue m_ |
m =
max(string s, ModuleValue m_ |
s = i.getAnImportedModuleName() and
m_.importedAs(s)
|

View File

@@ -12,33 +12,35 @@
import python
predicate is_simple_import(Import imp) {
not exists(Attribute a | imp.contains(a))
}
predicate is_simple_import(Import imp) { not exists(Attribute a | imp.contains(a)) }
predicate double_import(Import original, Import duplicate, Module m) {
original != duplicate and
is_simple_import(original) and is_simple_import(duplicate) and
is_simple_import(original) and
is_simple_import(duplicate) and
/* Imports import the same thing */
exists (ImportExpr e1, ImportExpr e2 | e1.getName() = m.getName() and e2.getName() = m.getName() and
e1 = original.getAName().getValue() and e2 = duplicate.getAName().getValue()
exists(ImportExpr e1, ImportExpr e2 |
e1.getName() = m.getName() and
e2.getName() = m.getName() and
e1 = original.getAName().getValue() and
e2 = duplicate.getAName().getValue()
) and
original.getAName().getAsname().(Name).getId() = duplicate.getAName().getAsname().(Name).getId()
and
original.getAName().getAsname().(Name).getId() = duplicate.getAName().getAsname().(Name).getId() and
exists(Module enclosing |
original.getScope() = enclosing and
duplicate.getEnclosingModule() = enclosing and
(
/* Duplicate is not at top level scope */
duplicate.getScope() != enclosing
or
/* Original dominates duplicate */
original.getAnEntryNode().dominates(duplicate.getAnEntryNode())
/* Duplicate is not at top level scope */
duplicate.getScope() != enclosing
or
/* Original dominates duplicate */
original.getAnEntryNode().dominates(duplicate.getAnEntryNode())
)
)
)
}
from Import original, Import duplicate, Module m
where double_import(original, duplicate, m)
select duplicate, "This import of module " + m.getName() + " is redundant, as it was previously imported $@.",
original, "on line " + original.getLocation().getStartLine().toString()
select duplicate,
"This import of module " + m.getName() + " is redundant, as it was previously imported $@.",
original, "on line " + original.getLocation().getStartLine().toString()

View File

@@ -14,4 +14,4 @@ import python
from SyntaxError error
where not error instanceof EncodingError
select error, error.getMessage() + " (in Python " + major_version() + ")."
select error, error.getMessage() + " (in Python " + major_version() + ")."

View File

@@ -25,8 +25,8 @@ predicate all_defined(ModuleValue exporter) {
exporter.getScope().getInitModule().(ImportTimeScope).definesName("__all__")
}
from ImportStar imp, ModuleValue exporter
where import_star(imp, exporter) and not all_defined(exporter)
select imp, "Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
exporter, exporter.getName()
select imp,
"Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.",
exporter, exporter.getName()

View File

@@ -12,7 +12,6 @@
*/
import python
import Lexical.CommentedOutCode
from CommentedOutCodeBlock c

View File

@@ -1,6 +1,5 @@
import python
private predicate def_statement(Comment c) {
c.getText().regexpMatch("#(\\S*\\s+)?def\\s.*\\(.*\\).*:\\s*(#.*)?")
}
@@ -12,7 +11,7 @@ private predicate if_statement(Comment c) {
}
private predicate for_statement(Comment c) {
c.getText().regexpMatch("#(\\S*\\s+)?for\\s.*\\sin\\s.*:\\s*(#.*)?")
c.getText().regexpMatch("#(\\S*\\s+)?for\\s.*\\sin\\s.*:\\s*(#.*)?")
}
private predicate with_statement(Comment c) {
@@ -20,11 +19,11 @@ private predicate with_statement(Comment c) {
}
private predicate try_statement(Comment c) {
c.getText().regexpMatch("#(\\S*\\s+)?try:\\s*(#.*)?")
c.getText().regexpMatch("#(\\S*\\s+)?try:\\s*(#.*)?")
or
c.getText().regexpMatch("#(\\S*\\s+)?except\\s*(\\w+\\s*(\\sas\\s+\\w+\\s*)?)?:\\s*(#.*)?")
or
c.getText().regexpMatch("#(\\S*\\s+)?finally:\\s*(#.*)?")
c.getText().regexpMatch("#(\\S*\\s+)?finally:\\s*(#.*)?")
}
private int indentation(Comment c) {
@@ -39,18 +38,16 @@ private predicate class_statement(Comment c) {
c.getText().regexpMatch("#(\\S*\\s+)?class\\s+\\w+.*:\\s*(#.*)?")
}
private predicate triple_quote(Comment c) {
c.getText().regexpMatch("#.*(\"\"\"|''').*")
}
private predicate triple_quote(Comment c) { c.getText().regexpMatch("#.*(\"\"\"|''').*") }
private predicate triple_quoted_string_part(Comment start, Comment end) {
triple_quote(start) and end = start
or
exists(Comment mid |
triple_quote(start) and end = start
or
exists(Comment mid |
triple_quoted_string_part(start, mid) and
end = non_empty_following(mid) and
not triple_quote(end)
)
)
}
private predicate maybe_code(Comment c) {
@@ -59,41 +56,37 @@ private predicate maybe_code(Comment c) {
commented_out_comment(c)
}
private predicate commented_out_comment(Comment c) {
c.getText().regexpMatch("#+\\s+#.*")
}
private predicate commented_out_comment(Comment c) { c.getText().regexpMatch("#+\\s+#.*") }
private int scope_start(Comment start) {
(
def_statement(start) or
class_statement(start)
)
and
result = indentation(start)
and
def_statement(start) or
class_statement(start)
) and
result = indentation(start) and
not non_code(start)
}
private int block_start(Comment start) {
(
if_statement(start) or
for_statement(start) or
try_statement(start) or
with_statement(start)
)
and
result = indentation(start)
and
if_statement(start) or
for_statement(start) or
try_statement(start) or
with_statement(start)
) and
result = indentation(start) and
not non_code(start)
}
private int scope_doc_string_part(Comment start, Comment end) {
result = scope_start(start) and
triple_quote(end) and end = non_empty_following(start)
triple_quote(end) and
end = non_empty_following(start)
or
exists(Comment mid |
exists(Comment mid |
result = scope_doc_string_part(start, mid) and
end = non_empty_following(mid) |
end = non_empty_following(mid)
|
not triple_quote(end)
)
}
@@ -101,15 +94,16 @@ private int scope_doc_string_part(Comment start, Comment end) {
private int scope_part(Comment start, Comment end) {
result = scope_start(start) and end = start
or
exists(Comment mid |
exists(Comment mid |
result = scope_doc_string_part(start, mid) and
end = non_empty_following(mid) and
triple_quote(end)
)
or
exists(Comment mid |
exists(Comment mid |
result = scope_part(start, mid) and
end = non_empty_following(mid) |
end = non_empty_following(mid)
|
indentation(end) > result
)
}
@@ -119,9 +113,10 @@ private int block_part(Comment start, Comment end) {
end = non_empty_following(start) and
indentation(end) > result
or
exists(Comment mid |
exists(Comment mid |
result = block_part(start, mid) and
end = non_empty_following(mid) |
end = non_empty_following(mid)
|
indentation(end) > result
or
result = block_start(end)
@@ -145,13 +140,11 @@ private predicate commented_out_code(Comment c) {
}
private predicate commented_out_code_part(Comment start, Comment end) {
commented_out_code(start) and end = start and
not exists(Comment prev |
non_empty_following(prev) = start |
commented_out_code(prev)
)
commented_out_code(start) and
end = start and
not exists(Comment prev | non_empty_following(prev) = start | commented_out_code(prev))
or
exists(Comment mid |
exists(Comment mid |
commented_out_code_part(start, mid) and
non_empty_following(mid) = end and
commented_out_code(end)
@@ -167,12 +160,7 @@ private predicate commented_out_code_block(Comment start, Comment end) {
/* A single line comment that appears to be commented out code */
class CommentedOutCodeLine extends Comment {
CommentedOutCodeLine () {
exists(CommentedOutCodeBlock b |
b.contains(this)
)
}
CommentedOutCodeLine() { exists(CommentedOutCodeBlock b | b.contains(this)) }
/* Whether this commented-out code line is likely to be example code embedded in a larger comment. */
predicate maybeExampleCode() {
@@ -181,19 +169,13 @@ class CommentedOutCodeLine extends Comment {
block.maybeExampleCode()
)
}
}
/** A block of comments that appears to be commented out code */
class CommentedOutCodeBlock extends @py_comment {
CommentedOutCodeBlock() {
commented_out_code_block(this, _)
}
CommentedOutCodeBlock() { commented_out_code_block(this, _) }
string toString() {
result = "Commented out code"
}
string toString() { result = "Commented out code" }
/** Whether this commented-out code block contains the comment c */
predicate contains(Comment c) {
@@ -207,28 +189,22 @@ class CommentedOutCodeBlock extends @py_comment {
}
/** The length of this comment block (in comments) */
int length() {
result = count(Comment c | this.contains(c))
}
int length() { result = count(Comment c | this.contains(c)) }
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
((Comment)this).getLocation().hasLocationInfo(filepath, bl, bc, _, _)
and
exists(Comment end |
commented_out_code_block(this, end) |
this.(Comment).getLocation().hasLocationInfo(filepath, bl, bc, _, _) and
exists(Comment end | commented_out_code_block(this, end) |
end.getLocation().hasLocationInfo(_, _, _, el, ec)
)
}
/** Whether this commented-out code block is likely to be example code embedded in a larger comment. */
predicate maybeExampleCode() {
exists(CommentBlock block |
block.contains((Comment)this) |
exists(CommentBlock block | block.contains(this.(Comment)) |
exists(int all_code |
all_code = sum (CommentedOutCodeBlock code | block.contains((Comment)code) | code.length())
and
all_code = sum(CommentedOutCodeBlock code | block.contains(code.(Comment)) | code.length()) and
/* This ratio may need fine tuning */
block.length() > all_code*2
block.length() > all_code * 2
)
)
}
@@ -239,13 +215,14 @@ private predicate word_pair(Comment c, string s1, string s2) {
exists(int i1, int i2, int o1, int o2 |
s1 = c.getText().regexpFind("\\w+", i1, o1) and
s2 = c.getText().regexpFind("\\w+", i2, o2) and
i2 = i1 + 1 and
i2 = i1 + 1 and
c.getText().prefix(o1).regexpMatch("[^'\"]*") and
c.getText().substring(o1 + s1.length(), o2).regexpMatch("\\s+")
)
}
/** The comment c cannot be code if it contains a word pair "word1 word2" and
/**
* The comment c cannot be code if it contains a word pair "word1 word2" and
* either:
* 1. word1 is not a keyword and word2 is not an operator:
* "x is" could be code, "return y" could be code, but "isnt code" cannot be code.
@@ -257,48 +234,44 @@ private predicate non_code(Comment c) {
exists(string word1, string word2 |
word_pair(c, word1, word2) and
not word2 = operator_keyword()
|
|
not word1 = a_keyword()
or
word1 = keyword_requiring_colon() and not c.getText().matches("%:%")
) and
/* Except comments of the form: # (maybe code) # some comment */
not c.getText().regexpMatch("#\\S+\\s.*#.*")
not c.getText().regexpMatch("#\\S+\\s.*#.*")
or
/* Don't count doctests as code */
c.getText().matches("%>>>%") or c.getText().matches("%...%")
c.getText().matches("%>>>%")
or
c.getText().matches("%...%")
}
private predicate filler(Comment c) {
c.getText().regexpMatch("#+[\\s*#-_=+]*")
}
private predicate filler(Comment c) { c.getText().regexpMatch("#+[\\s*#-_=+]*") }
/** Gets the first non empty comment following c */
/** Gets the first non empty comment following c */
private Comment non_empty_following(Comment c) {
not empty(result) and
(
not empty(result) and
(
result = empty_following(c).getFollowing()
or
not empty(c) and result = c.getFollowing()
)
not empty(c) and result = c.getFollowing()
)
}
/* Helper for non_empty_following() */
private Comment empty_following(Comment c) {
not empty(c) and
empty(result)
and
exists(Comment prev |
result = prev.getFollowing() |
empty(result) and
exists(Comment prev | result = prev.getFollowing() |
prev = c
or
prev = empty_following(c)
)
}
private predicate empty(Comment c) {
c.getText().regexpMatch("#+\\s*")
}
private predicate empty(Comment c) { c.getText().regexpMatch("#+\\s*") }
/* A comment following code on the same line */
private predicate endline_comment(Comment c) {
@@ -315,21 +288,40 @@ private predicate file_or_url(Comment c) {
}
private string operator_keyword() {
result = "import" or result = "and" or result = "is" or result = "or" or result = "in" or result = "not" or result = "as"
result = "import" or
result = "and" or
result = "is" or
result = "or" or
result = "in" or
result = "not" or
result = "as"
}
private string keyword_requiring_colon() {
result = "try" or result = "while" or result = "elif" or result = "else" or result = "if" or
result = "except" or result = "def" or result = "class"
result = "try" or
result = "while" or
result = "elif" or
result = "else" or
result = "if" or
result = "except" or
result = "def" or
result = "class"
}
private string other_keyword() {
result = "del" or result = "lambda" or result = "from" or
result = "global" or result = "with" or result = "assert" or
result = "yield" or result = "finally" or
result = "print" or
result = "exec" or result = "raise" or
result = "return" or result = "for"
result = "del" or
result = "lambda" or
result = "from" or
result = "global" or
result = "with" or
result = "assert" or
result = "yield" or
result = "finally" or
result = "print" or
result = "exec" or
result = "raise" or
result = "return" or
result = "for"
}
private string a_keyword() {

View File

@@ -11,10 +11,8 @@
import python
import Lexical.CommentedOutCode
import python
from File f, int n
where n = count(CommentedOutCodeLine c | not c.maybeExampleCode() and c.getLocation().getFile() = f)
select f, n
order by n desc
select f, n order by n desc

View File

@@ -12,8 +12,7 @@
import python
predicate is_old_octal(IntegerLiteral i) {
exists(string text |
text = i.getText() |
exists(string text | text = i.getText() |
text.charAt(0) = "0" and
not text = "00" and
exists(text.charAt(1).toInt()) and

View File

@@ -8,8 +8,8 @@
* @metricAggregate avg sum max
* @tags maintainability
*/
import python
from Function f
select f, f.getMetrics().getNumberOfLinesOfCode() as n
order by n desc
select f, f.getMetrics().getNumberOfLinesOfCode() as n order by n desc

View File

@@ -13,6 +13,4 @@
import python
from ClassMetrics cls
select cls, cls.getAfferentCoupling() as n
order by n desc
select cls, cls.getAfferentCoupling() as n order by n desc

View File

@@ -13,6 +13,4 @@
import python
from ClassMetrics cls
select cls, cls.getEfferentCoupling() as n
order by n desc
select cls, cls.getEfferentCoupling() as n order by n desc

View File

@@ -10,9 +10,10 @@
* @tags maintainability
* documentation
*/
import python
from Module m, ModuleMetrics mm
where mm = m.getMetrics() and mm.getNumberOfLines() > 0
select m, 100.0 * ((float)mm.getNumberOfLinesOfComments() / (float)mm.getNumberOfLines()) as ratio
order by ratio desc
select m, 100.0 * (mm.getNumberOfLinesOfComments().(float) / mm.getNumberOfLines().(float)) as ratio
order by ratio desc

View File

@@ -11,9 +11,9 @@
* complexity
* maintainability
*/
import python
from Function func, int complexity
where complexity = func.getMetrics().getCyclomaticComplexity()
select func, complexity
order by complexity desc
select func, complexity order by complexity desc

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