Skip to content

Commit f51a2d9

Browse files
committed
Python points-to: Fix up test-evaluate for ABCs and tests involving type().
1 parent 8a2fb54 commit f51a2d9

File tree

12 files changed

+204
-53
lines changed

12 files changed

+204
-53
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class Value extends TObject {
5353
result = this.(ObjectInternal).getSource()
5454
}
5555

56+
predicate isBuiltin() {
57+
this.(ObjectInternal).isBuiltin()
58+
}
5659
}
5760

5861
class ModuleValue extends Value {

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,5 +365,15 @@ library class ClassDecl extends @py_object {
365365
this instanceof Builtin
366366
}
367367

368+
predicate isAbstractBaseClass(string name) {
369+
exists(Module m |
370+
m.getName() = "_abcoll"
371+
or
372+
m.getName() = "_collections_abc"
373+
|
374+
this.getClass().getScope() = m and
375+
this.getName() = name
376+
)
377+
}
368378
}
369379

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

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -817,24 +817,7 @@ module InterProceduralPointsTo {
817817
PointsToInternal::pointsTo(f.getArg(0), context, value, origin)
818818
)
819819
or
820-
value = call_to_type(f, context) and
821-
(
822-
value.isBuiltin() and origin = f
823-
or
824-
origin = value.getOrigin()
825-
or
826-
value = ObjectInternal::unknownClass() and origin = f
827-
)
828-
}
829-
830-
pragma [noinline]
831-
private ObjectInternal call_to_type(CallNode f, PointsToContext context) {
832-
count(f.getArg(_)) = 1 and
833-
call(f, context, ObjectInternal::builtin("type")) and
834-
exists(ObjectInternal arg |
835-
PointsToInternal::pointsTo(f.getArg(0), context, arg, _) and
836-
result = arg.getClass()
837-
)
820+
Expressions::typeCallPointsTo(f, context, value, origin, _, _)
838821
}
839822

840823
/** Points-to for parameter. `def foo(param): ...`. */
@@ -1174,6 +1157,16 @@ module Expressions {
11741157
origin = call
11751158
}
11761159

