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

This commit is contained in:
Rebecca Valentine
2020-03-31 23:36:15 -07:00
193 changed files with 14898 additions and 8211 deletions

View File

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

View File

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

View File

@@ -15,9 +15,9 @@ predicate slice_method_name(string name) {
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
}
from PyFunctionObject f, string meth
from PythonFunctionValue f, string meth
where f.getFunction().isMethod() and not f.isOverridingMethod() and
where f.getScope().isMethod() and not f.isOverridingMethod() and
slice_method_name(meth) and f.getName() = meth

View File

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

View File

@@ -26,7 +26,5 @@ where
value = test.(NameConstant).toString()
) and
/* Exclude asserts appearing at the end of a chain of `elif`s */
not exists(If i |
i.getElif().getAnOrelse() = a
)
not exists(If i | i.getElif().getAnOrelse() = a)
select a, "Assert of literal constant " + value + "."

View File

@@ -14,11 +14,14 @@
import python
from Assert a, string b, string non
where a.getTest() instanceof Tuple and
(if exists(((Tuple)a.getTest()).getAnElt()) then
(b = "True" and non = "non-")
else
(b = "False" and non = "")
)
where
a.getTest() instanceof Tuple and
(
if exists(a.getTest().(Tuple).getAnElt())
then (
b = "True" and non = "non-"
) else (
b = "False" and non = ""
)
)
select a, "Assertion of " + non + "empty tuple is always " + b + "."

View File

@@ -15,13 +15,13 @@
import python
from Stmt s, string kind
where
s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s))
or
s instanceof Break and kind = "break" and
exists(Try t | t.getFinalbody().contains(s) |
not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop))
and
not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop))
)
where
s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s))
or
s instanceof Break and
kind = "break" and
exists(Try t | t.getFinalbody().contains(s) |
not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) and
not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop))
)
select s, "'" + kind + "' in a finally block will swallow any exceptions raised."

View File

