Skip to content

Commit 4dde9b4

Browse files
maksim-grebeniuk-sonarsourcesonartech
authored andcommitted
SONARPY-2852 Rework SymbolVisitor to be a thread safe NewSymbolsCollector (#233)
GitOrigin-RevId: 1eaf362d8dba2c48c39cd01775e2f93f28a3253d
1 parent f5456dd commit 4dde9b4

4 files changed

Lines changed: 124 additions & 105 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.python;
18+
19+
import java.util.ArrayList;
20+
import java.util.Comparator;
21+
import java.util.List;
22+
import org.sonar.api.batch.sensor.symbol.NewSymbol;
23+
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
24+
import org.sonar.plugins.python.api.symbols.Symbol;
25+
import org.sonar.plugins.python.api.symbols.Usage;
26+
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
27+
import org.sonar.plugins.python.api.tree.ClassDef;
28+
import org.sonar.plugins.python.api.tree.ComprehensionExpression;
29+
import org.sonar.plugins.python.api.tree.DictCompExpression;
30+
import org.sonar.plugins.python.api.tree.FileInput;
31+
import org.sonar.plugins.python.api.tree.FunctionDef;
32+
import org.sonar.plugins.python.api.tree.LambdaExpression;
33+
import org.sonar.plugins.python.api.tree.Tree;
34+
35+
public class NewSymbolsCollector {
36+
private final Object monitor;
37+
38+
public NewSymbolsCollector(Object monitor) {
39+
this.monitor = monitor;
40+
}
41+
42+
public void collect(NewSymbolTable newSymbolTable, FileInput fileInput) {
43+
var visitor = new SymbolsCollectingVisitor(newSymbolTable);
44+
visitor.visitFileInput(fileInput);
45+
save(newSymbolTable);
46+
}
47+
48+
private void save(NewSymbolTable newSymbolTable) {
49+
synchronized (monitor) {
50+
newSymbolTable.save();
51+
}
52+
}
53+
54+
private static class SymbolsCollectingVisitor extends BaseTreeVisitor {
55+
private final NewSymbolTable newSymbolTable;
56+
57+
public SymbolsCollectingVisitor(NewSymbolTable newSymbolTable) {
58+
this.newSymbolTable = newSymbolTable;
59+
}
60+
61+
@Override
62+
public void visitClassDef(ClassDef classDef) {
63+
classDef.classFields().forEach(this::handleSymbol);
64+
classDef.instanceFields().forEach(this::handleSymbol);
65+
super.visitClassDef(classDef);
66+
}
67+
68+
@Override
69+
public void visitFunctionDef(FunctionDef functionDef) {
70+
functionDef.localVariables().forEach(this::handleSymbol);
71+
super.visitFunctionDef(functionDef);
72+
}
73+
74+
@Override
75+
public void visitLambda(LambdaExpression lambdaExpression) {
76+
lambdaExpression.localVariables().forEach(this::handleSymbol);
77+
super.visitLambda(lambdaExpression);
78+
}
79+
80+
@Override
81+
public void visitPyListOrSetCompExpression(ComprehensionExpression tree) {
82+
tree.localVariables().forEach(this::handleSymbol);
83+
super.visitPyListOrSetCompExpression(tree);
84+
}
85+
86+
@Override
87+
public void visitDictCompExpression(DictCompExpression tree) {
88+
tree.localVariables().forEach(this::handleSymbol);
89+
super.visitDictCompExpression(tree);
90+
}
91+
92+
@Override
93+
public void visitFileInput(FileInput fileInput) {
94+
fileInput.globalVariables().forEach(this::handleSymbol);
95+
super.visitFileInput(fileInput);
96+
}
97+
98+
private void handleSymbol(Symbol symbol) {
99+
if (symbol.usages().isEmpty()) {
100+
// global symbols might not have any usages
101+
return;
102+
}
103+
List<Usage> usages = new ArrayList<>(symbol.usages());
104+
usages.sort(Comparator.comparingInt(u -> u.tree().firstToken().line()));
105+
Tree firstUsageTree = usages.get(0).tree();
106+
NewSymbol newSymbol = newSymbolTable.newSymbol(firstUsageTree.firstToken().line(), firstUsageTree.firstToken().column(),
107+
firstUsageTree.lastToken().line(), firstUsageTree.lastToken().column() + firstUsageTree.lastToken().value().length());
108+
for (int i = 1; i < usages.size(); i++) {
109+
Tree usageTree = usages.get(i).tree();
110+
newSymbol.newReference(usageTree.firstToken().line(), usageTree.firstToken().column(),
111+
usageTree.lastToken().line(), usageTree.lastToken().column() + usageTree.lastToken().value().length());
112+
}
113+
}
114+
}
115+
}

python-commons/src/main/java/org/sonar/plugins/python/PythonScanner.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public class PythonScanner extends Scanner {
9191
private final AtomicBoolean foundDatabricks;
9292
private final PythonFileConsumer architectureCallback;
9393
private final Map<String, Object> repositoryLocks;
94+
private final NewSymbolsCollector newSymbolsCollector;
9495

9596
public PythonScanner(
9697
SensorContext context, PythonChecks checks, FileLinesContextFactory fileLinesContextFactory, NoSonarFilter noSonarFilter,
@@ -108,6 +109,7 @@ public PythonScanner(
108109
this.recognitionErrorCount = new AtomicInteger(0);
109110
this.foundDatabricks = new AtomicBoolean(false);
110111
this.repositoryLocks = new ConcurrentHashMap<>();
112+
this.newSymbolsCollector = new NewSymbolsCollector(this);
111113
}
112114

113115
@Override
@@ -154,8 +156,8 @@ protected void scanFile(PythonInputFile inputFile) throws IOException {
154156
saveIssues(inputFile, visitorContext.getIssues());
155157

156158
if (visitorContext.rootTree() != null && !isInSonarLint(context)) {
157-
new SymbolVisitor(context.newSymbolTable().onFile(inputFile.wrappedFile())).visitFileInput(visitorContext.rootTree());
158159
new PythonHighlighter(context, inputFile).scanFile(visitorContext);
160+
newSymbolsCollector.collect(context.newSymbolTable().onFile(inputFile.wrappedFile()), visitorContext.rootTree());
159161
}
160162

161163
searchForDataBricks(visitorContext);

python-commons/src/main/java/org/sonar/plugins/python/SymbolVisitor.java

Lines changed: 0 additions & 97 deletions
This file was deleted.

python-commons/src/test/java/org/sonar/plugins/python/SymbolVisitorTest.java renamed to python-commons/src/test/java/org/sonar/plugins/python/NewSymbolsCollectorTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,11 @@
2727
import org.sonar.api.batch.fs.internal.DefaultTextRange;
2828
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
2929
import org.sonar.api.batch.sensor.internal.SensorContextTester;
30-
import org.sonar.plugins.python.api.PythonVisitorContext;
31-
import org.sonar.plugins.python.api.tree.FileInput;
3230
import org.sonar.python.TestPythonVisitorRunner;
3331

3432
import static org.assertj.core.api.Assertions.assertThat;
3533

36-
class SymbolVisitorTest {
34+
class NewSymbolsCollectorTest {
3735

3836
private static SensorContextTester context;
3937
private static String componentKey;
@@ -49,10 +47,11 @@ static void scanFile() {
4947
context.fileSystem().add(inputFile);
5048
componentKey = inputFile.key();
5149

52-
SymbolVisitor symbolVisitor = new SymbolVisitor(context.newSymbolTable().onFile(inputFile));
53-
PythonVisitorContext context = TestPythonVisitorRunner.createContext(file);
54-
FileInput fileInput = context.rootTree();
55-
fileInput.accept(symbolVisitor);
50+
var newSymbolTable = context.newSymbolTable().onFile(inputFile);
51+
var visitorContext = TestPythonVisitorRunner.createContext(file);
52+
var fileInput = visitorContext.rootTree();
53+
var newSymbolsCollector = new NewSymbolsCollector(new Object());
54+
newSymbolsCollector.collect(newSymbolTable, fileInput);
5655
}
5756

5857
@Test

0 commit comments

Comments
 (0)