1616 */
1717package org .sonar .python .checks ;
1818
19+ import java .util .IdentityHashMap ;
20+ import java .util .Map ;
1921import org .sonar .check .Rule ;
2022import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
21- import org .sonar .plugins .python .api .symbols .Symbol ;
23+ import org .sonar .plugins .python .api .symbols .v2 .SymbolV2 ;
24+ import org .sonar .plugins .python .api .tree .BaseTreeVisitor ;
25+ import org .sonar .plugins .python .api .tree .CallExpression ;
26+ import org .sonar .plugins .python .api .tree .ClassDef ;
2227import org .sonar .plugins .python .api .tree .FunctionDef ;
28+ import org .sonar .plugins .python .api .tree .Name ;
29+ import org .sonar .plugins .python .api .tree .Parameter ;
30+ import org .sonar .plugins .python .api .tree .QualifiedExpression ;
2331import org .sonar .plugins .python .api .tree .RaiseStatement ;
2432import org .sonar .plugins .python .api .tree .Tree ;
33+ import org .sonar .python .checks .utils .CheckUtils ;
2534import org .sonar .python .tree .TreeUtils ;
2635
2736import static org .sonar .plugins .python .api .tree .Tree .Kind .EXCEPT_CLAUSE ;
3241
3342@ Rule (key = "S5747" )
3443public class RaiseOutsideExceptCheck extends PythonSubscriptionCheck {
44+ private final Map <FunctionDef , Boolean > methodCallsInsideExceptBlock = new IdentityHashMap <>();
45+
3546 @ Override
3647 public void initialize (Context context ) {
3748 context .registerSyntaxNodeConsumer (RAISE_STMT , ctx -> {
@@ -46,7 +57,12 @@ public void initialize(Context context) {
4657 });
4758 }
4859
49- private static boolean isInsideExceptOrFinally (Tree tree ) {
60+ @ Override
61+ public void leaveFile () {
62+ methodCallsInsideExceptBlock .clear ();
63+ }
64+
65+ private boolean isInsideExceptOrFinally (Tree tree ) {
5066 Tree parent = tree .parent ();
5167 while (parent != null ) {
5268 if ((parent .is (FUNCDEF ))) {
@@ -61,10 +77,89 @@ private static boolean isInsideExceptOrFinally(Tree tree) {
6177 return false ;
6278 }
6379
64- private static boolean isFunctionCalledInsideExceptBlock (FunctionDef functionDef ) {
65- Symbol symbol = functionDef .name ().symbol ();
66- return symbol != null && symbol .usages ().stream ()
80+ private boolean isFunctionCalledInsideExceptBlock (FunctionDef functionDef ) {
81+ SymbolV2 symbol = functionDef .name ().symbolV2 ();
82+ boolean hasUsageInsideExceptBlock = symbol != null && symbol .usages ().stream ()
6783 .filter (usage -> usage .tree () != functionDef .name ())
6884 .anyMatch (usage -> TreeUtils .firstAncestorOfKind (usage .tree (), EXCEPT_CLAUSE , EXCEPT_GROUP_CLAUSE , FINALLY_CLAUSE ) != null );
85+ return hasUsageInsideExceptBlock || methodCallsInsideExceptBlock .computeIfAbsent (functionDef , RaiseOutsideExceptCheck ::isMethodCalledInsideExceptBlock );
86+ }
87+
88+ private static boolean isMethodCalledInsideExceptBlock (FunctionDef functionDef ) {
89+ ClassDef classDef = CheckUtils .getParentClassDef (functionDef );
90+ if (classDef == null || !functionDef .isMethodDefinition ()) {
91+ return false ;
92+ }
93+ Tree compilationUnit = classDef ;
94+ while (compilationUnit .parent () != null ) {
95+ compilationUnit = compilationUnit .parent ();
96+ }
97+ var visitor = new MethodCallInsideExceptVisitor (functionDef .name ().name (), classDef );
98+ return visitor .isCalledInsideExceptBlock (compilationUnit );
99+ }
100+
101+ private static class MethodCallInsideExceptVisitor extends BaseTreeVisitor {
102+ private final String methodName ;
103+ private final ClassDef classDef ;
104+ private final SymbolV2 classSymbol ;
105+ private boolean calledInsideExceptBlock ;
106+ private SymbolV2 receiverSymbol ;
107+
108+ MethodCallInsideExceptVisitor (String methodName , ClassDef classDef ) {
109+ this .methodName = methodName ;
110+ this .classDef = classDef ;
111+ this .classSymbol = classDef .name ().symbolV2 ();
112+ }
113+
114+ boolean isCalledInsideExceptBlock (Tree tree ) {
115+ scan (tree );
116+ return calledInsideExceptBlock ;
117+ }
118+
119+ @ Override
120+ public void visitCallExpression (CallExpression callExpression ) {
121+ if (callExpression .callee ().is (Tree .Kind .QUALIFIED_EXPR )) {
122+ QualifiedExpression callee = (QualifiedExpression ) callExpression .callee ();
123+ SymbolV2 qualifierSymbol = callee .qualifier () instanceof Name name ? name .symbolV2 () : null ;
124+ if (qualifierSymbol != null && methodName .equals (callee .name ().name ())
125+ && (qualifierSymbol == receiverSymbol || qualifierSymbol == classSymbol )
126+ && TreeUtils .firstAncestorOfKind (callExpression , EXCEPT_CLAUSE , EXCEPT_GROUP_CLAUSE , FINALLY_CLAUSE ) != null ) {
127+ calledInsideExceptBlock = true ;
128+ }
129+ }
130+ super .visitCallExpression (callExpression );
131+ }
132+
133+ @ Override
134+ public void visitFunctionDef (FunctionDef functionDef ) {
135+ SymbolV2 previousReceiverSymbol = receiverSymbol ;
136+ if (functionDef .isMethodDefinition ()) {
137+ receiverSymbol = isMethodOfClass (functionDef ) ? receiverSymbol (functionDef ) : null ;
138+ }
139+ super .visitFunctionDef (functionDef );
140+ receiverSymbol = previousReceiverSymbol ;
141+ }
142+
143+ @ Override
144+ public void visitClassDef (ClassDef nestedClassDef ) {
145+ SymbolV2 previousReceiverSymbol = receiverSymbol ;
146+ receiverSymbol = null ;
147+ super .visitClassDef (nestedClassDef );
148+ receiverSymbol = previousReceiverSymbol ;
149+ }
150+
151+ private boolean isMethodOfClass (FunctionDef functionDef ) {
152+ return functionDef .isMethodDefinition ()
153+ && CheckUtils .getParentClassDef (functionDef ) == classDef
154+ && functionDef .decorators ().stream ().noneMatch (decorator -> "staticmethod" .equals (TreeUtils .decoratorNameFromExpression (decorator .expression ())));
155+ }
156+
157+ private static SymbolV2 receiverSymbol (FunctionDef functionDef ) {
158+ if (functionDef .parameters ().all ().isEmpty () || !functionDef .parameters ().all ().get (0 ).is (Tree .Kind .PARAMETER )) {
159+ return null ;
160+ }
161+ Name receiver = ((Parameter ) functionDef .parameters ().all ().get (0 )).name ();
162+ return receiver != null ? receiver .symbolV2 () : null ;
163+ }
69164 }
70165}
0 commit comments