diff --git a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/CxxPreprocessor.java b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/CxxPreprocessor.java index 9a1dd0324a..b1cd7610a9 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/CxxPreprocessor.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/CxxPreprocessor.java @@ -35,7 +35,6 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; -import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -78,21 +77,16 @@ public class CxxPreprocessor extends Preprocessor { private final SquidAstVisitorContext context; private final CxxSquidConfiguration squidConfig; - private boolean ctorInProgress = true; - private final SourceCodeProvider mockCodeProvider; - private final MapChain globalMacros = new MapChain<>(); - private final Deque globalStateStack = new LinkedList<>(); + + private MapChain unitMacros = null; private SourceCodeProvider unitCodeProvider; - private MapChain unitMacros; private File currentContextFile; - private State currentFileState = new State(null); private final Set analysedFiles = new HashSet<>(); private final Parser pplineParser; - private static final String EVALUATED_TO_FALSE = "[{}:{}]: '{}' evaluated to false, skipping tokens that follow"; private static final String MISSING_INCLUDE_MSG = "Preprocessor: {} include directive error(s). " + "This is only relevant if parser creates syntax errors." @@ -121,23 +115,109 @@ public CxxPreprocessor(SquidAstVisitorContext context, this.squidConfig = squidConfig; this.mockCodeProvider = mockCodeProvider; pplineParser = CppParser.create(squidConfig); - initPredefinedMacros(); if (this.mockCodeProvider != null) { this.mockCodeProvider.setIncludeRoots( - squidConfig.getValues(CxxSquidConfiguration.GLOBAL, CxxSquidConfiguration.INCLUDE_DIRECTORIES), + this.squidConfig.getValues(CxxSquidConfiguration.GLOBAL, CxxSquidConfiguration.INCLUDE_DIRECTORIES), + this.squidConfig.getBaseDir()); + } + + addPredefinedMacros(); + } + + /** + * Method called before the lexing starts which can be overridden to initialize a state for instance. + * + * Method can be called: + * a) for a new "physical" file (including SquidAstVisitorContext mock) + * b) while processing of #include directive. + */ + @Override + public void init() { + // make sure, that the following code is executed for a new file only + if (currentContextFile != context.getFile()) { + + // In case "physical" file is preprocessed, SquidAstVisitorContext::getFile() cannot return null. + // Did you forget to setup the mock properly? + currentContextFile = context.getFile(); + Objects.requireNonNull(currentContextFile, "SquidAstVisitorContext::getFile() must be non-null"); + + unitCodeProvider = new SourceCodeProvider(); + + // unit specific include directories + unitCodeProvider.setIncludeRoots( + squidConfig.getValues(currentContextFile.getAbsolutePath(), CxxSquidConfiguration.INCLUDE_DIRECTORIES), squidConfig.getBaseDir()); + + // unit specific macros + unitMacros = new MapChain<>(); + addUnitMacros(); + parseForcedIncludes(); } - try { - globalMacros.setHighPrio(true); - // PREDEFINED_MACROS & GLOBAL - var defines = squidConfig.getValues(CxxSquidConfiguration.GLOBAL, CxxSquidConfiguration.DEFINES); - Collections.reverse(defines); - globalMacros.putAll(parseMacroDefinitions(defines)); - } finally { - globalMacros.setHighPrio(false); + } + + @Override + public PreprocessorAction process(List tokens) { //TODO: deprecated PreprocessorAction + Token token = tokens.get(0); + TokenType ttype = token.getType(); + String rootFilePath = getFileUnderAnalysis().getAbsolutePath(); + + if (ttype.equals(PREPROCESSOR)) { + + AstNode lineAst; + try { + lineAst = pplineParser.parse(token.getValue()).getFirstChild(); + } catch (com.sonar.sslr.api.RecognitionException e) { + LOG.warn("Cannot parse '{}', ignoring...", token.getValue()); + LOG.debug("Parser exception: '{}'", e); + return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), + new ArrayList<>()); + } + + AstNodeType lineKind = lineAst.getType(); + + if (lineKind.equals(ifdefLine)) { + return handleIfdefLine(lineAst, token, rootFilePath); + } else if (lineKind.equals(ifLine)) { + return handleIfLine(lineAst, token, rootFilePath); + } else if (lineKind.equals(endifLine)) { + return handleEndifLine(token, rootFilePath); + } else if (lineKind.equals(elseLine)) { + return handleElseLine(token, rootFilePath); + } else if (lineKind.equals(elifLine)) { + return handleElIfLine(lineAst, token, rootFilePath); + } + + if (unitCodeProvider.doSkipBlock()) { + return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), + new ArrayList<>()); + } + + if (lineKind.equals(defineLine)) { + return handleDefineLine(lineAst, token, rootFilePath); + } else if (lineKind.equals(includeLine)) { + return handleIncludeLine(lineAst, token, rootFilePath, squidConfig.getCharset()); + } else if (lineKind.equals(undefLine)) { + return handleUndefLine(lineAst, token); + } + + // Ignore all other preprocessor directives (which are not handled explicitly) and strip them from the stream + return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), + new ArrayList<>()); } - ctorInProgress = false; + + if (!ttype.equals(EOF)) { + if (unitCodeProvider.doSkipBlock()) { + return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), + new ArrayList<>()); + } + + if (!ttype.equals(STRING) && !ttype.equals(NUMBER)) { + return handleIdentifiersAndKeywords(tokens, token, rootFilePath); + } + } + + return PreprocessorAction.NO_OPERATION; } public static void finalReport() { @@ -421,117 +501,16 @@ private static String stripQuotes(String str) { return str.substring(1, str.length() - 1); } - /** - * Method called before the lexing starts which can be overridden to initialize a state for instance. - * - * Method can be called: - * a) while construction, - * b) for a new "physical" file or - * c) while processing of #include directive. - */ - @Override - public void init() { - // Make sure, that the following code is executed for a new "physical" file only. - 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"); - - unitCodeProvider = new SourceCodeProvider(); - unitCodeProvider.setIncludeRoots( - squidConfig.getValues(currentContextFile.getAbsolutePath(), CxxSquidConfiguration.INCLUDE_DIRECTORIES), - squidConfig.getBaseDir()); - - unitMacros = new MapChain<>(); - var defines = squidConfig.getValues(currentContextFile.getAbsolutePath(), CxxSquidConfiguration.DEFINES); - Collections.reverse(defines); - unitMacros.putAll(parseMacroDefinitions(defines)); - parseForcedIncludes(); - } - } - - @Override - public PreprocessorAction process(List tokens) { //TODO: deprecated PreprocessorAction - final Token token = tokens.get(0); - final TokenType ttype = token.getType(); - final String rootFilePath = getFileUnderAnalysis().getAbsolutePath(); - - if (ttype.equals(PREPROCESSOR)) { - - AstNode lineAst; - try { - lineAst = pplineParser.parse(token.getValue()).getFirstChild(); - } catch (com.sonar.sslr.api.RecognitionException e) { - LOG.warn("Cannot parse '{}', ignoring...", token.getValue()); - LOG.debug("Parser exception: '{}'", e); - return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), - new ArrayList<>()); - } - - AstNodeType lineKind = lineAst.getType(); - - if (lineKind.equals(ifdefLine)) { - return handleIfdefLine(lineAst, token, rootFilePath); - } else if (lineKind.equals(ifLine)) { - return handleIfLine(lineAst, token, rootFilePath); - } else if (lineKind.equals(endifLine)) { - return handleEndifLine(token, rootFilePath); - } else if (lineKind.equals(elseLine)) { - return handleElseLine(token, rootFilePath); - } else if (lineKind.equals(elifLine)) { - return handleElIfLine(lineAst, token, rootFilePath); - } - - if (currentFileState.skipPreprocessorDirectives) { - return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), - new ArrayList<>()); - } - - if (lineKind.equals(defineLine)) { - return handleDefineLine(lineAst, token, rootFilePath); - } else if (lineKind.equals(includeLine)) { - return handleIncludeLine(lineAst, token, rootFilePath, squidConfig.getCharset()); - } else if (lineKind.equals(undefLine)) { - return handleUndefLine(lineAst, token); - } - - // Ignore all other preprocessor directives (which are not handled explicitly) - // and strip them from the stream - return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), - new ArrayList<>()); - } - - if (!ttype.equals(EOF)) { - if (currentFileState.skipPreprocessorDirectives) { - return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), - new ArrayList<>()); - } - - if (!ttype.equals(STRING) && !ttype.equals(NUMBER)) { - return handleIdentifiersAndKeywords(tokens, token, rootFilePath); - } - } - - return PreprocessorAction.NO_OPERATION; - } - public void finishedPreprocessing(File file) { // From 16.3.5 "Scope of macro definitions": - // A macro definition lasts (independent of block structure) until - // a corresponding #undef directive is encountered or (if none - // is encountered) until the end of the translation unit. + // A macro definition lasts (independent of block structure) until a corresponding #undef directive is encountered + // or (if none is encountered) until the end of the translation unit. LOG.debug("finished preprocessing '{}'", file); analysedFiles.clear(); - globalMacros.clearLowPrio(); unitMacros = null; unitCodeProvider = null; - currentFileState.reset(); currentContextFile = null; } @@ -539,12 +518,8 @@ public SourceCodeProvider getCodeProvider() { return mockCodeProvider != null ? mockCodeProvider : unitCodeProvider; } - public final MapChain getMacros() { - return unitMacros != null ? unitMacros : globalMacros; - } - - public final Macro getMacro(String macroname) { - return getMacros().get(macroname); + public Macro getMacro(String macroname) { + return unitMacros.get(macroname); } public String valueOf(String macroname) { @@ -563,7 +538,7 @@ public String expandFunctionLikeMacro(String macroName, List restTokens) } public Boolean expandHasIncludeExpression(AstNode exprAst) { - final File file = getFileUnderAnalysis(); + File file = getFileUnderAnalysis(); return findIncludedFile(exprAst, exprAst.getToken(), file.getAbsolutePath()) != null; } @@ -571,7 +546,7 @@ public Boolean expandHasIncludeExpression(AstNode exprAst) { * This is a collection of standard macros according to * http://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html */ - private void initPredefinedMacros() { + private void addPredefinedMacros() { String[] predefinedMacros = { "__FILE__ \"file\"", "__LINE__ 1", @@ -591,19 +566,25 @@ private void initPredefinedMacros() { } } + private void addUnitMacros() { + var defines = squidConfig.getValues(currentContextFile.getAbsolutePath(), CxxSquidConfiguration.DEFINES); + Collections.reverse(defines); + unitMacros.putAll(parseMacroDefinitions(defines)); + } + private PreprocessorAction handleIfdefLine(AstNode ast, Token token, String filename) { - if (!currentFileState.skipPreprocessorDirectives) { + if (unitCodeProvider.doNotSkipBlock()) { Macro macro = getMacro(getMacroName(ast)); TokenType tokType = ast.getToken().getType(); if ((tokType.equals(IFDEF) && macro == null) || (tokType.equals(IFNDEF) && macro != null)) { - LOG.trace(EVALUATED_TO_FALSE, filename, token.getLine(), token.getValue()); - currentFileState.skipPreprocessorDirectives = true; + // evaluated to false + unitCodeProvider.skipBlock(true); } - if (!currentFileState.skipPreprocessorDirectives) { - currentFileState.conditionWasTrue = true; + if (unitCodeProvider.doNotSkipBlock()) { + unitCodeProvider.expressionWas(true); } } else { - currentFileState.conditionalInclusionCounter++; + unitCodeProvider.nestedBlock(+1); } return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), @@ -644,8 +625,7 @@ private int expandFunctionLikeMacro(String macroName, List restTokens, Li } /** - * Parse the configured forced includes and store into the macro library. Current macro library depends on the return - * value of CxxPreprocessor#getMacros() + * Parse the configured forced includes and store into the macro library. */ private void parseForcedIncludes() { for (var include : squidConfig.getValues(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, @@ -661,16 +641,15 @@ private void parseForcedIncludes() { private List expandMacro(String macroName, String macroExpression) { // C++ standard 16.3.4/2 Macro Replacement - Rescanning and further replacement List tokens = null; - getMacros().disable(macroName); + unitMacros.disable(macroName); try { tokens = stripEOF(CxxLexer.create(this).lex(macroExpression)); } finally { - getMacros().enable(macroName); + unitMacros.enable(macroName); } - // make sure that all expanded Tokens are marked as generated - // it will prevent them from being involved into NCLOC / complexity / - // highlighting + // make sure that all expanded Tokens are marked as generated it will prevent them from being involved into + // NCLOC / complexity / highlighting for (var token : tokens) { if (!token.isGeneratedCode()) { token = Token.builder(token).setGeneratedCode(true).build(); @@ -681,8 +660,8 @@ private List expandMacro(String macroName, String macroExpression) { } private List replaceParams(List body, List parameters, List arguments) { - // Replace all parameters by according arguments - // "Stringify" the argument if the according parameter is preceded by an # + // replace all parameters by according arguments "Stringify" the argument if the according parameter is + // preceded by an # var newTokens = new ArrayList(); if (!body.isEmpty()) { @@ -703,9 +682,8 @@ private List replaceParams(List body, List parameters, List } newTokens.add(curr); } else if (index == arguments.size()) { - // EXTENSION: GCC's special meaning of token paste operator - // If variable argument is left out then the comma before the paste - // operator will be deleted + // EXTENSION: GCC's special meaning of token paste operator: + // If variable argument is left out then the comma before the paste operator will be deleted. int j = i; while (j > 0 && body.get(j - 1).getType().equals(WS)) { j--; @@ -745,20 +723,17 @@ private List replaceParams(List body, List parameters, List tokenPastingRightOp = false; } else { if (i > 0 && "#".equals(body.get(i - 1).getValue())) { - // If the token is a macro, the macro is not expanded - the macro - // name is converted into a string. + // if the token is a macro, the macro is not expanded - the macro name is converted into a string newTokens.remove(newTokens.size() - 1); newValue = encloseWithQuotes(quote(replacement.getValue())); } else { - // otherwise the arguments have to be fully expanded before - // expanding the body of the macro + // otherwise the arguments have to be fully expanded before expanding the body of the macro newValue = serialize(expandMacro("", replacement.getValue())); } } if (newValue.isEmpty() && VARIADICPARAMETER.equals(curr.getValue())) { - // the Visual C++ implementation will suppress a trailing comma - // if no arguments are passed to the ellipsis + // the Visual C++ implementation will suppress a trailing comma if no arguments are passed to the ellipsis for (var n = newTokens.size() - 1; n != 0; n = newTokens.size() - 1) { if (newTokens.get(n).getType().equals(WS)) { newTokens.remove(n); @@ -814,10 +789,10 @@ private Map parseMacroDefinitions(List defines) { continue; } - final String defineString = "#define " + define; + String defineString = "#define " + define; LOG.debug("parsing external macro: '{}'", defineString); - final Macro macro = parseMacroDefinition(defineString); + Macro macro = parseMacroDefinition(defineString); if (macro != null) { LOG.debug("storing external macro: '{}'", macro); @@ -853,9 +828,6 @@ private File findIncludedFile(AstNode ast, Token token, String currFileName) { // expand and recurse String includeBody = serialize(stripEOF(node.getTokens()), ""); String expandedIncludeBody = serialize(stripEOF(CxxLexer.create(this).lex(includeBody)), ""); - LOG - .trace("Include resolve macros: includeBody '{}' - expandedIncludeBody: '{}'", includeBody, expandedIncludeBody); - boolean parseError = false; AstNode includeBodyAst = null; try { @@ -876,8 +848,8 @@ private File findIncludedFile(AstNode ast, Token token, String currFileName) { } if (includedFileName != null) { - final File file = getFileUnderAnalysis(); - final String dir = file.getParent(); + File file = getFileUnderAnalysis(); + String dir = file.getParent(); return getCodeProvider().getSourceCodeFile(includedFileName, dir, quoted); } @@ -885,50 +857,40 @@ private File findIncludedFile(AstNode ast, Token token, String currFileName) { } private File getFileUnderAnalysis() { - 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(squidConfig.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; + if (unitCodeProvider.getIncludeUnderAnalysis() != null) { + // a) CxxPreprocessor is called recursively in order to parse the #include directive. + // Return path to the included file. + return unitCodeProvider.getIncludeUnderAnalysis(); + } else { + // b) 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(); } - - // 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) { - if (!currentFileState.skipPreprocessorDirectives) { - currentFileState.conditionWasTrue = false; - LOG.trace("[{}:{}]: handling #if line '{}'", filename, token.getLine(), token.getValue()); - try { - currentFileState.skipPreprocessorDirectives = false; - currentFileState.skipPreprocessorDirectives = !ExpressionEvaluator.eval(squidConfig, this, - ast.getFirstDescendant( - CppGrammar.constantExpression)); + void handleConstantExpression(AstNode ast,Token token, String filename){ + try { + unitCodeProvider.skipBlock(false); + boolean result = ExpressionEvaluator.eval(squidConfig, this, ast.getFirstDescendant(CppGrammar.constantExpression)); + unitCodeProvider.expressionWas(result); + unitCodeProvider.skipBlock(!result); } catch (EvaluationException e) { LOG.error("[{}:{}]: error evaluating the expression {} assume 'true' ...", filename, token.getLine(), token.getValue()); LOG.error("{}", e); - currentFileState.skipPreprocessorDirectives = false; + unitCodeProvider.expressionWas(true); + unitCodeProvider.skipBlock(false); } + } - if (currentFileState.skipPreprocessorDirectives) { - LOG.trace(EVALUATED_TO_FALSE, filename, token.getLine(), token.getValue()); - } else { - currentFileState.conditionWasTrue = true; - } + PreprocessorAction handleIfLine(AstNode ast, Token token, String filename) { + if (unitCodeProvider.doNotSkipBlock()) { + unitCodeProvider.expressionWas(false); + handleConstantExpression(ast, token, filename); } else { - currentFileState.conditionalInclusionCounter++; + unitCodeProvider.nestedBlock(+1); } return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), @@ -936,32 +898,14 @@ PreprocessorAction handleIfLine(AstNode ast, Token token, String filename) { } PreprocessorAction handleElIfLine(AstNode ast, Token token, String filename) { - // Handling of an elif line is similar to handling of an if line but doesn't increase the nesting level - if (currentFileState.conditionalInclusionCounter == 0) { - //the preceding clauses had been evaluated to false - if (currentFileState.skipPreprocessorDirectives && !currentFileState.conditionWasTrue) { - try { - LOG.trace("[{}:{}]: handling #elif line '{}'", filename, token.getLine(), token.getValue()); - - currentFileState.skipPreprocessorDirectives = false; - currentFileState.skipPreprocessorDirectives = !ExpressionEvaluator.eval(squidConfig, this, - ast.getFirstDescendant( - CppGrammar.constantExpression)); - } catch (EvaluationException e) { - LOG.error("[{}:{}]: error evaluating the expression {} assume 'true' ...", - filename, token.getLine(), token.getValue()); - LOG.error("{}", e); - currentFileState.skipPreprocessorDirectives = false; - } - - if (currentFileState.skipPreprocessorDirectives) { - LOG.trace(EVALUATED_TO_FALSE, filename, token.getLine(), token.getValue()); - } else { - currentFileState.conditionWasTrue = true; - } + // handling of an #elif line is similar to handling of an #if line but doesn't increase the nesting level + if (unitCodeProvider.isNotNestedBlock()) { + if (unitCodeProvider.doSkipBlock() && unitCodeProvider.expressionWasFalse()) { + // the preceding expression had been evaluated to false + handleConstantExpression(ast, token, filename); } else { - LOG.trace("[{}:{}]: skipping tokens inside the #elif", filename, token.getLine()); - currentFileState.skipPreprocessorDirectives = true; + // other block was already true: skipping tokens inside this #elif + unitCodeProvider.skipBlock(true); } } @@ -970,14 +914,14 @@ PreprocessorAction handleElIfLine(AstNode ast, Token token, String filename) { } PreprocessorAction handleElseLine(Token token, String filename) { - if (currentFileState.conditionalInclusionCounter == 0) { - if (currentFileState.skipPreprocessorDirectives && !currentFileState.conditionWasTrue) { - LOG.trace("[{}:{}]: #else, returning to non-skipping mode", filename, token.getLine()); - currentFileState.skipPreprocessorDirectives = false; - currentFileState.conditionWasTrue = true; + if (unitCodeProvider.isNotNestedBlock()) { + if (unitCodeProvider.doSkipBlock() && unitCodeProvider.expressionWasFalse()) { + // other block(s) were false: #else returning to non-skipping mode + unitCodeProvider.expressionWas(true); + unitCodeProvider.skipBlock(false); } else { - LOG.trace("[{}:{}]: skipping tokens inside the #else", filename, token.getLine()); - currentFileState.skipPreprocessorDirectives = true; + // other block was true: skipping tokens inside the #else + unitCodeProvider.skipBlock(true); } } @@ -986,14 +930,13 @@ PreprocessorAction handleElseLine(Token token, String filename) { } PreprocessorAction handleEndifLine(Token token, String filename) { - if (currentFileState.conditionalInclusionCounter > 0) { - currentFileState.conditionalInclusionCounter--; + if (unitCodeProvider.isNestedBlock()) { + // nested #endif + unitCodeProvider.nestedBlock(-1); } else { - if (currentFileState.skipPreprocessorDirectives) { - LOG.trace("[{}:{}]: #endif, returning to non-skipping mode", filename, token.getLine()); - currentFileState.skipPreprocessorDirectives = false; - } - currentFileState.conditionWasTrue = false; + // after last #endif, switching to non-skipping mode + unitCodeProvider.skipBlock(false); + unitCodeProvider.expressionWas(false); } return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), @@ -1001,11 +944,9 @@ PreprocessorAction handleEndifLine(Token token, String filename) { } PreprocessorAction handleDefineLine(AstNode ast, Token token, String filename) { - // Here we have a define directive. Parse it and store the result in a dictionary. - + // Here we have a define directive. Parse it and store the macro in a dictionary. Macro macro = parseMacroDefinition(ast); - LOG.trace("[{}:{}]: storing macro: '{}'", filename, token.getLine(), macro); - getMacros().put(macro.name, macro); + unitMacros.put(macro.name, macro); return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), new ArrayList<>()); @@ -1013,32 +954,25 @@ PreprocessorAction handleDefineLine(AstNode ast, Token token, String filename) { PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename, Charset charset) { // - // Included files have to be scanned with the (only) goal of gathering macros. - // This is done as follows: + // Included files have to be scanned with the (only) goal of gathering macros. This is done as follows: // // a) pipe the body of the include directive through a lexer to properly expand // all macros which may be in there. // b) extract the filename out of the include body and try to find it // c) if not done yet, process it using a special lexer, which calls back only // if it finds relevant preprocessor directives (currently: include's and define's) - - final File includedFile = findIncludedFile(ast, token, filename); + File includedFile = findIncludedFile(ast, token, filename); if (includedFile == null) { missingIncludeFilesCounter++; LOG.debug("[" + filename + ":" + token.getLine() + "]: cannot find include file '" + token.getValue() + "'"); } else if (analysedFiles.add(includedFile.getAbsoluteFile())) { - LOG.trace("[{}:{}]: processing {}, resolved to file '{}'", filename, token.getLine(), token.getValue(), - includedFile.getAbsolutePath()); - - globalStateStack.push(currentFileState); - currentFileState = new State(includedFile); - + unitCodeProvider.pushFileState(includedFile); try { IncludeLexer.create(this).lex(getCodeProvider().getSourceCode(includedFile, charset)); } catch (IOException e) { - LOG.error("[{}: Cannot read file]: {}", includedFile.getAbsoluteFile(), e); + LOG.error("[{}: Cannot read include file]: {}", includedFile.getAbsoluteFile(), e); } finally { - currentFileState = globalStateStack.pop(); + unitCodeProvider.popFileState(); } } @@ -1048,18 +982,16 @@ PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename, PreprocessorAction handleUndefLine(AstNode ast, Token token) { String macroName = ast.getFirstDescendant(IDENTIFIER).getTokenValue(); - getMacros().removeLowPrio(macroName); + unitMacros.remove(macroName); return new PreprocessorAction(1, Collections.singletonList(Trivia.createSkippedText(token)), new ArrayList<>()); } PreprocessorAction handleIdentifiersAndKeywords(List tokens, Token curr, String filename) { // - // Every identifier and every keyword can be a macro instance. - // Pipe the resulting string through a lexer to create proper Tokens - // and to expand recursively all macros which may be in there. + // Every identifier and every keyword can be a macro instance. Pipe the resulting string through a lexer to + // create proper Tokens and to expand recursively all macros which may be in there. // - PreprocessorAction ppaction = PreprocessorAction.NO_OPERATION; Macro macro = getMacro(curr.getValue()); if (macro != null) { @@ -1082,7 +1014,7 @@ PreprocessorAction handleIdentifiersAndKeywords(List tokens, Token curr, // Rescanning to expand function like macros, in case it requires consuming more tokens List outTokens = new LinkedList<>(); - getMacros().disable(macro.name); + unitMacros.disable(macro.name); while (!replTokens.isEmpty()) { Token c = replTokens.get(0); PreprocessorAction action = PreprocessorAction.NO_OPERATION; @@ -1106,18 +1038,9 @@ PreprocessorAction handleIdentifiersAndKeywords(List tokens, Token curr, } } replTokens = outTokens; - getMacros().enable(macro.name); - + unitMacros.enable(macro.name); replTokens = reallocate(replTokens, curr); - if (LOG.isTraceEnabled()) { - LOG.trace("[{}:{}]: replacing '" + curr.getValue() - + (tokensConsumed == 1 - ? "" - : serialize(tokens.subList(1, tokensConsumed))) + "' -> '" + serialize(replTokens) + '\'', - filename, curr.getLine()); - } - ppaction = new PreprocessorAction( tokensConsumed, Collections.singletonList(Trivia.createSkippedText(tokens.subList(0, tokensConsumed))), @@ -1128,73 +1051,6 @@ PreprocessorAction handleIdentifiersAndKeywords(List tokens, Token curr, return ppaction; } - public static class Include { - - private final int line; - private final String path; - - Include(int line, String path) { - this.line = line; - this.path = path; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - Include that = (Include) o; - - if (line != that.line) { - return false; - } - - return (path == null) ? that.path == null : path.equals(that.path); - } - - @Override - public int hashCode() { - return 31 * line + (path != null ? path.hashCode() : 0); - } - - public String getPath() { - return path; - } - - public int getLine() { - return line; - } - } - - private static class State { - - private boolean skipPreprocessorDirectives; - private boolean conditionWasTrue; - private int conditionalInclusionCounter; - private File includeUnderAnalysis; - - public State(@Nullable File includeUnderAnalysis) { - this.skipPreprocessorDirectives = false; - this.conditionWasTrue = false; - this.conditionalInclusionCounter = 0; - this.includeUnderAnalysis = includeUnderAnalysis; - } - - /** - * reset preprocessor state - */ - public final void reset() { - skipPreprocessorDirectives = false; - conditionWasTrue = false; - conditionalInclusionCounter = 0; - includeUnderAnalysis = null; - } - } - static class MismatchException extends Exception { private static final long serialVersionUID = 1960113363232807009L; @@ -1216,20 +1072,4 @@ static class MismatchException extends Exception { } } - static class PreprocessorRuntimeException extends RuntimeException { - - /** - * - */ - private static final long serialVersionUID = -6568372484065119533L; - - public PreprocessorRuntimeException(String message) { - super(message); - } - - public PreprocessorRuntimeException(String message, Throwable throwable) { - super(message, throwable); - } - } - } diff --git a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/MapChain.java b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/MapChain.java index 18ccbe52f3..a90f095737 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/MapChain.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/MapChain.java @@ -32,11 +32,8 @@ */ public class MapChain { - private final Map highPrioMap = new HashMap<>(); - private final Map lowPrioMap = new HashMap<>(); - private final Map highPrioDisabled = new HashMap<>(); - private final Map lowPrioDisabled = new HashMap<>(); - private boolean isHighPrioEnabled = false; + private final Map enabled = new HashMap<>(); + private final Map disabled = new HashMap<>(); /** * get @@ -45,12 +42,7 @@ public class MapChain { * @return V */ public V get(Object key) { - V value = highPrioMap.get(key); - return value != null ? value : lowPrioMap.get(key); - } - - public void setHighPrio(boolean value) { - isHighPrioEnabled = value; + return enabled.get(key); } /** @@ -61,37 +53,29 @@ public void setHighPrio(boolean value) { * @return V */ public V put(K key, V value) { - if (isHighPrioEnabled) { - return highPrioMap.put(key, value); - } else { - return lowPrioMap.put(key, value); - } + return enabled.put(key, value); } public void putAll(Map m) { - if (isHighPrioEnabled) { - highPrioMap.putAll(m); - } else { - lowPrioMap.putAll(m); - } + enabled.putAll(m); } /** - * removeLowPrio + * remove * * @param key * @return V */ - public V removeLowPrio(K key) { - return lowPrioMap.remove(key); + public V remove(K key) { + return enabled.remove(key); } /** - * clearLowPrio + * clear */ - public void clearLowPrio() { - lowPrioMap.clear(); - lowPrioDisabled.clear(); + public void clear() { + enabled.clear(); + disabled.clear(); } /** @@ -100,8 +84,7 @@ public void clearLowPrio() { * @param key */ public void disable(K key) { - move(key, lowPrioMap, lowPrioDisabled); - move(key, highPrioMap, highPrioDisabled); + move(key, enabled, disabled); } /** @@ -110,12 +93,11 @@ public void disable(K key) { * @param key */ public void enable(K key) { - move(key, lowPrioDisabled, lowPrioMap); - move(key, highPrioDisabled, highPrioMap); + move(key, disabled, enabled); } - public Map getHighPrioMap() { - return Collections.unmodifiableMap(highPrioMap); + public Map getMap() { + return Collections.unmodifiableMap(enabled); } private void move(K key, Map from, Map to) { diff --git a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/SourceCodeProvider.java b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/SourceCodeProvider.java index 02dcdfc7c4..6979c99138 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/SourceCodeProvider.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/SourceCodeProvider.java @@ -26,9 +26,11 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Deque; import java.util.LinkedList; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -40,7 +42,13 @@ public class SourceCodeProvider { private static final Logger LOG = Loggers.get(SourceCodeProvider.class); + private final List includeRoots = new LinkedList<>(); + private final Deque ppState = new LinkedList<>(); + + public SourceCodeProvider() { + pushFileState(null); + } public void setIncludeRoots(List roots, String baseDir) { for (var root : roots) { @@ -63,38 +71,92 @@ public void setIncludeRoots(List roots, String baseDir) { } } + public void pushFileState(File currentFile) { + ppState.push(new State(currentFile)); + } + + public void popFileState() { + ppState.pop(); + } + + public void skipBlock(boolean state) { + ppState.peek().skipBlock = state; + } + + public boolean doSkipBlock() { + return ppState.peek().skipBlock; + } + + public boolean doNotSkipBlock() { + return !ppState.peek().skipBlock; + } + + public void expressionWas(boolean state) { + ppState.peek().expression = state; + } + + public boolean expressionWasTrue() { + return ppState.peek().expression; + } + + public boolean expressionWasFalse() { + return !ppState.peek().expression; + } + + public void nestedBlock(int dir) { + ppState.peek().nestedBlock += dir; + } + + public boolean isNestedBlock() { + return ppState.peek().nestedBlock > 0; + } + + public boolean isNotNestedBlock() { + return ppState.peek().nestedBlock <= 0; + } + + public File getIncludeUnderAnalysis() { + return ppState.peek().includeUnderAnalysis; + } + @CheckForNull public File getSourceCodeFile(String filename, String cwd, boolean quoted) { File result = null; var file = new File(filename); - // If the file name is fully specified for an include file that has a path that - // includes a colon (for example, F:\MSVC\SPECIAL\INCL\TEST.H), the preprocessor - // follows the path. + // If the file name is fully specified for an include file that has a path that includes a colon + // (for example F:\MSVC\SPECIAL\INCL\TEST.H) the preprocessor follows the path. if (file.isAbsolute()) { if (file.isFile()) { result = file; } } else { if (quoted) { - // Quoted form: The preprocessor searches for include files in this order: - // 1) In the same directory as the file that contains the #include statement. - // 2) In the directories of the currently opened include files, in the reverse - // order in which they were opened. The search begins in the directory of the parent - // include file and continues upward through the directories of any grandparent include files. var abspath = new File(new File(cwd), file.getPath()); if (abspath.isFile()) { + // 1) In the same directory as the file that contains the #include statement. result = abspath; } else { - // fall back to use include paths instead of local folder - result = null; + result = null; // 3) fallback to use include paths instead of local folder + + // 2) In the directories of the currently opened include files, in the reverse order in which they were opened. + // The search begins in the directory of the parent include file and continues upward through the + // directories of any grandparent include files. + for (var parent : ppState) { + if (parent.includeUnderAnalysis != null) { + abspath = new File(parent.includeUnderAnalysis.getParentFile(), file.getPath()); + if (abspath.exists()) { + result = abspath; + break; + } + } + } } } // Angle-bracket form: lookup relative to to the include roots. - // The quoted case falls back to this, if its special handling wasn't - // successful. + // The quoted case falls back to this, if its special handling wasn't successful. if (result == null) { for (var path : includeRoots) { Path abspath = path.resolve(filename); @@ -122,4 +184,20 @@ public String getSourceCode(File file, Charset charset) throws IOException { return new String(encoded, charset); } + private static class State { + + private boolean skipBlock; + private boolean expression; + private int nestedBlock; + private File includeUnderAnalysis; + + public State(@Nullable File includeUnderAnalysis) { + this.skipBlock = false; + this.expression = false; + this.nestedBlock = 0; + this.includeUnderAnalysis = includeUnderAnalysis; + } + + } + } diff --git a/cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerIncludeTest.java b/cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerIncludeTest.java new file mode 100644 index 0000000000..f1457c25a5 --- /dev/null +++ b/cxx-squid/src/test/java/org/sonar/cxx/lexer/CxxLexerIncludeTest.java @@ -0,0 +1,171 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2020 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.cxx.lexer; + +import com.sonar.sslr.api.Grammar; +import com.sonar.sslr.api.Token; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.preprocessor.CxxPreprocessor; +import org.sonar.cxx.preprocessor.JoinStringsPreprocessor; +import org.sonar.cxx.utils.TestUtils; +import org.sonar.squidbridge.SquidAstVisitorContext; + +public class CxxLexerIncludeTest { + + @Test + public void quoted_include_without_IncludeDirectories() { + // Quoted form / preprocessor include file search order: + // 1) In the same directory as the file that contains the #include statement. + + String result = tryInclude("\"a.h\"", "INCLUDE", absolute(""), null); + assertThat(result).isEqualTo("\"using: include/a.h\""); + } + + @Test + public void quoted_include_with_IncludeDirectories1() { + // Quoted form / preprocessor include file search order: + // 1) In the same directory as the file that contains the #include statement. + + String result = tryInclude("\"a.h\"", "INCLUDE", absolute("B", "C"), null); + assertThat(result).isEqualTo("\"using: include/a.h\""); + } + + @Test + public void quoted_include_with_IncludeDirectories2() { + // Quoted form / preprocessor include file search order: + // 2) In the directories of the currently opened include files, in the reverse order in which they were opened. + // The search begins in the directory of the parent include file and continues upward through the directories of + // any grandparent include files. + + String result = tryInclude("\"p.h\"", "INCLUDE", absolute(""), null); + assertThat(result).isEqualTo("\"using: include/a.h\""); + } + + @Test + public void quoted_include_with_IncludeDirectories3a() { + // Quoted form / preprocessor include file search order: + // 3) Along the path that's specified by each /I compiler option. + + String result = tryInclude("\"b.h\"", "INCLUDE", absolute("B", "C"), null); + assertThat(result).isEqualTo("\"using: include/B/b.h\""); + } + + @Test + public void quoted_include_with_IncludeDirectories3b() { + // Quoted form / preprocessor include file search order: + // 3) Along the path that's specified by each /I compiler option. + + String result = tryInclude("\"c.h\"", "INCLUDE", absolute("B", "C"), null); + assertThat(result).isEqualTo("\"using: include/C/c.h\""); + } + + @Test + public void bracket_include_without_IncludeDirectories() { + // Angle-bracket form / preprocessor include file search order: + // 1) Along the path that's specified by each /I compiler option (IncludeDirectories). + + String result = tryInclude("", "INCLUDE", absolute(""), null); + assertThat(result).isEqualTo("INCLUDE"); // should not find the include + } + + @Test + public void bracket_include_with_IncludeDirectories1a() { + // Angle-bracket form / preprocessor include file search order: + // 1) Along the path that's specified by each /I compiler option (IncludeDirectories). + + String result = tryInclude("", "INCLUDE", absolute("."), null); + assertThat(result).isEqualTo("\"using: include/a.h\""); + } + + @Test + public void bracket_include_with_IncludeDirectories1b() { + // Angle-bracket form / preprocessor include file search order: + // 1) Along the path that's specified by each /I compiler option (IncludeDirectories). + + String result = tryInclude("", "INCLUDE", absolute("B", "C"), null); + assertThat(result).isEqualTo("\"using: include/B/b.h\""); + } + + @Test + public void bracket_include_with_IncludeDirectories1c() { + // Angle-bracket form / preprocessor include file search order: + // 1) Along the path that's specified by each /I compiler option (IncludeDirectories). + + String result = tryInclude("", "INCLUDE", absolute("B", "C"), null); + assertThat(result).isEqualTo("\"using: include/B/a.h\""); + } + + @Test + public void compilation_database_settings_propagated() { + // test: are compilation database settings propagated in case of nested includes + + List defines = Arrays.asList("GLOBAL 1"); + String result = tryInclude("\"p.h\"", "PROPAGATED", absolute(""), defines); + assertThat(result).isEqualTo("\"propagated\""); + } + + private File root() { + return TestUtils.loadResource("/preprocessor/include"); + } + + private List absolute(String... path) { + var result = new ArrayList(); + for (String item : path) { + if (!item.isBlank()) { + result.add(new File(root(), item).getAbsolutePath()); + } + } + return result; + } + + private String tryInclude(String include, String macro, + @Nullable List includeDirectories, + @Nullable List defines) { + var squidConfig = new CxxSquidConfiguration(); + if (includeDirectories != null) { + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.INCLUDE_DIRECTORIES, + includeDirectories); + } + if (defines != null) { + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.DEFINES, + defines); + } + + var file = new File(root(), "root.cpp"); + SquidAstVisitorContext context = mock(SquidAstVisitorContext.class); + when(context.getFile()).thenReturn(file); + + var pp = new CxxPreprocessor(context, squidConfig); + var lexer = CxxLexer.create(squidConfig, pp, new JoinStringsPreprocessor()); + + String fileContent = "#include " + include + "\n" + macro; + List tokens = lexer.lex(fileContent); + return tokens.get(0).getValue(); + } +} diff --git a/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/ExpressionEvaluatorTest.java b/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/ExpressionEvaluatorTest.java index 9707f0ce4f..1c8055ea26 100644 --- a/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/ExpressionEvaluatorTest.java +++ b/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/ExpressionEvaluatorTest.java @@ -19,6 +19,8 @@ */ package org.sonar.cxx.preprocessor; +import com.sonar.sslr.api.Grammar; +import java.io.File; import java.math.BigInteger; import org.assertj.core.api.SoftAssertions; import static org.junit.Assert.assertEquals; @@ -395,7 +397,12 @@ public void throw_on_invalid_expressions() { @Test public void std_macro_evaluated_as_expected() { - var pp = new CxxPreprocessor(mock(SquidAstVisitorContext.class)); + var file = new File("dummy.cpp"); + SquidAstVisitorContext context = mock(SquidAstVisitorContext.class); + when(context.getFile()).thenReturn(file); + + var pp = new CxxPreprocessor(context); + pp.init(); assertTrue(eval("__LINE__", pp)); assertTrue(eval("__STDC__", pp)); diff --git a/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/MapChainTest.java b/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/MapChainTest.java index 9a44d34492..6a3fa1121f 100644 --- a/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/MapChainTest.java +++ b/cxx-squid/src/test/java/org/sonar/cxx/preprocessor/MapChainTest.java @@ -32,85 +32,43 @@ public MapChainTest() { } @Test - public void gettingHighPrioMapping() { - mc.setHighPrio(true); + public void getMapping() { mc.put("k", "v"); assertEquals("v", mc.get("k")); } @Test - public void gettingLowPrioMapping() { - mc.setHighPrio(false); + public void removeMapping() { mc.put("k", "v"); - assertEquals("v", mc.get("k")); - } - - @Test - public void removeLowPrioMapping() { - mc.setHighPrio(false); - mc.put("k", "v"); - mc.removeLowPrio("k"); + mc.remove("k"); assertNull(mc.get("k")); } @Test - public void gettingNotExistingMapping() { - mc.setHighPrio(false); - mc.put("k", "vlow"); - mc.setHighPrio(true); - mc.put("k", "vhigh"); - assertEquals("vhigh", mc.get("k")); - } - - @Test - public void gettingOverwrittenMapping() { + public void noValueMapping() { assertNull(mc.get("k")); } @Test - public void clearingLowPrioDeletesLowPrioMappings() { - mc.setHighPrio(false); + public void clearMapping() { mc.put("k", "v"); - mc.clearLowPrio(); + mc.clear(); assertNull(mc.get("k")); } - @Test - public void clearingLowPrioDoesntAffectHighPrioMappings() { - mc.setHighPrio(true); - mc.put("k", "v"); - mc.clearLowPrio(); - assertEquals("v", mc.get("k")); - } - @Test public void disable() { - mc.setHighPrio(true); - mc.put("khigh", "vhigh"); - mc.setHighPrio(false); - mc.put("klow", "vlow"); - - mc.disable("khigh"); - mc.disable("klow"); - - assertNull(mc.get("khigh")); - assertNull(mc.get("klow")); + mc.put("k", "v"); + mc.disable("k"); + assertNull(mc.get("k")); } @Test public void enable() { - mc.setHighPrio(true); - mc.put("khigh", "vhigh"); - mc.setHighPrio(false); - mc.put("klow", "vlow"); - mc.disable("khigh"); - mc.disable("klow"); - - mc.enable("khigh"); - mc.enable("klow"); - - assertEquals("vhigh", mc.get("khigh")); - assertEquals("vlow", mc.get("klow")); + mc.put("k", "v"); + mc.disable("k"); + mc.enable("k"); + assertEquals("v", mc.get("k")); } } diff --git a/cxx-squid/src/test/resources/preprocessor/include/B/a.h b/cxx-squid/src/test/resources/preprocessor/include/B/a.h new file mode 100644 index 0000000000..eab12aead3 --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/B/a.h @@ -0,0 +1 @@ +#define INCLUDE "using: include/B/a.h" \ No newline at end of file diff --git a/cxx-squid/src/test/resources/preprocessor/include/B/b.h b/cxx-squid/src/test/resources/preprocessor/include/B/b.h new file mode 100644 index 0000000000..4dcc1b98d5 --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/B/b.h @@ -0,0 +1 @@ +#define INCLUDE "using: include/B/b.h" \ No newline at end of file diff --git a/cxx-squid/src/test/resources/preprocessor/include/C/a.h b/cxx-squid/src/test/resources/preprocessor/include/C/a.h new file mode 100644 index 0000000000..6ccb7e1761 --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/C/a.h @@ -0,0 +1 @@ +#define INCLUDE "using: include/C/a.h" \ No newline at end of file diff --git a/cxx-squid/src/test/resources/preprocessor/include/C/c.h b/cxx-squid/src/test/resources/preprocessor/include/C/c.h new file mode 100644 index 0000000000..a8e7d365ea --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/C/c.h @@ -0,0 +1 @@ +#define INCLUDE "using: include/C/c.h" \ No newline at end of file diff --git a/cxx-squid/src/test/resources/preprocessor/include/D/E/e.h b/cxx-squid/src/test/resources/preprocessor/include/D/E/e.h new file mode 100644 index 0000000000..8c0e0086c6 --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/D/E/e.h @@ -0,0 +1,9 @@ +#define INCLUDE "using: include/D/E/e.h" +#include "a.h" + +// test: are compilation database settings propagated +#if GLOBAL +# define PROPAGATED "propagated" +#else +# define PROPAGATED "not propagated" +#endif diff --git a/cxx-squid/src/test/resources/preprocessor/include/D/d.h b/cxx-squid/src/test/resources/preprocessor/include/D/d.h new file mode 100644 index 0000000000..92b0cc167e --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/D/d.h @@ -0,0 +1,2 @@ +#define INCLUDE "using: include/D/d.h" +#include "E/e.h" diff --git a/cxx-squid/src/test/resources/preprocessor/include/a.h b/cxx-squid/src/test/resources/preprocessor/include/a.h new file mode 100644 index 0000000000..67d6ab61f1 --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/a.h @@ -0,0 +1 @@ +#define INCLUDE "using: include/a.h" \ No newline at end of file diff --git a/cxx-squid/src/test/resources/preprocessor/include/p.h b/cxx-squid/src/test/resources/preprocessor/include/p.h new file mode 100644 index 0000000000..eaef1b7f0f --- /dev/null +++ b/cxx-squid/src/test/resources/preprocessor/include/p.h @@ -0,0 +1,2 @@ +#define INCLUDE "using: include/p.h" +#include "D/d.h"