Skip to content

Commit b68ef89

Browse files
thomas-serre-sonarsourcesonartech
authored andcommitted
SONARPY-3424 S112: Raise when passing a newly constructed Exception or BaseException to function (#935)
GitOrigin-RevId: 41f1ed016cc89b82303afa1f2ef50dca4645e55e
1 parent 4c4d88e commit b68ef89

File tree

2 files changed

+58
-18
lines changed

2 files changed

+58
-18
lines changed

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

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
import java.util.List;
2020
import org.sonar.check.Rule;
2121
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
22+
import org.sonar.plugins.python.api.SubscriptionContext;
2223
import org.sonar.plugins.python.api.symbols.v2.SymbolV2;
2324
import org.sonar.plugins.python.api.symbols.v2.UsageV2;
25+
import org.sonar.plugins.python.api.tree.Argument;
26+
import org.sonar.plugins.python.api.tree.CallExpression;
2427
import org.sonar.plugins.python.api.tree.Expression;
2528
import org.sonar.plugins.python.api.tree.Name;
2629
import org.sonar.plugins.python.api.tree.RaiseStatement;
30+
import org.sonar.plugins.python.api.tree.RegularArgument;
2731
import org.sonar.plugins.python.api.tree.Tree;
2832
import org.sonar.plugins.python.api.tree.Tree.Kind;
2933
import org.sonar.plugins.python.api.types.v2.matchers.TypeMatcher;
@@ -36,42 +40,65 @@
3640
@Rule(key = "S112")
3741
public class GenericExceptionRaisedCheck extends PythonSubscriptionCheck {
3842

43+
private static final String MESSAGE = "Replace this generic exception class with a more specific one.";
44+
3945
private final TypeMatcher isExceptionOrBaseExceptionMatcher = TypeMatchers.any(
4046
TypeMatchers.isObjectOfType(EXCEPTION),
4147
TypeMatchers.isObjectOfType(BASE_EXCEPTION),
4248
TypeMatchers.isType(EXCEPTION),
4349
TypeMatchers.isType(BASE_EXCEPTION)
4450
);
4551

52+
private final TypeMatcher isObjectOfTypeExceptionOrBaseExceptionMatcher = TypeMatchers.any(
53+
TypeMatchers.isObjectOfType(EXCEPTION),
54+
TypeMatchers.isObjectOfType(BASE_EXCEPTION)
55+
);
56+
4657
@Override
4758
public void initialize(Context context) {
48-
context.registerSyntaxNodeConsumer(Kind.RAISE_STMT, ctx -> {
49-
RaiseStatement raise = (RaiseStatement) ctx.syntaxNode();
50-
List<Expression> expressions = raise.expressions();
51-
if (expressions.isEmpty()) {
52-
return;
53-
}
59+
context.registerSyntaxNodeConsumer(Kind.RAISE_STMT, this::checkRaise);
60+
context.registerSyntaxNodeConsumer(Kind.CALL_EXPR, this::checkFunctionCall);
61+
}
62+
63+
private void checkRaise(SubscriptionContext ctx) {
64+
RaiseStatement raise = (RaiseStatement) ctx.syntaxNode();
65+
List<Expression> expressions = raise.expressions();
66+
if (expressions.isEmpty()) {
67+
return;
68+
}
5469

55-
Expression expression = expressions.get(0);
56-
if (!isExceptionOrBaseExceptionMatcher.isTrueFor(expression, ctx)) {
57-
return;
70+
Expression expression = expressions.get(0);
71+
if (!isExceptionOrBaseExceptionMatcher.isTrueFor(expression, ctx)) {
72+
return;
73+
}
74+
if (!isExceptionFunctionLocal(expression, raise)) {
75+
return;
76+
}
77+
ctx.addIssue(expression, MESSAGE);
78+
}
79+
80+
private void checkFunctionCall(SubscriptionContext ctx) {
81+
CallExpression call = (CallExpression) ctx.syntaxNode();
82+
List<Argument> arguments = call.arguments();
83+
for (Argument arg : arguments) {
84+
if (!(arg instanceof RegularArgument regArg) || regArg.keywordArgument() != null) {
85+
continue;
5886
}
59-
if (!isExceptionFunctionLocal(expression, raise)) {
60-
return;
87+
Expression argExpr = regArg.expression();
88+
if (isObjectOfTypeExceptionOrBaseExceptionMatcher.isTrueFor(argExpr, ctx) && isExceptionFunctionLocal(argExpr, call)) {
89+
ctx.addIssue(argExpr, MESSAGE);
6190
}
62-
63-
ctx.addIssue(expression, "Replace this generic exception class with a more specific one.");
64-
});
91+
}
6592
}
6693

67-
private static boolean isExceptionFunctionLocal(Expression expression, RaiseStatement raise) {
94+
private static boolean isExceptionFunctionLocal(Expression expression, Tree contextTree) {
6895
if (!(expression instanceof Name name)) return true;
6996
SymbolV2 symbolV2 = name.symbolV2();
70-
return symbolV2 == null || isLocalVariable(symbolV2, raise);
97+
return symbolV2 == null || isLocalVariable(symbolV2, contextTree);
7198
}
7299

73-
private static boolean isLocalVariable(SymbolV2 symbol, Tree raiseStatement) {
74-
Tree function = TreeUtils.firstAncestorOfKind(raiseStatement, Kind.FUNCDEF);
100+
private static boolean isLocalVariable(SymbolV2 symbol, Tree contextTree) {
101+
Tree function = TreeUtils.firstAncestorOfKind(contextTree, Kind.FUNCDEF);
75102
if (function == null) {
76103
return false;
77104
}

python-checks/src/test/resources/checks/genericException/genericExceptionRaised.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,16 @@ def raised_exception_is_the_parameter(exception: BaseException):
5555
global_exception = BaseException()
5656
def raise_global_exception():
5757
raise global_exception
58+
59+
def handle_exception(e: Exception):
60+
raise e
61+
62+
def intermediate_handle_exception(e: Exception):
63+
handle_exception(e)
64+
65+
def constructed_exceptions_passed_to_handle_exception_raise():
66+
handle_exception(Exception()) # Noncompliant
67+
handle_exception(BaseException()) # Noncompliant
68+
69+
def is_instance_do_not_raise(e):
70+
isInstance(e, Exception)

0 commit comments

Comments
 (0)