Skip to content

Commit e727b42

Browse files
SONARPY-1184 Rule S6468 : ExceptionGroup and BaseExceptionGroup should not be caught in except* clauses (#1298)
1 parent 9fc6137 commit e727b42

File tree

8 files changed

+195
-1
lines changed

8 files changed

+195
-1
lines changed

its/ruling/src/test/java/org/sonar/python/it/RulingHelper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ static List<String> bugRuleKeys() {
134134
"S6002",
135135
"S6323",
136136
"S6328",
137+
"S6468",
137138
"S905",
138139
"S930"
139140
);

python-checks/src/main/java/org/sonar/python/checks/CheckList.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ public static Iterable<Class> getChecks() {
168168
EmptyFunctionCheck.class,
169169
EmptyNestedBlockCheck.class,
170170
EmptyStringRepetitionCheck.class,
171+
ExceptionGroupCheck.class,
171172
ExceptionCauseTypeCheck.class,
172173
ExceptionNotThrownCheck.class,
173174
ExceptionSuperClassDeclarationCheck.class,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import java.util.Set;
23+
import org.sonar.check.Rule;
24+
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
25+
import org.sonar.plugins.python.api.SubscriptionContext;
26+
import org.sonar.plugins.python.api.tree.ExceptClause;
27+
import org.sonar.plugins.python.api.tree.Expression;
28+
import org.sonar.plugins.python.api.tree.Name;
29+
import org.sonar.plugins.python.api.tree.Tree;
30+
import org.sonar.plugins.python.api.tree.Tuple;
31+
32+
import static org.sonar.plugins.python.api.tree.Tree.Kind.EXCEPT_GROUP_CLAUSE;
33+
34+
@Rule(key = "S6468")
35+
public class ExceptionGroupCheck extends PythonSubscriptionCheck {
36+
private static final Set<String> EXCEPTION_GROUP = Set.of("ExceptionGroup", "BaseExceptionGroup");
37+
private static final String MESSAGE = "Avoid catching %s exception with 'except*'";
38+
39+
@Override
40+
public void initialize(Context context) {
41+
context.registerSyntaxNodeConsumer(EXCEPT_GROUP_CLAUSE, ctx -> {
42+
ExceptClause exceptClause = (ExceptClause) ctx.syntaxNode();
43+
Expression exception = exceptClause.exception();
44+
45+
if (isExceptionGroup(exception)) {
46+
raiseIssue(ctx, exception);
47+
} else if (exception.is(Tree.Kind.TUPLE)) {
48+
Tuple exceptionTuple = (Tuple) exception;
49+
for (Expression exceptionEl : exceptionTuple.elements()) {
50+
if (isExceptionGroup(exceptionEl)) {
51+
raiseIssue(ctx, exceptionEl);
52+
}
53+
}
54+
}
55+
});
56+
}
57+
58+
private static boolean isExceptionGroup(Expression exception) {
59+
// TODO : replace this logic by using type/symbol from typeshed
60+
return exception.is(Tree.Kind.NAME) && EXCEPTION_GROUP.contains(((Name) exception).name());
61+
}
62+
63+
private static void raiseIssue(SubscriptionContext ctx, Expression exception) {
64+
ctx.addIssue(exception, String.format(MESSAGE, ((Name) exception).name()));
65+
}
66+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<p>Python 3.11 introduced <code>except*</code> and <code>ExceptionGroup</code>. While it’s possible to catch the ExceptionGroup and BaseExceptionGroup
2+
types with <code>except`</code>, a Runtime error will be raised when this is done with <code>except*</code>.</p>
3+
<h2>Noncompliant Code Example</h2>
4+
<pre>
5+
try:
6+
...
7+
except* ExceptionGroup: # Noncompliant
8+
pass
9+
10+
try:
11+
...
12+
except* (TypeError, ExceptionGroup): # Noncompliant
13+
pass
14+
</pre>
15+
<h2>Compliant Solution</h2>
16+
<pre>
17+
try:
18+
...
19+
except ExceptionGroup:
20+
pass
21+
</pre>
22+
<h2>See</h2>
23+
<p><a href="https://peps.python.org/pep-0654/#forbidden-combinations">PEP-654</a></p>
24+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"title": "ExceptionGroup and BaseExceptionGroup should not be caught with except*",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [],
10+
"defaultSeverity": "Major",
11+
"ruleSpecification": "RSPEC-6468",
12+
"sqKey": "S6468",
13+
"scope": "All",
14+
"quickfix": "unknown"
15+
}

python-checks/src/main/resources/org/sonar/l10n/py/rules/python/Sonar_way_profile.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
"S6395",
183183
"S6396",
184184
"S6397",
185-
"S6463"
185+
"S6463",
186+
"S6468"
186187
]
187188
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2022 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.python.checks;
21+
22+
import org.junit.Test;
23+
import org.sonar.python.checks.utils.PythonCheckVerifier;
24+
25+
public class ExceptionGroupCheckTest {
26+
27+
@Test
28+
public void test() {
29+
PythonCheckVerifier.verify("src/test/resources/checks/exceptionGroup.py", new ExceptionGroupCheck());
30+
}
31+
32+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
def except_star_caught_exception_group():
2+
try:
3+
...
4+
except* ExceptionGroup: # Noncompliant {{Avoid catching ExceptionGroup exception with 'except*'}}
5+
# ^^^^^^^^^^^^^^
6+
...
7+
except* BaseExceptionGroup: # Noncompliant {{Avoid catching BaseExceptionGroup exception with 'except*'}}
8+
...
9+
10+
def except_star_caught_exception_group_multiple():
11+
try:
12+
...
13+
except* (TypeError, ExceptionGroup): # Noncompliant
14+
...
15+
except* (ExceptionGroup, BaseExceptionGroup): # Noncompliant 2
16+
...
17+
except* (TypeError, ValueError):
18+
...
19+
20+
def except_star_caught_valid_exception():
21+
try:
22+
...
23+
except* ValueError:
24+
...
25+
26+
def except_caught_exception_group():
27+
try:
28+
...
29+
except ValueError:
30+
...
31+
except ExceptionGroup:
32+
...
33+
except BaseExceptionGroup:
34+
...
35+
except (TypeError, ExceptionGroup):
36+
...
37+
except (ExceptionGroup, BaseExceptionGroup):
38+
...
39+
except (TypeError, ValueError):
40+
...
41+
42+
def except_caught_global():
43+
try:
44+
...
45+
except:
46+
...
47+
48+
def exception_group_replaced():
49+
ExceptionGroup = 5
50+
try:
51+
...
52+
# FP : should not be raised as it is no more the original ExceptionGroup class
53+
except* ExceptionGroup: # Noncompliant
54+
...

0 commit comments

Comments
 (0)