Skip to content

Commit d8ee0de

Browse files
SONARPY-1235 Prevent executing the same rule again in case of successful scanWithoutParsing (#1327)
1 parent 6690d95 commit d8ee0de

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
import java.io.File;
2525
import java.util.ArrayDeque;
2626
import java.util.ArrayList;
27+
import java.util.Collections;
2728
import java.util.Deque;
29+
import java.util.HashMap;
30+
import java.util.HashSet;
2831
import java.util.List;
32+
import java.util.Map;
2933
import java.util.Optional;
3034
import java.util.Set;
3135
import javax.annotation.CheckForNull;
@@ -76,6 +80,7 @@ public class PythonScanner extends Scanner {
7680
private final NoSonarFilter noSonarFilter;
7781
private final PythonCpdAnalyzer cpdAnalyzer;
7882
private final PythonIndexer indexer;
83+
private final Map<InputFile, Set<PythonCheck>> checksExecutedWithoutParsingByFiles = new HashMap<>();
7984

8085
public PythonScanner(
8186
SensorContext context, PythonChecks checks,
@@ -120,7 +125,8 @@ protected void scanFile(InputFile inputFile) {
120125
}
121126
List<PythonSubscriptionCheck> checksBasedOnTree = new ArrayList<>();
122127
for (PythonCheck check : checks.all()) {
123-
if (!isCheckApplicable(check, fileType)) {
128+
if (!isCheckApplicable(check, fileType)
129+
|| checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, Collections.emptySet()).contains(check)) {
124130
continue;
125131
}
126132
if (check instanceof PythonSubscriptionCheck) {
@@ -151,6 +157,9 @@ public boolean scanFileWithoutParsing(InputFile inputFile) {
151157
if (!check.scanWithoutParsing(inputFileContext)) {
152158
return false;
153159
}
160+
Set<PythonCheck> executedChecks = checksExecutedWithoutParsingByFiles.getOrDefault(inputFile, new HashSet<>());
161+
executedChecks.add(check);
162+
checksExecutedWithoutParsingByFiles.putIfAbsent(inputFile, executedChecks);
154163
}
155164

156165
return restoreAndPushMeasuresIfApplicable(inputFile);

sonar-python-plugin/src/test/java/org/sonar/plugins/python/PythonSensorTest.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public class PythonSensorTest {
128128
private static final String FILE_COMPLEXITY_RULE_KEY = "FileComplexity";
129129
private static final String CUSTOM_REPOSITORY_KEY = "customKey";
130130
private static final String CUSTOM_RULE_KEY = "key";
131+
private static final String RULE_CRASHING_ON_SCAN_KEY = "key2";
131132

132133
private static final Version SONARLINT_DETECTABLE_VERSION = Version.create(6, 0);
133134

@@ -141,7 +142,7 @@ public String repositoryKey() {
141142

142143
@Override
143144
public List<Class> checkClasses() {
144-
return Collections.singletonList(MyCustomRule.class);
145+
return List.of(MyCustomRule.class, RuleCrashingOnRegularScan.class);
145146
}
146147
}};
147148
private static Path workDir;
@@ -177,6 +178,24 @@ public void endOfAnalysis(CacheContext cacheContext) {
177178
}
178179
}
179180

181+
@Rule(
182+
key = RULE_CRASHING_ON_SCAN_KEY,
183+
name = "rule_crashing_on_scan",
184+
description = "desc",
185+
tags = {"bug"})
186+
public static class RuleCrashingOnRegularScan implements PythonCheck {
187+
188+
@Override
189+
public void scanFile(PythonVisitorContext visitorContext) {
190+
throw new IllegalStateException("Should not be executed!");
191+
}
192+
193+
@Override
194+
public boolean scanWithoutParsing(PythonInputFileContext inputFile) {
195+
return true;
196+
}
197+
}
198+
180199
private final File baseDir = new File("src/test/resources/org/sonar/plugins/python/sensor").getAbsoluteFile();
181200

182201
private SensorContextTester context;
@@ -1058,6 +1077,50 @@ public void read_cpd_tokens_cache_disabled() {
10581077
assertThat(tokensLines.get(0).getValue()).isEqualTo("pass");
10591078
}
10601079

1080+
@Test
1081+
public void cpd_tokens_failure_does_not_execute_checks_multiple_times() throws IOException {
1082+
activeRules = new ActiveRulesBuilder()
1083+
.addRule(new NewActiveRule.Builder()
1084+
.setRuleKey(RuleKey.of(CUSTOM_REPOSITORY_KEY, RULE_CRASHING_ON_SCAN_KEY))
1085+
.build())
1086+
.build();
1087+
1088+
InputFile inputFile = inputFile("pass.py", Type.MAIN, InputFile.Status.SAME);
1089+
1090+
TestReadCache readCache = getValidReadCache();
1091+
byte[] serializedSymbolTable = toProtobufModuleDescriptor(Collections.emptySet()).toByteArray();
1092+
readCache.put(IMPORTS_MAP_CACHE_KEY_PREFIX + inputFile.key(), String.join(";", Collections.emptyList()).getBytes(StandardCharsets.UTF_8));
1093+
readCache.put(PROJECT_SYMBOL_TABLE_CACHE_KEY_PREFIX + inputFile.key(), serializedSymbolTable);
1094+
1095+
TestWriteCache writeCache = new TestWriteCache();
1096+
writeCache.bind(readCache);
1097+
1098+
context.setPreviousCache(readCache);
1099+
context.setNextCache(writeCache);
1100+
context.setCacheEnabled(true);
1101+
context.setSettings(
1102+
new MapSettings()
1103+
.setProperty("sonar.python.skipUnchanged", true)
1104+
.setProperty("sonar.internal.analysis.failFast", true)
1105+
);
1106+
1107+
sensor().execute(context);
1108+
1109+
// Verify the written CPD tokens
1110+
List<TokensLine> tokensLines = context.cpdTokens("moduleKey:pass.py");
1111+
assertThat(tokensLines)
1112+
.isNotNull()
1113+
.hasSize(1);
1114+
1115+
assertThat(tokensLines.get(0).getValue()).isEqualTo("pass");
1116+
1117+
// Verify that we carried the tokens over to the next cache
1118+
List<Token> expectedTokens = List.of(new TokenImpl(passToken(inputFile.uri())));
1119+
1120+
assertThat(writeCache.getData())
1121+
.containsEntry(Caching.CPD_TOKENS_CACHE_KEY_PREFIX + inputFile.key(), CpdSerializer.toBytes(expectedTokens));
1122+
}
1123+
10611124
private com.sonar.sslr.api.Token passToken(URI uri) {
10621125
return com.sonar.sslr.api.Token.builder()
10631126
.setType(PythonKeyword.PASS)

0 commit comments

Comments
 (0)