Skip to content

Commit ecf0a79

Browse files
SONARPY-747: S5864: 'raise' used with a non-exception type (SonarSource#844)
1 parent 5c70d38 commit ecf0a79

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,20 @@
2424
import org.sonar.check.Rule;
2525
import org.sonar.plugins.python.api.LocationInFile;
2626
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
27+
import org.sonar.plugins.python.api.SubscriptionContext;
2728
import org.sonar.plugins.python.api.symbols.Symbol;
2829
import org.sonar.plugins.python.api.tree.Expression;
2930
import org.sonar.plugins.python.api.tree.Name;
31+
import org.sonar.plugins.python.api.tree.RaiseStatement;
3032
import org.sonar.plugins.python.api.tree.Token;
3133
import org.sonar.plugins.python.api.tree.Tree;
3234
import org.sonar.plugins.python.api.types.InferredType;
3335
import org.sonar.python.types.InferredTypes;
36+
import org.sonar.python.types.TypeShed;
3437

3538
import static org.sonar.python.types.InferredTypes.containsDeclaredType;
3639
import static org.sonar.python.types.InferredTypes.typeClassLocation;
40+
import static org.sonar.python.types.InferredTypes.typeName;
3741

3842
@Rule(key = "S5864")
3943
public class ConfusingTypeCheckingCheck extends PythonSubscriptionCheck {
@@ -43,6 +47,7 @@ public void initialize(Context context) {
4347
new IncompatibleOperandsCheck().initialize(context);
4448
new ItemOperationsTypeCheck().initialize(context);
4549
new IterationOnNonIterableCheck().initialize(context);
50+
context.registerSyntaxNodeConsumer(Tree.Kind.RAISE_STMT, ConfusingTypeCheckingCheck::checkIncorrectExceptionType);
4651
}
4752

4853
private static class NonCallableCalledCheck extends NonCallableCalled {
@@ -136,12 +141,30 @@ boolean isAsyncIterable(Expression expression) {
136141
// No need to check again if the type contains a declared type
137142
return type.declaresMember("__aiter__");
138143
}
144+
}
139145

140-
private static String nameFromExpression(Expression expression) {
141-
if (expression.is(Tree.Kind.NAME)) {
142-
return ((Name) expression).name();
143-
}
144-
return null;
146+
private static void checkIncorrectExceptionType(SubscriptionContext ctx) {
147+
RaiseStatement raiseStatement = (RaiseStatement) ctx.syntaxNode();
148+
if (raiseStatement.expressions().isEmpty()) {
149+
return;
150+
}
151+
Expression raisedExpression = raiseStatement.expressions().get(0);
152+
InferredType type = raisedExpression.type();
153+
if (!containsDeclaredType(type)) {
154+
return;
155+
}
156+
if (!type.isCompatibleWith(InferredTypes.runtimeType(TypeShed.typeShedClass("BaseException")))) {
157+
String expressionName = nameFromExpression(raisedExpression) != null ? String.format("\"%s\"", nameFromExpression(raisedExpression)) : "this expression";
158+
String typeName = typeName(type);
159+
ctx.addIssue(raiseStatement, String.format("Fix this \"raise\" statement; Previous type checks suggest that %s has type \"%s\" and is not an exception.",
160+
expressionName, typeName));
161+
}
162+
}
163+
164+
private static String nameFromExpression(Expression expression) {
165+
if (expression.is(Tree.Kind.NAME)) {
166+
return ((Name) expression).name();
145167
}
168+
return null;
146169
}
147170
}

python-checks/src/test/java/org/sonar/python/checks/ConfusingTypeCheckingCheckTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ public void iteration_on_non_iterable() {
6060
assertNoIssuesInCorrespondingBugRule("src/test/resources/checks/iterationOnNonIterable.py");
6161
}
6262

63+
@Test
64+
public void incorrect_exception_type() {
65+
PythonCheckVerifier.verify("src/test/resources/checks/confusingTypeChecking/incorrectExceptionType.py", check);
66+
assertNoIssuesInCorrespondingBugRule("src/test/resources/checks/incorrectExceptionType/incorrectExceptionType.py");
67+
}
68+
6369
private void assertNoIssuesInCorrespondingBugRule(String path) {
6470
PythonVisitorContext context = TestPythonVisitorRunner.createContext(new File(path));
6571
SubscriptionVisitor.analyze(Collections.singletonList(check), context);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class A: ...
2+
class CustomException(BaseException): ...
3+
class AnotherException(CustomException): ...
4+
5+
def my_int() -> int:
6+
...
7+
8+
def custom(param1: A, param2: CustomException, param3: AnotherException):
9+
raise param1 # Noncompliant {{Fix this "raise" statement; Previous type checks suggest that "param1" has type "A" and is not an exception.}}
10+
# ^^^^^^^^^^^^
11+
raise param2
12+
raise param3
13+
14+
15+
def builtin(param1: str, param2: Exception, param3: BaseException, param4: ValueError):
16+
raise param1 # Noncompliant {{Fix this "raise" statement; Previous type checks suggest that "param1" has type "str" and is not an exception.}}
17+
raise param2
18+
raise param3
19+
raise param4
20+
raise my_int() # Noncompliant {{Fix this "raise" statement; Previous type checks suggest that this expression has type "int" and is not an exception.}}

0 commit comments

Comments
 (0)