@@ -14,19 +14,22 @@
import python
from Expr e, Location l, string kind, string what
where e.isParenthesized() and
not e instanceof Tuple and
(
exists(If i | i.getTest() = e) and kind = "if" and what = "condition"
or
exists(While w | w.getTest() = e) and kind = "while" and what = "condition"
or
exists(Return r | r.getValue() = e) and kind = "return" and what = "value"
or
exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and kind = "assert" and what = "test"
)
and
// These require parentheses
(not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and
l = e.getLocation() and l.getStartLine() = l.getEndLine()
where
e.isParenthesized() and
not e instanceof Tuple and
(
exists(If i | i.getTest() = e) and kind = "if" and what = "condition"
or
exists(While w | w.getTest() = e) and kind = "while" and what = "condition"
or
exists(Return r | r.getValue() = e) and kind = "return" and what = "value"
or
exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and
kind = "assert" and
what = "test"
) and
// These require parentheses
(not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and
l = e.getLocation() and
l.getStartLine() = l.getEndLine()
select e, "Parenthesized " + what + " in '" + kind + "' statement."

View File

@@ -15,16 +15,15 @@
import python
predicate is_condition(Expr cond) {
exists(If i | i.getTest() = cond) or
exists(IfExp ie | ie.getTest() = cond)
exists(If i | i.getTest() = cond) or
exists(IfExp ie | ie.getTest() = cond)
}
/* Treat certain unmodified builtins as constants as well. */
predicate effective_constant(Name cond) {
exists(GlobalVariable var | var = cond.getVariable() and not exists(NameNode f | f.defines(var)) |
var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented"
var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented"
)
}
@@ -34,9 +33,10 @@ predicate test_makes_code_unreachable(Expr cond) {
exists(While w | w.getTest() = cond and w.getStmt(0).isUnreachable())
}
from Expr cond
where is_condition(cond) and (cond.isConstant() or effective_constant(cond)) and
/* Ignore cases where test makes code unreachable, as that is handled in different query */
not test_makes_code_unreachable(cond)
where
is_condition(cond) and
(cond.isConstant() or effective_constant(cond)) and
/* Ignore cases where test makes code unreachable, as that is handled in different query */
not test_makes_code_unreachable(cond)
select cond, "Testing a constant will always give the same result."

View File

@@ -9,7 +9,9 @@
* @precision medium
* @id py/missing-docstring
*/
/* NOTE: precision of 'medium' reflects the lack of precision in the underlying rule.
/*
* NOTE: precision of 'medium' reflects the lack of precision in the underlying rule.
* Do we care whether a function has a docstring? That often depends on the reader of that docstring.
*/
@@ -18,25 +20,26 @@ import python
predicate needs_docstring(Scope s) {
s.isPublic() and
(
not s instanceof Function
or
function_needs_docstring(s)
not s instanceof Function
or
function_needs_docstring(s)
)
}
predicate function_needs_docstring(Function f) {
not exists(FunctionObject fo, FunctionObject base | fo.overrides(base) and fo.getFunction() = f |
not function_needs_docstring(base.getFunction())) and
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f |
not function_needs_docstring(base.getScope())
) and
f.getName() != "lambda" and
(f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2
and not exists(PythonPropertyObject p |
(f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
not exists(PythonPropertyObject p |
p.getGetter().getFunction() = f or
p.getSetter().getFunction() = f
)
}
string scope_type(Scope s) {
result = "Module" and s instanceof Module and not ((Module)s).isPackage()
result = "Module" and s instanceof Module and not s.(Module).isPackage()
or
result = "Class" and s instanceof Class
or
@@ -46,5 +49,3 @@ string scope_type(Scope s) {
from Scope s
where needs_docstring(s) and not exists(s.getDocString())
select s, scope_type(s) + " " + s.getName() + " does not have a docstring"

View File

@@ -19,9 +19,9 @@ string message() {
}
predicate exec_function_call(Call c) {
exists(GlobalVariable exec | exec = ((Name)c.getFunc()).getVariable() and exec.getId() = "exec")
exists(GlobalVariable exec | exec = c.getFunc().(Name).getVariable() and exec.getId() = "exec")
}
from AstNode exec
where exec_function_call(exec) or exec instanceof Exec
select exec, message()
select exec, message()

View File

@@ -13,18 +13,20 @@
import python
predicate is_a_string_type(ClassObject seqtype) {
seqtype = theBytesType() and major_version() = 2
or
seqtype = theUnicodeType()
predicate has_string_type(Value v) {
v.getClass() = ClassValue::str()
or
v.getClass() = ClassValue::unicode() and major_version() = 2
}
from For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin
where loop.getIter().getAFlowNode() = iter and
iter.refersTo(str, strtype, str_origin) and
iter.refersTo(seq, seqtype, seq_origin) and
is_a_string_type(strtype) and
seqtype.isIterable() and
not is_a_string_type(seqtype)
select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string"
from
For loop, ControlFlowNode iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin
where
loop.getIter().getAFlowNode() = iter and
iter.pointsTo(str, str_origin) and
iter.pointsTo(seq, seq_origin) and
has_string_type(str) and
seq.getClass().isIterable() and
not has_string_type(seq)
select loop, "Iteration over $@, of class " + seq.getClass().getName() + ", may also iterate over $@.",
seq_origin, "sequence", str_origin, "string"

View File

@@ -14,18 +14,19 @@
import python
private int len(ExprList el) {
result = count(el.getAnItem())
}
private int len(ExprList el) { result = count(el.getAnItem()) }
predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequenceType) {
exists(ExprList l, ExprList r |
(a.getATarget().(Tuple).getElts() = l or
a.getATarget().(List).getElts() = l)
and
((a.getValue().(Tuple).getElts() = r and sequenceType = "tuple") or
(a.getValue().(List).getElts() = r and sequenceType = "list"))
and
(
a.getATarget().(Tuple).getElts() = l or
a.getATarget().(List).getElts() = l
) and
(
a.getValue().(Tuple).getElts() = r and sequenceType = "tuple"
or
a.getValue().(List).getElts() = r and sequenceType = "list"
) and
loc = a.getValue().getLocation() and
lcount = len(l) and
rcount = len(r) and
@@ -35,24 +36,26 @@ predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequ
}
predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) {
exists(ExprList l, TupleObject r, AstNode origin |
(a.getATarget().(Tuple).getElts() = l or
a.getATarget().(List).getElts() = l)
and
a.getValue().refersTo(r, origin) and
exists(ExprList l, TupleValue r, AstNode origin |
(
a.getATarget().(Tuple).getElts() = l or
a.getATarget().(List).getElts() = l
) and
a.getValue().pointsTo(r, origin) and
loc = origin.getLocation() and
lcount = len(l) and
rcount = r.getLength() and
rcount = r.length() and
lcount != rcount and
not exists(Starred s | l.getAnItem() = s)
)
}
from Assign a, int lcount, int rcount, Location loc, string sequenceType
where
mismatched(a, lcount, rcount, loc, sequenceType)
or
mismatched_tuple_rhs(a, lcount, rcount, loc) and
sequenceType = "tuple"
select a, "Left hand side of assignment contains " + lcount + " variables, but right hand side is a $@ of length " + rcount + "." , loc, sequenceType
select a,
"Left hand side of assignment contains " + lcount +
" variables, but right hand side is a $@ of length " + rcount + ".", loc, sequenceType

View File

@@ -12,24 +12,18 @@
import python
Object aFunctionLocalsObject() {
exists(Call c, Name n, GlobalVariable v |
c = result.getOrigin() and
n = c.getFunc() and
n.getVariable() = v and
v.getId() = "locals" and
c.getScope() instanceof FastLocalsFunction
)
predicate originIsLocals(ControlFlowNode n) {
n.pointsTo(_, _, Value::named("locals").getACall())
}
predicate modification_of_locals(ControlFlowNode f) {
f.(SubscriptNode).getObject().refersTo(aFunctionLocalsObject()) and (f.isStore() or f.isDelete())
originIsLocals(f.(SubscriptNode).getObject()) and
(f.isStore() or f.isDelete())
or
exists(string mname, AttrNode attr |
attr = f.(CallNode).getFunction() and
attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) |
originIsLocals(attr.getObject(mname))
|
mname = "pop" or
mname = "popitem" or
mname = "update" or
@@ -39,5 +33,4 @@ predicate modification_of_locals(ControlFlowNode f) {
from AstNode a, ControlFlowNode f
where modification_of_locals(f) and a = f.getNode()
select a, "Modification of the locals() dictionary will have no effect on the local variables."

View File

@@ -10,20 +10,21 @@
* @precision very-high
* @id py/nested-loops-with-same-variable
*/
import python
predicate loop_variable(For f, Variable v) {
f.getTarget().defines(v)
}
predicate loop_variable(For f, Variable v) { f.getTarget().defines(v) }
predicate variableUsedInNestedLoops(For inner, For outer, Variable v) {
/* Only treat loops in body as inner loops. Loops in the else clause are ignored. */
outer.getBody().contains(inner) and loop_variable(inner, v) and loop_variable(outer, v)
outer.getBody().contains(inner) and
loop_variable(inner, v) and
loop_variable(outer, v) and
/* Ignore cases where there is no use of the variable or the only use is in the inner loop */
and exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n))
exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n))
}
from For inner, For outer, Variable v
where variableUsedInNestedLoops(inner, outer, v)
select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.",
outer, "for statement"
select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", outer,
"for statement"

View File

@@ -19,18 +19,18 @@ predicate loop_variable_ssa(For f, Variable v, SsaVariable s) {
predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) {
/* Ignore cases where there is no use of the variable or the only use is in the inner loop. */
outer.contains(n)
and not inner.contains(n)
outer.contains(n) and
not inner.contains(n) and
/* Only treat loops in body as inner loops. Loops in the else clause are ignored. */
and outer.getBody().contains(inner)
and exists(SsaVariable s |
loop_variable_ssa(inner, v, s.getAnUltimateDefinition())
and loop_variable_ssa(outer, v, _)
and s.getAUse().getNode() = n
outer.getBody().contains(inner) and
exists(SsaVariable s |
loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) and
loop_variable_ssa(outer, v, _) and
s.getAUse().getNode() = n
)
}
from For inner, For outer, Variable v, Name n
where variableUsedInNestedLoops(inner, outer, v, n)
select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, "uses",
outer, "for statement"
select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n,
"uses", outer, "for statement"

View File

@@ -17,7 +17,7 @@ for addressing the defect. </p>
</recommendation>
<example>
<p>
In this example, the loop may attempt to iterate over <code>None</code>, which is not an iterator.
In this example, the loop may attempt to iterate over <code>None</code>, which is not an iterable.
It is likely that the programmer forgot to test for <code>None</code> before the loop.
</p>
<sample src="NonIteratorInForLoop.py" />

View File

@@ -14,10 +14,12 @@
import python
from For loop, ControlFlowNode iter, Value v, ClassValue t, ControlFlowNode origin
where loop.getIter().getAFlowNode() = iter and
iter.pointsTo(_, v, origin) and v.getClass() = t and
not t.isIterable() and not t.failedInference(_) and
not v = Value::named("None") and
not t.isDescriptorType()
select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterator", t, t.getName()
where
loop.getIter().getAFlowNode() = iter and
iter.pointsTo(_, v, origin) and
v.getClass() = t and
not t.isIterable() and
not t.failedInference(_) and
not v = Value::named("None") and
not t.isDescriptorType()
select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterable", t, t.getName()

View File

@@ -12,8 +12,8 @@
*/
import python
predicate assignment(AssignStmt a, Expr left, Expr right)
{
predicate assignment(AssignStmt a, Expr left, Expr right) {
a.getATarget() = left and a.getValue() = right
}
@@ -23,7 +23,8 @@ predicate corresponding(Expr left, Expr right) {
exists(Attribute la, Attribute ra |
corresponding(la, ra) and
left = la.getObject() and
right = ra.getObject())
right = ra.getObject()
)
}
predicate same_value(Expr left, Expr right) {
@@ -33,34 +34,41 @@ predicate same_value(Expr left, Expr right) {
}
predicate maybe_defined_in_outer_scope(Name n) {
exists(SsaVariable v | v.getAUse().getNode() = n |
v.maybeUndefined()
)
exists(SsaVariable v | v.getAUse().getNode() = n | v.maybeUndefined())
}
Variable relevant_var(Name n) {
n.getVariable() = result and
(corresponding(n, _) or corresponding(_, n))
/* Protection against FPs in projects that offer compatibility between Python 2 and 3,
* since many of them make assignments such as
*
* if PY2:
* bytes = str
* else:
* bytes = bytes
*
*/
predicate isBuiltin(string name) {
exists(Value v | v = Value::named(name) and v.isBuiltin())
}
predicate same_name(Name n1, Name n2) {
corresponding(n1, n2) and
relevant_var(n1) = relevant_var(n2) and
not exists(Object::builtin(n1.getId())) and
n1.getVariable() = n2.getVariable() and
not isBuiltin(n1.getId()) and
not maybe_defined_in_outer_scope(n2)
}
ClassObject value_type(Attribute a) {
a.getObject().refersTo(_, result, _)
}
ClassValue value_type(Attribute a) { a.getObject().pointsTo().getClass() = result }
predicate is_property_access(Attribute a) {
value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject
value_type(a).lookup(a.getName()) instanceof PropertyValue
}
predicate same_attribute(Attribute a1, Attribute a2) {
corresponding(a1, a2) and a1.getName() = a2.getName() and same_value(a1.getObject(), a2.getObject()) and
exists(value_type(a1)) and not is_property_access(a1)
corresponding(a1, a2) and
a1.getName() = a2.getName() and
same_value(a1.getObject(), a2.getObject()) and
exists(value_type(a1)) and
not is_property_access(a1)
}
int pyflakes_commented_line(File file) {
@@ -72,21 +80,25 @@ int pyflakes_commented_line(File file) {
predicate pyflakes_commented(AssignStmt assignment) {
exists(Location loc |
assignment.getLocation() = loc and
loc.getStartLine() = pyflakes_commented_line(loc.getFile()))
loc.getStartLine() = pyflakes_commented_line(loc.getFile())
)
}
predicate side_effecting_lhs(Attribute lhs) {
exists(ClassObject cls, ClassObject decl |
lhs.getObject().refersTo(_, cls, _) and
decl = cls.getAnImproperSuperType() and
not decl.isBuiltin() |
exists(ClassValue cls, ClassValue decl |
lhs.getObject().pointsTo().getClass() = cls and
decl = cls.getASuperType() and
not decl.isBuiltin()
|
decl.declaresAttribute("__setattr__")
)
}
from AssignStmt a, Expr left, Expr right
where assignment(a, left, right)
and same_value(left, right)
and not pyflakes_commented(a) and
not side_effecting_lhs(left)
where
assignment(a, left, right) and
same_value(left, right) and
// some people use self-assignment to shut Pyflakes up, such as `ok = ok # Pyflakes`
not pyflakes_commented(a) and
not side_effecting_lhs(left)
select a, "This assignment assigns a variable to itself."

View File

@@ -14,12 +14,12 @@ import python
from AstNode node, string kind
where
not node.getScope() instanceof Function and
(
node instanceof Return and kind = "return"
or
node instanceof Yield and kind = "yield"
or
node instanceof YieldFrom and kind = "yield from"
)
not node.getScope() instanceof Function and
(
node instanceof Return and kind = "return"
or
node instanceof Yield and kind = "yield"
or
node instanceof YieldFrom and kind = "yield from"
)
select node, "'" + kind + "' is used outside a function."

View File

@@ -14,24 +14,26 @@
import python
predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") }
predicate calls_close(Call c) {
exists (Attribute a | c.getFunc() = a and a.getName() = "close")
predicate only_stmt_in_finally(Try t, Call c) {
exists(ExprStmt s |
t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1
)
}
predicate
only_stmt_in_finally(Try t, Call c) {
exists(ExprStmt s | t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1)
predicate points_to_context_manager(ControlFlowNode f, ClassValue cls) {
forex(Value v | f.pointsTo(v) | v.getClass() = cls) and
cls.isContextManager()
}
predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) {
cls.isContextManager() and
forex(Object obj | f.refersTo(obj) | f.refersTo(obj, cls, _))
}
from Call close, Try t, ClassObject cls
where only_stmt_in_finally(t, close) and calls_close(close) and
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() |
points_to_context_manager(f, cls))
select close, "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", cls, cls.getName()
from Call close, Try t, ClassValue cls
where
only_stmt_in_finally(t, close) and
calls_close(close) and
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() |
points_to_context_manager(f, cls)
)
select close,
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
cls, cls.getName()

View File

@@ -1 +1 @@
assert(subprocess.call(['run-backup']) == 0)
assert subprocess.call(['run-backup']) == 0

View File

@@ -14,22 +14,37 @@
import python
predicate func_with_side_effects(Expr e) {
exists(string name |
name = ((Attribute)e).getName() or name = ((Name)e).getId() |
name = "print" or name = "write" or name = "append" or
name = "pop" or name = "remove" or name = "discard" or
name = "delete" or name = "close" or name = "open" or
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
name = "print" or
name = "write" or
name = "append" or
name = "pop" or
name = "remove" or
name = "discard" or
name = "delete" or
name = "close" or
name = "open" or
name = "exit"
)
}
predicate call_with_side_effect(Call e) {
e.getAFlowNode() = Value::named("subprocess.call").getACall()
or
e.getAFlowNode() = Value::named("subprocess.check_call").getACall()
or
e.getAFlowNode() = Value::named("subprocess.check_output").getACall()
}
predicate probable_side_effect(Expr e) {
// Only consider explicit yields, not artificial ones in comprehensions
e instanceof Yield and not exists(Comp c | c.contains(e))
or
e instanceof YieldFrom
or
e instanceof Call and func_with_side_effects(((Call)e).getFunc())
e instanceof Call and func_with_side_effects(e.(Call).getFunc())
or
e instanceof Call and call_with_side_effect(e)
}
from Assert a, Expr e

View File

@@ -13,73 +13,75 @@
import python
predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) {
exists(string name |
attr.getName() = name |
attr.getObject().refersTo(_, cls, _) and
cls.attributeRefersTo(name, _, attr_cls, _)
predicate understood_attribute(Attribute attr, ClassValue cls, ClassValue attr_cls) {
exists(string name | attr.getName() = name |
attr.getObject().pointsTo().getClass() = cls and
cls.attr(name).getClass() = attr_cls
)
}
/* Conservative estimate of whether attribute lookup has a side effect */
predicate side_effecting_attribute(Attribute attr) {
exists(ClassObject cls, ClassObject attr_cls |
exists(ClassValue cls, ClassValue attr_cls |
understood_attribute(attr, cls, attr_cls) and
side_effecting_descriptor_type(attr_cls)
)
}
predicate maybe_side_effecting_attribute(Attribute attr) {
not understood_attribute(attr, _, _) and not attr.refersTo(_)
not understood_attribute(attr, _, _) and not attr.pointsTo(_)
or
side_effecting_attribute(attr)
}
predicate side_effecting_descriptor_type(ClassObject descriptor) {
predicate side_effecting_descriptor_type(ClassValue descriptor) {
descriptor.isDescriptorType() and
/* Technically all descriptor gets have side effects,
* but some are indicative of a missing call and
* we want to treat them as having no effect. */
not descriptor = thePyFunctionType() and
not descriptor = theStaticMethodType() and
not descriptor = theClassMethodType()
// Technically all descriptor gets have side effects,
// but some are indicative of a missing call and
// we want to treat them as having no effect.
not descriptor = ClassValue::functionType() and
not descriptor = ClassValue::staticmethod() and
not descriptor = ClassValue::classmethod()
}
/** Side effecting binary operators are rare, so we assume they are not
/**
* Side effecting binary operators are rare, so we assume they are not
* side-effecting unless we know otherwise.
*/
predicate side_effecting_binary(Expr b) {
exists(Expr sub, ClassObject cls, string method_name |
exists(Expr sub, ClassValue cls, string method_name |
binary_operator_special_method(b, sub, cls, method_name)
or
comparison_special_method(b, sub, cls, method_name)
|
|
method_name = special_method() and
cls.hasAttribute(method_name)
and
not exists(ClassObject declaring |
declaring.declaresAttribute(method_name)
and declaring = cls.getAnImproperSuperType() and
declaring.isBuiltin() and not declaring = theObjectType()
cls.hasAttribute(method_name) and
not exists(ClassValue declaring |
declaring.declaresAttribute(method_name) and
declaring = cls.getASuperType() and
declaring.isBuiltin() and
not declaring = ClassValue::object()
)
)
}
pragma[nomagic]
private predicate binary_operator_special_method(BinaryExpr b, Expr sub, ClassObject cls, string method_name) {
private predicate binary_operator_special_method(
BinaryExpr b, Expr sub, ClassValue cls, string method_name
) {
method_name = special_method() and
sub = b.getLeft() and
method_name = b.getOp().getSpecialMethodName() and
sub.refersTo(_, cls, _)
sub.pointsTo().getClass() = cls
}
pragma[nomagic]
private predicate comparison_special_method(Compare b, Expr sub, ClassObject cls, string method_name) {
private predicate comparison_special_method(Compare b, Expr sub, ClassValue cls, string method_name) {
exists(Cmpop op |
b.compares(sub, op, _) and
method_name = op.getSpecialMethodName()
) and
sub.refersTo(_, cls, _)
sub.pointsTo().getClass() = cls
}
private string special_method() {
@@ -89,19 +91,16 @@ private string special_method() {
}
predicate is_notebook(File f) {
exists(Comment c |
c.getLocation().getFile() = f |
exists(Comment c | c.getLocation().getFile() = f |
c.getText().regexpMatch("#\\s*<nbformat>.+</nbformat>\\s*")
)
}
/** Expression (statement) in a jupyter/ipython notebook */
predicate in_notebook(Expr e) {
is_notebook(e.getScope().(Module).getFile())
}
predicate in_notebook(Expr e) { is_notebook(e.getScope().(Module).getFile()) }
FunctionObject assertRaises() {
result = ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises")
FunctionValue assertRaises() {
result = Value::named("unittest.TestCase").(ClassValue).lookup("assertRaises")
}
/** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */
@@ -121,14 +120,11 @@ predicate python2_print(Expr e) {
}
predicate no_effect(Expr e) {
// strings can be used as comments
not e instanceof StrConst and
not ((StrConst)e).isDocString() and
not e.hasSideEffects() and
forall(Expr sub |
sub = e.getASubExpression*()
|
not side_effecting_binary(sub)
and
forall(Expr sub | sub = e.getASubExpression*() |
not side_effecting_binary(sub) and
not maybe_side_effecting_attribute(sub)
) and
not in_notebook(e) and
@@ -139,4 +135,3 @@ predicate no_effect(Expr e) {
from ExprStmt stmt
where no_effect(stmt.getValue())
select stmt, "This statement has no effect."

View File

@@ -3,13 +3,13 @@
"qhelp.dtd">
<qhelp>
<overview>
<p>If you concatenate strings in a loop then the time taken by the loop is quadratic in the number
<p>If you concatenate strings in a loop then the time taken by the loop is quadratic in the number
of iterations.</p>
</overview>
<recommendation>
<p>Initialize an empty list before the start of the list.
<p>Initialize an empty list before the start of the loop.
During the loop append the substrings to the list.
At the end of the loop, convert the list to a string by using <code>''.join(list)</code>.</p>

View File

@@ -13,17 +13,16 @@
import python
predicate string_concat_in_loop(BinaryExpr b) {
b.getOp() instanceof Add
and
exists(SsaVariable d, SsaVariable u, BinaryExprNode add, ClassObject str_type |
add.getNode() = b and d = u.getAnUltimateDefinition() |
d.getDefinition().(DefinitionNode).getValue() = add and u.getAUse() = add.getAnOperand() and
add.getAnOperand().refersTo(_, str_type, _) and
(str_type = theBytesType() or str_type = theUnicodeType())
b.getOp() instanceof Add and
exists(SsaVariable d, SsaVariable u, BinaryExprNode add |
add.getNode() = b and d = u.getAnUltimateDefinition()
|
d.getDefinition().(DefinitionNode).getValue() = add and
u.getAUse() = add.getAnOperand() and
add.getAnOperand().pointsTo().getClass() = ClassValue::str()
)
}
from BinaryExpr b, Stmt s
where string_concat_in_loop(b) and s.getASubExpression() = b
select s, "String concatenation in a loop is quadratic in the number of iterations."

View File

@@ -13,10 +13,10 @@
import python
predicate main_eq_name(If i) {
exists(Name n, StrConst m, Compare c |
i.getTest() = c and c.getLeft() = n and
i.getTest() = c and
c.getLeft() = n and
c.getAComparator() = m and
n.getId() = "__name__" and
m.getText() = "__main__"
@@ -24,12 +24,17 @@ predicate main_eq_name(If i) {
}
predicate is_print_stmt(Stmt s) {
s instanceof Print or
exists(ExprStmt e, Call c, Name n | e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print")
s instanceof Print
or
exists(ExprStmt e, Call c, Name n |
e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print"
)
}
from Stmt p
where is_print_stmt(p) and
exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
where
is_print_stmt(p) and
// TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future
exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
select p, "Print statement may execute during import."

View File

@@ -12,7 +12,6 @@
* @id py/unnecessary-delete
*/
import python
from Delete del, Expr e, Function f
@@ -23,11 +22,11 @@ where
not e instanceof Subscript and
not e instanceof Attribute and
not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and
/* False positive: calling `sys.exc_info` within a function results in a
reference cycle,and an explicit call to `del` helps break this cycle. */
not exists(FunctionObject ex |
ex.hasLongName("sys.exc_info") and
// False positive: calling `sys.exc_info` within a function results in a
// reference cycle, and an explicit call to `del` helps break this cycle.
not exists(FunctionValue ex |
ex = Value::named("sys.exc_info") and
ex.getACall().getScope() = f
)
select del, "Unnecessary deletion of local variable $@ in function $@.",
e.getLocation(), e.toString(), f.getLocation(), f.getName()
select del, "Unnecessary deletion of local variable $@ in function $@.", e.getLocation(),
e.toString(), f.getLocation(), f.getName()

View File

@@ -14,9 +14,11 @@ import python
from Stmt loop, StmtList body, StmtList clause, string kind
where
(exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for")
or
exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while")
)
and not exists(Break b | body.contains(b))
select loop, "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body."
(
exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for")
or
exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while")
) and
not exists(Break b | body.contains(b))
select loop,
"This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body."

View File

@@ -13,21 +13,20 @@
import python
predicate is_doc_string(ExprStmt s) {
s.getValue() instanceof Unicode or s.getValue() instanceof Bytes
s.getValue() instanceof Unicode or s.getValue() instanceof Bytes
}
predicate has_doc_string(StmtList stmts) {
stmts.getParent() instanceof Scope
and
stmts.getParent() instanceof Scope and
is_doc_string(stmts.getItem(0))
}
from Pass p, StmtList list
where list.getAnItem() = p and
(
strictcount(list.getAnItem()) = 2 and not has_doc_string(list)
or
strictcount(list.getAnItem()) > 2
)
where
list.getAnItem() = p and
(
strictcount(list.getAnItem()) = 2 and not has_doc_string(list)
or
strictcount(list.getAnItem()) > 2
)
select p, "Unnecessary 'pass' statement."

View File

@@ -12,8 +12,9 @@
import python
from Call call, ClassObject ex
where call.getFunc().refersTo(ex) and ex.getAnImproperSuperType() = theExceptionType()
and exists(ExprStmt s | s.getValue() = call)
from Call call, ClassValue ex
where
call.getFunc().pointsTo(ex) and
ex.getASuperType() = ClassValue::exception() and
exists(ExprStmt s | s.getValue() = call)
select call, "Instantiating an exception, but not raising it, has no effect"

View File

@@ -12,5 +12,7 @@
import python
from CallNode call, string name
where call.getFunction().refersTo(Object::quitter(name))
select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified."
where call.getFunction().pointsTo(Value::siteQuitter(name))
select call,
"The '" + name +
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."

View File

@@ -20,13 +20,39 @@ class PropertyInternal extends ObjectInternal, TProperty {
this = TProperty(_, _, result)
}
private CallNode getCallNode() { this = TProperty(result, _, _) }
/** Gets the setter function of this property */
CallableObjectInternal getSetter() {
// @x.setter
exists(CallNode call, AttrNode setter |
call.getFunction() = setter and
call.getFunction() = setter and
PointsToInternal::pointsTo(setter.getObject("setter"), this.getContext(), this, _) and
PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _)
)
or
// x = property(getter, setter, deleter)
exists(ControlFlowNode setter_arg |
setter_arg = getCallNode().getArg(1) or setter_arg = getCallNode().getArgByName("fset")
|
PointsToInternal::pointsTo(setter_arg, this.getContext(), result, _)
)
}
/** Gets the setter function of this property */
CallableObjectInternal getDeleter() {
exists(CallNode call, AttrNode setter |
call.getFunction() = setter and
PointsToInternal::pointsTo(setter.getObject("deleter"), this.getContext(), this, _) and
PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _)
)
or
// x = property(getter, setter, deleter)
exists(ControlFlowNode deleter_arg |
deleter_arg = getCallNode().getArg(2) or deleter_arg = getCallNode().getArgByName("fdel")
|
PointsToInternal::pointsTo(deleter_arg, this.getContext(), result, _)
)
}
private Context getContext() { this = TProperty(_,result, _) }

View File

@@ -44,6 +44,12 @@ class Value extends TObject {
PointsToInternal::pointsTo(result, _, this, _)
}
/** Gets the origin CFG node for this value. */
ControlFlowNode getOrigin() {
result = this.(ObjectInternal).getOrigin()
}
/** Gets the class of this object.
* Strictly, the `Value` representing the class of the objects
* represented by this Value.
@@ -78,7 +84,7 @@ class Value extends TObject {
predicate isBuiltin() {
this.(ObjectInternal).isBuiltin()
}
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec)
or
@@ -187,6 +193,37 @@ class ModuleValue extends Value {
result.importedAs(this.getScope().getAnImportedModuleName())
}
/** When used as a normal module (for example, imported and used by other modules) */
predicate isUsedAsModule() {
this.isBuiltin()
or
this.isPackage()
or
exists(ImportingStmt i | this.importedAs(i.getAnImportedModuleName()))
or
this.getPath().getBaseName() = "__init__.py"
}
/** When used (exclusively) as a script (will not include normal modules that can also be run as a script) */
predicate isUsedAsScript() {
not isUsedAsModule() and
(
not this.getPath().getExtension() = "py"
or
exists(If i, Name name, StrConst main, Cmpop op |
i.getScope() = this.getScope() and
op instanceof Eq and
i.getTest().(Compare).compares(name, op, main) and
name.getId() = "__name__" and main.getText() = "__main__"
)
or
exists(Comment c |
c.getLocation().getFile() = this.getPath() and
c.getLocation().getStartLine() = 1 and
c.getText().regexpMatch("^#!/.*python(2|3)?[ \\\\t]*$")
)
)
}
}
module Module {
@@ -301,6 +338,19 @@ module Value {
result = ObjectInternal::none_()
}
/**
* Shorcuts added by the `site` module to exit your interactive session.
*
* see https://docs.python.org/3/library/constants.html#constants-added-by-the-site-module
*/
Value siteQuitter(string name) {
(
name = "exit"
or
name = "quit"
) and
result = Value::named(name)
}
}
/** Class representing callables in the Python program
@@ -412,12 +462,57 @@ class ClassValue extends Value {
or
this.hasAttribute("__getitem__")
}
/** Holds if this class is a container(). That is, does it have a __getitem__ method.*/
predicate isContainer() {
exists(this.lookup("__getitem__"))
}
/** Holds if this class is probably a sequence. */
predicate isSequence() {
/* To determine whether something is a sequence or a mapping is not entirely clear,
* so we need to guess a bit.
*/
this.getASuperType() = ClassValue::tuple()
or
this.getASuperType() = ClassValue::list()
or
this.getASuperType() = ClassValue::range()
or
this.getASuperType() = ClassValue::bytes()
or
this.getASuperType() = ClassValue::unicode()
or
major_version() = 2 and this.getASuperType() = Value::named("collections.Sequence")
or
major_version() = 3 and this.getASuperType() = Value::named("collections.abc.Sequence")
or
/* Does it have an index or __reversed__ method? */
this.isContainer() and
(
this.hasAttribute("index") or
this.hasAttribute("__reversed__")
)
}
/** Holds if this class is a mapping. */
predicate isMapping() {
this.hasAttribute("__getitem__")
and
not this.isSequence()
}
/** Holds if this class is a descriptor. */
predicate isDescriptorType() {
this.hasAttribute("__get__")
}
/** Holds if this class is a context manager. */
predicate isContextManager() {
this.hasAttribute("__enter__") and
this.hasAttribute("__exit__")
}
/** Gets the qualified name for this class.
* Should return the same name as the `__qualname__` attribute on classes in Python 3.
*/
@@ -481,11 +576,12 @@ class ClassValue extends Value {
predicate declaresAttribute(string name) {
this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name)
}
/** Whether this class is a legal exception class.
/** Whether this class is a legal exception class.
* What constitutes a legal exception class differs between major versions */
predicate isLegalExceptionType() {
not this.isNewStyle() or
not this.isNewStyle()
or
this.getASuperType() = ClassValue::baseException()
or
major_version() = 2 and this = ClassValue::tuple()
@@ -514,7 +610,7 @@ abstract class FunctionValue extends CallableValue {
predicate isOverriddenMethod() {
exists(Value f | f.overrides(this))
}
/** Whether `name` is a legal argument name for this function */
bindingset[name]
predicate isLegalArgumentName(string name) {
@@ -524,8 +620,8 @@ abstract class FunctionValue extends CallableValue {
or
this.getScope().hasKwArg()
}
/** Whether this is a "normal" method, that is, it is exists as a class attribute
/** Whether this is a "normal" method, that is, it is exists as a class attribute
* which is not a lambda and not the __new__ method. */
predicate isNormalMethod() {
exists(ClassValue cls, string name |
@@ -681,6 +777,34 @@ class NumericValue extends Value {
}
}
/** A Python property:
* @property
* def f():
* ....
*
* https://docs.python.org/3/howto/descriptor.html#properties
* https://docs.python.org/3/library/functions.html#property
*/
class PropertyValue extends Value {
PropertyValue() {
this instanceof PropertyInternal
}
CallableValue getGetter(){
result = this.(PropertyInternal).getGetter()
}
CallableValue getSetter(){
result = this.(PropertyInternal).getSetter()
}
CallableValue getDeleter(){
result = this.(PropertyInternal).getDeleter()
}
}
/** A method-resolution-order sequence of classes */
class MRO extends TClassList {
@@ -729,34 +853,34 @@ module ClassValue {
ClassValue bool() {
result = TBuiltinClassObject(Builtin::special("bool"))
}
/** Get the `ClassValue` for the `tuple` class. */
ClassValue tuple() {
result = TBuiltinClassObject(Builtin::special("tuple"))
}
/** Get the `ClassValue` for the `list` class. */
ClassValue list() {
result = TBuiltinClassObject(Builtin::special("list"))
}
/** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */
ClassValue range() {
major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange"))
or
major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range"))
}
/** Get the `ClassValue` for the `dict` class. */
ClassValue dict() {
result = TBuiltinClassObject(Builtin::special("dict"))
}
/** Get the `ClassValue` for the `set` class. */
ClassValue set() {
result = TBuiltinClassObject(Builtin::special("set"))
}
/** Get the `ClassValue` for the `object` class. */
ClassValue object() {
result = TBuiltinClassObject(Builtin::special("object"))
@@ -766,7 +890,7 @@ module ClassValue {
ClassValue int_() {
result = TBuiltinClassObject(Builtin::special("int"))
}
/** Get the `ClassValue` for the `long` class. */
ClassValue long() {
result = TBuiltinClassObject(Builtin::special("long"))
@@ -776,12 +900,12 @@ module ClassValue {
ClassValue float_() {
result = TBuiltinClassObject(Builtin::special("float"))
}
/** Get the `ClassValue` for the `complex` class. */
ClassValue complex() {
result = TBuiltinClassObject(Builtin::special("complex"))
}
/** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */
ClassValue bytes() {
result = TBuiltinClassObject(Builtin::special("bytes"))
@@ -801,12 +925,12 @@ module ClassValue {
else
result = unicode()
}
/** Get the `ClassValue` for the `property` class. */
ClassValue property() {
result = TBuiltinClassObject(Builtin::special("property"))
}
/** Get the `ClassValue` for the class of Python functions. */
ClassValue functionType() {
result = TBuiltinClassObject(Builtin::special("FunctionType"))
@@ -816,32 +940,32 @@ module ClassValue {
ClassValue builtinFunction() {
result = Value::named("len").getClass()
}
/** Get the `ClassValue` for the `generatorType` class. */
ClassValue generator() {
result = TBuiltinClassObject(Builtin::special("generator"))
}
/** Get the `ClassValue` for the `type` class. */
ClassValue type() {
result = TType()
}
/** Get the `ClassValue` for `ClassType`. */
ClassValue classType() {
result = TBuiltinClassObject(Builtin::special("ClassType"))
}
/** Get the `ClassValue` for `InstanceType`. */
ClassValue instanceType() {
result = TBuiltinClassObject(Builtin::special("InstanceType"))
}
/** Get the `ClassValue` for `super`. */
ClassValue super_() {
result = TBuiltinClassObject(Builtin::special("super"))
}
/** Get the `ClassValue` for the `classmethod` class. */
ClassValue classmethod() {
result = TBuiltinClassObject(Builtin::special("ClassMethod"))
@@ -851,23 +975,23 @@ module ClassValue {
ClassValue staticmethod() {
result = TBuiltinClassObject(Builtin::special("StaticMethod"))
}
/** Get the `ClassValue` for the `MethodType` class. */
pragma [noinline]
ClassValue methodType() {
result = TBuiltinClassObject(Builtin::special("MethodType"))
}
/** Get the `ClassValue` for the `MethodDescriptorType` class. */
ClassValue methodDescriptorType() {
result = TBuiltinClassObject(Builtin::special("MethodDescriptorType"))
}
/** Get the `ClassValue` for the `GetSetDescriptorType` class. */
ClassValue getSetDescriptorType() {
result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType"))
}
/** Get the `ClassValue` for the `StopIteration` class. */
ClassValue stopIteration() {
result = TBuiltinClassObject(Builtin::builtin("StopIteration"))
@@ -882,17 +1006,17 @@ module ClassValue {
ClassValue exception() {
result = TBuiltinClassObject(Builtin::special("Exception"))
}
/** Get the `ClassValue` for the `BaseException` class. */
ClassValue baseException() {
result = TBuiltinClassObject(Builtin::special("BaseException"))
}
/** Get the `ClassValue` for the `NoneType` class. */
ClassValue nonetype() {
result = TBuiltinClassObject(Builtin::special("NoneType"))
}
/** Get the `ClassValue` for the `TypeError` class */
ClassValue typeError() {
result = TBuiltinClassObject(Builtin::special("TypeError"))
@@ -902,27 +1026,27 @@ module ClassValue {
ClassValue nameError() {
result = TBuiltinClassObject(Builtin::builtin("NameError"))
}
/** Get the `ClassValue` for the `AttributeError` class. */
ClassValue attributeError() {
result = TBuiltinClassObject(Builtin::builtin("AttributeError"))
}
/** Get the `ClassValue` for the `KeyError` class. */
ClassValue keyError() {
result = TBuiltinClassObject(Builtin::builtin("KeyError"))
}
/** Get the `ClassValue` for the `LookupError` class. */
ClassValue lookupError() {
result = TBuiltinClassObject(Builtin::builtin("LookupError"))
}
/** Get the `ClassValue` for the `IOError` class. */
ClassValue ioError() {
result = TBuiltinClassObject(Builtin::builtin("IOError"))
}
/** Get the `ClassValue` for the `NotImplementedError` class. */
ClassValue notImplementedError() {
result = TBuiltinClassObject(Builtin::builtin("NotImplementedError"))
@@ -932,12 +1056,12 @@ module ClassValue {
ClassValue importError() {
result = TBuiltinClassObject(Builtin::builtin("ImportError"))
}
/** Get the `ClassValue` for the `UnicodeEncodeError` class. */
ClassValue unicodeEncodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeEncodeError"))
}
/** Get the `ClassValue` for the `UnicodeDecodeError` class. */
ClassValue unicodeDecodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError"))

View File

@@ -43,7 +43,7 @@ module ClearTextLogging {
PrintSink() {
exists(CallNode call |
call.getAnArg() = this and
thePrintFunction().(FunctionObject).getACall() = call
call = Value::named("print").getACall()
)
}
}

View File

@@ -12,13 +12,11 @@ abstract class WeakCryptoSink extends TaintSink {
}
}
/** Modeling the 'pycrypto' package https://github.com/dlitz/pycrypto (latest release 2013) */
module Pycrypto {
ModuleObject cipher(string name) {
exists(PackageObject crypto |
crypto.getName() = "Crypto.Cipher" |
crypto.submodule(name) = result
)
ModuleValue cipher(string name) {
result = Module::named("Crypto.Cipher").attr(name)
}
class CipherInstance extends TaintKind {
@@ -51,7 +49,7 @@ module Pycrypto {
CipherInstanceSource() {
exists(AttrNode attr |
this.(CallNode).getFunction() = attr and
attr.getObject("new").refersTo(cipher(instance.getName()))
attr.getObject("new").pointsTo(cipher(instance.getName()))
)
}
@@ -59,7 +57,7 @@ module Pycrypto {
result = "Source of " + instance
}
override predicate isSourceOf(TaintKind kind) {
override predicate isSourceOf(TaintKind kind) {
kind = instance
}
@@ -70,12 +68,12 @@ module Pycrypto {
string name;
PycryptoWeakCryptoSink() {
exists(CallNode call, AttrNode method, CipherInstance Cipher |
exists(CallNode call, AttrNode method, CipherInstance cipher |
call.getAnArg() = this and
call.getFunction() = method and
Cipher.taints(method.getObject("encrypt")) and
Cipher.isWeak() and
Cipher.getName() = name
cipher.taints(method.getObject("encrypt")) and
cipher.isWeak() and
cipher.getName() = name
)
}
@@ -89,25 +87,25 @@ module Pycrypto {
module Cryptography {
PackageObject ciphers() {
result.getName() = "cryptography.hazmat.primitives.ciphers"
ModuleValue ciphers() {
result = Module::named("cryptography.hazmat.primitives.ciphers") and
result.isPackage()
}
class CipherClass extends ClassObject {
class CipherClass extends ClassValue {
CipherClass() {
ciphers().attr("Cipher") = this
}
}
class AlgorithmClass extends ClassObject {
class AlgorithmClass extends ClassValue {
AlgorithmClass() {
ciphers().submodule("algorithms").attr(_) = this
ciphers().attr("algorithms").attr(_) = this
}
string getAlgorithmName() {
result = this.declaredAttribute("name").(StringObject).getText()
result = this.declaredAttribute("name").(StringValue).getText()
}
predicate isWeak() {
@@ -134,7 +132,7 @@ module Cryptography {
cls.isWeak()
}
override TaintKind getTaintOfMethodResult(string name) {
override TaintKind getTaintOfMethodResult(string name) {
name = "encryptor" and
result.(Encryptor).getAlgorithm() = this.getAlgorithm()
}
@@ -144,11 +142,11 @@ module Cryptography {
class CipherSource extends TaintSource {
CipherSource() {
this.(CallNode).getFunction().refersTo(any(CipherClass cls))
this.(CallNode).getFunction().pointsTo(any(CipherClass cls))
}
override predicate isSourceOf(TaintKind kind) {
this.(CallNode).getArg(0).refersTo(_, kind.(CipherInstance).getAlgorithm(), _)
this.(CallNode).getArg(0).pointsTo().getClass() = kind.(CipherInstance).getAlgorithm()
}
override string toString() {
@@ -203,5 +201,3 @@ private class CipherConfig extends TaintTracking::Configuration {
}
}

View File

@@ -27,7 +27,7 @@ class ExceptionInfo extends StringKind {
}
/** A class representing sources of information about
/** A class representing sources of information about
* execution state exposed in tracebacks and the like.
*/
abstract class ErrorInfoSource extends TaintSource {}
@@ -59,9 +59,9 @@ class ExceptionKind extends TaintKind {
class ExceptionSource extends ErrorInfoSource {
ExceptionSource() {
exists(ClassObject cls |
cls.isSubclassOf(theExceptionType()) and
this.(ControlFlowNode).refersTo(_, cls, _)
exists(ClassValue cls |
cls.getASuperType() = ClassValue::baseException() and
this.(ControlFlowNode).pointsTo().getClass() = cls
)
or
this = any(ExceptStmt s).getName().getAFlowNode()
@@ -116,7 +116,7 @@ class CallToTracebackFunction extends ErrorInfoSource {
}
}
/**
/**
* Represents calls to functions in the `traceback` module that return a single
* string of information about an exception.
*/

View File

@@ -166,9 +166,9 @@ module SensitiveData {
SensitiveRequestParameter() {
this.(CallNode).getFunction().(AttrNode).getName() = "get" and
exists(string sensitive |
this.(CallNode).getAnArg().refersTo(any(StringObject s | s.getText() = sensitive)) and
data = HeuristicNames::getSensitiveDataForName(sensitive)
exists(StringValue sensitive |
this.(CallNode).getAnArg().pointsTo(sensitive) and
data = HeuristicNames::getSensitiveDataForName(sensitive.getText())
)
}

View File

@@ -71,11 +71,7 @@ private predicate str_method_call(ControlFlowNode fromnode, CallNode tonode) {
/* tonode = ....format(fromnode) */
private predicate str_format(ControlFlowNode fromnode, CallNode tonode) {
tonode.getFunction().(AttrNode).getName() = "format" and
(
tonode.getAnArg() = fromnode
or
tonode.getNode().getAKeyword().getValue() = fromnode.getNode()
)
tonode.getAnArg() = fromnode
}
/* tonode = codec.[en|de]code(fromnode)*/
@@ -93,9 +89,10 @@ private predicate encode_decode(ControlFlowNode fromnode, CallNode tonode) {
/* tonode = str(fromnode)*/
private predicate to_str(ControlFlowNode fromnode, CallNode tonode) {
tonode.getAnArg() = fromnode and
exists(ClassObject str |
tonode.getFunction().refersTo(str) |
str = theUnicodeType() or str = theBytesType()
(
tonode = ClassValue::bytes().getACall()
or
tonode = ClassValue::unicode().getACall()
)
}
@@ -110,11 +107,8 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) {
/* tonode = os.path.join(..., fromnode, ...) */
private predicate os_path_join(ControlFlowNode fromnode, CallNode tonode) {
exists(FunctionObject path_join |
path_join = ModuleObject::named("os").attr("path").(ModuleObject).attr("join")
and
tonode = path_join.getACall() and tonode.getAnArg() = fromnode
)
tonode = Value::named("os.path.join").getACall()
and tonode.getAnArg() = fromnode
}
/** A kind of "taint", representing a dictionary mapping str->"taint" */
@@ -125,5 +119,3 @@ class StringDictKind extends DictKind {
}
}

View File

@@ -5,12 +5,12 @@ import python
predicate copy_call(ControlFlowNode fromnode, CallNode tonode) {
tonode.getFunction().(AttrNode).getObject("copy") = fromnode
or
exists(ModuleObject copy, string name |
exists(ModuleValue copy, string name |
name = "copy" or name = "deepcopy" |
copy.attr(name).(FunctionObject).getACall() = tonode and
copy.attr(name).(FunctionValue).getACall() = tonode and
tonode.getArg(0) = fromnode
)
or
tonode.getFunction().refersTo(Object::builtin("reversed")) and
tonode.getFunction().pointsTo(Value::named("reversed")) and
tonode.getArg(0) = fromnode
}

View File

@@ -139,11 +139,8 @@ private predicate json_subscript_taint(
}
private predicate json_load(ControlFlowNode fromnode, CallNode tonode) {
exists(FunctionObject json_loads |
ModuleObject::named("json").attr("loads") = json_loads and
json_loads.getACall() = tonode and
tonode.getArg(0) = fromnode
)
tonode = Value::named("json.loads").getACall() and
tonode.getArg(0) = fromnode
}
private predicate urlsplit(ControlFlowNode fromnode, CallNode tonode) {

View File

@@ -260,35 +260,58 @@ class ExceptFlowNode extends ControlFlowNode {
)
}
private predicate handledObject(Object obj, ClassObject cls, ControlFlowNode origin) {
private predicate handledObject_objectapi(Object obj, ClassObject cls, ControlFlowNode origin) {
this.getType().refersTo(obj, cls, origin)
or
exists(Object tup |
this.handledObject(tup, theTupleType(), _) |
element_from_tuple(tup).refersTo(obj, cls, origin)
this.handledObject_objectapi(tup, theTupleType(), _) |
element_from_tuple_objectapi(tup).refersTo(obj, cls, origin)
)
}
private predicate handledObject(Value val, ClassValue cls, ControlFlowNode origin) {
val.getClass() = cls and
(
this.getType().pointsTo(val, origin)
or
exists(TupleValue tup |
this.handledObject(tup, ClassValue::tuple(), _) |
val = tup.getItem(_) and origin = val.getOrigin()
)
)
}
/** Gets the inferred type(s) that are handled by this node, splitting tuples if possible. */
pragma [noinline]
predicate handledException(Object obj, ClassObject cls, ControlFlowNode origin) {
this.handledObject(obj, cls, origin) and not cls = theTupleType()
predicate handledException_objectapi(Object obj, ClassObject cls, ControlFlowNode origin) {
this.handledObject_objectapi(obj, cls, origin) and not cls = theTupleType()
or
not exists(this.getNode().(ExceptStmt).getType()) and obj = theBaseExceptionType() and cls = theTypeType() and
origin = this
}
/** Gets the inferred type(s) that are handled by this node, splitting tuples if possible. */
pragma [noinline]
predicate handledException(Value val, ClassValue cls, ControlFlowNode origin) {
this.handledObject(val, cls, origin) and not cls = ClassValue::tuple()
or
not exists(this.getNode().(ExceptStmt).getType()) and val = ClassValue::baseException() and cls = ClassValue::type() and
origin = this
}
/** Whether this `except` handles `cls` */
predicate handles(ClassObject cls) {
exists(ClassObject handled |
this.handledException(handled, _, _) |
this.handledException_objectapi(handled, _, _) |
cls.getAnImproperSuperType() = handled
)
}
}
private ControlFlowNode element_from_tuple(Object tuple) {
private ControlFlowNode element_from_tuple_objectapi(Object tuple) {
exists(Tuple t |
t = tuple.getOrigin() and result = t.getAnElt().getAFlowNode()
)

View File

@@ -2,5 +2,5 @@
import python
from ExceptFlowNode ex, Object obj
where ex.handledException(obj, _, _)
where ex.handledException_objectapi(obj, _, _)
select ex.getLocation().getStartLine(), ex.toString(), obj.toString()

View File

@@ -1 +1 @@
| nonsense.py:1:14:1:14 | Syntax Error | Syntax Error (in Python 2.7). |
| nonsense.py:1:14:1:14 | Syntax Error | Syntax Error (in Python 2). |

View File

@@ -2,5 +2,5 @@
import python
from ExceptFlowNode ex, Object obj
where ex.handledException(obj, _, _)
where ex.handledException_objectapi(obj, _, _)
select ex.getLocation().getStartLine(), ex.toString(), obj.toString()

View File

@@ -0,0 +1,3 @@
| mwe_failure.py:7:1:7:23 | class MyTest | <MISSING BASE TYPE> |
| mwe_failure_2.py:7:1:7:23 | class MyTest | <MISSING BASE TYPE> |
| mwe_success.py:7:1:7:23 | class MyTest | class TestCase |

View File

@@ -0,0 +1,10 @@
import python
// as used in semmle.python.filters.Tests
from ClassValue c, string base
where
c.getScope().getLocation().getFile().getShortName().matches("mwe%.py") and
c.getName() = "MyTest" and
if exists(c.getABaseType()) then base = c.getABaseType().toString() else base = "<MISSING BASE TYPE>"
select c, base

View File

@@ -0,0 +1,10 @@
import subprocess
assert subprocess.call(['run-backup']) == 0
class TestCase:
pass
class MyTest(TestCase):
pass
# found by /home/rasmus/code/ql/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref

View File

@@ -0,0 +1,8 @@
import subprocess
assert subprocess.call(['run-backup'])
class TestCase:
pass
class MyTest(TestCase):
pass

View File

@@ -0,0 +1,8 @@
import subprocess
subprocess.call(['run-backup'])
class TestCase:
pass
class MyTest(TestCase):
pass

View File

@@ -0,0 +1 @@
semmle-extractor-options: --lang=3 --max-import-depth=1

View File

@@ -1 +1 @@
| nonsense.py:1:2:1:2 | Syntax Error | Syntax Error (in Python 3.5). |
| nonsense.py:1:2:1:2 | Syntax Error | Syntax Error (in Python 3). |

View File

@@ -1,2 +1,2 @@
| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterator | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter |
| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterator | file://:0:0:0:0 | builtin-class int | int |
| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterable | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter |
| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterable | file://:0:0:0:0 | builtin-class int | int |

View File

@@ -1,6 +1,6 @@
| 16 | classmethod() | 17 | Function c1 |
| 23 | classmethod() | 20 | Function c2 |
| 24 | classmethod() | 20 | Function c2 |
| 26 | staticmethod() | 27 | Function s1 |
| 33 | staticmethod() | 30 | Function s2 |
| 34 | staticmethod() | 30 | Function s2 |
| 104 | classmethod() | 105 | Function c1 |
| 111 | classmethod() | 108 | Function c2 |
| 112 | classmethod() | 108 | Function c2 |
| 114 | staticmethod() | 115 | Function s1 |
| 121 | staticmethod() | 118 | Function s2 |
| 122 | staticmethod() | 118 | Function s2 |

View File

@@ -1 +1,8 @@
| 6 | Property f | 7 | Function f | 11 | Function f |
| test.py:6:5:6:16 | Function WithDecorator.x | getter | test.py:5:6:5:13 | property x |
| test.py:11:5:11:23 | Function WithDecorator.x | setter | test.py:5:6:5:13 | property x |
| test.py:15:5:15:16 | Function WithDecorator.x | deleter | test.py:5:6:5:13 | property x |
| test.py:21:5:21:16 | Function WithDecoratorOnlyGetter.x | getter | test.py:20:6:20:13 | property x |
| test.py:28:5:28:19 | Function WithoutDecorator.getx | getter | test.py:37:9:37:59 | property getx |
| test.py:31:5:31:26 | Function WithoutDecorator.setx | setter | test.py:37:9:37:59 | property getx |
| test.py:34:5:34:19 | Function WithoutDecorator.delx | deleter | test.py:37:9:37:59 | property getx |
| test.py:41:5:41:19 | Function WithoutDecoratorOnlyGetter.getx | getter | test.py:44:9:44:22 | property getx |

View File

@@ -1,13 +1,11 @@
import python
import semmle.python.types.Descriptors
int lineof(Object o) {
result = o.getOrigin().getLocation().getStartLine()
}
from PropertyObject p, FunctionObject getter, FunctionObject setter
from PropertyValue p, string method_name, FunctionValue method
where
getter = p.getGetter() and setter = p.getSetter()
select lineof(p), p.toString(), lineof(getter), getter.toString(), lineof(setter), setter.toString()
method_name = "getter" and method = p.getGetter()
or
method_name = "setter" and method = p.getSetter()
or
method_name = "deleter" and method = p.getDeleter()
select method, method_name, p

View File

@@ -1,15 +1,103 @@
class C(object):
class WithDecorator(object):
def __init__(self):
self._x = None
@property
def f(self):
return self._f
def x(self):
"""I'm the 'x' property."""
return self._x
@f.setter
def f(self):
return self._f
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
class WithDecoratorOnlyGetter(object):
@property
def x(self):
return 42
class WithoutDecorator(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
class WithoutDecoratorOnlyGetter(object):
def getx(self):
return 42
x = property(getx)
class WithoutDecoratorOnlyGetterKWArg(object):
def getx(self):
return 42
x = property(fget=getx)
class WithoutDecoratorOnlySetter(object):
def setx(self, value):
self._x = value
x = property(fset=setx) # TODO: Not handled
class WithDecoratorOnlySetter(object):
x = property()
@x.setter
def x(self, value):
print('{} setting value to {}'.format(self.__class__, value))
class FunkyButValid(object):
def delx(self):
print("deleting x")
x = property(fdel=delx)
@x.setter
def y(self, value):
print('setting value to {}'.format(value))
@y.getter
def z(self):
return 42
wat = FunkyButValid()
try:
wat.x
except AttributeError as e:
print("x can't be read")
del wat.x
try:
wat.y
except AttributeError as e:
print("y can't be read")
wat.y = 1234
del wat.y
print(wat.z)
wat.z = 10
del wat.z
class D(object):

View File

@@ -1,5 +1,5 @@
import python
from ExceptFlowNode ex, Object t
where ex.handledException(t, _, _)
where ex.handledException_objectapi(t, _, _)
select ex.getLocation().getStartLine(), ex.toString(), t.toString()

View File

@@ -0,0 +1,5 @@
| file://:0:0:0:0 | Module sys | isUsedAsModule |
| imported.py:0:0:0:0 | Module imported | isUsedAsModule |
| main.py:0:0:0:0 | Module main | isUsedAsScript |
| myscript.py:0:0:0:0 | Script myscript | isUsedAsScript |
| script:0:0:0:0 | Script script | isUsedAsScript |

View File

@@ -0,0 +1,16 @@
import python
from ModuleValue mv, string usage
where
// builtin module has different name in Python 2 and 3
not mv = Module::builtinModule() and
(
mv.isUsedAsModule() and usage = "isUsedAsModule"
or
mv.isUsedAsScript() and usage = "isUsedAsScript"
or
not mv.isUsedAsModule() and
not mv.isUsedAsScript() and
usage = "<UNKNOWN>"
)
select mv, usage

View File

@@ -0,0 +1,6 @@
def func():
pass
if __name__ == "__main__":
print("I could have done something interesting...")
print("but I didn't")

View File

@@ -0,0 +1,5 @@
import imported
if __name__ == "__main__":
imported.func()
print('Done')

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python
print("I'm actually a script you see ;)")

View File

@@ -0,0 +1 @@
semmle-extractor-options: -F script

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env python
print('Under construction :)')

View File

@@ -1 +1 @@
| str_fmt_test.py:5:26:5:26 | x | Right hand side of a % operator must be a mapping, not class $@. | file://:Compiled Code:0:0:0:0 | builtin-class list | list |
| str_fmt_test.py:5:26:5:26 | x | Right hand side of a % operator must be a mapping, not class $@. | file://:0:0:0:0 | builtin-class list | list |

View File

@@ -1,3 +1,3 @@
| functions_test.py:99:5:99:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0 |
| functions_test.py:102:5:102:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0 |
| functions_test.py:105:5:105:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0 |
| functions_test.py:99:5:99:40 | Function DeprecatedSliceMethods.__getslice__ | __getslice__ method has been deprecated since Python 2.0 |
| functions_test.py:102:5:102:47 | Function DeprecatedSliceMethods.__setslice__ | __setslice__ method has been deprecated since Python 2.0 |
| functions_test.py:105:5:105:40 | Function DeprecatedSliceMethods.__delslice__ | __delslice__ method has been deprecated since Python 2.0 |

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=0

View File

@@ -1,2 +1,3 @@
| assert.py:5:5:5:20 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:5:13:5:19 | Yield | an expression |
| assert.py:8:5:8:22 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:8:12:8:22 | Attribute() | an expression |
| side_effect.py:5:1:5:43 | Assert | This 'assert' statement contains $@ which may have side effects. | side_effect.py:5:8:5:38 | Attribute() | an expression |

View File

@@ -103,4 +103,4 @@ def error_assert_in_intermediate_branch(x):
elif yks(x):
pass
else:
pass
pass

View File

@@ -0,0 +1,5 @@
# For now, this test lives in its own file, since including them in the top of assert.py
# messes up the results of the refers-to/points-to analysis
# see /home/rasmus/code/ql/python/ql/test/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py
import subprocess
assert subprocess.call(['run-backup']) == 0

View File

@@ -1,4 +1,4 @@
| statements_test.py:19:5:19:18 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 2. | statements_test.py:19:15:19:18 | statements_test.py:19 | tuple |
| statements_test.py:163:5:163:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:163:13:163:23 | statements_test.py:163 | list |
| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:16:167:24 | statements_test.py:167 | tuple |
| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:169:16:169:26 | statements_test.py:169 | tuple |
| statements_test.py:169:5:169:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:169:13:169:23 | statements_test.py:169 | list |
| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:173:16:173:24 | statements_test.py:173 | tuple |
| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:175:16:175:26 | statements_test.py:175 | tuple |

View File

@@ -1 +1 @@
| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterator | test.py:45:1:45:26 | class NonIterator | NonIterator |
| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterable | test.py:45:1:45:26 | class NonIterator | NonIterator |

View File

@@ -1,3 +1,3 @@
| statements_test.py:54:5:54:9 | AssignStmt | This assignment assigns a variable to itself. |
| statements_test.py:57:9:57:19 | AssignStmt | This assignment assigns a variable to itself. |
| statements_test.py:117:9:117:23 | AssignStmt | This assignment assigns a variable to itself. |
| statements_test.py:119:9:119:23 | AssignStmt | This assignment assigns a variable to itself. |

View File

@@ -1 +1 @@
| statements_test.py:181:5:181:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:181:9:181:9 | statements_test.py:181 | x | statements_test.py:179:1:179:31 | statements_test.py:179 | error_unnecessary_delete |
| statements_test.py:187:5:187:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:187:9:187:9 | statements_test.py:187 | x | statements_test.py:185:1:185:31 | statements_test.py:185 | error_unnecessary_delete |

View File

@@ -1,2 +1,2 @@
| statements_test.py:63:1:63:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. |
| statements_test.py:68:1:68:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. |
| statements_test.py:65:1:65:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. |
| statements_test.py:70:1:70:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. |

View File

@@ -56,27 +56,29 @@ class Redundant(object):
def __init__(self, args):
args = args # violation
#Non redundant assignment
len = len
if sys.version_info < (3,):
bytes = str
else:
bytes = bytes # Should not be flagged
#Pointless else clauses
for x in range(10):
func(x)
else:
do_something()
while x < 10:
func(x)
else:
do_something()
#OK else clauses:
for x in range(10):
if func(x):
break
else:
do_something()
while x < 10:
if func(x):
break
@@ -95,24 +97,24 @@ else:
#Not a redundant assignment if a property.
class WithProp(object):
@property
def x(self):
return self._x
@prop.setter
def set_x(self, x):
side_effect(x)
self._x = x
def meth(self):
self.x = self.x
def maybe_property(x):
x.y = x.y
class WithoutProp(object):
def meth(self):
self.x = self.x
@@ -143,8 +145,12 @@ for e in EnumDerived:
class SideEffectingAttr(object):
def __init__(self):
self.foo = 'foo'
def __setattr__(self, name, val):
print("hello!")
super().__setattr__(name, val)
s = SideEffectingAttr()
s.foo = s.foo