Skip to content

Commit ae00483

Browse files
SONARPY-1210 except* can not contain continue, break or return instruction (#1307)
1 parent 18fbb00 commit ae00483

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.tree;
21+
22+
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
23+
import org.sonar.plugins.python.api.tree.BreakStatement;
24+
import org.sonar.plugins.python.api.tree.ContinueStatement;
25+
import org.sonar.plugins.python.api.tree.ReturnStatement;
26+
import org.sonar.plugins.python.api.tree.Tree;
27+
28+
/**
29+
* Purpose of this class is to detect and throw exception if the scanned tree that contain invalid syntax regarding the except* instruction.
30+
* It is currently not valid to have a break, continue or return statement in an except* body.
31+
*/
32+
public class ExceptGroupJumpInstructionsCheck extends BaseTreeVisitor {
33+
@Override
34+
public void visitBreakStatement(BreakStatement breakStatement) {
35+
checkExceptGroupStatement(breakStatement, "break statement cannot appear in except* block",
36+
Tree.Kind.EXCEPT_GROUP_CLAUSE, Tree.Kind.FOR_STMT, Tree.Kind.WHILE_STMT);
37+
}
38+
39+
@Override
40+
public void visitContinueStatement(ContinueStatement continueStatement) {
41+
checkExceptGroupStatement(continueStatement, "continue statement cannot appear in except* block",
42+
Tree.Kind.EXCEPT_GROUP_CLAUSE, Tree.Kind.FOR_STMT, Tree.Kind.WHILE_STMT);
43+
}
44+
45+
@Override
46+
public void visitReturnStatement(ReturnStatement returnStatement) {
47+
checkExceptGroupStatement(returnStatement, "return statement cannot appear in except* block",
48+
Tree.Kind.EXCEPT_GROUP_CLAUSE, Tree.Kind.FUNCDEF);
49+
}
50+
51+
private static void checkExceptGroupStatement(Tree statement, String message, Tree.Kind... possibleParents) {
52+
Tree parent = TreeUtils.firstAncestorOfKind(statement, possibleParents);
53+
if (parent != null && parent.is(Tree.Kind.EXCEPT_GROUP_CLAUSE)) {
54+
PythonTreeMaker.recognitionException(statement.firstToken().line(), message);
55+
}
56+
}
57+
}

python-frontend/src/main/java/org/sonar/python/tree/PythonTreeMaker.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ public FileInput fileInput(AstNode astNode) {
120120
Token endOfFile = toPyToken(astNode.getFirstChild(GenericTokenType.EOF).getToken());
121121
FileInputImpl pyFileInputTree = new FileInputImpl(statementList, endOfFile, DocstringExtractor.extractDocstring(statementList));
122122
setParents(pyFileInputTree);
123+
pyFileInputTree.accept(new ExceptGroupJumpInstructionsCheck());
123124
return pyFileInputTree;
124125
}
125126

126-
private static void recognitionException(int line, String message) {
127+
public static void recognitionException(int line, String message) {
127128
throw new RecognitionException(line, "Parse error at line " + line + ": " + message + ".");
128129
}
129130

python-frontend/src/test/java/org/sonar/python/tree/PythonTreeMakerTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,6 +2597,72 @@ public void except_group() {
25972597
.hasMessage("Parse error at line 2: except* clause must specify the type of the expected exception.");
25982598
}
25992599

2600+
/**
2601+
* except* body cannot contain continue, break or return instruction
2602+
*/
2603+
@Test
2604+
public void except_group_invalid_instruction() {
2605+
String code1 = "try:pass\n" +
2606+
"except* OSError:\n" +
2607+
" continue";
2608+
assertThatThrownBy(() -> parse(code1, treeMaker::fileInput))
2609+
.isInstanceOf(RecognitionException.class)
2610+
.hasMessage("Parse error at line 3: continue statement cannot appear in except* block.");
2611+
2612+
String code2 = "try:pass\n" +
2613+
"except* OSError:\n" +
2614+
" break";
2615+
assertThatThrownBy(() -> parse(code2, treeMaker::fileInput))
2616+
.isInstanceOf(RecognitionException.class)
2617+
.hasMessage("Parse error at line 3: break statement cannot appear in except* block.");
2618+
2619+
String code3 = "try:pass\n" +
2620+
"except* OSError:\n" +
2621+
" return";
2622+
assertThatThrownBy(() -> parse(code3, treeMaker::fileInput))
2623+
.isInstanceOf(RecognitionException.class)
2624+
.hasMessage("Parse error at line 3: return statement cannot appear in except* block.");
2625+
2626+
// should not return parse error if continue/break is in a loop
2627+
String code4 = "try:pass\n" +
2628+
"except* OSError:\n" +
2629+
" while true:break";
2630+
FileInput tree = parse(code4, treeMaker::fileInput);
2631+
TryStatement tryStatement = (TryStatement) tree.statements().statements().get(0);
2632+
assertThat(tryStatement.exceptClauses().get(0).getKind()).isEqualTo(Kind.EXCEPT_GROUP_CLAUSE);
2633+
2634+
String code5 = "try:pass\n" +
2635+
"except* OSError:\n" +
2636+
" for x in \"bob\":continue";
2637+
tree = parse(code5, treeMaker::fileInput);
2638+
tryStatement = (TryStatement) tree.statements().statements().get(0);
2639+
assertThat(tryStatement.exceptClauses().get(0).getKind()).isEqualTo(Kind.EXCEPT_GROUP_CLAUSE);
2640+
2641+
// should not return parse error if return is in a function
2642+
String code6 = "try:pass\n" +
2643+
"except* OSError:\n" +
2644+
" def foo():return";
2645+
tree = parse(code6, treeMaker::fileInput);
2646+
tryStatement = (TryStatement) tree.statements().statements().get(0);
2647+
assertThat(tryStatement.exceptClauses().get(0).getKind()).isEqualTo(Kind.EXCEPT_GROUP_CLAUSE);
2648+
2649+
String code7 = "for x in range(42):\n" +
2650+
" try:pass\n" +
2651+
" except* OSError:\n" +
2652+
" continue";
2653+
assertThatThrownBy(() -> parse(code7, treeMaker::fileInput))
2654+
.isInstanceOf(RecognitionException.class)
2655+
.hasMessage("Parse error at line 4: continue statement cannot appear in except* block.");
2656+
2657+
String code8 = "def foo():\n" +
2658+
" try:pass\n" +
2659+
" except* OSError:\n" +
2660+
" continue";
2661+
assertThatThrownBy(() -> parse(code8, treeMaker::fileInput))
2662+
.isInstanceOf(RecognitionException.class)
2663+
.hasMessage("Parse error at line 4: continue statement cannot appear in except* block.");
2664+
}
2665+
26002666
@Test
26012667
public void except_group_multiple() {
26022668
FileInput tree = parse("try:pass\nexcept* OSError:pass\nexcept* ValueError:pass", treeMaker::fileInput);

0 commit comments

Comments
 (0)