mirror of
https://github.com/github/codeql.git
synced 2025-12-29 15:16:34 +01:00
Will need subsequent PRs fixing up test failures (due to deprecated methods moving around), but other than that everything should be straight-forward.
138 lines
4.0 KiB
Plaintext
138 lines
4.0 KiB
Plaintext
/**
|
|
* @name Statement has no effect
|
|
* @description A statement has no effect
|
|
* @kind problem
|
|
* @tags maintainability
|
|
* useless-code
|
|
* external/cwe/cwe-561
|
|
* @problem.severity recommendation
|
|
* @sub-severity high
|
|
* @precision high
|
|
* @id py/ineffectual-statement
|
|
*/
|
|
|
|
import python
|
|
|
|
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(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.pointsTo(_)
|
|
or
|
|
side_effecting_attribute(attr)
|
|
}
|
|
|
|
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 = 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 unless we know otherwise.
|
|
*/
|
|
predicate side_effecting_binary(Expr b) {
|
|
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(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, ClassValue cls, string method_name
|
|
) {
|
|
method_name = special_method() and
|
|
sub = b.getLeft() and
|
|
method_name = b.getOp().getSpecialMethodName() and
|
|
sub.pointsTo().getClass() = cls
|
|
}
|
|
|
|
pragma[nomagic]
|
|
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.pointsTo().getClass() = cls
|
|
}
|
|
|
|
private string special_method() {
|
|
result = any(Cmpop c).getSpecialMethodName()
|
|
or
|
|
result = any(BinaryExpr b).getOp().getSpecialMethodName()
|
|
}
|
|
|
|
predicate is_notebook(File 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()) }
|
|
|
|
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. */
|
|
predicate in_raises_test(Expr e) {
|
|
exists(With w |
|
|
w.contains(e) and
|
|
w.getContextExpr() = assertRaises().getACall().getNode()
|
|
)
|
|
}
|
|
|
|
/** Holds if expression has the form of a Python 2 `print >> out, ...` statement */
|
|
predicate python2_print(Expr e) {
|
|
e.(BinaryExpr).getLeft().(Name).getId() = "print" and
|
|
e.(BinaryExpr).getOp() instanceof RShift
|
|
or
|
|
python2_print(e.(Tuple).getElt(0))
|
|
}
|
|
|
|
predicate no_effect(Expr e) {
|
|
// strings can be used as comments
|
|
not e instanceof StrConst and
|
|
not e.hasSideEffects() 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
|
|
not in_raises_test(e) and
|
|
not python2_print(e)
|
|
}
|
|
|
|
from ExprStmt stmt
|
|
where no_effect(stmt.getValue())
|
|
select stmt, "This statement has no effect."
|