Skip to content

Commit 89cb8a9

Browse files
committed
CxxPreprocessor: fix handling of SquidAstVisitorContext
* `SquidAstVisitorContext::getFile()` never returns `null` during productive execution * the only exception is preprocessing inside of preprocessor's constructor (error-prone design in my humble opinion) * Fixes: * wrong assumptions in `CxxPreprocessor` were fixed * documentation was added * poorly parametrized mocks were fixed in the unit tests * minor refactoring
1 parent ec2efd0 commit 89cb8a9

File tree

7 files changed

+113
-47
lines changed

7 files changed

+113
-47
lines changed

cxx-squid/src/main/java/org/sonar/cxx/preprocessor/CxxPreprocessor.java

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@
3535
import com.sonar.sslr.impl.Parser;
3636
import java.io.File;
3737
import java.io.IOException;
38-
import java.net.URI;
39-
import java.net.URISyntaxException;
4038
import java.nio.charset.Charset;
41-
import java.nio.file.FileSystemNotFoundException;
42-
import java.nio.file.Paths;
4339
import java.util.ArrayList;
4440
import java.util.Collection;
4541
import java.util.Collections;
@@ -49,6 +45,7 @@
4945
import java.util.LinkedList;
5046
import java.util.List;
5147
import java.util.Map;
48+
import java.util.Objects;
5249
import java.util.Set;
5350
import java.util.stream.Collectors;
5451
import javax.annotation.Nullable;
@@ -93,7 +90,6 @@ public class CxxPreprocessor extends Preprocessor {
9390

9491
private final CxxLanguage language;
9592
private File currentContextFile;
96-
private String rootFilePath;
9793

9894
private final Parser<Grammar> pplineParser;
9995
private final MapChain<String, Macro> fixedMacros = new MapChain<>();
@@ -107,6 +103,7 @@ public class CxxPreprocessor extends Preprocessor {
107103
private CxxCompilationUnitSettings compilationUnitSettings;
108104
private final Multimap<String, Include> includedFiles = HashMultimap.create();
109105
private final Multimap<String, Include> missingIncludeFiles = HashMultimap.create();
106+
private boolean ctorInProgress = true;
110107

111108
private State currentFileState = new State(null);
112109
private final Deque<State> globalStateStack = new LinkedList<>();
@@ -159,6 +156,7 @@ public CxxPreprocessor(SquidAstVisitorContext<Grammar> context,
159156
}
160157
} finally {
161158
getMacros().setHighPrio(false);
159+
ctorInProgress = false;
162160
}
163161
}
164162

