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 a27caab6c3..735ed7d0d6 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 @@ -179,7 +179,6 @@ public boolean checkArgumentsCount(int count) { private SourceCodeProvider codeProvider = new SourceCodeProvider(); private SourceCodeProvider unitCodeProvider; private SquidAstVisitorContext context; - private ExpressionEvaluator ifExprEvaluator; private List cFilesPatterns; private CxxConfiguration conf; private CxxCompilationUnitSettings compilationUnitSettings; @@ -248,7 +247,6 @@ public CxxPreprocessor(SquidAstVisitorContext context, SourceCodeProvider sourceCodeProvider, CxxLanguage language) { this.context = context; - this.ifExprEvaluator = new ExpressionEvaluator(conf, this); this.cFilesPatterns = conf.getCFilesPatterns(); this.conf = conf; this.language = language; @@ -518,8 +516,8 @@ PreprocessorAction handleIfLine(AstNode ast, Token token, String filename) { //T } try { currentFileState.skipPreprocessorDirectives = false; - currentFileState.skipPreprocessorDirectives = !ifExprEvaluator.eval( - ast.getFirstDescendant(CppGrammar.constantExpression)); + currentFileState.skipPreprocessorDirectives = !ExpressionEvaluator.eval(conf, this, + ast.getFirstDescendant(CppGrammar.constantExpression)); } catch (EvaluationException e) { LOG.error("[{}:{}]: error evaluating the expression {} assume 'true' ...", filename, token.getLine(), token.getValue()); @@ -553,8 +551,8 @@ PreprocessorAction handleElIfLine(AstNode ast, Token token, String filename) { / filename, token.getLine(), token.getValue()); } currentFileState.skipPreprocessorDirectives = false; - currentFileState.skipPreprocessorDirectives = !ifExprEvaluator.eval( - ast.getFirstDescendant(CppGrammar.constantExpression)); + currentFileState.skipPreprocessorDirectives = !ExpressionEvaluator.eval(conf, this, + ast.getFirstDescendant(CppGrammar.constantExpression)); } catch (EvaluationException e) { LOG.error("[{}:{}]: error evaluating the expression {} assume 'true' ...", filename, token.getLine(), token.getValue()); @@ -658,7 +656,7 @@ private void parseIncludeLine(String includeLine, String filename, Charset chars .getToken(), filename, charset); } - PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename, Charset charset) { //TODO: deprecated + PreprocessorAction handleIncludeLine(AstNode ast, Token token, String filename, Charset charset) { //TODO: deprecated // // Included files have to be scanned with the (only) goal of gathering macros. // This is done as follows: @@ -1035,7 +1033,7 @@ private List replaceParams(List body, List parameters, List } } - // replace # with "" if sequence HASH BR occurs for body HASH __VA_ARGS__ + // replace # with "" if sequence HASH BR occurs for body HASH __VA_ARGS__ if (newTokens.size() > 3 && newTokens.get(newTokens.size() - 2).getType().equals(HASH) && newTokens.get(newTokens.size() - 1).getType().equals(BR_RIGHT)) { for (int n = newTokens.size() - 2; n != 0; n--) { diff --git a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/EvaluationException.java b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/EvaluationException.java index a1869e6994..b248cdc9be 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/EvaluationException.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/EvaluationException.java @@ -20,15 +20,9 @@ package org.sonar.cxx.preprocessor; public class EvaluationException extends RuntimeException { + private static final long serialVersionUID = 336015352128912495L; - private final String why; - - public EvaluationException(String why) { - this.why = why; - } - - @Override - public String toString() { - return why; + public EvaluationException(String message) { + super(message); } } diff --git a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/ExpressionEvaluator.java b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/ExpressionEvaluator.java index f67ec24f6f..dbaca0b55e 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/ExpressionEvaluator.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/preprocessor/ExpressionEvaluator.java @@ -26,8 +26,12 @@ import com.sonar.sslr.api.Token; import com.sonar.sslr.impl.Parser; import java.math.BigInteger; +import java.util.Deque; +import java.util.LinkedList; import java.util.List; + import javax.annotation.Nullable; + import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.cxx.CxxConfiguration; @@ -41,19 +45,21 @@ public final class ExpressionEvaluator { private final Parser parser; private final CxxPreprocessor preprocessor; + private final Deque macroEvaluationStack; - public ExpressionEvaluator(CxxConfiguration conf, CxxPreprocessor preprocessor) { + private ExpressionEvaluator(CxxConfiguration conf, CxxPreprocessor preprocessor) { parser = CppParser.createConstantExpressionParser(conf); this.preprocessor = preprocessor; + macroEvaluationStack = new LinkedList<>(); } - public boolean eval(String constExpr) { - return evalToInt(constExpr, null).compareTo(BigInteger.ZERO) != 0; + public static boolean eval(CxxConfiguration conf, CxxPreprocessor preprocessor, String constExpr) { + return new ExpressionEvaluator(conf, preprocessor).evalToBoolean(constExpr, null); } - public boolean eval(AstNode constExpr) { - return evalToInt(constExpr).compareTo(BigInteger.ZERO) != 0; + public static boolean eval(CxxConfiguration conf, CxxPreprocessor preprocessor, AstNode constExpr) { + return new ExpressionEvaluator(conf, preprocessor).evalToBoolean(constExpr); } private BigInteger evalToInt(String constExpr, @Nullable AstNode exprAst) { @@ -86,6 +92,16 @@ private BigInteger evalToInt(AstNode exprAst) { return evalComplexAst(exprAst); } + private boolean evalToBoolean(AstNode exprAst) + { + return !BigInteger.ZERO.equals(evalToInt(exprAst)); + } + + private boolean evalToBoolean(String constExpr, @Nullable AstNode exprAst) + { + return !BigInteger.ZERO.equals(evalToInt(constExpr, exprAst)); + } + private BigInteger evalLeaf(AstNode exprAst) { // Evaluation of leafs // @@ -96,8 +112,27 @@ private BigInteger evalLeaf(AstNode exprAst) { } else if (nodeType.equals(CxxTokenType.CHARACTER)) { return evalCharacter(exprAst.getTokenValue()); } else if (nodeType.equals(GenericTokenType.IDENTIFIER)) { - String value = preprocessor.valueOf(exprAst.getTokenValue()); - return value == null ? BigInteger.ZERO : evalToInt(value, exprAst); + + final String id = exprAst.getTokenValue(); + if (macroEvaluationStack.contains(id)) + { + if (LOG.isDebugEnabled()) + { + LOG.debug("ExpressionEvaluator: self-referential macro '{}' detected; assume true; evaluation stack = ['{} <- {}']", id, id, String.join(" <- ", macroEvaluationStack)); + } + return BigInteger.ONE; + } + final String value = preprocessor.valueOf(id); + if (value == null) + { + return BigInteger.ZERO; + } + + macroEvaluationStack.addFirst(id); + BigInteger expansion = evalToInt(value, exprAst); + macroEvaluationStack.removeFirst(); + return expansion; + } else { throw new EvaluationException("Unknown expression type '" + nodeType + "'"); } @@ -194,10 +229,10 @@ private static AstNode getNextOperand(@Nullable AstNode node) { // ////////////// logical expressions /////////////////////////// private BigInteger evalLogicalOrExpression(AstNode exprAst) { AstNode operand = exprAst.getFirstChild(); - boolean result = eval(operand); + boolean result = evalToBoolean(operand); while (!result && ((operand = getNextOperand(operand)) != null)) { - result = eval(operand); + result = evalToBoolean(operand); } return result ? BigInteger.ONE : BigInteger.ZERO; @@ -205,10 +240,10 @@ private BigInteger evalLogicalOrExpression(AstNode exprAst) { private BigInteger evalLogicalAndExpression(AstNode exprAst) { AstNode operand = exprAst.getFirstChild(); - boolean result = eval(operand); + boolean result = evalToBoolean(operand); while (result && ((operand = getNextOperand(operand)) != null)) { - result = eval(operand); + result = evalToBoolean(operand); } return result ? BigInteger.ONE : BigInteger.ZERO; @@ -233,9 +268,9 @@ private BigInteger evalEqualityExpression(AstNode exprAst) { operatorType = operator.getType(); rhs = operator.getNextSibling(); if (operatorType.equals(CppPunctuator.EQ)) { - result = result == eval(rhs); + result = result == evalToBoolean(rhs); } else if (operatorType.equals(CppPunctuator.NOT_EQ)) { - result = result != eval(rhs); + result = result != evalToBoolean(rhs); } else { throw new EvaluationException("Unknown equality operator '" + operatorType + "'"); } @@ -332,7 +367,7 @@ private BigInteger evalUnaryExpression(AstNode exprAst) { } else if (operatorType.equals(CppPunctuator.MINUS)) { return evalToInt(operand).negate(); } else if (operatorType.equals(CppPunctuator.NOT)) { - boolean result = !eval(operand); + boolean result = !evalToBoolean(operand); return result ? BigInteger.ONE : BigInteger.ZERO; } else if (operatorType.equals(CppPunctuator.BW_NOT)) { //todo: need more information (signed/unsigned, data type length) to invert bits in all cases correct @@ -414,7 +449,7 @@ private BigInteger evalConditionalExpression(AstNode exprAst) { AstNode trueCaseOperand = operator.getNextSibling(); operator = trueCaseOperand.getNextSibling(); AstNode falseCaseOperand = operator.getNextSibling(); - return eval(decisionOperand) ? evalToInt(trueCaseOperand) : evalToInt(falseCaseOperand); + return evalToBoolean(decisionOperand) ? evalToInt(trueCaseOperand) : evalToInt(falseCaseOperand); } else { AstNode decisionOperand = exprAst.getFirstChild(); AstNode operator = decisionOperand.getNextSibling(); 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 33679788f4..2265ac5ab9 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,266 +19,323 @@ */ package org.sonar.cxx.preprocessor; -import java.math.BigInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.junit.Test; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; + +import java.math.BigInteger; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; import org.sonar.cxx.CxxConfiguration; import org.sonar.cxx.CxxFileTesterHelper; import org.sonar.squidbridge.SquidAstVisitorContext; public class ExpressionEvaluatorTest { - private final ExpressionEvaluator evaluator - = new ExpressionEvaluator(mock(CxxConfiguration.class), - mock(CxxPreprocessor.class)); + static boolean eval(String constExpr, CxxPreprocessor pp) { + return ExpressionEvaluator.eval(mock(CxxConfiguration.class), pp, constExpr); + } + + static boolean eval(String constExpr) { + return eval(constExpr, mock(CxxPreprocessor.class)); + } @Test public void bools() { - assertTrue(evaluator.eval("true")); + assertTrue(eval("true")); - assertFalse(evaluator.eval("false")); + assertFalse(eval("false")); } @Test public void numbers() { - assertTrue(evaluator.eval("1")); - assertTrue(evaluator.eval("0xAA")); - assertTrue(evaluator.eval("0XAA")); - assertTrue(evaluator.eval("1L")); - assertTrue(evaluator.eval("01L")); - assertTrue(evaluator.eval("1u")); - assertTrue(evaluator.eval("1000000000UL")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFF")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFFui64")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFFLL")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFFuLL")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFFll")); - assertTrue(evaluator.eval("0xFFFFFFFFFFFFFFFFull")); - assertTrue(evaluator.eval("0xffffffffffffffffui64")); - assertTrue(evaluator.eval("0xffffffffffffffffi64")); - assertTrue(evaluator.eval("0x7FFFFFL")); - - assertFalse(evaluator.eval("0")); - assertFalse(evaluator.eval("0x0")); + assertTrue(eval("1")); + assertTrue(eval("0xAA")); + assertTrue(eval("0XAA")); + assertTrue(eval("1L")); + assertTrue(eval("01L")); + assertTrue(eval("1u")); + assertTrue(eval("1000000000UL")); + assertTrue(eval("0xFFFFFFFFFFFFFFFF")); + assertTrue(eval("0xFFFFFFFFFFFFFFFFui64")); + assertTrue(eval("0xFFFFFFFFFFFFFFFFLL")); + assertTrue(eval("0xFFFFFFFFFFFFFFFFuLL")); + assertTrue(eval("0xFFFFFFFFFFFFFFFFll")); + assertTrue(eval("0xFFFFFFFFFFFFFFFFull")); + assertTrue(eval("0xffffffffffffffffui64")); + assertTrue(eval("0xffffffffffffffffi64")); + assertTrue(eval("0x7FFFFFL")); + + assertFalse(eval("0")); + assertFalse(eval("0x0")); } @Test public void characters() { - assertTrue(evaluator.eval("'1'")); - assertTrue(evaluator.eval("'a'")); + assertTrue(eval("'1'")); + assertTrue(eval("'a'")); - assertFalse(evaluator.eval("'\0'")); + assertFalse(eval("'\0'")); } @Test public void conditional_expression() { - assertTrue(evaluator.eval("1 ? 1 : 0")); - assertTrue(evaluator.eval("0 ? 0 : 1")); + assertTrue(eval("1 ? 1 : 0")); + assertTrue(eval("0 ? 0 : 1")); - assertFalse(evaluator.eval("1 ? 0 : 1")); - assertFalse(evaluator.eval("0 ? 1 : 0")); + assertFalse(eval("1 ? 0 : 1")); + assertFalse(eval("0 ? 1 : 0")); - assertTrue(evaluator.eval("1 ? : 0")); - assertTrue(evaluator.eval("0 ? : 1")); + assertTrue(eval("1 ? : 0")); + assertTrue(eval("0 ? : 1")); } @Test public void logical_or() { - assertTrue(evaluator.eval("1 || 0")); - assertTrue(evaluator.eval("0 || 1")); - assertTrue(evaluator.eval("1 || 1")); - assertTrue(evaluator.eval("0 || 0 || 1")); + assertTrue(eval("1 || 0")); + assertTrue(eval("0 || 1")); + assertTrue(eval("1 || 1")); + assertTrue(eval("0 || 0 || 1")); - assertFalse(evaluator.eval("0 || 0")); - assertFalse(evaluator.eval("0 || 0 || 0")); + assertFalse(eval("0 || 0")); + assertFalse(eval("0 || 0 || 0")); } @Test public void logical_and() { - assertTrue(evaluator.eval("1 && 1")); - assertTrue(evaluator.eval("1 && 1 && 1")); + assertTrue(eval("1 && 1")); + assertTrue(eval("1 && 1 && 1")); - assertFalse(evaluator.eval("1 && 0")); - assertFalse(evaluator.eval("0 && 1")); - assertFalse(evaluator.eval("0 && 0")); - assertFalse(evaluator.eval("1 && 1 && 0")); + assertFalse(eval("1 && 0")); + assertFalse(eval("0 && 1")); + assertFalse(eval("0 && 0")); + assertFalse(eval("1 && 1 && 0")); } @Test public void inclusive_or() { - assertTrue(evaluator.eval("1 | 0")); - assertTrue(evaluator.eval("0 | 1")); - assertTrue(evaluator.eval("1 | 1")); - assertTrue(evaluator.eval("0 | 0 | 1")); + assertTrue(eval("1 | 0")); + assertTrue(eval("0 | 1")); + assertTrue(eval("1 | 1")); + assertTrue(eval("0 | 0 | 1")); - assertFalse(evaluator.eval("0 | 0 | 0")); + assertFalse(eval("0 | 0 | 0")); } @Test public void exclusive_or() { - assertTrue(evaluator.eval("1 ^ 0")); - assertTrue(evaluator.eval("0 ^ 1")); - assertTrue(evaluator.eval("0 ^ 1 ^ 0")); + assertTrue(eval("1 ^ 0")); + assertTrue(eval("0 ^ 1")); + assertTrue(eval("0 ^ 1 ^ 0")); - assertFalse(evaluator.eval("0 ^ 0")); - assertFalse(evaluator.eval("0 ^ 1 ^ 1")); + assertFalse(eval("0 ^ 0")); + assertFalse(eval("0 ^ 1 ^ 1")); } @Test public void and_expr() { - assertTrue(evaluator.eval("1 & 1")); - assertTrue(evaluator.eval("2 & 2 & 2")); - - assertFalse(evaluator.eval("0 & 1")); - assertFalse(evaluator.eval("1 & 0")); - assertFalse(evaluator.eval("0 & 0")); - assertFalse(evaluator.eval("2 & 4")); - assertFalse(evaluator.eval("1 & 1 & 4")); + assertTrue(eval("1 & 1")); + assertTrue(eval("2 & 2 & 2")); + + assertFalse(eval("0 & 1")); + assertFalse(eval("1 & 0")); + assertFalse(eval("0 & 0")); + assertFalse(eval("2 & 4")); + assertFalse(eval("1 & 1 & 4")); } @Test public void equality_expr() { - assertTrue(evaluator.eval("1 == 1")); - assertTrue(evaluator.eval("1 == true")); - assertTrue(evaluator.eval("true == true")); - assertTrue(evaluator.eval("true == 1")); - assertTrue(evaluator.eval("false == 0")); - assertTrue(evaluator.eval("0 == false")); - - assertTrue(evaluator.eval("true != 2")); - assertTrue(evaluator.eval("false != 1")); - assertTrue(evaluator.eval("1 != 2")); - - assertFalse(evaluator.eval("1 == 0")); - assertFalse(evaluator.eval("3 != 3")); - assertFalse(evaluator.eval("2 != 3 != 4")); - assertFalse(evaluator.eval("0 != 1 != true")); - - assertTrue(evaluator.eval("1 == 1 == true")); + assertTrue(eval("1 == 1")); + assertTrue(eval("1 == true")); + assertTrue(eval("true == true")); + assertTrue(eval("true == 1")); + assertTrue(eval("false == 0")); + assertTrue(eval("0 == false")); + + assertTrue(eval("true != 2")); + assertTrue(eval("false != 1")); + assertTrue(eval("1 != 2")); + + assertFalse(eval("1 == 0")); + assertFalse(eval("3 != 3")); + assertFalse(eval("2 != 3 != 4")); + assertFalse(eval("0 != 1 != true")); + + assertTrue(eval("1 == 1 == true")); } @Test public void relational_expr() { - assertTrue(evaluator.eval("0 < 1")); - assertTrue(evaluator.eval("0 <= 1")); - assertTrue(evaluator.eval("1 > 0")); - assertTrue(evaluator.eval("1 >= 0")); - assertTrue(evaluator.eval("0 < 0 < 2")); - - assertFalse(evaluator.eval("3 < 2")); - assertFalse(evaluator.eval("3 <= 2")); - assertFalse(evaluator.eval("0 > 1")); - assertFalse(evaluator.eval("0 >= 1")); - assertFalse(evaluator.eval("0 < 1 < 1")); - - assertTrue(evaluator.eval("2 > 1 > false")); - assertTrue(evaluator.eval("0 >= 0 >= false")); - assertTrue(evaluator.eval("0 <= 0 >= true")); - - assertFalse(evaluator.eval("1 < 1 > false")); - assertFalse(evaluator.eval("0 >= 1 >= true")); - assertFalse(evaluator.eval("2 <= 2 <= false")); + assertTrue(eval("0 < 1")); + assertTrue(eval("0 <= 1")); + assertTrue(eval("1 > 0")); + assertTrue(eval("1 >= 0")); + assertTrue(eval("0 < 0 < 2")); + + assertFalse(eval("3 < 2")); + assertFalse(eval("3 <= 2")); + assertFalse(eval("0 > 1")); + assertFalse(eval("0 >= 1")); + assertFalse(eval("0 < 1 < 1")); + + assertTrue(eval("2 > 1 > false")); + assertTrue(eval("0 >= 0 >= false")); + assertTrue(eval("0 <= 0 >= true")); + + assertFalse(eval("1 < 1 > false")); + assertFalse(eval("0 >= 1 >= true")); + assertFalse(eval("2 <= 2 <= false")); } @Test public void shift_expr() { - assertTrue(evaluator.eval("1 << 2")); - assertTrue(evaluator.eval("1 >> 0")); + assertTrue(eval("1 << 2")); + assertTrue(eval("1 >> 0")); - assertFalse(evaluator.eval("0 << 1")); - assertFalse(evaluator.eval("0 >> 1")); - assertFalse(evaluator.eval("10 >> 1 >> 10")); + assertFalse(eval("0 << 1")); + assertFalse(eval("0 >> 1")); + assertFalse(eval("10 >> 1 >> 10")); } @Test public void additive_expr() { - assertTrue(evaluator.eval("1 + 1")); - assertTrue(evaluator.eval("2 - 1")); - assertTrue(evaluator.eval("3 - 3 + 2")); + assertTrue(eval("1 + 1")); + assertTrue(eval("2 - 1")); + assertTrue(eval("3 - 3 + 2")); - assertFalse(evaluator.eval("0 + 0")); - assertFalse(evaluator.eval("1 - 1")); - assertFalse(evaluator.eval("3 - 2 - 1")); + assertFalse(eval("0 + 0")); + assertFalse(eval("1 - 1")); + assertFalse(eval("3 - 2 - 1")); } @Test public void multiplicative_expr() { - assertTrue(evaluator.eval("1 * 2")); - assertTrue(evaluator.eval("1 / 1")); - assertTrue(evaluator.eval("1 % 2")); - - assertFalse(evaluator.eval("0 * 1")); - assertFalse(evaluator.eval("0 / 1")); - assertFalse(evaluator.eval("1 % 1")); - assertFalse(evaluator.eval("1 * 1 * 0")); + assertTrue(eval("1 * 2")); + assertTrue(eval("1 / 1")); + assertTrue(eval("1 % 2")); + + assertFalse(eval("0 * 1")); + assertFalse(eval("0 / 1")); + assertFalse(eval("1 % 1")); + assertFalse(eval("1 * 1 * 0")); } @Test public void primary_expr() { - assertTrue(evaluator.eval("(1)")); + assertTrue(eval("(1)")); - assertFalse(evaluator.eval("(0)")); - assertFalse(evaluator.eval("( 0 )")); - assertFalse(evaluator.eval("(1 || 0) && 0")); + assertFalse(eval("(0)")); + assertFalse(eval("( 0 )")); + assertFalse(eval("(1 || 0) && 0")); } @Test public void unary_expression() { - assertTrue(evaluator.eval("+1")); - assertTrue(evaluator.eval("-1")); - assertTrue(evaluator.eval("!0")); - assertTrue(evaluator.eval("~0")); - - assertFalse(evaluator.eval("+0")); - assertFalse(evaluator.eval("-0")); - assertFalse(evaluator.eval("!1")); - assertFalse(evaluator.eval("~0xFFFFFFFFFFFFFFFF")); + assertTrue(eval("+1")); + assertTrue(eval("-1")); + assertTrue(eval("!0")); + assertTrue(eval("~0")); + + assertFalse(eval("+0")); + assertFalse(eval("-0")); + assertFalse(eval("!1")); + assertFalse(eval("~0xFFFFFFFFFFFFFFFF")); } @Test public void identifier_defined() { CxxPreprocessor pp = mock(CxxPreprocessor.class); when(pp.valueOf(anyString())).thenReturn("1"); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertTrue(evaluator.eval("LALA")); + assertTrue(eval("LALA", pp)); + } + + @Test + public void self_referential_identifier0() { + CxxPreprocessor pp = mock(CxxPreprocessor.class); + when(pp.valueOf("A")).thenReturn("A"); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(eval("A", pp)).isTrue(); + softly.assertThat(eval("A && A", pp)).isTrue(); + softly.assertThat(eval("A && !A", pp)).isFalse(); + softly.assertAll(); + } + + @Test + public void self_referential_identifier1() { + CxxPreprocessor pp = mock(CxxPreprocessor.class); + when(pp.valueOf("A")).thenReturn("B"); + when(pp.valueOf("B")).thenReturn("A"); + + assertTrue(eval("A", pp)); + } + + @Test + public void self_referential_identifier2() { + CxxPreprocessor pp = mock(CxxPreprocessor.class); + when(pp.valueOf("C")).thenReturn("B"); + when(pp.valueOf("B")).thenReturn("C"); + when(pp.valueOf("A")).thenReturn("B"); + + assertTrue(eval("A", pp)); } + @Test + public void self_referential_identifier3() { + CxxPreprocessor pp = mock(CxxPreprocessor.class); + when(pp.valueOf("C")).thenReturn("B"); + when(pp.valueOf("B")).thenReturn("C"); + when(pp.valueOf("A1")).thenReturn("1"); + when(pp.valueOf("A0")).thenReturn("0"); + when(pp.valueOf("A")).thenReturn("A0 + A1 + B"); + + assertTrue(eval("A", pp)); + } + + @Test + public void self_referential_identifier4() { + // https://gcc.gnu.org/onlinedocs/gcc-3.0.1/cpp_3.html#SEC31 + CxxPreprocessor pp = mock(CxxPreprocessor.class); + when(pp.valueOf("x")).thenReturn("(4 + y)"); + when(pp.valueOf("y")).thenReturn("(2 * x)"); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(eval("x", pp)).isTrue(); + softly.assertThat(eval("y", pp)).isTrue(); + softly.assertAll(); + } + + @Test public void identifier_undefined() { - ExpressionEvaluator evaluator - = new ExpressionEvaluator(mock(CxxConfiguration.class), - mock(CxxPreprocessor.class)); - assertFalse(evaluator.eval("LALA")); + assertFalse(eval("LALA")); } @Test public void functionlike_macro_defined_true() { CxxPreprocessor pp = mock(CxxPreprocessor.class); when(pp.expandFunctionLikeMacro(anyString(), anyList())).thenReturn("1"); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertTrue(evaluator.eval("has_feature(URG)")); + assertTrue(eval("has_feature(URG)", pp)); } @Test public void functionlike_macro_defined_false() { CxxPreprocessor pp = mock(CxxPreprocessor.class); when(pp.valueOf(anyString())).thenReturn("0"); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertFalse(evaluator.eval("has_feature(URG)")); + assertFalse(eval("has_feature(URG)", pp)); } @Test public void functionlike_macro_undefined() { CxxPreprocessor pp = mock(CxxPreprocessor.class); when(pp.valueOf(anyString())).thenReturn(null); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertFalse(evaluator.eval("has_feature(URG)")); + assertFalse(eval("has_feature(URG)", pp)); } @Test @@ -286,16 +343,12 @@ public void defined_true_without_parantheses() { CxxPreprocessor pp = mock(CxxPreprocessor.class); String macro = "LALA"; when(pp.valueOf(macro)).thenReturn("1"); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertTrue(evaluator.eval("defined " + macro)); + assertTrue(eval("defined " + macro, pp)); } @Test public void defined_false_without_parantheses() { - ExpressionEvaluator evaluator - = new ExpressionEvaluator(mock(CxxConfiguration.class), - mock(CxxPreprocessor.class)); - assertFalse(evaluator.eval("defined LALA")); + assertFalse(eval("defined LALA")); } @Test @@ -303,18 +356,14 @@ public void defined_true_with_parantheses() { CxxPreprocessor pp = mock(CxxPreprocessor.class); String macro = "LALA"; when(pp.valueOf(macro)).thenReturn("1"); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertTrue(evaluator.eval("defined (" + macro + ")")); - assertTrue(evaluator.eval("defined(" + macro + ")")); + assertTrue(eval("defined (" + macro + ")", pp)); + assertTrue(eval("defined(" + macro + ")", pp)); } @Test public void defined_false_with_parantheses() { - ExpressionEvaluator evaluator - = new ExpressionEvaluator(mock(CxxConfiguration.class), - mock(CxxPreprocessor.class)); - assertFalse(evaluator.eval("defined (LALA)")); - assertFalse(evaluator.eval("defined(LALA)")); + assertFalse(eval("defined (LALA)")); + assertFalse(eval("defined(LALA)")); } @Test @@ -345,17 +394,16 @@ public void decode_numbers() { @Test(expected = EvaluationException.class) public void throw_on_invalid_expressions() { - evaluator.eval("\"\""); + eval("\"\""); } @Test public void std_macro_evaluated_as_expected() { CxxPreprocessor pp = new CxxPreprocessor(mock(SquidAstVisitorContext.class), CxxFileTesterHelper.mockCxxLanguage()); - ExpressionEvaluator evaluator = new ExpressionEvaluator(mock(CxxConfiguration.class), pp); - assertTrue(evaluator.eval("__LINE__")); - assertTrue(evaluator.eval("__STDC__")); - assertTrue(evaluator.eval("__STDC_HOSTED__")); - assertTrue(evaluator.eval("__cplusplus")); + assertTrue(eval("__LINE__", pp)); + assertTrue(eval("__STDC__", pp)); + assertTrue(eval("__STDC_HOSTED__", pp)); + assertTrue(eval("__cplusplus", pp)); } } diff --git a/cxx-squid/src/test/resources/parser/preprocessor/self_referential_macros.hpp b/cxx-squid/src/test/resources/parser/preprocessor/self_referential_macros.hpp new file mode 100644 index 0000000000..c681aac699 --- /dev/null +++ b/cxx-squid/src/test/resources/parser/preprocessor/self_referential_macros.hpp @@ -0,0 +1,36 @@ +// https://gcc.gnu.org/onlinedocs/gcc-3.0.1/cpp_3.html#SEC31 + +#define A0 A0 +#if A0 +#endif + +#define B1 A1 +#define A1 B1 +#if A1 +#endif + +#define C2 B2 +#define B2 C2 +#define A2 B2 +#if A2 +#endif + +#define x (4 + y) +#define y (2 * x) +#define NULL 0 +#if NULL +#elif y +#endif + +#if NULL +#elif x +#endif + +// macros are not affected by recursive evaluation +// this is here for completeness only +#define MACRO_FUNC0( A, B ) MACRO_FUNC( A, B ) +#define MACRO_FUNC( A, B ) MACRO_FUNC0( A, B ) + +int main() { + MACRO_FUNC( 1, 2 ); +}