Merge branch 'master' into python-add-points-to-for-missing-builtin-return-types

This commit is contained in:
Taus
2020-03-18 17:59:17 +01:00
committed by GitHub
262 changed files with 15939 additions and 8481 deletions

View File

@@ -22,8 +22,8 @@ predicate multiple_calls_to_superclass_method(ClassObject self, FunctionObject m
multiple_invocation_paths(top, i1, i2, multi) and
top.runtime(self.declaredAttribute(name)) and
self.getASuperType().declaredAttribute(name) = multi |
/* Only called twice if called from different functions,
* or if one call-site can reach the other */
// Only called twice if called from different functions,
// or if one call-site can reach the other.
i1.getCall().getScope() != i2.getCall().getScope()
or
i1.getCall().strictlyReaches(i2.getCall())

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

@@ -39,9 +39,9 @@ private Keyword not_keyword_only_arg(Call call, FunctionValue func) {
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_for_call_objectapi(Call call, Object callable) {
@@ -59,9 +59,9 @@ private int positional_arg_count_for_call_objectapi(Call call, Object callable)
}
/** Gets the count of arguments that are passed as positional parameters even if they
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
* are named in the call.
* This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg
* plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs).
*/
private int positional_arg_count_for_call(Call call, Value callable) {
@@ -139,10 +139,9 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) {
arg_count_objectapi(call) < limit and
exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) |
call = func.getAFunctionCall().getNode() and limit = func.minParameters() and
/* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
// The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function.
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
@@ -160,10 +159,9 @@ predicate too_few_args(Call call, Value callable, int limit) {
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().getNode() and limit = func.minParameters() and
/* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
// The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function.
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1

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,29 +15,30 @@
import python
import semmle.python.strings
predicate string_format(BinaryExpr operation, StrConst str, Object args, AstNode origin) {
exists(Object fmt, Context ctx | operation.getOp() instanceof Mod |
operation.getLeft().refersTo(ctx, fmt, _, str) and
operation.getRight().refersTo(ctx, args, _, origin)
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)
)
}
int sequence_length(Object args) {
int sequence_length(Value args) {
/* Guess length of sequence */
exists(Tuple seq |
seq = args.getOrigin() |
exists(Tuple seq, AstNode origin |
seq.pointsTo(args,origin) |
result = strictcount(seq.getAnElt()) and
not seq.getAnElt() instanceof Starred
)
or
exists(ImmutableLiteral i |
i.getLiteralObject() = args |
i.getLiteralValue() = args |
result = 1
)
}
from BinaryExpr operation, StrConst fmt, Object args, int slen, int alen, AstNode origin, string provided
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,

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

@@ -1,7 +1,6 @@
import python
/** Whether `mox` or `.StubOutWithMock()` is used in thin module `m`.
*/
/** Whether `mox` or `.StubOutWithMock()` is used in thin module `m`. */
predicate useOfMoxInModule(Module m) {
exists(ModuleObject mox |
mox.getName() = "mox" or mox.getName() = "mox3.mox" |

View File

@@ -48,8 +48,7 @@ class Symbol extends TSymbol {
)
}
/** Finds the `AstNode` that this `Symbol` refers to.
*/
/** Finds the `AstNode` that this `Symbol` refers to. */
AstNode find() {
this = TModule(result)
or
@@ -91,8 +90,7 @@ class Symbol extends TSymbol {
)
}
/** Gets the `Symbol` that is the named member of this `Symbol`.
*/
/** Gets the `Symbol` that is the named member of this `Symbol`. */
Symbol getMember(string name) {
result = TMember(this, name)
}

View File

@@ -10,8 +10,7 @@ private newtype TDefinition =
a instanceof Expr or a instanceof Stmt or a instanceof Module
}
/** A definition for the purposes of jump-to-definition.
*/
/** A definition for the purposes of jump-to-definition. */
class Definition extends TLocalDefinition {
@@ -159,8 +158,7 @@ private predicate delete_defn(DeletionDefinition def, Definition defn) {
none()
}
/* Implicit "defn" of the names of submodules at the start of an `__init__.py` file.
*/
/* Implicit "defn" of the names of submodules at the start of an `__init__.py` file. */
private predicate implicit_submodule_defn(ImplicitSubModuleDefinition def, Definition defn) {
exists(PackageObject package, ModuleObject mod |
package.getInitModule().getModule() = def.getDefiningNode().getScope() and
@@ -170,7 +168,9 @@ private predicate implicit_submodule_defn(ImplicitSubModuleDefinition def, Defin
}
/* Helper for scope_entry_value_transfer(...). Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters */
/* Helper for scope_entry_value_transfer(...).
* Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters
*/
private predicate scope_entry_value_transfer_at_callsite(EssaVariable pred_var, ScopeEntryDefinition succ_def) {
exists(CallNode callsite, FunctionObject f |
f.getACall() = callsite and
@@ -469,8 +469,8 @@ class NiceLocationExpr extends @py_expr {
or
this.(Name).getLocation().hasLocationInfo(f, bl, bc, el, ec)
or
/* Show xxx for `xxx` in `from xxx import y` or
* for `import xxx` or for `import xxx as yyy`. */
// Show xxx for `xxx` in `from xxx import y` or
// for `import xxx` or for `import xxx as yyy`.
this.(ImportExpr).getLocation().hasLocationInfo(f, bl, bc, el, ec)
or
/* Show y for `y` in `from xxx import y` */

View File

@@ -24,7 +24,7 @@ abstract class AstNode extends AstNode_ {
}
/** Whether this syntactic element is artificial, that is it is generated
* by the compiler and is not present in the source */
* by the compiler and is not present in the source */
predicate isArtificial() {
none()
}

View File

@@ -92,7 +92,7 @@ class ClassDef extends Assign {
class Class extends Class_, Scope, AstNode {
/** Use getADecorator() instead of getDefinition().getADecorator()
* Use getMetaClass() instead of getDefinition().getMetaClass()
* Use getMetaClass() instead of getDefinition().getMetaClass()
*/
deprecated ClassExpr getDefinition() {
result = this.getParent()

View File

@@ -1,8 +1,7 @@
import python
/* A class representing the six comparison operators, ==, !=, <, <=, > and >=.
* */
/** A class representing the six comparison operators, ==, !=, <, <=, > and >=. */
class CompareOp extends int {
CompareOp() {

View File

@@ -356,7 +356,7 @@ class Repr extends Repr_ {
/* Constants */
/** A bytes constant, such as `b'ascii'`. Note that unadorned string constants such as
`"hello"` are treated as Bytes for Python2, but Unicode for Python3. */
* `"hello"` are treated as Bytes for Python2, but Unicode for Python3. */
class Bytes extends StrConst {
/* syntax: b"hello" */
@@ -395,8 +395,7 @@ class Ellipsis extends Ellipsis_ {
}
/** Immutable literal expressions (except tuples).
* Consists of string (both unicode and byte) literals
* and numeric literals.
* Consists of string (both unicode and byte) literals and numeric literals.
*/
abstract class ImmutableLiteral extends Expr {
@@ -433,8 +432,10 @@ class IntegerLiteral extends Num {
not this instanceof FloatLiteral and not this instanceof ImaginaryLiteral
}
/** Gets the (integer) value of this constant. Will not return a result if the value does not fit into
a 32 bit signed value */
/**
* Gets the (integer) value of this constant. Will not return a result if the value does not fit into
* a 32 bit signed value
*/
int getValue() {
result = this.getN().toInt()
}
@@ -540,8 +541,10 @@ class NegativeIntegerLiteral extends ImmutableLiteral, UnaryExpr {
py_cobjectnames(result, "-" + this.getOperand().(IntegerLiteral).getN())
}
/** Gets the (integer) value of this constant. Will not return a result if the value does not fit into
a 32 bit signed value */
/**
* Gets the (integer) value of this constant. Will not return a result if the value does not fit into
* a 32 bit signed value
*/
int getValue() {
result = -(this.getOperand().(IntegerLiteral).getValue())
}
@@ -549,7 +552,8 @@ class NegativeIntegerLiteral extends ImmutableLiteral, UnaryExpr {
}
/** A unicode string expression, such as `u"\u20ac"`. Note that unadorned string constants such as
"hello" are treated as Bytes for Python2, but Unicode for Python3. */
* "hello" are treated as Bytes for Python2, but Unicode for Python3.
*/
class Unicode extends StrConst {
/* syntax: "hello" */

View File

@@ -129,8 +129,10 @@ class Folder extends Container {
}
/** A container is an abstract representation of a file system object that can
hold elements of interest. */
/**
* A container is an abstract representation of a file system object that can
* hold elements of interest.
*/
abstract class Container extends @container {
Container getParent() {
@@ -473,8 +475,10 @@ class Line extends @py_line {
}
/** A syntax error. Note that if there is a syntax error in a module,
much information about that module will be lost */
/**
* A syntax error. Note that if there is a syntax error in a module,
* much information about that module will be lost
*/
class SyntaxError extends Location {
SyntaxError() {
@@ -492,8 +496,10 @@ class SyntaxError extends Location {
}
/** An encoding error. Note that if there is an encoding error in a module,
much information about that module will be lost */
/**
* An encoding error. Note that if there is an encoding error in a module,
* much information about that module will be lost
*/
class EncodingError extends SyntaxError {
EncodingError() {

View File

@@ -27,8 +27,8 @@ private AstNode toAst(ControlFlowNode n) {
/** A control flow node. Control flow nodes have a many-to-one relation with syntactic nodes,
* although most syntactic nodes have only one corresponding control flow node.
* Edges between control flow nodes include exceptional as well as normal control flow.
*/
* Edges between control flow nodes include exceptional as well as normal control flow.
*/
class ControlFlowNode extends @py_flow_node {
/** Whether this control flow node is a load (including those in augmented assignments) */
@@ -235,18 +235,18 @@ class ControlFlowNode extends @py_flow_node {
PointsTo::pointsTo(this, context, value, origin)
}
/** Gets what this flow node might "refer-to". Performs a combination of localized (intra-procedural) points-to
* analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly
* precise, but may not provide information for a significant number of flow-nodes.
* If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead.
/**
* Gets what this flow node might "refer-to". Performs a combination of localized (intra-procedural) points-to
* analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly
* precise, but may not provide information for a significant number of flow-nodes.
* If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead.
*/
pragma [nomagic]
predicate refersTo(Object obj, ClassObject cls, ControlFlowNode origin) {
this.refersTo(_, obj, cls, origin)
}
/** Gets what this expression might "refer-to" in the given `context`.
*/
/** Gets what this expression might "refer-to" in the given `context`. */
pragma [nomagic]
predicate refersTo(Context context, Object obj, ClassObject cls, ControlFlowNode origin) {
not obj = unknownValue() and
@@ -329,8 +329,8 @@ class ControlFlowNode extends @py_flow_node {
exists(BasicBlock b |
start_bb_likely_reachable(b) and
not end_bb_likely_reachable(b) and
/* If there is an unlikely successor edge earlier in the BB
* than this node, then this node must be unreachable */
// If there is an unlikely successor edge earlier in the BB
// than this node, then this node must be unreachable.
exists(ControlFlowNode p, int i, int j |
p.(RaisingNode).unlikelySuccessor(_) and
p = b.getNode(i) and
@@ -531,7 +531,7 @@ class AttrNode extends ControlFlowNode {
}
/** Gets the flow node corresponding to the object of the attribute expression corresponding to this flow node,
with the matching name */
* with the matching name */
ControlFlowNode getObject(string name) {
exists(Attribute a |
this.getNode() = a and a.getObject() = result.getNode() and
@@ -555,7 +555,7 @@ class ImportMemberNode extends ControlFlowNode {
}
/** Gets the flow node corresponding to the module in the import-member expression corresponding to this flow node,
with the matching name*/
* with the matching name */
ControlFlowNode getModule(string name) {
exists(ImportMember i |
this.getNode() = i and i.getModule() = result.getNode() |

View File

@@ -1,7 +1,9 @@
import python
/** A function, independent of defaults and binding.
It is the syntactic entity that is compiled to a code object. */
/**
* A function, independent of defaults and binding.
* It is the syntactic entity that is compiled to a code object.
*/
class Function extends Function_, Scope, AstNode {
/** The expression defining this function */
@@ -10,7 +12,7 @@ class Function extends Function_, Scope, AstNode {
}
/** The scope in which this function occurs, will be a class for a method,
* another function for nested functions, generator expressions or comprehensions,
* another function for nested functions, generator expressions or comprehensions,
* or a module for a plain function. */
override Scope getEnclosingScope() {
result = this.getParent().(Expr).getScope()
@@ -33,8 +35,10 @@ class Function extends Function_, Scope, AstNode {
name != "__init__")
}
/** Whether this function is a generator function,
that is whether it contains a yield or yield-from expression */
/**
* Whether this function is a generator function,
* that is whether it contains a yield or yield-from expression
*/
predicate isGenerator() {
exists(Yield y | y.getScope() = this)
or
@@ -323,7 +327,7 @@ class Parameter extends Parameter_ {
abstract class CallableExpr extends Expr {
/** Gets the parameters of this callable.
* This predicate is called getArgs(), rather than getParameters() for compatibility with Python's AST module. */
* This predicate is called getArgs(), rather than getParameters() for compatibility with Python's AST module. */
abstract Arguments getArgs();
/** Gets the function scope of this code expression. */

View File

@@ -9,38 +9,39 @@ class ConditionBlock extends BasicBlock {
/** Basic blocks controlled by this condition, i.e. those BBs for which the condition is testIsTrue */
predicate controls(BasicBlock controlled, boolean testIsTrue) {
/* For this block to control the block 'controlled' with 'testIsTrue' the following must be true:
Execution must have passed through the test i.e. 'this' must strictly dominate 'controlled'.
Execution must have passed through the 'testIsTrue' edge leaving 'this'.
Although "passed through the true edge" implies that this.getATrueSuccessor() dominates 'controlled',
the reverse is not true, as flow may have passed through another edge to get to this.getATrueSuccessor()
so we need to assert that this.getATrueSuccessor() dominates 'controlled' *and* that
all predecessors of this.getATrueSuccessor() are either this or dominated by this.getATrueSuccessor().
For example, in the following python snippet:
<code>
if x:
controlled
false_successor
uncontrolled
</code>
false_successor dominates uncontrolled, but not all of its predecessors are this (if x)
or dominated by itself. Whereas in the following code:
<code>
if x:
while controlled:
also_controlled
false_successor
uncontrolled
</code>
the block 'while controlled' is controlled because all of its predecessors are this (if x)
or (in the case of 'also_controlled') dominated by itself.
The additional constraint on the predecessors of the test successor implies
that `this` strictly dominates `controlled` so that isn't necessary to check
directly.
*/
/*
* For this block to control the block 'controlled' with 'testIsTrue' the following must be true:
* Execution must have passed through the test i.e. 'this' must strictly dominate 'controlled'.
* Execution must have passed through the 'testIsTrue' edge leaving 'this'.
*
* Although "passed through the true edge" implies that this.getATrueSuccessor() dominates 'controlled',
* the reverse is not true, as flow may have passed through another edge to get to this.getATrueSuccessor()
* so we need to assert that this.getATrueSuccessor() dominates 'controlled' *and* that
* all predecessors of this.getATrueSuccessor() are either this or dominated by this.getATrueSuccessor().
*
* For example, in the following python snippet:
* <code>
* if x:
* controlled
* false_successor
* uncontrolled
* </code>
* false_successor dominates uncontrolled, but not all of its predecessors are this (if x)
* or dominated by itself. Whereas in the following code:
* <code>
* if x:
* while controlled:
* also_controlled
* false_successor
* uncontrolled
* </code>
* the block 'while controlled' is controlled because all of its predecessors are this (if x)
* or (in the case of 'also_controlled') dominated by itself.
*
* The additional constraint on the predecessors of the test successor implies
* that `this` strictly dominates `controlled` so that isn't necessary to check
* directly.
*/
exists(BasicBlock succ |
testIsTrue = true and succ = this.getATrueSuccessor()
or

View File

@@ -2,7 +2,7 @@ import python
private import semmle.python.types.Builtins
/** An alias in an import statement, the `mod as name` part of `import mod as name`. May be artificial;
`import x` is transformed into `import x as x` */
* `import x` is transformed into `import x as x` */
class Alias extends Alias_ {
Location getLocation() {
@@ -38,7 +38,7 @@ class ImportExpr extends ImportExpr_ {
}
/** The language specifies level as -1 if relative imports are to be tried first, 0 for absolute imports,
and level > 0 for explicit relative imports. */
* and level > 0 for explicit relative imports. */
override int getLevel() {
exists(int l | l = super.getLevel() |
l > 0 and result = l
@@ -73,8 +73,8 @@ class ImportExpr extends ImportExpr_ {
}
/** Gets the name by which the lowest level module or package is imported.
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
string bottomModuleName() {
result = relativeTopName() + this.remainderOfName()
or
@@ -95,8 +95,8 @@ class ImportExpr extends ImportExpr_ {
}
/** Gets the full name of the module resulting from evaluating this import.
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
string getImportedModuleName() {
exists(string bottomName | bottomName = this.bottomModuleName() |
if this.isTop() then
@@ -158,8 +158,8 @@ class ImportMember extends ImportMember_ {
}
/** Gets the full name of the module resulting from evaluating this import.
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
* NOTE: This is the name that used to import the module,
* which may not be the name of the module. */
string getImportedModuleName() {
result = this.getModule().(ImportExpr).getImportedModuleName() + "." + this.getName()
}

View File

@@ -4,7 +4,7 @@ import python
class FunctionMetrics extends Function {
/** Gets the total number of lines (including blank lines)
from the definition to the end of the function */
* from the definition to the end of the function */
int getNumberOfLines() {
py_alllines(this, result)
}
@@ -53,8 +53,8 @@ class FunctionMetrics extends Function {
}
/** Dependency of Callables
One callable "this" depends on another callable "result"
if "this" makes some call to a method that may end up being "result".
* One callable "this" depends on another callable "result"
* if "this" makes some call to a method that may end up being "result".
*/
FunctionMetrics getADependency() {
result != this and
@@ -78,17 +78,17 @@ class FunctionMetrics extends Function {
}
/** Afferent Coupling
the number of callables that depend on this method.
This is sometimes called the "fan-in" of a method.
*/
* the number of callables that depend on this method.
* This is sometimes called the "fan-in" of a method.
*/
int getAfferentCoupling() {
result = count(FunctionMetrics m | m.getADependency() = this )
}
/** Efferent Coupling
the number of methods that this method depends on
This is sometimes called the "fan-out" of a method.
*/
* the number of methods that this method depends on
* This is sometimes called the "fan-out" of a method.
*/
int getEfferentCoupling() {
result = count(FunctionMetrics m | this.getADependency() = m)
}
@@ -112,7 +112,7 @@ class FunctionMetrics extends Function {
class ClassMetrics extends Class {
/** Gets the total number of lines (including blank lines)
from the definition to the end of the class */
* from the definition to the end of the class */
int getNumberOfLines() {
py_alllines(this, result)
}
@@ -149,14 +149,14 @@ class ClassMetrics extends Class {
}
/** The afferent coupling of a class is the number of classes that
* directly depend on it.
* directly depend on it.
*/
int getAfferentCoupling() {
result = count(ClassMetrics t | t.dependsOn(this))
}
/** The efferent coupling of a class is the number of classes that
* it directly depends on.
* it directly depends on.
*/
int getEfferentCoupling() {
result = count(ClassMetrics t | this.dependsOn(t))
@@ -172,20 +172,19 @@ class ClassMetrics extends Class {
/* -------- CHIDAMBER AND KEMERER LACK OF COHESION IN METHODS ------------ */
/* The aim of this metric is to try and determine whether a class
represents one abstraction (good) or multiple abstractions (bad).
If a class represents multiple abstractions, it should be split
up into multiple classes.
In the Chidamber and Kemerer method, this is measured as follows:
n1 = number of pairs of distinct methods in a class that do *not*
have at least one commonly accessed field
n2 = number of pairs of distinct methods in a class that do
have at least one commonly accessed field
lcom = ((n1 - n2)/2 max 0)
We divide by 2 because each pair (m1,m2) is counted twice in n1 and n2.
*/
* represents one abstraction (good) or multiple abstractions (bad).
* If a class represents multiple abstractions, it should be split
* up into multiple classes.
*
* In the Chidamber and Kemerer method, this is measured as follows:
* n1 = number of pairs of distinct methods in a class that do *not*
* have at least one commonly accessed field
* n2 = number of pairs of distinct methods in a class that do
* have at least one commonly accessed field
* lcom = ((n1 - n2)/2 max 0)
*
* We divide by 2 because each pair (m1,m2) is counted twice in n1 and n2.
*/
/** should function f be excluded from the cohesion computation? */
predicate ignoreLackOfCohesion(Function f) {

View File

@@ -170,7 +170,7 @@ class Compare extends Compare_ {
}
/** Whether as part of this comparison 'left' is compared with 'right' using the operator 'op'.
* For example, the comparison `a<b<c` compares(`a`, `b`, `<`) and compares(`b`, `c`, `<`). */
* For example, the comparison `a<b<c` compares(`a`, `b`, `<`) and compares(`b`, `c`, `<`). */
predicate compares(Expr left, Cmpop op, Expr right)
{
this.getLeft() = left and this.getComparator(0) = right and op = this.getOp(0)

View File

@@ -1,8 +1,8 @@
import python
/** A Scope. A scope is the lexical extent over which all identifiers with the same name refer to the same variable.
* Modules, Classes and Functions are all Scopes. There are no other scopes.
* The scopes for expressions that create new scopes, lambdas and comprehensions, are handled by creating an anonymous Function. */
* Modules, Classes and Functions are all Scopes. There are no other scopes.
* The scopes for expressions that create new scopes, lambdas and comprehensions, are handled by creating an anonymous Function. */
class Scope extends Scope_ {
Module getEnclosingModule() {
@@ -74,7 +74,7 @@ class Scope extends Scope_ {
}
/** Gets an exit from this Scope's control flow graph,
* that does not result from an exception */
* that does not result from an exception */
ControlFlowNode getANormalExit() {
result = this.getFallthroughNode()
or

View File

@@ -41,10 +41,9 @@ class SelfAttributeRead extends SelfAttribute {
SelfAttributeRead() {
this.getCtx() instanceof Load and
/* Be stricter for loads.
* We want to generous as to what is defined (ie stores),
* but strict as to what needs to be defined (ie loads).
*/
// Be stricter for loads.
// We want to generous as to what is defined (i.e. stores),
// but strict as to what needs to be defined (i.e. loads).
exists(ClassObject cls, FunctionObject func |
cls.declaredAttribute(_) = func |
func.getFunction() = this.getScope() and

View File

@@ -17,7 +17,7 @@ string remove_prefix_before_substring(string str, string sub) {
}
/** Removes the part of the `resources/lib` Python library path that may vary
* from machine to machine. */
* from machine to machine. */
string remove_library_prefix(Location loc) {
result = remove_prefix_before_substring(loc.toString(), "resources/lib")

View File

@@ -81,8 +81,8 @@ class PythonUse extends DependencyKind {
}
/** Whether there is a more specific dependency source than this one.
* E.g. if the expression pack.mod.func is a dependency on the function 'func' in 'pack.mod'
* don't make pack.mod depend on the module 'pack.mod'
* E.g. if the expression pack.mod.func is a dependency on the function 'func' in 'pack.mod'
* don't make pack.mod depend on the module 'pack.mod'
*/
private predicate has_more_specific_dependency_source(Expr e) {
exists(Attribute member |

View File

@@ -142,8 +142,8 @@ private cached newtype TEssaDefinition =
}
/** Definition of an extended-SSA (ESSA) variable.
* There is exactly one definition for each variable,
* and exactly one variable for each definition.
* There is exactly one definition for each variable,
* and exactly one variable for each definition.
*/
abstract class EssaDefinition extends TEssaDefinition {
@@ -471,8 +471,7 @@ class EssaNodeDefinition extends EssaDefinition, TEssaNodeDefinition {
}
/** A definition of an ESSA variable that takes another ESSA variable as an input.
*/
/** A definition of an ESSA variable that takes another ESSA variable as an input. */
class EssaNodeRefinement extends EssaDefinition, TEssaNodeRefinement {
override string toString() {
@@ -609,6 +608,7 @@ class MultiAssignmentDefinition extends EssaNodeDefinition {
)
}
/** Holds if `this` has (zero-based) index `index` in `lhs`. */
predicate indexOf(int index, SequenceNode lhs) {
SsaSource::multi_assignment_definition(this.getSourceVariable(), this.getDefiningNode(), index, lhs)
}

View File

@@ -308,9 +308,8 @@ private Builtin getBuiltinFunctionReturnType(Builtin func) {
)
}
/** Class representing methods of built-in classes (otherwise known as method-descriptors) such as `list.append`. */
/** Class representing methods of built-in classes (otherwise known as method-descriptors) such as `list.append`.
*/
class BuiltinMethodObjectInternal extends CallableObjectInternal, TBuiltinMethodObject {
override Builtin getBuiltin() {

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
@@ -107,8 +113,8 @@ class Value extends TObject {
}
/** Whether this overrides v. In this context, "overrides" means that this object
* is a named attribute of a some class C and `v` is a named attribute of another
* class S, both attributes having the same name, and S is a super class of C.
* is a named attribute of a some class C and `v` is a named attribute of another
* class S, both attributes having the same name, and S is a super class of C.
*/
predicate overrides(Value v) {
exists(ClassValue my_class, ClassValue other_class, string name |
@@ -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.
*/
@@ -447,13 +542,13 @@ class ClassValue extends Value {
}
/** Holds if this class is a new style class.
A new style class is one that implicitly or explicitly inherits from `object`. */
* A new style class is one that implicitly or explicitly inherits from `object`. */
predicate isNewStyle() {
Types::isNewStyle(this)
}
/** Holds if this class is an old style class.
An old style class is one that does not inherit from `object`. */
* An old style class is one that does not inherit from `object`. */
predicate isOldStyle() {
Types::isOldStyle(this)
}
@@ -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.
* What constitutes a legal exception class differs between major versions */
/** 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,9 +620,9 @@ abstract class FunctionValue extends CallableValue {
or
this.getScope().hasKwArg()
}
/** 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. */
/** 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 |
cls.declaredAttribute(name) = this and
@@ -646,7 +742,7 @@ class TupleValue extends SequenceValue {
}
/** A class representing strings, either present in the source as a literal, or
in a builtin as a value. */
* in a builtin as a value. */
class StringValue extends Value {
StringValue() {
@@ -662,7 +758,7 @@ class StringValue extends Value {
}
/** A class representing numbers (ints and floats), either present in the source as a literal,
* or in a builtin as a value.
* or in a builtin as a value.
*/
class NumericValue extends Value {
NumericValue() {
@@ -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"))
@@ -794,19 +918,19 @@ module ClassValue {
}
/** Get the `ClassValue` for the `str` class. This is `bytes` in Python 2,
and `str` in Python 3. */
* and `str` in Python 3. */
ClassValue str() {
if major_version() = 2 then
result = bytes()
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

@@ -1,4 +1,4 @@
/**
/*
*
* ## Points-to analysis for Python
*

View File

@@ -506,12 +506,21 @@ cached module PointsToInternal {
value = sequence.getItem(index)
}
pragma [noinline]
private predicate multi_assignment_points_to(MultiAssignmentDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
exists(int index, ControlFlowNode rhs, SequenceObjectInternal sequence |
def.indexOf(index, rhs) and
sequence_index_points_to(rhs, context, sequence, value, index) and
pragma[noinline]
private predicate multi_assignment_points_to(
MultiAssignmentDefinition def, PointsToContext context, ObjectInternal value,
ControlFlowNode origin
) {
exists(int index, ControlFlowNode lhs, ControlFlowNode rhs, ObjectInternal sequence |
def.indexOf(index, lhs) and
lhs.(DefinitionNode).getValue() = rhs and
origin = def.getDefiningNode()
|
sequence_index_points_to(rhs, context, sequence, value, index)
or
pointsTo(rhs, context, sequence, _) and
sequence.subscriptUnknown() and
value = TUnknown()
)
}
@@ -1176,7 +1185,7 @@ module InterProceduralPointsTo {
}
/** INTERNAL -- Use `FunctionObject.neverReturns()` instead.
* Whether function `func` never returns. Slightly conservative approximation, this predicate may be false
* Whether function `func` never returns. Slightly conservative approximation, this predicate may be false
* for a function that can never return. */
cached predicate neverReturns(Function f) {
/* A Python function never returns if it has no normal exits that are not dominated by a

View File

@@ -38,7 +38,7 @@ private int syntactic_call_count(Scope s) {
private int incoming_call_cost(Scope s) {
/* Syntactic call count will often be a considerable overestimate
* of the actual number of calls, so we use the square root.
* of the actual number of calls, so we use the square root.
* Cost = log(sqrt(call-count))
*/
result = ((syntactic_call_count(s)+1).log(2)*0.5).floor()

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

@@ -59,7 +59,7 @@ class FirstElementFlow extends DataFlowExtension::DataFlowNode {
}
/** A taint sink that is potentially vulnerable to malicious shell commands.
* The `vuln` in `subprocess.call(shell=vuln)` and similar calls.
* The `vuln` in `subprocess.call(shell=vuln)` and similar calls.
*/
class ShellCommand extends TaintSink {
@@ -98,7 +98,7 @@ class ShellCommand extends TaintSink {
}
/** A taint sink that is potentially vulnerable to malicious shell commands.
* The `vuln` in `subprocess.call(vuln, ...)` and similar calls.
* The `vuln` in `subprocess.call(vuln, ...)` and similar calls.
*/
class OsCommandFirstArgument extends TaintSink {

View File

@@ -3,8 +3,7 @@ private import Common
import semmle.python.security.TaintTracking
/** An extensible kind of taint representing any kind of string.
*/
/** An extensible kind of taint representing any kind of string. */
abstract class StringKind extends TaintKind {
bindingset[this]
@@ -71,11 +70,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 +88,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 +106,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 +118,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

@@ -73,13 +73,13 @@ class ClassObject extends Object {
}
/** Whether this class is a new style class.
A new style class is one that implicitly or explicitly inherits from `object`. */
* A new style class is one that implicitly or explicitly inherits from `object`. */
predicate isNewStyle() {
Types::isNewStyle(theClass())
}
/** Whether this class is an old style class.
An old style class is one that does not inherit from `object`. */
* An old style class is one that does not inherit from `object`. */
predicate isOldStyle() {
Types::isOldStyle(theClass())
}
@@ -112,7 +112,7 @@ class ClassObject extends Object {
}
/** Returns an attribute as it would be when looked up at runtime on this class.
Will include attributes of super-classes */
* Will include attributes of super-classes */
Object lookupAttribute(string name) {
exists(ObjectInternal val |
theClass().lookup(name, val, _) and
@@ -155,7 +155,7 @@ class ClassObject extends Object {
}
/** Whether it is impossible to know all the attributes of this class. Usually because it is
impossible to calculate the full class hierarchy or because some attribute is too dynamic. */
* impossible to calculate the full class hierarchy or because some attribute is too dynamic. */
predicate unknowableAttributes() {
/* True for a class with undeterminable superclasses, unanalysable metaclasses, or other confusions */
this.failedInference()
@@ -204,8 +204,8 @@ class ClassObject extends Object {
}
/** Gets an object which is the sole instance of this class, if this class is probably a singleton.
* Note the 'probable' in the name; there is no guarantee that this class is in fact a singleton.
* It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject. */
* Note the 'probable' in the name; there is no guarantee that this class is in fact a singleton.
* It is guaranteed that getProbableSingletonInstance() returns at most one Object for each ClassObject. */
Object getProbableSingletonInstance() {
exists(ControlFlowNode use, Expr origin |
use.refersTo(result, this, origin.getAFlowNode())
@@ -284,9 +284,9 @@ class ClassObject extends Object {
}
/** Holds if this class is an improper subclass of the other class.
* True if this is a sub-class of other or this is the same class as other.
* True if this is a sub-class of other or this is the same class as other.
*
* Equivalent to the Python builtin function issubclass().
* Equivalent to the Python builtin function issubclass().
*/
predicate isSubclassOf(ClassObject other) {
this = other or this.getASuperType() = other

View File

@@ -35,7 +35,7 @@ class RaisingNode extends ControlFlowNode {
}
/** Gets the type of an exception that may be raised
at this control flow node */
* at this control flow node */
ClassObject getARaisedType() {
result = this.localRaisedType()
or
@@ -118,8 +118,7 @@ class RaisingNode extends ControlFlowNode {
)
}
/** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ.
*/
/** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. */
predicate unlikelySuccessor(ControlFlowNode succ) {
succ = this.getAnExceptionalSuccessor() and
not this.viableExceptionEdge(succ, _) and
@@ -260,35 +259,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

@@ -70,27 +70,26 @@ abstract class FunctionObject extends Object {
}
/** Gets the `ControlFlowNode` that will be passed as the nth argument to `this` when called at `call`.
This predicate will correctly handle `x.y()`, treating `x` as the zeroth argument.
* This predicate will correctly handle `x.y()`, treating `x` as the zeroth argument.
*/
ControlFlowNode getArgumentForCall(CallNode call, int n) {
result = theCallable().getArgumentForCall(call, n)
}
/** Gets the `ControlFlowNode` that will be passed as the named argument to `this` when called at `call`.
This predicate will correctly handle `x.y()`, treating `x` as the self argument.
* This predicate will correctly handle `x.y()`, treating `x` as the self argument.
*/
ControlFlowNode getNamedArgumentForCall(CallNode call, string name) {
result = theCallable().getNamedArgumentForCall(call, name)
}
/** Whether this function never returns. This is an approximation.
*/
/** Whether this function never returns. This is an approximation. */
predicate neverReturns() {
theCallable().neverReturns()
}
/** Whether this is a "normal" method, that is, it is exists as a class attribute
* which is not wrapped and not the __new__ method. */
* which is not wrapped and not the __new__ method. */
predicate isNormalMethod() {
exists(ClassObject cls, string name |
cls.declaredAttribute(name) = this and

View File

@@ -79,7 +79,7 @@ abstract class ModuleObject extends Object {
}
/** Whether this module "exports" `name`. That is, whether using `import *` on this module
will result in `name` being added to the namespace. */
* will result in `name` being added to the namespace. */
predicate exports(string name) {
theModule().exports(name)
}

View File

@@ -44,8 +44,8 @@ class Object extends @py_object {
this = unknownValue() and result = theUnknownType()
}
/** Whether this a builtin object. A builtin object is one defined by the implementation,
such as the integer 4 or by a native extension, such as a NumPy array class. */
/** Whether this is a builtin object. A builtin object is one defined by the implementation,
* such as the integer 4 or by a native extension, such as a NumPy array class. */
predicate isBuiltin() {
exists(this.asBuiltin())
}
@@ -57,7 +57,7 @@ class Object extends @py_object {
/** Gets the point in the source code from which this object "originates".
*
* WARNING: The lack of context makes this less accurate than f.refersTo(this, _, result)
* WARNING: The lack of context makes this less accurate than f.refersTo(this, _, result)
* for a control flow node 'f'.
*/
AstNode getOrigin() {
@@ -114,8 +114,8 @@ class Object extends @py_object {
}
/** Whether this overrides o. In this context, "overrides" means that this object
* is a named attribute of a some class C and `o` is a named attribute of another
* class S, both attributes having the same name, and S is a super class of C.
* is a named attribute of a some class C and `o` is a named attribute of another
* class S, both attributes having the same name, and S is a super class of C.
*/
predicate overrides(Object o) {
exists(string name |
@@ -216,8 +216,8 @@ private Object findByName3(string longName) {
/** Numeric objects (ints and floats).
* Includes those occurring in the source as a literal
* or in a builtin module as a value.
* Includes those occurring in the source as a literal
* or in a builtin module as a value.
*/
class NumericObject extends Object {
@@ -228,7 +228,7 @@ class NumericObject extends Object {
}
/** Gets the Boolean value that this object
* would evaluate to in a Boolean context,
* would evaluate to in a Boolean context,
* such as `bool(x)` or `if x: ...`
*/
override boolean booleanValue() {
@@ -272,8 +272,8 @@ class NumericObject extends Object {
}
/** String objects (unicode or bytes).
* Includes those occurring in the source as a literal
* or in a builtin module as a value.
* Includes those occurring in the source as a literal
* or in a builtin module as a value.
*/
class StringObject extends Object {