@@ -456,11 +454,19 @@ public PreprocessorAction process(List<Token> tokens) { //TODO: deprecated Prepr
456454
Token token = tokens.get(0);
457455
TokenType ttype = token.getType();
458456

459-
File file = getFileUnderAnalysis();
460-
rootFilePath = file == null ? token.getURI().toString() : file.getAbsolutePath();
457+
final String rootFilePath = getFileUnderAnalysis().getAbsolutePath();
461458

462-
if (context.getFile() != currentContextFile) {
459+
// CxxPreprocessor::process() can be called a) while construction,
460+
// b) for a new "physical" file or c) for #include directive.
461+
// Make sure, that the following code is executed for a new "physical" file
462+
// only.
463+
final boolean processingNewSourceFile = !ctorInProgress && (context.getFile() != currentContextFile);
464+
465+
if (processingNewSourceFile) {
463466
currentContextFile = context.getFile();
467+
// In case "physical" file is preprocessed, SquidAstVisitorContext::getFile() cannot return null
468+
// Did you forget to setup the mock properly?
469+
Objects.requireNonNull(context.getFile(), "SquidAstVisitorContext::getFile() must be non-null");
464470
compilationUnitSettings = conf.getCompilationUnitSettings(currentContextFile.getAbsolutePath());
465471

466472
if (compilationUnitSettings != null) {
@@ -640,9 +646,8 @@ public String expandFunctionLikeMacro(String macroName, List<Token> restTokens)
640646
}
641647

642648
public Boolean expandHasIncludeExpression(AstNode exprAst) {
643-
File file = getFileUnderAnalysis();
644-
String filePath = file == null ? rootFilePath : file.getAbsolutePath();
645-
return findIncludedFile(exprAst, exprAst.getToken(), filePath) != null;
649+
final File file = getFileUnderAnalysis();
650+
return findIncludedFile(exprAst, exprAst.getToken(), file.getAbsolutePath()) != null;
646651
}
647652

648653
private boolean isCFile(String filePath) {
@@ -857,7 +862,6 @@ private Macro parseMacroDefinition(String macroDef) {
857862

858863
private File findIncludedFile(AstNode ast, Token token, String currFileName) {
859864
String includedFileName = null;
860-
File includedFile = null;
861865
boolean quoted = false;
862866

863867
AstNode node = ast.getFirstDescendant(CppGrammar.includeBodyQuoted);
@@ -908,28 +912,34 @@ private File findIncludedFile(AstNode ast, Token token, String currFileName) {
908912
}
909913

910914
if (includedFileName != null) {
911-
File file = getFileUnderAnalysis();
912-
String dir;
913-
if (file != null) {
914-
dir = file.getParent();
915-
} else {
916-
try {
917-
dir = Paths.get(new URI(currFileName)).getParent().toString();
918-
} catch (IllegalArgumentException | FileSystemNotFoundException | SecurityException | URISyntaxException e) {
919-
dir = "";
920-
}
921-
}
922-
includedFile = getCodeProvider().getSourceCodeFile(includedFileName, dir, quoted);
915+
final File file = getFileUnderAnalysis();
916+
final String dir = file.getParent();
917+
return getCodeProvider().getSourceCodeFile(includedFileName, dir, quoted);
923918
}
924919

925-
return includedFile;
920+
return null;
926921
}
927922

928923
private File getFileUnderAnalysis() {
929-
if (currentFileState.includeUnderAnalysis == null) {
930-
return context.getFile();
924+
if (ctorInProgress) {
925+
// a) CxxPreprocessor is parsing artificial #include and #define
926+
// directives in order to initialize preprocessor with default macros
927+
// and forced includes.
928+
// This code is running in constructor of CxxPreprocessor. There is no
929+
// information about the current file. Therefore return some artificial
930+
// path under the project base directory.
931+
return new File(conf.getBaseDir(), "CxxPreprocessorCtorInProgress.cpp").getAbsoluteFile();
932+
} else if (currentFileState.includeUnderAnalysis != null) {
933+
// b) CxxPreprocessor is called recursively in order to parse the #include
934+
// directive. Return path to the included file.
935+
return currentFileState.includeUnderAnalysis;
931936
}
932-
return currentFileState.includeUnderAnalysis;
937+
938+
// c) CxxPreprocessor is called in the ordinary mode: it is preprocessing the
939+
// file, tracked in org.sonar.squidbridge.SquidAstVisitorContext. This file cannot
940+
// be null. If it is null - you forgot to setup the test mock.
941+
Objects.requireNonNull(context.getFile(), "SquidAstVisitorContext::getFile() must be non-null");
942+
return context.getFile();
933943
}
934944

935945
PreprocessorAction handleIfLine(AstNode ast, Token token, String filename) {

cxx-squid/src/main/java/org/sonar/cxx/preprocessor/MapChain.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public V removeLowPrio(K key) {
9090
*/
9191
public void clearLowPrio() {
9292
lowPrioMap.clear();
93+
lowPrioDisabled.clear();
9394
}
9495

9596
/**

cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
package org.sonar.cxx.lexer;
2121

2222
import com.sonar.sslr.api.GenericTokenType;
23+
import com.sonar.sslr.api.Grammar;
2324
import com.sonar.sslr.impl.Lexer;
25+
26+
import java.io.File;
2427
import java.net.URISyntaxException;
2528
import java.util.ArrayList;
2629
import java.util.Arrays;
@@ -30,6 +33,8 @@
3033
import org.junit.BeforeClass;
3134
import org.junit.Test;
3235
import static org.mockito.Mockito.mock;
36+
import static org.mockito.Mockito.when;
37+
3338
import org.sonar.cxx.CxxFileTesterHelper;
3439
import org.sonar.cxx.CxxLanguage;
3540
import org.sonar.cxx.api.CxxKeyword;
@@ -46,8 +51,12 @@ public class CxxLexerTest {
4651

4752
@BeforeClass
4853
public static void init() {
54+
File file = new File("snippet.cpp").getAbsoluteFile();
55+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
56+
when(context.getFile()).thenReturn(file);
57+
4958
CxxLanguage language = CxxFileTesterHelper.mockCxxLanguage();
50-
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), language);
59+
CxxPreprocessor cxxpp = new CxxPreprocessor(context, language);
5160
lexer = CxxLexer.create(cxxpp, new JoinStringsPreprocessor());
5261
}
5362

cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerWithPreprocessingTest.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,16 @@ public class CxxLexerWithPreprocessingTest {
5252

5353
private static Lexer lexer;
5454
private final CxxLanguage language;
55+
private final SquidAstVisitorContext<Grammar> context;
5556

5657
public CxxLexerWithPreprocessingTest() {
5758
language = CxxFileTesterHelper.mockCxxLanguage();
58-
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), language);
59+
60+
File file = new File("snippet.cpp").getAbsoluteFile();
61+
context = mock(SquidAstVisitorContext.class);
62+
when(context.getFile()).thenReturn(file);
63+
64+
CxxPreprocessor cxxpp = new CxxPreprocessor(context, language);
5965
lexer = CxxLexer.create(cxxpp, new JoinStringsPreprocessor());
6066
}
6167

@@ -331,7 +337,7 @@ public void bodyless_defines() {
331337
public void external_define() {
332338
CxxConfiguration conf = new CxxConfiguration();
333339
conf.setDefines(new String[]{"M body"});
334-
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), conf, language);
340+
CxxPreprocessor cxxpp = new CxxPreprocessor(context, conf, language);
335341
lexer = CxxLexer.create(conf, cxxpp, new JoinStringsPreprocessor());
336342

337343
List<Token> tokens = lexer.lex("M");
@@ -345,7 +351,7 @@ public void external_define() {
345351
public void external_defines_with_params() {
346352
CxxConfiguration conf = new CxxConfiguration();
347353
conf.setDefines(new String[]{"minus(a, b) a - b"});
348-
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), conf, language);
354+
CxxPreprocessor cxxpp = new CxxPreprocessor(context, conf, language);
349355
lexer = CxxLexer.create(conf, cxxpp, new JoinStringsPreprocessor());
350356

351357
List<Token> tokens = lexer.lex("minus(1, 2)");
@@ -612,7 +618,7 @@ public void ignore_irrelevant_preprocessor_directives() {
612618
public void externalMacrosCannotBeOverriden() {
613619
CxxConfiguration conf = mock(CxxConfiguration.class);
614620
when(conf.getDefines()).thenReturn(Arrays.asList("name goodvalue"));
615-
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), conf, language);
621+
CxxPreprocessor cxxpp = new CxxPreprocessor(context, conf, language);
616622
lexer = CxxLexer.create(conf, cxxpp);
617623

618624
List<Token> tokens = lexer.lex("#define name badvalue\n"

cxx-squid/src/test/java/org/sonar/cxx/parser/CxxParserTest.java

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121

2222
import com.sonar.sslr.api.AstNode;
2323
import com.sonar.sslr.api.Grammar;
24+
import com.sonar.sslr.impl.Parser;
25+
2426
import java.io.File;
2527
import java.net.URISyntaxException;
2628
import java.util.ArrayList;
2729
import java.util.Arrays;
28-
import java.util.Collection;
2930
import java.util.HashMap;
3031
import java.util.List;
3132
import org.apache.commons.io.FileUtils;
@@ -38,7 +39,7 @@
3839
import org.sonar.cxx.CxxFileTesterHelper;
3940
import org.sonar.squidbridge.SquidAstVisitorContext;
4041

41-
public class CxxParserTest extends ParserBaseTestHelper {
42+
public class CxxParserTest {
4243

4344
String errSources = "/parser/bad/error_recovery_declaration.cc";
4445
String[] goodFiles = {"own", "VC", "cli", "cuda", "examples"};
@@ -54,7 +55,7 @@ public CxxParserTest() throws URISyntaxException {
5455

5556
@Test
5657
public void testParsingOnDiverseSourceFiles() {
57-
Collection<File> files = listFiles(goodFiles, new String[]{"cc", "cpp", "hpp"});
58+
List<File> files = listFiles(goodFiles, new String[]{"cc", "cpp", "hpp"});
5859
HashMap<String, Integer> map = new HashMap<String, Integer>() {
5960
private static final long serialVersionUID = 6029310517902718597L;
6061

@@ -76,7 +77,15 @@ public void testParsingOnDiverseSourceFiles() {
7677
put("outbuf2.cpp", 2);
7778
}
7879
};
80+
81+
CxxConfiguration conf = new CxxConfiguration();
82+
conf.setErrorRecoveryEnabled(false);
83+
84+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
85+
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
86+
7987
for (File file : files) {
88+
when(context.getFile()).thenReturn(file);
8089
AstNode root = p.parse(file);
8190
CxxParser.finishedParsing(file);
8291
if (map.containsKey(file.getName())) {
@@ -90,7 +99,7 @@ public void testParsingOnDiverseSourceFiles() {
9099
@SuppressWarnings("unchecked")
91100
@Test
92101
public void testPreproccessorParsingOnDiverseSourceFiles() {
93-
conf = new CxxConfiguration();
102+
CxxConfiguration conf = new CxxConfiguration();
94103
conf.setErrorRecoveryEnabled(false);
95104
String baseDir = new File("src/test").getAbsolutePath();
96105
conf.setBaseDir(baseDir);
@@ -118,9 +127,11 @@ public void testPreproccessorParsingOnDiverseSourceFiles() {
118127
}
119128
};
120129

121-
p = CxxParser.create(mock(SquidAstVisitorContext.class), conf, CxxFileTesterHelper.mockCxxLanguage());
122-
Collection<File> files = listFiles(preprocessorFiles, new String[]{"cc", "cpp", "hpp", "h"});
130+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
131+
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
132+
List<File> files = listFiles(preprocessorFiles, new String[]{"cc", "cpp", "hpp", "h"});
123133
for (File file : files) {
134+
when(context.getFile()).thenReturn(file);
124135
AstNode root = p.parse(file);
125136
CxxParser.finishedParsing(file);
126137
if (map.containsKey(file.getName())) {
@@ -139,13 +150,15 @@ public void testParsingInCCompatMode() { //ToDo: Fix this compatibility test - c
139150
// This mode works if such a file causes parsing errors when the mode
140151
// is switched off and doesn't, if the mode is switched on.
141152

142-
File cfile = (File) listFiles(cCompatibilityFiles, new String[]{"c"}).toArray()[0];
153+
File cfile = listFiles(cCompatibilityFiles, new String[]{"c"}).get(0);
143154

144155
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
145156
when(context.getFile()).thenReturn(cfile);
146157

158+
CxxConfiguration conf = new CxxConfiguration();
159+
conf.setErrorRecoveryEnabled(false);
147160
conf.setCFilesPatterns(new String[]{""});
148-
p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
161+
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
149162

150163
AstNode root0 = p.parse(cfile);
151164
assertThat(root0.getNumberOfChildren()).isEqualTo(2);
@@ -158,6 +171,14 @@ public void testParsingInCCompatMode() { //ToDo: Fix this compatibility test - c
158171

159172
@Test
160173
public void testParseErrorRecoveryDisabled() {
174+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
175+
when(context.getFile()).thenReturn(erroneousSources);
176+
177+
CxxConfiguration conf = new CxxConfiguration();
178+
conf.setErrorRecoveryEnabled(false);
179+
180+
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
181+
161182
// The error recovery works, if:
162183
// - a syntacticly incorrect file causes a parse error when recovery is disabled
163184
assertThatThrownBy(() -> {
@@ -168,15 +189,19 @@ public void testParseErrorRecoveryDisabled() {
168189
@SuppressWarnings("unchecked")
169190
@Test
170191
public void testParseErrorRecoveryEnabled() {
192+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
193+
when(context.getFile()).thenReturn(erroneousSources);
194+
171195
// The error recovery works, if:
172196
// - but doesn't cause such an error if we run with default settings
197+
CxxConfiguration conf = new CxxConfiguration();
173198
conf.setErrorRecoveryEnabled(true);
174-
p = CxxParser.create(mock(SquidAstVisitorContext.class), conf, CxxFileTesterHelper.mockCxxLanguage());
199+
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
175200
AstNode root = p.parse(erroneousSources); //<-- this shouldn't throw now
176201
assertThat(root.getNumberOfChildren()).isEqualTo(6);
177202
}
178203

179-
private Collection<File> listFiles(String[] dirs, String[] extensions) {
204+
private List<File> listFiles(String[] dirs, String[] extensions) {
180205
List<File> files = new ArrayList<>();
181206
for (String dir : dirs) {
182207
files.addAll(FileUtils.listFiles(new File(rootDir, dir), extensions, true));

cxx-squid/src/test/java/org/sonar/cxx/parser/ParserBaseTestHelper.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,24 @@
1919
*/
2020
package org.sonar.cxx.parser;
2121

22-
import com.sonar.sslr.api.Grammar;
23-
import com.sonar.sslr.impl.Parser;
2422
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.when;
24+
25+
import java.io.File;
26+
2527
import org.sonar.cxx.CxxConfiguration;
2628
import org.sonar.cxx.CxxFileTesterHelper;
2729
import org.sonar.squidbridge.SquidAstVisitorContext;
2830
import org.sonar.sslr.grammar.GrammarRuleKey;
2931

32+
import com.sonar.sslr.api.Grammar;
33+
import com.sonar.sslr.impl.Parser;
34+
35+
/**
36+
* SquidAstVisitorContext is mock with a fake file path. You can use this base
37+
* class for preprocessing tokens. You shouldn't use it for preprocessing
38+
* "physical" files.
39+
*/
3040
public class ParserBaseTestHelper {
3141

3242
protected CxxConfiguration conf = null;
@@ -36,7 +46,12 @@ public class ParserBaseTestHelper {
3646
public ParserBaseTestHelper() {
3747
conf = new CxxConfiguration();
3848
conf.setErrorRecoveryEnabled(false);
39-
p = CxxParser.create(mock(SquidAstVisitorContext.class), conf, CxxFileTesterHelper.mockCxxLanguage());
49+
50+
File file = new File("snippet.cpp").getAbsoluteFile();
51+
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
52+
when(context.getFile()).thenReturn(file);
53+
54+
p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
4055
g = p.getGrammar();
4156
}
4257

0 commit comments

Comments
 (0)