Skip to content

Commit 3a19fd5

Browse files
SONARPY-1345 Fix stack overflow when a nested class inherits from a class with the same name (#1498)
1 parent 30aa413 commit 3a19fd5

File tree

2 files changed

+27
-4
lines changed

2 files changed

+27
-4
lines changed

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.Collections;
2424
import java.util.EnumSet;
25+
import java.util.HashSet;
2526
import java.util.List;
2627
import java.util.Objects;
2728
import java.util.Optional;
@@ -145,21 +146,22 @@ public static ClassSymbol getClassSymbolFromDef(@Nullable ClassDef classDef) {
145146
}
146147

147148
public static List<String> getParentClassesFQN(ClassDef classDef) {
148-
return getParentClasses(TreeUtils.getClassSymbolFromDef(classDef)).stream()
149+
return getParentClasses(TreeUtils.getClassSymbolFromDef(classDef), new HashSet<>()).stream()
149150
.map(Symbol::fullyQualifiedName)
150151
.filter(Objects::nonNull)
151152
.collect(Collectors.toList());
152153
}
153154

154-
private static List<Symbol> getParentClasses(@Nullable ClassSymbol classSymbol) {
155+
private static List<Symbol> getParentClasses(@Nullable ClassSymbol classSymbol, Set<ClassSymbol> visitedSymbols) {
155156
List<Symbol> superClasses = new ArrayList<>();
156-
if (classSymbol == null) {
157+
if (classSymbol == null || visitedSymbols.contains(classSymbol)) {
157158
return superClasses;
158159
}
160+
visitedSymbols.add(classSymbol);
159161
for (Symbol symbol : classSymbol.superClasses()) {
160162
superClasses.add(symbol);
161163
if (symbol instanceof ClassSymbol) {
162-
superClasses.addAll(getParentClasses((ClassSymbol) symbol));
164+
superClasses.addAll(getParentClasses((ClassSymbol) symbol, visitedSymbols));
163165
}
164166
}
165167
return superClasses;

python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,18 @@
3838
import org.sonar.plugins.python.api.symbols.Symbol;
3939
import org.sonar.plugins.python.api.symbols.Usage;
4040
import org.sonar.plugins.python.api.tree.CallExpression;
41+
import org.sonar.plugins.python.api.tree.ClassDef;
4142
import org.sonar.plugins.python.api.tree.FileInput;
4243
import org.sonar.plugins.python.api.tree.FunctionDef;
4344
import org.sonar.plugins.python.api.tree.ImportFrom;
4445
import org.sonar.plugins.python.api.tree.QualifiedExpression;
46+
import org.sonar.plugins.python.api.tree.Statement;
4547
import org.sonar.plugins.python.api.tree.Tree;
4648
import org.sonar.python.PythonTestUtils;
4749
import org.sonar.python.index.Descriptor;
4850
import org.sonar.python.index.DescriptorUtils;
4951
import org.sonar.python.index.VariableDescriptor;
52+
import org.sonar.python.tree.TreeUtils;
5053
import org.sonar.python.types.DeclaredType;
5154
import org.sonar.python.types.InferredTypes;
5255

@@ -697,7 +700,25 @@ public void class_having_itself_as_superclass_should_not_trigger_error() {
697700
FileInput fileInput = parseWithoutSymbols("class A(A): pass");
698701
Set<Symbol> globalSymbols = globalSymbols(fileInput, "mod");
699702
ClassSymbol a = (ClassSymbol) globalSymbols.iterator().next();
703+
// SONARPY-1350: The parent "A" is not yet defined at the time it is read, so this is actually not correct
700704
assertThat(a.superClasses()).containsExactly(a);
705+
ClassDef classDef = (ClassDef) fileInput.statements().statements().get(0);
706+
assertThat(TreeUtils.getParentClassesFQN(classDef)).containsExactly("mod.mod.A");
707+
}
708+
709+
710+
@Test
711+
public void class_having_another_class_with_same_name_should_not_trigger_error() {
712+
FileInput fileInput = parseWithoutSymbols(
713+
"from external import B",
714+
"class A:",
715+
" class B(B): pass"
716+
);
717+
globalSymbols(fileInput, "mod");
718+
ClassDef outerClassDef = (ClassDef) fileInput.statements().statements().get(1);
719+
ClassDef innerClassDef = (ClassDef) outerClassDef.body().statements().get(0);
720+
// SONARPY-1350: Parent should be external.B
721+
assertThat(TreeUtils.getParentClassesFQN(innerClassDef)).containsExactly("mod.mod.A.B");
701722
}
702723

703724
@Test

0 commit comments

Comments
 (0)