Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CxxPreprocessor: fix handling of SquidAstVisitorContext #1645

Merged
merged 2 commits into from
Dec 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@
import com.sonar.sslr.impl.Parser;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -49,6 +45,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -93,7 +90,6 @@ public class CxxPreprocessor extends Preprocessor {

private final CxxLanguage language;
private File currentContextFile;
private String rootFilePath;

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

private State currentFileState = new State(null);
private final Deque<State> globalStateStack = new LinkedList<>();
Expand Down Expand Up @@ -159,6 +156,7 @@ public CxxPreprocessor(SquidAstVisitorContext<Grammar> context,
}
} finally {
getMacros().setHighPrio(false);
ctorInProgress = false;
}
}

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

File file = getFileUnderAnalysis();
rootFilePath = file == null ? token.getURI().toString() : file.getAbsolutePath();
final String rootFilePath = getFileUnderAnalysis().getAbsolutePath();

if (context.getFile() != currentContextFile) {
// CxxPreprocessor::process() can be called a) while construction,
// b) for a new "physical" file or c) for #include directive.
// Make sure, that the following code is executed for a new "physical" file
// only.
final boolean processingNewSourceFile = !ctorInProgress && (context.getFile() != currentContextFile);

if (processingNewSourceFile) {
currentContextFile = context.getFile();
// In case "physical" file is preprocessed, SquidAstVisitorContext::getFile() cannot return null
// Did you forget to setup the mock properly?
Objects.requireNonNull(context.getFile(), "SquidAstVisitorContext::getFile() must be non-null");
compilationUnitSettings = conf.getCompilationUnitSettings(currentContextFile.getAbsolutePath());

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

public Boolean expandHasIncludeExpression(AstNode exprAst) {
File file = getFileUnderAnalysis();
String filePath = file == null ? rootFilePath : file.getAbsolutePath();
return findIncludedFile(exprAst, exprAst.getToken(), filePath) != null;
final File file = getFileUnderAnalysis();
return findIncludedFile(exprAst, exprAst.getToken(), file.getAbsolutePath()) != null;
}

private boolean isCFile(String filePath) {
Expand Down Expand Up @@ -857,7 +862,6 @@ private Macro parseMacroDefinition(String macroDef) {

private File findIncludedFile(AstNode ast, Token token, String currFileName) {
String includedFileName = null;
File includedFile = null;
boolean quoted = false;

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

if (includedFileName != null) {
File file = getFileUnderAnalysis();
String dir;
if (file != null) {
dir = file.getParent();
} else {
try {
dir = Paths.get(new URI(currFileName)).getParent().toString();
} catch (IllegalArgumentException | FileSystemNotFoundException | SecurityException | URISyntaxException e) {
dir = "";
}
}
includedFile = getCodeProvider().getSourceCodeFile(includedFileName, dir, quoted);
final File file = getFileUnderAnalysis();
final String dir = file.getParent();
return getCodeProvider().getSourceCodeFile(includedFileName, dir, quoted);
}

return includedFile;
return null;
}

private File getFileUnderAnalysis() {
if (currentFileState.includeUnderAnalysis == null) {
return context.getFile();
if (ctorInProgress) {
// a) CxxPreprocessor is parsing artificial #include and #define
// directives in order to initialize preprocessor with default macros
// and forced includes.
// This code is running in constructor of CxxPreprocessor. There is no
// information about the current file. Therefore return some artificial
// path under the project base directory.
return new File(conf.getBaseDir(), "CxxPreprocessorCtorInProgress.cpp").getAbsoluteFile();
} else if (currentFileState.includeUnderAnalysis != null) {
// b) CxxPreprocessor is called recursively in order to parse the #include
// directive. Return path to the included file.
return currentFileState.includeUnderAnalysis;
}
return currentFileState.includeUnderAnalysis;

// c) CxxPreprocessor is called in the ordinary mode: it is preprocessing the
// file, tracked in org.sonar.squidbridge.SquidAstVisitorContext. This file cannot
// be null. If it is null - you forgot to setup the test mock.
Objects.requireNonNull(context.getFile(), "SquidAstVisitorContext::getFile() must be non-null");
return context.getFile();
}

PreprocessorAction handleIfLine(AstNode ast, Token token, String filename) {
Expand Down Expand Up @@ -1068,7 +1078,7 @@ PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename,
File includedFile = findIncludedFile(ast, token, filename);

File currentFile = this.getFileUnderAnalysis();
if (currentFile != null && includedFile != null) {
if (includedFile != null) {
includedFiles.put(currentFile.getPath(), new Include(token.getLine(), includedFile.getAbsolutePath()));
}

Expand All @@ -1077,9 +1087,7 @@ PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename,
if (LOG.isDebugEnabled()) {
LOG.debug("[" + filename + ":" + token.getLine() + "]: cannot find include file '" + token.getValue() + "'");
}
if (currentFile != null) {
missingIncludeFiles.put(currentFile.getPath(), new Include(token.getLine(), token.getValue()));
}
missingIncludeFiles.put(currentFile.getPath(), new Include(token.getLine(), token.getValue()));
} else if (analysedFiles.add(includedFile.getAbsoluteFile())) {
if (LOG.isTraceEnabled()) {
LOG.trace("[{}:{}]: processing {}, resolved to file '{}'",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public V removeLowPrio(K key) {
*/
public void clearLowPrio() {
lowPrioMap.clear();
lowPrioDisabled.clear();
}

/**
Expand Down
11 changes: 10 additions & 1 deletion cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
package org.sonar.cxx.lexer;

import com.sonar.sslr.api.GenericTokenType;
import com.sonar.sslr.api.Grammar;
import com.sonar.sslr.impl.Lexer;

import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -30,6 +33,8 @@
import org.junit.BeforeClass;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.sonar.cxx.CxxFileTesterHelper;
import org.sonar.cxx.CxxLanguage;
import org.sonar.cxx.api.CxxKeyword;
Expand All @@ -46,8 +51,12 @@ public class CxxLexerTest {

@BeforeClass
public static void init() {
File file = new File("snippet.cpp").getAbsoluteFile();
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
when(context.getFile()).thenReturn(file);

CxxLanguage language = CxxFileTesterHelper.mockCxxLanguage();
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), language);
CxxPreprocessor cxxpp = new CxxPreprocessor(context, language);
lexer = CxxLexer.create(cxxpp, new JoinStringsPreprocessor());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,16 @@ public class CxxLexerWithPreprocessingTest {

private static Lexer lexer;
private final CxxLanguage language;
private final SquidAstVisitorContext<Grammar> context;

public CxxLexerWithPreprocessingTest() {
language = CxxFileTesterHelper.mockCxxLanguage();
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), language);

File file = new File("snippet.cpp").getAbsoluteFile();
context = mock(SquidAstVisitorContext.class);
when(context.getFile()).thenReturn(file);

CxxPreprocessor cxxpp = new CxxPreprocessor(context, language);
lexer = CxxLexer.create(cxxpp, new JoinStringsPreprocessor());
}

Expand Down Expand Up @@ -331,7 +337,7 @@ public void bodyless_defines() {
public void external_define() {
CxxConfiguration conf = new CxxConfiguration();
conf.setDefines(new String[]{"M body"});
CxxPreprocessor cxxpp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), conf, language);
CxxPreprocessor cxxpp = new CxxPreprocessor(context, conf, language);
lexer = CxxLexer.create(conf, cxxpp, new JoinStringsPreprocessor());

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

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

List<Token> tokens = lexer.lex("#define name badvalue\n"
Expand Down
45 changes: 35 additions & 10 deletions cxx-squid/src/test/java/org/sonar/cxx/parser/CxxParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@

import com.sonar.sslr.api.AstNode;
import com.sonar.sslr.api.Grammar;
import com.sonar.sslr.impl.Parser;

import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.io.FileUtils;
Expand All @@ -38,7 +39,7 @@
import org.sonar.cxx.CxxFileTesterHelper;
import org.sonar.squidbridge.SquidAstVisitorContext;

public class CxxParserTest extends ParserBaseTestHelper {
public class CxxParserTest {

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

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

Expand All @@ -76,7 +77,15 @@ public void testParsingOnDiverseSourceFiles() {
put("outbuf2.cpp", 2);
}
};

CxxConfiguration conf = new CxxConfiguration();
conf.setErrorRecoveryEnabled(false);

SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());

for (File file : files) {
when(context.getFile()).thenReturn(file);
AstNode root = p.parse(file);
CxxParser.finishedParsing(file);
if (map.containsKey(file.getName())) {
Expand All @@ -90,7 +99,7 @@ public void testParsingOnDiverseSourceFiles() {
@SuppressWarnings("unchecked")
@Test
public void testPreproccessorParsingOnDiverseSourceFiles() {
conf = new CxxConfiguration();
CxxConfiguration conf = new CxxConfiguration();
conf.setErrorRecoveryEnabled(false);
String baseDir = new File("src/test").getAbsolutePath();
conf.setBaseDir(baseDir);
Expand Down Expand Up @@ -118,9 +127,11 @@ public void testPreproccessorParsingOnDiverseSourceFiles() {
}
};

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

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

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

CxxConfiguration conf = new CxxConfiguration();
conf.setErrorRecoveryEnabled(false);
conf.setCFilesPatterns(new String[]{""});
p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());

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

@Test
public void testParseErrorRecoveryDisabled() {
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
when(context.getFile()).thenReturn(erroneousSources);

CxxConfiguration conf = new CxxConfiguration();
conf.setErrorRecoveryEnabled(false);

Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());

// The error recovery works, if:
// - a syntacticly incorrect file causes a parse error when recovery is disabled
assertThatThrownBy(() -> {
Expand All @@ -168,15 +189,19 @@ public void testParseErrorRecoveryDisabled() {
@SuppressWarnings("unchecked")
@Test
public void testParseErrorRecoveryEnabled() {
SquidAstVisitorContext<Grammar> context = mock(SquidAstVisitorContext.class);
when(context.getFile()).thenReturn(erroneousSources);

// The error recovery works, if:
// - but doesn't cause such an error if we run with default settings
CxxConfiguration conf = new CxxConfiguration();
conf.setErrorRecoveryEnabled(true);
p = CxxParser.create(mock(SquidAstVisitorContext.class), conf, CxxFileTesterHelper.mockCxxLanguage());
Parser<Grammar> p = CxxParser.create(context, conf, CxxFileTesterHelper.mockCxxLanguage());
AstNode root = p.parse(erroneousSources); //<-- this shouldn't throw now
assertThat(root.getNumberOfChildren()).isEqualTo(6);
}

private Collection<File> listFiles(String[] dirs, String[] extensions) {
private List<File> listFiles(String[] dirs, String[] extensions) {
List<File> files = new ArrayList<>();
for (String dir : dirs) {
files.addAll(FileUtils.listFiles(new File(rootDir, dir), extensions, true));
Expand Down
Loading