1160+
pragma [noinline]
1161+
predicate typeCallPointsTo(CallNode call, PointsToContext context, ObjectInternal value, ControlFlowNode origin, ControlFlowNode arg, ObjectInternal argvalue) {
1162+
not exists(call.getArg(1)) and
1163+
arg = call.getArg(0) and
1164+
InterProceduralPointsTo::call(call, context, ObjectInternal::builtin("type")) and
1165+
PointsToInternal::pointsTo(arg, context, argvalue, _) and
1166+
value = argvalue.getClass() and
1167+
origin = CfgOrigin::fromObject(value).asCfgNodeOrHere(call)
1168+
}
1169+
11771170
pragma [noinline]
11781171
private predicate lenCallPointsTo(CallNode call, PointsToContext context, ObjectInternal value, ControlFlowNode origin, ControlFlowNode arg, ObjectInternal argvalue) {
11791172
len_call(call, arg, context, argvalue) and
@@ -1317,6 +1310,8 @@ module Expressions {
13171310
or
13181311
lenCallPointsTo(expr, context, value, origin, subexpr, subvalue)
13191312
or
1313+
typeCallPointsTo(expr, context, value, origin, subexpr, subvalue)
1314+
or
13201315
getattrPointsTo(expr, context, value, origin, subexpr, subvalue)
13211316
or
13221317
value = ObjectInternal::bool(evaluatesTo(expr, context, subexpr, subvalue)) and origin = expr
@@ -1754,7 +1749,9 @@ cached module Types {
17541749
cached boolean improperSubclass(ObjectInternal sub, ObjectInternal sup) {
17551750
sub = sup and result = true
17561751
or
1757-
result = mroContains(Types::getMro(sub), sup, 0)
1752+
result = true and mroContains(Types::getMro(sub), sup)
1753+
or
1754+
result = false and mroDoesnotContain(Types::getMro(sub), sup, 0)
17581755
or
17591756
result = tupleSubclass(sub, sup, 0)
17601757
}
@@ -1768,32 +1765,47 @@ cached module Types {
17681765
)
17691766
}
17701767

1771-
private boolean mroContains(ClassList mro, ClassObjectInternal sup, int n) {
1768+
private predicate mroContains(ClassList mro, ClassObjectInternal sup) {
1769+
mro.contains(sup)
1770+
or
1771+
exists(ClassDecl item, ClassDecl sdecl |
1772+
item = mro.getAnItem().getClassDeclaration() and
1773+
sdecl = sup.getClassDeclaration() and
1774+
is_abstract_subclass(item, sdecl)
1775+
)
1776+
}
1777+
1778+
private predicate mroDoesnotContain(ClassList mro, ClassObjectInternal sup, int n) {
17721779
exists(ClassObjectInternal cls |
17731780
Expressions::requireSubClass(cls, sup) and
17741781
mro = getMro(cls)
17751782
)
17761783
and
17771784
(
1778-
n = mro.length() and result = false
1779-
or
1780-
mro.getItem(n) = sup and result = true
1781-
or
1782-
mro.getItem(n) = abc_to_concrete(sup) and result = true
1783-
or
1784-
mro.getItem(n) != sup and sup != AbstractBaseClass::named("Iterable") and
1785-
mro.getItem(n) != abc_to_concrete(sup) and result = mroContains(mro, sup, n+1)
1786-
or
1787-
sup = AbstractBaseClass::named("Iterable") and result = mro.getItem(n).isIterableSubclass()
1785+
n = mro.length()
1786+
or
1787+
mroDoesnotContain(mro, sup, n+1) and
1788+
mro.getItem(n) != sup and
1789+
exists(ClassDecl item, ClassDecl sdecl |
1790+
item = mro.getItem(n).getClassDeclaration() and
1791+
sdecl = sup.getClassDeclaration() and
1792+
not is_abstract_subclass(item, sdecl)
1793+
)
17881794
)
17891795
}
17901796

1791-
private ClassObjectInternal abc_to_concrete(ClassObjectInternal c) {
1792-
c = AbstractBaseClass::named("Sequence") and result = ObjectInternal::builtin("list")
1797+
private predicate is_abstract_subclass(ClassDecl cls, ClassDecl sup) {
1798+
cls = Builtin::builtin("list") and sup.isAbstractBaseClass("Sequence")
1799+
or
1800+
cls = Builtin::builtin("set") and sup.isAbstractBaseClass("Set")
17931801
or
1794-
c = AbstractBaseClass::named("Set") and result = ObjectInternal::builtin("set")
1802+
cls = Builtin::builtin("dict") and sup.isAbstractBaseClass("Mapping")
17951803
or
1796-
c = AbstractBaseClass::named("Mapping") and result = ObjectInternal::builtin("dict")
1804+
cls = Builtin::builtin("list") and sup.isAbstractBaseClass("Iterable")
1805+
or
1806+
cls = Builtin::builtin("set") and sup.isAbstractBaseClass("Iterable")
1807+
or
1808+
cls = Builtin::builtin("dict") and sup.isAbstractBaseClass("Iterable")
17971809
}
17981810

17991811
cached boolean hasAttr(ObjectInternal cls, string name) {
@@ -1822,19 +1834,6 @@ cached module Types {
18221834
}
18231835

18241836

1825-
module AbstractBaseClass {
1826-
1827-
ClassObjectInternal named(string name) {
1828-
exists(ModuleObjectInternal m |
1829-
m.getName() = "_abcoll"
1830-
or
1831-
m.getName() = "_collections_abc"
1832-
|
1833-
m.attribute(name, result, _)
1834-
)
1835-
}
1836-
}
1837-
18381837
module AttributePointsTo {
18391838

18401839
predicate pointsTo(AttrNode f, Context context, ObjectInternal value, ControlFlowNode origin) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
| builtin-class dict | builtin-class dict |
2+
| builtin-class dict | builtin-class list |
3+
| builtin-class int | (builtin-class float, builtin-class dict) |
4+
| builtin-class int | (builtin-class list, builtin-class int) |
5+
| builtin-class int | (builtin-class list, builtin-class int) |
6+
| builtin-class int | builtin-class dict |
7+
| builtin-class int | builtin-class float |
8+
| builtin-class int | builtin-class int |
9+
| builtin-class int | builtin-class list |
10+
| builtin-class int | builtin-class object |
11+
| builtin-class int | builtin-class tuple |
12+
| builtin-class tuple | builtin-class int |
13+
| builtin-class tuple | builtin-class tuple |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import python
3+
import semmle.python.pointsto.PointsTo
4+
import semmle.python.pointsto.PointsTo
5+
6+
from Value sup, Value cls
7+
where Expressions::requireSubClass(cls, sup)
8+
select cls, sup
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
| 3 | isinstance() | true | x | int 7 |
2+
| 4 | issubclass() | true | x | int 7 |
3+
| 6 | issubclass() | true | d | builtin-class dict |
4+
| 7 | UnaryExpr | true | d | builtin-class dict |
5+
| 10 | isinstance() | false | x | int 0 |
6+
| 10 | isinstance() | true | x | () |
7+
| 13 | isinstance() | false | x | () |
8+
| 13 | isinstance() | true | x | int 0 |
9+
| 14 | isinstance() | true | x | int 0 |
10+
| 15 | issubclass() | true | x | int 0 |
11+
| 16 | issubclass() | false | x | int 0 |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
import python
3+
import semmle.python.pointsto.PointsTo
4+
import semmle.python.objects.ObjectInternal
5+
import semmle.python.pointsto.PointsToContext
6+
7+
8+
from ControlFlowNode test, ControlFlowNode use, ObjectInternal val, boolean eval, PointsToContext ctx
9+
where
10+
PointsTo::pointsTo(use, ctx, val, _) and
11+
eval = Conditionals::testEvaluates(test, use, ctx, val, _)
12+
select test.getLocation().getStartLine(), test.getNode().toString(), eval.toString(), use.getNode().toString(), val.toString()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
| 2 | ControlFlowNode for IntegerLiteral | int 7 | 2 |
2+
| 3 | ControlFlowNode for int | builtin-class int | 3 |
3+
| 3 | ControlFlowNode for isinstance | Builtin-function isinstance | 3 |
4+
| 3 | ControlFlowNode for isinstance() | bool True | 3 |
5+
| 3 | ControlFlowNode for x | int 7 | 2 |
6+
| 4 | ControlFlowNode for issubclass | Builtin-function issubclass | 4 |
7+
| 4 | ControlFlowNode for issubclass() | bool True | 4 |
8+
| 4 | ControlFlowNode for object | builtin-class object | 4 |
9+
| 4 | ControlFlowNode for type | builtin-class type | 4 |
10+
| 4 | ControlFlowNode for type() | builtin-class int | 4 |
11+
| 4 | ControlFlowNode for x | int 7 | 2 |
12+
| 5 | ControlFlowNode for dict | builtin-class dict | 5 |
13+
| 6 | ControlFlowNode for d | builtin-class dict | 5 |
14+
| 6 | ControlFlowNode for dict | builtin-class dict | 6 |
15+
| 6 | ControlFlowNode for issubclass | Builtin-function issubclass | 6 |
16+
| 6 | ControlFlowNode for issubclass() | bool True | 6 |
17+
| 7 | ControlFlowNode for UnaryExpr | bool True | 7 |
18+
| 7 | ControlFlowNode for d | builtin-class dict | 5 |
19+
| 7 | ControlFlowNode for issubclass | Builtin-function issubclass | 7 |
20+
| 7 | ControlFlowNode for issubclass() | bool False | 7 |
21+
| 7 | ControlFlowNode for list | builtin-class list | 7 |
22+
| 9 | ControlFlowNode for IfExp | () | 9 |
23+
| 9 | ControlFlowNode for IfExp | int 0 | 9 |
24+
| 9 | ControlFlowNode for IntegerLiteral | int 0 | 9 |
25+
| 9 | ControlFlowNode for Tuple | () | 9 |
26+
| 9 | ControlFlowNode for condition | Unknown value | 9 |
27+
| 10 | ControlFlowNode for isinstance | Builtin-function isinstance | 10 |
28+
| 10 | ControlFlowNode for isinstance() | bool False | 10 |
29+
| 10 | ControlFlowNode for isinstance() | bool True | 10 |
30+
| 10 | ControlFlowNode for tuple | builtin-class tuple | 10 |
31+
| 10 | ControlFlowNode for x | () | 9 |
32+
| 10 | ControlFlowNode for x | int 0 | 9 |
33+
| 12 | ControlFlowNode for IntegerLiteral | int 3 | 12 |
34+
| 12 | ControlFlowNode for isinstance | Builtin-function isinstance | 12 |
35+
| 12 | ControlFlowNode for isinstance() | bool False | 12 |
36+
| 12 | ControlFlowNode for isinstance() | bool True | 12 |
37+
| 12 | ControlFlowNode for unknown | Unknown value | 12 |
38+
| 12 | ControlFlowNode for unknown() | Unknown value | 12 |
39+
| 13 | ControlFlowNode for int | builtin-class int | 13 |
40+
| 13 | ControlFlowNode for isinstance | Builtin-function isinstance | 13 |
41+
| 13 | ControlFlowNode for isinstance() | bool False | 13 |
42+
| 13 | ControlFlowNode for isinstance() | bool True | 13 |
43+
| 13 | ControlFlowNode for x | () | 9 |
44+
| 13 | ControlFlowNode for x | int 0 | 9 |
45+
| 14 | ControlFlowNode for Tuple | (builtin-class list, builtin-class int) | 14 |
46+
| 14 | ControlFlowNode for int | builtin-class int | 14 |
47+
| 14 | ControlFlowNode for isinstance | Builtin-function isinstance | 14 |
48+
| 14 | ControlFlowNode for isinstance() | bool True | 14 |
49+
| 14 | ControlFlowNode for list | builtin-class list | 14 |
50+
| 14 | ControlFlowNode for x | int 0 | 9 |
51+
| 15 | ControlFlowNode for Tuple | (builtin-class list, builtin-class int) | 15 |
52+
| 15 | ControlFlowNode for int | builtin-class int | 15 |
53+
| 15 | ControlFlowNode for issubclass | Builtin-function issubclass | 15 |
54+
| 15 | ControlFlowNode for issubclass() | bool True | 15 |
55+
| 15 | ControlFlowNode for list | builtin-class list | 15 |
56+
| 15 | ControlFlowNode for type | builtin-class type | 15 |
57+
| 15 | ControlFlowNode for type() | builtin-class int | 15 |
58+
| 15 | ControlFlowNode for x | int 0 | 9 |
59+
| 16 | ControlFlowNode for Tuple | (builtin-class float, builtin-class dict) | 16 |
60+
| 16 | ControlFlowNode for dict | builtin-class dict | 16 |
61+
| 16 | ControlFlowNode for float | builtin-class float | 16 |
62+
| 16 | ControlFlowNode for issubclass | Builtin-function issubclass | 16 |
63+
| 16 | ControlFlowNode for issubclass() | bool False | 16 |
64+
| 16 | ControlFlowNode for type | builtin-class type | 16 |
65+
| 16 | ControlFlowNode for type() | builtin-class int | 16 |
66+
| 16 | ControlFlowNode for x | int 0 | 9 |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import python
3+
import semmle.python.pointsto.PointsTo
4+
import semmle.python.objects.ObjectInternal
5+
6+
from ControlFlowNode f, ObjectInternal v, ControlFlowNode x
7+
8+
where PointsTo::pointsTo(f, _, v, x)
9+
10+
select f.getLocation().getStartLine(), f.toString(), v, x.getLocation().getStartLine()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
x = 7
3+
assert isinstance(x, int)
4+
assert issubclass(type(x), object)
5+
d = dict
6+
assert issubclass(d, dict)
7+
assert not issubclass(d, list)
8+
9+
x = 0 if condition else ()
10+
if isinstance(x, tuple):
11+
pass
12+
isinstance(3, unknown())
13+
assert isinstance(x, int)
14+
assert isinstance(x, (list, int))
15+
assert issubclass(type(x), (list, int))
16+
if issubclass(type(x), (float, dict)):
17+
pass
18+

0 commit comments

Comments
 (0)