1
+ /** INTERNAL - Helper predicates for queries concerning comparison of objects using `is`. */
2
+
1
3
import python
2
4
5
+ /** Holds if `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
3
6
predicate comparison_using_is ( Compare comp , ControlFlowNode left , Cmpop op , ControlFlowNode right ) {
4
7
exists ( CompareNode fcomp | fcomp = comp .getAFlowNode ( ) |
5
8
fcomp .operands ( left , op , right ) and
6
9
( op instanceof Is or op instanceof IsNot )
7
10
)
8
11
}
9
12
13
+ /** Holds if the class `c` overrides the default notion of equality or comparison. */
10
14
predicate overrides_eq_or_cmp ( ClassValue c ) {
11
15
major_version ( ) = 2 and c .hasAttribute ( "__eq__" )
12
16
or
@@ -19,12 +23,14 @@ predicate overrides_eq_or_cmp(ClassValue c) {
19
23
major_version ( ) = 2 and c .hasAttribute ( "__cmp__" )
20
24
}
21
25
26
+ /** Holds if the class `cls` is likely to only have a single instance throughout the program. */
22
27
predicate probablySingleton ( ClassValue cls ) {
23
28
strictcount ( Value inst | inst .getClass ( ) = cls ) = 1
24
29
or
25
30
cls = Value:: named ( "None" ) .getClass ( )
26
31
}
27
32
33
+ /** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */
28
34
predicate invalid_to_use_is_portably ( ClassValue c ) {
29
35
overrides_eq_or_cmp ( c ) and
30
36
// Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__
@@ -35,6 +41,7 @@ predicate invalid_to_use_is_portably(ClassValue c) {
35
41
not probablySingleton ( c )
36
42
}
37
43
44
+ /** Holds if `f` points to either `True`, `False`, or `None`. */
38
45
predicate simple_constant ( ControlFlowNode f ) {
39
46
exists ( Value val | f .pointsTo ( val ) |
40
47
val = Value:: named ( "True" ) or val = Value:: named ( "False" ) or val = Value:: named ( "None" )
@@ -66,10 +73,12 @@ private predicate universally_interned_value(Expr e) {
66
73
e .( StrConst ) .getText ( ) = ""
67
74
}
68
75
76
+ /** Holds if the expression `e` points to an interned constant in CPython. */
69
77
predicate cpython_interned_constant ( Expr e ) {
70
78
exists ( Expr const | e .pointsTo ( _, const ) | cpython_interned_value ( const ) )
71
79
}
72
80
81
+ /** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */
73
82
predicate universally_interned_constant ( Expr e ) {
74
83
exists ( Expr const | e .pointsTo ( _, const ) | universally_interned_value ( const ) )
75
84
}
@@ -92,6 +101,10 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
92
101
)
93
102
}
94
103
104
+ /**
105
+ * Holds if the comparison `comp` (with operator `op`) is an invalid comparison using `is` or `is not`
106
+ * when applied to instances of the class `cls`.
107
+ */
95
108
predicate invalid_portable_is_comparison ( Compare comp , Cmpop op , ClassValue cls ) {
96
109
// OK to use 'is' when defining '__eq__'
97
110
not exists ( Function eq | eq .getName ( ) = "__eq__" or eq .getName ( ) = "__ne__" |
0 commit comments