Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8af6cb6

Browse files
committed
Python points-to: Use objects, not booleans when doing evaluation of tests.
1 parent 610a35c commit 8af6cb6

5 files changed

Lines changed: 140 additions & 81 deletions

File tree

python/ql/src/semmle/python/objects/Callables.qll

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,9 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
8787
rval = func.getAReturnValueFlowNode() and
8888
PointsToInternal::pointsTo(rval, callee, obj, origin)
8989
or
90-
exists(Return ret |
91-
ret.getScope() = func and
92-
PointsToInternal::reachableBlock(ret.getAFlowNode().getBasicBlock(), callee) and
93-
not exists(ret.getValue()) and
94-
obj = ObjectInternal::none_() and
95-
origin = CfgOrigin::unknown()
96-
)
90+
PointsToInternal::reachableBlock(blockReturningNone(func), callee) and
91+
obj = ObjectInternal::none_() and
92+
origin = CfgOrigin::unknown()
9793
)
9894
}
9995

@@ -121,12 +117,22 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
121117
PointsTo::pointsTo(result.getFunction(), ctx, this, _)
122118
or
123119
exists(BoundMethodObjectInternal bm |
124-
bm.getACall() = result and this = bm.getFunction()
120+
bm.getACall(ctx) = result and this = bm.getFunction()
125121
)
126122
}
127123

128124
}
129125

