Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- [#108](https://github.com/green-code-initiative/creedengo-python/pull/108) Add rule GCI109 Avoid using exceptions for control flow

### Changed

- compatibility updates for SonarQube 25.9.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,4 +462,19 @@ void testGCI108(){
checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY, TYPE, EFFORT_10MIN);
}

@Test
void testGCI109(){
String filePath = "src/avoidExceptionsForControlFlow.py";
String ruleId = "creedengo-python:GCI109";
String ruleMsg = "Avoid using exceptions for control flow";
int[] startLines = new int[]{
4, 10, 16, 22, 29
};
int[] endLines = new int[]{
4, 10, 16, 22, 29
};

checkIssuesForFile(filePath, ruleId, ruleMsg, startLines, endLines, SEVERITY_MAJOR, TYPE, EFFORT_5MIN);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# using KeyError for control flow
try:
value = my_dict[key]
except KeyError: # Noncompliant {{Avoid using exceptions for control flow}}
value = default

# using IndexError for control flow
try:
item = my_list[index]
except IndexError: # Noncompliant {{Avoid using exceptions for control flow}}
item = None

# using AttributeError for control flow
try:
value = obj.attribute
except AttributeError: # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# using StopIteration for control flow
try:
value = next(iterator)
except StopIteration: # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# KeyError in loop
for key in keys:
try:
values.append(my_dict[key])
except KeyError: # Noncompliant {{Avoid using exceptions for control flow}}
values.append(default)

# multiple exceptions in tuple
try:
value = my_dict[key]
except (KeyError, ValueError): # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# multiple control flow exceptions in tuple
try:
item = my_list[index]
except (IndexError, KeyError): # Noncompliant {{Avoid using exceptions for control flow}}
item = None

# AttributeError with other exceptions in tuple
try:
value = obj.attribute
except (AttributeError, TypeError): # Noncompliant {{Avoid using exceptions for control flow}}
value = None



### Compliant cases ###
value = my_dict.get(key, default)



if 0 <= index < len(my_list):
item = my_list[index]
else:
item = None



value = getattr(obj, 'attribute', None)


value = next(iterator, None)


try:
result = risky_operation()
except ValueError:
result = None



try:
process_file()
except (IOError, OSError):
handle_error()



for key in keys:
values.append(my_dict.get(key, default))
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public record PythonRuleRepository(SonarRuntime sonarRuntime) implements RulesDe
DisableGradientForModelEval.class,
StringConcatenation.class,
PreferAppendLeft.class,
AvoidCreatingTensorUsingNumpyOrNativePython.class
AvoidCreatingTensorUsingNumpyOrNativePython.class,
AvoidExceptionsForControlFlowCheck.class
);

public static final String LANGUAGE = "py";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.python.checks;

import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.SubscriptionContext;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.TryStatement;
import org.sonar.plugins.python.api.tree.Tuple;

import java.util.Arrays;
import java.util.List;

@Rule(key = "GCI109")
public class AvoidExceptionsForControlFlowCheck extends PythonSubscriptionCheck {

public static final String DESCRIPTION = "Avoid using exceptions for control flow";

private static final List<String> CONTROL_FLOW_EXCEPTIONS = Arrays.asList(
"KeyError",
"IndexError",
"AttributeError",
"StopIteration"
);

@Override
public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Tree.Kind.TRY_STMT, this::visitTryStatement);
}

private void visitTryStatement(SubscriptionContext context) {
TryStatement tryStatement = (TryStatement) context.syntaxNode();

List<ExceptClause> exceptClauses = tryStatement.exceptClauses();
if (exceptClauses.isEmpty()) {
return;
}

for (ExceptClause exceptClause : exceptClauses) {
Expression exception = exceptClause.exception();
if (exception != null && isControlFlowException(exception)) {
context.addIssue(exceptClause.exceptKeyword(), DESCRIPTION);
}
}
}

private boolean isControlFlowException(Expression exception) {
if (exception.is(Tree.Kind.TUPLE)) {
Tuple tuple = (Tuple) exception;
for (Expression element : tuple.elements()) {
if (isControlFlowException(element)) {
return true;
}
}
return false;
}

String exceptionName = exception.firstToken().value();
return CONTROL_FLOW_EXCEPTIONS.contains(exceptionName);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"GCI106",
"GCI107",
"GCI108",
"GCI109",
"GCI203",
"GCI404"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* creedengo - Python language - Provides rules to reduce the environmental footprint of your Python programs
* Copyright © 2024 Green Code Initiative (https://green-code-initiative.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.greencodeinitiative.creedengo.python.checks;

import org.junit.jupiter.api.Test;
import org.sonar.python.checks.utils.PythonCheckVerifier;

public class AvoidExceptionsForControlFlowCheckTest {

@Test
public void test() {
PythonCheckVerifier.verify("src/test/resources/checks/avoidExceptionsForControlFlow.py", new AvoidExceptionsForControlFlowCheck());
}
}

85 changes: 85 additions & 0 deletions src/test/resources/checks/avoidExceptionsForControlFlow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# using KeyError for control flow
try:
value = my_dict[key]
except KeyError: # Noncompliant {{Avoid using exceptions for control flow}}
value = default

# using IndexError for control flow
try:
item = my_list[index]
except IndexError: # Noncompliant {{Avoid using exceptions for control flow}}
item = None

# using AttributeError for control flow
try:
value = obj.attribute
except AttributeError: # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# using StopIteration for control flow
try:
value = next(iterator)
except StopIteration: # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# KeyError in loop
for key in keys:
try:
values.append(my_dict[key])
except KeyError: # Noncompliant {{Avoid using exceptions for control flow}}
values.append(default)

# multiple exceptions in tuple
try:
value = my_dict[key]
except (KeyError, ValueError): # Noncompliant {{Avoid using exceptions for control flow}}
value = None

# multiple control flow exceptions in tuple
try:
item = my_list[index]
except (IndexError, KeyError): # Noncompliant {{Avoid using exceptions for control flow}}
item = None

# AttributeError with other exceptions in tuple
try:
value = obj.attribute
except (AttributeError, TypeError): # Noncompliant {{Avoid using exceptions for control flow}}
value = None



### Compliant cases ###
value = my_dict.get(key, default)



if 0 <= index < len(my_list):
item = my_list[index]
else:
item = None



value = getattr(obj, 'attribute', None)


value = next(iterator, None)


try:
result = risky_operation()
except ValueError:
result = None



try:
process_file()
except (IOError, OSError):
handle_error()



for key in keys:
values.append(my_dict.get(key, default))
Loading