1212 */
1313
1414import python
15+ import semmle.python.ApiGraphs
16+ import semmle.python.dataflow.new.internal.DataFlowDispatch
1517
16- private predicate attribute_method ( string name ) {
18+ private predicate attributeMethod ( string name ) {
1719 name = "__getattribute__" or name = "__getattr__" or name = "__setattr__"
1820}
1921
20- private predicate indexing_method ( string name ) {
22+ private predicate indexingMethod ( string name ) {
2123 name = "__getitem__" or name = "__setitem__" or name = "__delitem__"
2224}
2325
24- private predicate arithmetic_method ( string name ) {
26+ private predicate arithmeticMethod ( string name ) {
2527 name in [
2628 "__add__" , "__sub__" , "__or__" , "__xor__" , "__rshift__" , "__pow__" , "__mul__" , "__neg__" ,
2729 "__radd__" , "__rsub__" , "__rdiv__" , "__rfloordiv__" , "__div__" , "__rdiv__" , "__rlshift__" ,
@@ -32,21 +34,17 @@ private predicate arithmetic_method(string name) {
3234 ]
3335}
3436
35- private predicate ordering_method ( string name ) {
37+ private predicate orderingMethod ( string name ) {
3638 name = "__lt__"
3739 or
3840 name = "__le__"
3941 or
4042 name = "__gt__"
4143 or
4244 name = "__ge__"
43- or
44- name = "__cmp__" and major_version ( ) = 2
4545}
4646
47- private predicate cast_method ( string name ) {
48- name = "__nonzero__" and major_version ( ) = 2
49- or
47+ private predicate castMethod ( string name ) {
5048 name = "__int__"
5149 or
5250 name = "__float__"
@@ -58,63 +56,115 @@ private predicate cast_method(string name) {
5856 name = "__complex__"
5957}
6058
61- predicate correct_raise ( string name , ClassObject ex ) {
62- ex . getAnImproperSuperType ( ) = theTypeErrorType ( ) and
59+ predicate correctRaise ( string name , Expr exec ) {
60+ execIsOfType ( exec , "TypeError" ) and
6361 (
64- name = "__copy__" or
65- name = "__deepcopy__" or
66- name = "__call__" or
67- indexing_method ( name ) or
68- attribute_method ( name )
62+ indexingMethod ( name ) or
63+ attributeMethod ( name )
6964 )
7065 or
71- preferred_raise ( name , ex )
72- or
73- preferred_raise ( name , ex .getASuperType ( ) )
66+ exists ( string execName |
67+ preferredRaise ( name , execName , _) and
68+ execIsOfType ( exec , execName )
69+ )
7470}
7571
76- predicate preferred_raise ( string name , ClassObject ex ) {
77- attribute_method ( name ) and ex = theAttributeErrorType ( )
78- or
79- indexing_method ( name ) and ex = Object:: builtin ( "LookupError" )
80- or
81- ordering_method ( name ) and ex = theTypeErrorType ( )
82- or
83- arithmetic_method ( name ) and ex = Object:: builtin ( "ArithmeticError" )
84- or
85- name = "__bool__" and ex = theTypeErrorType ( )
72+ predicate preferredRaise ( string name , string execName , string message ) {
73+ // TODO: execName should be an IPA type
74+ attributeMethod ( name ) and
75+ execName = "AttributeError" and
76+ message = "should raise an AttributeError instead."
77+ or
78+ indexingMethod ( name ) and
79+ execName = "LookupError" and
80+ message = "should raise a LookupError (KeyError or IndexError) instead."
81+ or
82+ orderingMethod ( name ) and
83+ execName = "TypeError" and
84+ message = "should raise a TypeError, or return NotImplemented instead."
85+ or
86+ arithmeticMethod ( name ) and
87+ execName = "ArithmeticError" and
88+ message = "should raise an ArithmeticError, or return NotImplemented instead."
89+ or
90+ name = "__bool__" and
91+ execName = "TypeError" and
92+ message = "should raise a TypeError instead."
8693}
8794
88- predicate no_need_to_raise ( string name , string message ) {
89- name = "__hash__" and message = "use __hash__ = None instead"
90- or
91- cast_method ( name ) and message = "there is no need to implement the method at all."
95+ predicate execIsOfType ( Expr exec , string execName ) {
96+ exists ( string subclass |
97+ execName = "TypeError" and
98+ subclass = "TypeError"
99+ or
100+ execName = "LookupError" and
101+ subclass = [ "LookupError" , "KeyError" , "IndexError" ]
102+ or
103+ execName = "ArithmeticError" and
104+ subclass = [ "ArithmeticError" , "FloatingPointError" , "OverflowError" , "ZeroDivisionError" ]
105+ or
106+ execName = "AttributeError" and
107+ subclass = "AttributeError"
108+ |
109+ exec = API:: builtin ( subclass ) .getACall ( ) .asExpr ( )
110+ or
111+ exec = API:: builtin ( subclass ) .getASubclass ( ) .getACall ( ) .asExpr ( )
112+ )
92113}
93114
94- predicate is_abstract ( FunctionObject func ) {
95- func .getFunction ( ) .getADecorator ( ) .( Name ) .getId ( ) .matches ( "%abstract%" )
115+ predicate noNeedToAlwaysRaise ( Function meth , string message , boolean allowNotImplemented ) {
116+ meth .getName ( ) = "__hash__" and
117+ message = "use __hash__ = None instead." and
118+ allowNotImplemented = false
119+ or
120+ castMethod ( meth .getName ( ) ) and
121+ message = "this method does not need to be implemented." and
122+ allowNotImplemented = true and
123+ not exists ( Function overridden |
124+ overridden .getName ( ) = meth .getName ( ) and
125+ overridden .getScope ( ) = getADirectSuperclass + ( meth .getScope ( ) ) and
126+ alwaysRaises ( overridden , _)
127+ )
96128}
97129
98- predicate always_raises ( FunctionObject f , ClassObject ex ) {
99- ex = f . getARaisedType ( ) and
100- strictcount ( f . getARaisedType ( ) ) = 1 and
101- not exists ( f . getFunction ( ) . getANormalExit ( ) ) and
102- /* raising StopIteration is equivalent to a return in a generator */
103- not ex = theStopIterationType ( )
130+ predicate isAbstract ( Function func ) { func . getADecorator ( ) . ( Name ) . getId ( ) . matches ( "%abstract%" ) }
131+
132+ predicate alwaysRaises ( Function f , Expr exec ) {
133+ directlyRaises ( f , exec ) and
134+ strictcount ( Expr e | directlyRaises ( f , e ) ) = 1 and
135+ not exists ( f . getANormalExit ( ) )
104136}
105137
106- from FunctionObject f , ClassObject cls , string message
138+ predicate directlyRaises ( Function f , Expr exec ) {
139+ exists ( Raise r |
140+ r .getScope ( ) = f and
141+ exec = r .getException ( ) and
142+ not exec = API:: builtin ( "StopIteration" ) .asSource ( ) .asExpr ( )
143+ )
144+ }
145+
146+ predicate isNotImplementedError ( Expr exec ) {
147+ exec = API:: builtin ( "NotImplementedError" ) .getACall ( ) .asExpr ( )
148+ }
149+
150+ from Function f , Expr exec , string message
107151where
108- f .getFunction ( ) . isSpecialMethod ( ) and
109- not is_abstract ( f ) and
110- always_raises ( f , cls ) and
152+ f .isSpecialMethod ( ) and
153+ not isAbstract ( f ) and
154+ directlyRaises ( f , exec ) and
111155 (
112- no_need_to_raise ( f .getName ( ) , message ) and not cls .getName ( ) = "NotImplementedError"
156+ exists ( boolean allowNotImplemented , string subMessage |
157+ alwaysRaises ( f , exec ) and
158+ noNeedToAlwaysRaise ( f , subMessage , allowNotImplemented ) and
159+ ( allowNotImplemented = false or not isNotImplementedError ( exec ) ) and
160+ message = "This method always raises $@ - " + subMessage
161+ )
113162 or
114- not correct_raise ( f .getName ( ) , cls ) and
115- not cls .getName ( ) = "NotImplementedError" and
116- exists ( ClassObject preferred | preferred_raise ( f .getName ( ) , preferred ) |
117- message = "raise " + preferred .getName ( ) + " instead"
163+ alwaysRaises ( f , exec ) and // for now consider only alwaysRaises cases as original query
164+ not isNotImplementedError ( exec ) and
165+ not correctRaise ( f .getName ( ) , exec ) and
166+ exists ( string subMessage | preferredRaise ( f .getName ( ) , _, subMessage ) |
167+ message = "This method always raises $@ - " + subMessage
118168 )
119169 )
120- select f , "Function always raises $@; " + message , cls , cls .toString ( )
170+ select f , message , exec , exec .toString ( ) // TODO: remove tostring
0 commit comments