mirror of
https://github.com/github/codeql.git
synced 2026-05-04 05:05:12 +02:00
Merge pull request #3156 from tausbn/python-autoformat-all-ql-files
Python: Autoformat all `.ql` files.
This commit is contained in:
@@ -16,14 +16,14 @@ import python
|
||||
|
||||
from CallNode call_to_super, string name
|
||||
where
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
exists(GlobalVariable gv, ControlFlowNode cn |
|
||||
call_to_super = ClassValue::super_().getACall() and
|
||||
gv.getId() = "super" and
|
||||
cn = call_to_super.getArg(0) and
|
||||
name = call_to_super.getScope().getScope().(Class).getName() and
|
||||
exists(ClassValue other |
|
||||
cn.pointsTo(other) and
|
||||
not other.getScope().getName() = name
|
||||
)
|
||||
)
|
||||
)
|
||||
select call_to_super.getNode(), "First argument to super() should be " + name + "."
|
||||
|
||||
@@ -16,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."
|
||||
|
||||
@@ -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'."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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() + "\""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() + "\""
|
||||
|
||||
@@ -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() + "\""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 + "."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 + "."
|
||||
|
||||
|
||||
|
||||
@@ -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() + "."
|
||||
|
||||
@@ -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() + "."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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?"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -16,15 +16,20 @@ import CallArgs
|
||||
|
||||
from Call call, FunctionObject func, string too, string should, int limit
|
||||
where
|
||||
(
|
||||
too_many_args_objectapi(call, func, limit) and too = "too many arguments" and should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, func, limit) and too = "too few arguments" and should = "no fewer than "
|
||||
) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden))
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
and not func.getName() = "__new__"
|
||||
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()
|
||||
|
||||
(
|
||||
too_many_args_objectapi(call, func, limit) and
|
||||
too = "too many arguments" and
|
||||
should = "no more than "
|
||||
or
|
||||
too_few_args_objectapi(call, func, limit) and
|
||||
too = "too few arguments" and
|
||||
should = "no fewer than "
|
||||
) and
|
||||
not func.isAbstract() and
|
||||
not exists(FunctionObject overridden |
|
||||
func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)
|
||||
) and
|
||||
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
|
||||
not func.getName() = "__new__"
|
||||
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
|
||||
func.descriptiveString()
|
||||
|
||||
Reference in New Issue
Block a user