126+
127+
private BasicBlock blockReturningNone(Function func) {
128+
exists(Return ret |
129+
not exists(ret.getValue()) and
130+
ret.getScope() = func and
131+
result = ret.getAFlowNode().getBasicBlock()
132+
)
133+
}
134+
135+
130136
class BuiltinFunctionObjectInternal extends CallableObjectInternal, TBuiltinFunctionObject {
131137

132138
override Builtin getBuiltin() {

python/ql/src/semmle/python/objects/Classes.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class UnknownClassInternal extends ClassObjectInternal, TUnknownClass {
214214
class TypeInternal extends ClassObjectInternal, TType {
215215

216216
override string toString() {
217-
result = "type"
217+
result = "builtin-class type"
218218
}
219219

220220
override ClassDecl getClassDeclaration() {
@@ -229,7 +229,7 @@ class TypeInternal extends ClassObjectInternal, TType {
229229
none()
230230
}
231231

232-
override boolean isComparable() { result = false }
232+
override boolean isComparable() { result = true }
233233

234234
override Builtin getBuiltin() {
235235
result = Builtin::special("type")

python/ql/src/semmle/python/pointsto/Base.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,10 @@ class SingleSuccessorGuard extends PyNodeRefinement {
395395
not exists(this.getSense()) and
396396
result = PyNodeRefinement.super.getRepresentation() + " [??]"
397397
}
398+
399+
ControlFlowNode getTest() {
400+
result = this.getDefiningNode()
401+
}
398402
}
399403

400404
/** Implicit definition of the names of sub-modules in a package.

python/ql/src/semmle/python/pointsto/Filters.qll

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,6 @@ predicate isinstance(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
2525
cls = fc.getArg(1) and fc.getArg(0) = use
2626
}
2727

28-
/** Holds if `c` is a test comparing `x` and `y`. `is` is true if the operator is `is` or `==`, it is false if the operator is `is not` or `!=`. */
29-
predicate equality_test(CompareNode c, ControlFlowNode x, boolean is, ControlFlowNode y) {
30-
exists(Cmpop op |
31-
c.operands(x, op, y) or
32-
c.operands(y, op, x)
33-
|
34-
(is = true and op instanceof Is or
35-
is = false and op instanceof IsNot or
36-
is = true and op instanceof Eq or
37-
is = false and op instanceof NotEq
38-
)
39-
)
40-
}
41-
4228
/** Holds if `c` is a call to `issubclass(use, cls)`. */
4329
predicate issubclass(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
4430
fc.getFunction().(NameNode).getId() = "issubclass" and

python/ql/src/semmle/python/pointsto/PointsTo.qll

Lines changed: 120 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,13 @@ cached module PointsToInternal {
319319
pragma [noinline]
320320
private predicate attribute_load_points_to(AttrNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
321321
f.isLoad() and
322-
exists(ObjectInternal object, string name, CfgOrigin orig |
323-
pointsTo(f.getObject(name), context, object, _) |
324-
object.attribute(name, value, orig) and
325-
origin = orig.fix(f)
322+
exists(ObjectInternal object, string name |
323+
pointsTo(f.getObject(name), context, object, _)
324+
|
325+
exists(CfgOrigin orig |
326+
object.attribute(name, value, orig) and
327+
origin = orig.fix(f)
328+
)
326329
or
327330
object.attributesUnknown() and
328331
origin = f and value = ObjectInternal::unknown()
@@ -401,7 +404,8 @@ cached module PointsToInternal {
401404
if def.getName() = "__class__" then
402405
exists(ObjectInternal cls |
403406
pointsTo(def.getValue(), context, cls, _) and
404-
value = TUnknownInstance(cls)
407+
value = TUnknownInstance(cls) and
408+
origin = CfgOrigin::fromCfgNode(def.getDefiningNode())
405409
)
406410
else
407411
variablePointsTo(def.getInput(), context, value, origin)
@@ -419,9 +423,10 @@ cached module PointsToInternal {
419423

420424
/** Holds if ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */
421425
private predicate ssa_filter_definition_points_to(PyEdgeRefinement def, PointsToContext context, ObjectInternal value, CfgOrigin origin) {
422-
exists(ControlFlowNode test, ControlFlowNode use |
423-
refinement_test(test, use, Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode()), def)
424-
)
426+
def.getSense() = Conditionals::testEvaluates(def.getTest(), def.getInput().getASourceUse(), context, value, origin)
427+
//exists(ControlFlowNode test, ControlFlowNode use |
428+
// refinement_test(test, use, Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode()), def)
429+
//)
425430
}
426431

427432
/** Holds if ESSA definition, `uniphi`, refers to `(value, origin)`. */
@@ -431,9 +436,9 @@ cached module PointsToInternal {
431436
/* Because calls such as `len` may create a new variable, we need to go via the source variable
432437
* That is perfectly safe as we are only dealing with calls that do not mutate their arguments.
433438
*/
434-
use = uniphi.getInput().getSourceVariable().(Variable).getAUse() and
435-
test = uniphi.getDefiningNode() and
436-
uniphi.getSense() = Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode())
439+
use = uniphi.getInput().getASourceUse() and
440+
test = uniphi.getTest() and
441+
uniphi.getSense() = Conditionals::testEvaluates(test, use, context, value, origin.toCfgNode())
437442
)
438443
}
439444

@@ -535,7 +540,10 @@ cached module PointsToInternal {
535540

536541
pragma [noinline]
537542
private predicate test_expr_points_to(ControlFlowNode cmp, PointsToContext context, ObjectInternal value) {
538-
value = ObjectInternal::bool(Conditionals::testEvaluatesTo(cmp, _, context, _, _))
543+
exists(ControlFlowNode use |
544+
value = Conditionals::evaluates(cmp, use, context, _, _) and
545+
use != cmp
546+
)
539547
// or
540548
// value = version_tuple_compare(cmp, context)
541549
}
@@ -969,80 +977,110 @@ private predicate potential_builtin_points_to(NameNode f, ObjectInternal value,
969977

970978
module Conditionals {
971979

972-
/** Holds if `expr` is the operand of a unary `not` expression. */
973-
private ControlFlowNode not_operand(ControlFlowNode expr) {
974-
expr.(UnaryExprNode).getNode().getOp() instanceof Not and
975-
result = expr.(UnaryExprNode).getOperand()
980+
boolean testEvaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
981+
//pinode_test(expr, use) and
982+
result = evaluates(expr, use, context, value, origin).booleanValue()
976983
}
977984

978-
boolean branchEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
979-
contains_interesting_expression_within_test(expr, use) and
985+
ObjectInternal evaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
980986
PointsToInternal::pointsTo(use, context, val, origin) and
981-
(
982-
expr = use and
983-
val.booleanValue() = result
987+
pinode_test(_, use) and expr = use and result = val
988+
or
989+
exists(ControlFlowNode part, ObjectInternal partval |
990+
pinode_test_part(expr, part) and
991+
partval = evaluates(part, use, context, val, origin)
992+
|
993+
result = equalityEvaluates(expr, part, context, partval)
984994
or
985-
exists(string name, ObjectInternal attr |
986-
expr.(AttrNode).getObject(name) = use |
987-
val.attribute(name, attr, _) and
988-
result = attr.booleanValue()
995+
result = ObjectInternal::bool(inequalityEvaluatesBoolean(expr, part, context, partval))
996+
or
997+
result = ObjectInternal::bool(isinstance_test_evaluates_boolean(expr, part, context, partval))
998+
or
999+
result = ObjectInternal::bool(issubclass_test_evaluates_boolean(expr, part, context, partval))
1000+
or
1001+
exists(string attr |
1002+
expr.(AttrNode).getObject(attr) = use |
1003+
val.attribute(attr, result, _)
9891004
or
990-
val.attributesUnknown() and
991-
result = maybe()
1005+
val.attributesUnknown() and result = ObjectInternal::unknown()
9921006
)
1007+
or
1008+
expr instanceof BinaryExprNode and result = ObjectInternal::unknown()
1009+
or
1010+
part = not_operand(expr) and result = ObjectInternal::bool(partval.booleanValue().booleanNot())
1011+
or
1012+
result = evaluatesLen(expr, part, context, partval)
1013+
//or
1014+
//result = callable_test_evaluates_boolean(expr, use, context, val, origin)
1015+
//or
1016+
//result = hasattr_test_evaluates_boolean(expr, use, context, val, origin)
9931017
)
994-
or
995-
result = testEvaluatesTo(expr, use, context, val, origin)
996-
or
997-
result = branchEvaluatesTo(not_operand(expr), use, context, val, origin).booleanNot()
1018+
9981019
}
9991020

1000-
boolean testEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
1001-
result = equalityEvaluatesTo(expr, use, context, val, origin)
1002-
or
1003-
result = inequalityEvaluatesTo(expr, use, context, val, origin)
1004-
or
1005-
result = isinstance_test_evaluates_boolean(expr, use, context, val, origin)
1006-
or
1007-
result = issubclass_test_evaluates_boolean(expr, use, context, val, origin)
1008-
//or
1009-
//result = callable_test_evaluates_boolean(expr, use, context, val, origin)
1010-
//or
1011-
//result = hasattr_test_evaluates_boolean(expr, use, context, val, origin)
1021+
/** Holds if `expr` is the operand of a unary `not` expression. */
1022+
private ControlFlowNode not_operand(ControlFlowNode expr) {
1023+
expr.(UnaryExprNode).getNode().getOp() instanceof Not and
1024+
result = expr.(UnaryExprNode).getOperand()
10121025
}
10131026

10141027
pragma [noinline]
1015-
private boolean equalityEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
1028+
private ObjectInternal equalityEvaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
10161029
exists(ControlFlowNode r, boolean sense |
1017-
equality_test(expr, use, sense, r) and
1030+
pinode_test_part(expr, use) and equality_test(expr, use, sense, r) and
10181031
exists(ObjectInternal other |
1019-
PointsToInternal::pointsTo(use, context, val, origin) and
1032+
PointsToInternal::pointsTo(use, context, val, _) and
10201033
PointsToInternal::pointsTo(r, context, other, _) |
10211034
val.isComparable() = true and other.isComparable() = true and
10221035
(
1023-
other = val and result = sense
1036+
other = val and result = ObjectInternal::bool(sense)
10241037
or
1025-
other != val and result = sense.booleanNot()
1038+
other != val and result = ObjectInternal::bool(sense.booleanNot())
10261039
)
10271040
or
1028-
val.isComparable() = false and result = maybe()
1041+
val.isComparable() = false and result = ObjectInternal::bool(_)
10291042
or
1030-
other.isComparable() = false and result = maybe()
1043+
other.isComparable() = false and result = ObjectInternal::bool(_)
10311044
)
10321045
)
10331046
}
10341047

1035-
private predicate isinstance_call(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ObjectInternal cls, ControlFlowNode origin) {
1048+
/** Holds if `c` is a test comparing `x` and `y`. `is` is true if the operator is `is` or `==`, it is false if the operator is `is not` or `!=`. */
1049+
private predicate equality_test(CompareNode c, ControlFlowNode x, boolean is, ControlFlowNode y) {
1050+
exists(Cmpop op |
1051+
c.operands(x, op, y) or
1052+
c.operands(y, op, x)
1053+
|
1054+
(is = true and op instanceof Is or
1055+
is = false and op instanceof IsNot or
1056+
is = true and op instanceof Eq or
1057+
is = false and op instanceof NotEq
1058+
)
1059+
)
1060+
}
1061+
1062+
pragma [noinline]
1063+
private ObjectInternal evaluatesLen(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
1064+
pinode_test_part(call, use) and
1065+
PointsToInternal::pointsTo(use, context, val, _) and
1066+
PointsToInternal::pointsTo(call.getFunction(), context, ObjectInternal::builtin("len"), _) and
1067+
result = TInt(val.(SequenceObjectInternal).length())
1068+
}
1069+
1070+
//private
1071+
predicate isinstance_call(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ObjectInternal cls, ControlFlowNode origin) {
1072+
pinode_test_part(call, use) and
10361073
PointsToInternal::pointsTo(call.getFunction(), context, ObjectInternal::builtin("isinstance"), _) and
10371074
use = call.getArg(0) and
10381075
PointsToInternal::pointsTo(use, context, val, origin) and
10391076
PointsToInternal::pointsTo(call.getArg(1), context, cls, _)
10401077
}
10411078

10421079
pragma [nomagic]
1043-
private boolean isinstance_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
1080+
//private
1081+
boolean isinstance_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
10441082
exists(ObjectInternal cls |
1045-
isinstance_call(call, use, context, val, cls, origin) |
1083+
isinstance_call(call, use, context, val, cls, _) |
10461084
result = Types::improperSubclass(val.getClass(), cls)
10471085
or
10481086
val = ObjectInternal::unknown() and result = maybe()
@@ -1061,9 +1099,10 @@ module Conditionals {
10611099
}
10621100

10631101
pragma [nomagic]
1064-
private boolean issubclass_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
1102+
private boolean issubclass_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
1103+
pinode_test_part(call, use) and
10651104
exists(ObjectInternal cls |
1066-
issubclass_call(call, use, context, val, cls, origin) |
1105+
issubclass_call(call, use, context, val, cls, _) |
10671106
result = Types::improperSubclass(val, cls)
10681107
or
10691108
val = ObjectInternal::unknownClass() and result = maybe()
@@ -1091,15 +1130,16 @@ module Conditionals {
10911130
}
10921131

10931132
pragma [noinline]
1094-
private boolean inequalityEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
1133+
private boolean inequalityEvaluatesBoolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
1134+
pinode_test_part(expr, use) and
10951135
exists(ControlFlowNode r, boolean sense |
10961136
exists(boolean strict, ObjectInternal other |
10971137
(
10981138
inequality(expr, use, r, strict) and sense = true
10991139
or
11001140
inequality(expr, r, use, strict) and sense = false
11011141
) and
1102-
PointsToInternal::pointsTo(use, context, val, origin) and
1142+
PointsToInternal::pointsTo(use, context, val, _) and
11031143
PointsToInternal::pointsTo(r, context, other, _)
11041144
|
11051145
val.intValue() < other.intValue() and result = sense
@@ -1135,6 +1175,29 @@ module Conditionals {
11351175
)
11361176
}
11371177

1178+
private predicate pinode_test(ControlFlowNode test, NameNode use) {
1179+
exists(PyEdgeRefinement pi |
1180+
pi.getInput().getASourceUse() = use and
1181+
pi.getTest() = test and
1182+
test.getAChild*() = use
1183+
)
1184+
or
1185+
exists(SingleSuccessorGuard unipi |
1186+
unipi.getInput().getASourceUse() = use and
1187+
unipi.getTest() = test and
1188+
test.getAChild*() = use
1189+
)
1190+
}
1191+
1192+
private predicate pinode_test_part(ControlFlowNode outer, ControlFlowNode inner) {
1193+
exists(ControlFlowNode test, NameNode use |
1194+
pinode_test(test, use) and
1195+
test.getAChild*() = outer and
1196+
outer.getAChild+() = inner and
1197+
inner.getAChild*() = use
1198+
)
1199+
}
1200+
11381201
}
11391202

11401203
cached module Types {

0 commit comments

Comments
 (0)