diff --git a/change-notes/1.20/analysis-python.md b/change-notes/1.20/analysis-python.md index 978e6a0c30d..3eec4dc8999 100644 --- a/change-notes/1.20/analysis-python.md +++ b/change-notes/1.20/analysis-python.md @@ -20,6 +20,7 @@ Removes false positives seen when using Python 3.6, but not when using earlier v | **Query** | **Expected impact** | **Change** | |----------------------------|------------------------|------------------------------------------------------------------| +| Comparison using is when operands support \_\_eq\_\_ (`py/comparison-using-is`) | Fewer false positive results | Results where one of the objects being compared is an enum member are no longer reported. | | Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a `doctest` string are no longer reported. | | Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a type-hint comment are no longer reported. | diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index 270c951f3cb..0c6343daefd 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -107,6 +107,20 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassObject cls left.refersTo(obj) and right.refersTo(obj) and exists(ImmutableLiteral il | il.getLiteralObject() = obj) ) + and + /* OK to use 'is' when comparing with a member of an enum */ + not exists(Expr left, Expr right, AstNode origin | + comp.compares(left, op, right) and + enum_member(origin) | + left.refersTo(_, origin) or right.refersTo(_, origin) + ) } +private predicate enum_member(AstNode obj) { + exists(ClassObject cls, AssignStmt asgn | + cls.getASuperType().getName() = "Enum" | + cls.getPyClass() = asgn.getScope() and + asgn.getValue() = obj + ) +} diff --git a/python/ql/test/query-tests/Expressions/eq/expressions_test.py b/python/ql/test/query-tests/Expressions/eq/expressions_test.py index 2ffac276553..46b7a0c4005 100644 --- a/python/ql/test/query-tests/Expressions/eq/expressions_test.py +++ b/python/ql/test/query-tests/Expressions/eq/expressions_test.py @@ -24,7 +24,7 @@ -#ODASA-4519 + #OK as we are using identity tests for unique objects V2 = "v2" V3 = "v3" @@ -85,3 +85,21 @@ def both_sides_known(zero_based="auto", query_id=False): if zero_based is False: # False positive here pass +#Avoid depending on enum back port for Python 2 tests: +class Enum(object): + pass + +class MyEnum(Enum): + + memberA = None + memberB = 10 + memberC = ("Hello", "World") + +def comp_enum(x): + if x is MyEnum.memberA: + return + if x is MyEnum.memberB: + return + if x is MyEnum.memberC: + return +