1+ /** INTERNAL - Helper predicates for queries concerning comparison of objects using `is`. */
2+
13import python
24
5+ /** Holds if `comp` uses `is` or `is not` (represented as `op`) to compare its `left` and `right` arguments. */
36predicate comparison_using_is ( Compare comp , ControlFlowNode left , Cmpop op , ControlFlowNode right ) {
47 exists ( CompareNode fcomp | fcomp = comp .getAFlowNode ( ) |
58 fcomp .operands ( left , op , right ) and
69 ( op instanceof Is or op instanceof IsNot )
710 )
811}
912
13+ /** Holds if the class `c` overrides the default notion of equality or comparison. */
1014predicate overrides_eq_or_cmp ( ClassValue c ) {
1115 major_version ( ) = 2 and c .hasAttribute ( "__eq__" )
1216 or
@@ -19,12 +23,14 @@ predicate overrides_eq_or_cmp(ClassValue c) {
1923 major_version ( ) = 2 and c .hasAttribute ( "__cmp__" )
2024}
2125
26+ /** Holds if the class `cls` is likely to only have a single instance throughout the program. */
2227predicate probablySingleton ( ClassValue cls ) {
2328 strictcount ( Value inst | inst .getClass ( ) = cls ) = 1
2429 or
2530 cls = Value:: named ( "None" ) .getClass ( )
2631}
2732
33+ /** Holds if using `is` to compare instances of the class `c` is likely to cause unexpected behavior. */
2834predicate invalid_to_use_is_portably ( ClassValue c ) {
2935 overrides_eq_or_cmp ( c ) and
3036 // 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) {
3541 not probablySingleton ( c )
3642}
3743
44+ /** Holds if `f` points to either `True`, `False`, or `None`. */
3845predicate simple_constant ( ControlFlowNode f ) {
3946 exists ( Value val | f .pointsTo ( val ) |
4047 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) {
6673 e .( StrConst ) .getText ( ) = ""
6774}
6875
76+ /** Holds if the expression `e` points to an interned constant in CPython. */
6977predicate cpython_interned_constant ( Expr e ) {
7078 exists ( Expr const | e .pointsTo ( _, const ) | cpython_interned_value ( const ) )
7179}
7280
81+ /** Holds if the expression `e` points to a value that can be reasonably expected to be interned across all implementations of Python. */
7382predicate universally_interned_constant ( Expr e ) {
7483 exists ( Expr const | e .pointsTo ( _, const ) | universally_interned_value ( const ) )
7584}
@@ -92,6 +101,10 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) {
92101 )
93102}
94103
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+ */
95108predicate invalid_portable_is_comparison ( Compare comp , Cmpop op , ClassValue cls ) {
96109 // OK to use 'is' when defining '__eq__'
97110 not exists ( Function eq | eq .getName ( ) = "__eq__" or eq .getName ( ) = "__ne__" |
0 commit comments