All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.model.ExpressionUtils Maven / Gradle / Ivy

/*
 * SonarQube Java
 * Copyright (C) 2012-2023 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * 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.java.model;

import java.util.Optional;
import java.util.function.BiFunction;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.ParenthesizedTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import org.sonar.plugins.java.api.tree.VariableTree;

public final class ExpressionUtils {

  private ExpressionUtils() {
  }

  /**
   * In case of simple assignments, only the expression is evaluated, as we only use the reference to the variable to store the result.
   * For SE-Based checks, only a single value should be unstacked if its the case. For other cases, two values should be unstacked.
   * See JLS8-15.26
   *
   * @param tree The assignment tree
   * @return true if the tree is a simple assignment
   * @see #extractIdentifier(AssignmentExpressionTree)
   */
  public static boolean isSimpleAssignment(AssignmentExpressionTree tree) {
    if (!tree.is(Tree.Kind.ASSIGNMENT)) {
      // This can't possibly be a simple assignment.
      return false;
    }

    ExpressionTree variable = ExpressionUtils.skipParentheses(tree.variable());
    return variable.is(Tree.Kind.IDENTIFIER) || isSelectOnThisOrSuper(tree);
  }

  /**
   * Checks of is the given tree is a {@link MemberSelectExpressionTree} which is selecting with this or super
   * @param tree The tree to check.
   * @return true when the tree is a select on this or super
   * @see #isSelectOnThisOrSuper(MemberSelectExpressionTree)
   */
  public static boolean isSelectOnThisOrSuper(AssignmentExpressionTree tree) {
    ExpressionTree variable = ExpressionUtils.skipParentheses(tree.variable());
    return variable.is(Tree.Kind.MEMBER_SELECT) && isSelectOnThisOrSuper((MemberSelectExpressionTree) variable);
  }

  /**
   * Checks of is the given tree is selecting with this or super
   * @param tree The tree to check.
   * @return true when the tree is a select on this or super
   * @see #isSelectOnThisOrSuper(AssignmentExpressionTree)
   */
  public static boolean isSelectOnThisOrSuper(MemberSelectExpressionTree tree) {
    if (!tree.expression().is(Tree.Kind.IDENTIFIER)) {
      // This is no longer simple.
      return false;
    }

    String selectSourceName = ((IdentifierTree) tree.expression()).name();
    return "this".equalsIgnoreCase(selectSourceName) || "super".equalsIgnoreCase(selectSourceName);
  }

  public static IdentifierTree extractIdentifier(AssignmentExpressionTree tree) {
    Optional identifier = extractIdentifier(tree.variable());

    if (identifier.isPresent()) {
      return identifier.get();
    }

    // This should not be possible.
    // If it happens anyway, you should make sure the assignment is simple (by calling isSimpleAssignment) before.
    throw new IllegalArgumentException("Can not extract identifier.");
  }

  private static Optional extractIdentifier(ExpressionTree tree) {
    ExpressionTree cleanedExpression = ExpressionUtils.skipParentheses(tree);
    if (cleanedExpression.is(Tree.Kind.IDENTIFIER)) {
      return Optional.of(((IdentifierTree) cleanedExpression));
    } else if (cleanedExpression.is(Tree.Kind.MEMBER_SELECT)) {
      MemberSelectExpressionTree selectTree = (MemberSelectExpressionTree) cleanedExpression;
      if (isSelectOnThisOrSuper(selectTree)) {
        return Optional.of(selectTree.identifier());
      }
    }
    return Optional.empty();
  }

  public static Optional extractIdentifierSymbol(ExpressionTree tree) {
    return extractIdentifier(tree).map(IdentifierTree::symbol);
  }

  /**
   * Return whether we are sure that the method invocation is on a given variable.
   *
   * If unsure (variable is null, or we can not extract an identifier from the method invocation),
   * return a default value
   */
  public static boolean isInvocationOnVariable(MethodInvocationTree mit, @Nullable Symbol variable, boolean defaultReturn) {
    ExpressionTree methodSelect = mit.methodSelect();
    if (variable == null || !methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
      return defaultReturn;
    }
    return extractIdentifierSymbol(((MemberSelectExpressionTree) methodSelect).expression())
      .map(variable::equals)
      .orElse(defaultReturn);
  }

  public static ExpressionTree skipParentheses(ExpressionTree tree) {
    ExpressionTree result = tree;
    while (result.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) {
      result = ((ParenthesizedTree) result).expression();
    }
    return result;
  }

  public static boolean isNullLiteral(ExpressionTree tree) {
    return skipParentheses(tree).is(Tree.Kind.NULL_LITERAL);
  }

  public static boolean isSecuringByte(ExpressionTree expression) {
    if (expression.is(Tree.Kind.AND)) {
      BinaryExpressionTree and = (BinaryExpressionTree) expression;
      return LiteralUtils.is0xff(and.rightOperand()) || LiteralUtils.is0xff(and.leftOperand());
    }
    return false;
  }

  /**
   * Retrieve the identifier corresponding to the method name associated to the method invocation
   */
  public static IdentifierTree methodName(MethodInvocationTree mit) {
    ExpressionTree methodSelect = mit.methodSelect();
    IdentifierTree id;
    if (methodSelect.is(Tree.Kind.IDENTIFIER)) {
      id = (IdentifierTree) methodSelect;
    } else {
      id = ((MemberSelectExpressionTree) methodSelect).identifier();
    }
    return id;
  }

  /**
   * Return the first enclosing method or constructor containing the given expression.
   */
  @CheckForNull
  public static MethodTree getEnclosingMethod(ExpressionTree expr) {
    return getEnclosingElement(expr, Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR);
  }

  @CheckForNull
  public static MethodTree getEnclosingElement(ExpressionTree expr, Tree.Kind... kinds) {
    Tree result = expr.parent();
    while (result != null && !result.is(kinds)) {
      result = result.parent();
    }
    return (MethodTree) result;
  }

  @Nullable
  public static Tree getParentOfType(Tree tree, Tree.Kind... kinds) {
    Tree result = tree.parent();
    while (result != null && !result.is(kinds)) {
      result = result.parent();
    }
    return result;
  }

  public static Optional getAssignedSymbol(ExpressionTree exp) {
    Tree parent = exp.parent();
    if (parent != null) {
      if (parent.is(Tree.Kind.ASSIGNMENT)) {
        return extractIdentifierSymbol(((AssignmentExpressionTree) parent).variable());
      } else if (parent.is(Tree.Kind.VARIABLE)) {
        return Optional.of(((VariableTree) parent).simpleName().symbol());
      }
    }
    return Optional.empty();
  }

  /**
   * Checks if the given expression refers to "this"
   * @param expression the expression to check
   * @return true if this expression refers to "this"
   */
  public static boolean isThis(ExpressionTree expression) {
    ExpressionTree newExpression = ExpressionUtils.skipParentheses(expression);
    return newExpression.is(Tree.Kind.IDENTIFIER) && "this".equals(((IdentifierTree) newExpression).name());
  }

  @CheckForNull
  public static Object resolveAsConstant(ExpressionTree tree) {
    ExpressionTree expression = tree;
    while (expression.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) {
      expression = ((ParenthesizedTree) expression).expression();
    }
    if (expression.is(Tree.Kind.MEMBER_SELECT)) {
      expression = ((MemberSelectExpressionTree) expression).identifier();
    }
    if (expression.is(Tree.Kind.IDENTIFIER)) {
      return resolveIdentifier((IdentifierTree) expression);
    }
    if (expression.is(Tree.Kind.BOOLEAN_LITERAL)) {
      return Boolean.parseBoolean(((LiteralTree) expression).value());
    }
    if (expression.is(Tree.Kind.STRING_LITERAL, Tree.Kind.TEXT_BLOCK)) {
      return LiteralUtils.getAsStringValue((LiteralTree) expression);
    }
    if (expression instanceof UnaryExpressionTree) {
      return resolveUnaryExpression((UnaryExpressionTree) expression);
    }
    if (expression.is(Tree.Kind.INT_LITERAL)) {
      return LiteralUtils.intLiteralValue(expression);
    }
    if (expression.is(Tree.Kind.LONG_LITERAL)) {
      return LiteralUtils.longLiteralValue(expression);
    }
    if (expression.is(Tree.Kind.PLUS)) {
      return resolvePlus((BinaryExpressionTree) expression);
    }
    if (expression.is(Tree.Kind.OR)) {
      return resolveOr((BinaryExpressionTree) expression);
    }
    if (expression.is(Tree.Kind.MINUS)) {
      return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a - b, (a, b) -> a - b);
    }
    if (expression.is(Tree.Kind.MULTIPLY)) {
      return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a * b, (a, b) -> a * b);
    }
    if (expression.is(Tree.Kind.DIVIDE)) {
      return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a / b, (a, b) -> a / b);
    }
    if (expression.is(Tree.Kind.REMAINDER)) {
      return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a % b, (a, b) -> a % b);
    }
    return null;
  }

  @CheckForNull
  private static Object resolveUnaryExpression(UnaryExpressionTree unaryExpression) {
    Object value = resolveAsConstant(unaryExpression.expression());
    if (unaryExpression.is(Tree.Kind.UNARY_PLUS)) {
      return value;
    } else if (unaryExpression.is(Tree.Kind.UNARY_MINUS)) {
      if (value instanceof Long) {
        return -(Long) value;
      } else if (value instanceof Integer) {
        return -(Integer) value;
      }
    } else if (unaryExpression.is(Tree.Kind.BITWISE_COMPLEMENT)) {
      if (value instanceof Long) {
        return ~(Long) value;
      } else if (value instanceof Integer) {
        return ~(Integer) value;
      }
    } else if (unaryExpression.is(Tree.Kind.LOGICAL_COMPLEMENT) && value instanceof Boolean) {
      return !(Boolean) value;
    }
    return null;
  }

  @CheckForNull
  private static Object resolveIdentifier(IdentifierTree tree) {
    Symbol symbol = tree.symbol();
    if (!symbol.isVariableSymbol()) {
      return null;
    }
    Symbol owner = symbol.owner();
    if (owner.isTypeSymbol() && owner.type().is("java.lang.Boolean")) {
      if ("TRUE".equals(symbol.name())) {
        return Boolean.TRUE;
      } else if ("FALSE".equals(symbol.name())) {
        return Boolean.FALSE;
      }
    }
    return ((Symbol.VariableSymbol) symbol).constantValue().orElse(null);
  }

  @CheckForNull
  private static Object resolvePlus(BinaryExpressionTree binaryExpression) {
    Object left = resolveAsConstant(binaryExpression.leftOperand());
    Object right = resolveAsConstant(binaryExpression.rightOperand());
    if (left == null || right == null) {
      return null;
    } else if (left instanceof String) {
      return ((String) left) + right;
    } else if (right instanceof String) {
      return left + ((String) right);
    }
    return resolveArithmeticOperation(left, right, Long::sum, Integer::sum);
  }

  @CheckForNull
  private static Object resolveArithmeticOperation(BinaryExpressionTree binaryExpression,
                                                   BiFunction longOperation,
                                                   BiFunction intOperation) {
    Object left = resolveAsConstant(binaryExpression.leftOperand());
    Object right = resolveAsConstant(binaryExpression.rightOperand());
    if (left == null || right == null) {
      return null;
    }
    return resolveArithmeticOperation(left, right, longOperation, intOperation);
  }

  @CheckForNull
  private static Object resolveArithmeticOperation(Object left, Object right, BiFunction longOperation, BiFunction intOperation) {
    if (left instanceof Integer && right instanceof Integer) {
      return intOperation.apply(((Number) left).intValue(), ((Number) right).intValue());
    } else if ((left instanceof Long || right instanceof Long) && (left instanceof Integer || right instanceof Integer)) {
      return longOperation.apply(((Number) left).longValue(), ((Number) right).longValue());
    }
    return null;
  }

  @CheckForNull
  private static Object resolveOr(BinaryExpressionTree binaryExpression) {
    Object left = resolveAsConstant(binaryExpression.leftOperand());
    Object right = resolveAsConstant(binaryExpression.rightOperand());
    if (left == null || right == null) {
      return null;
    } else if (left instanceof Long && right instanceof Long) {
      return ((Long) left) | ((Long) right);
    } else if (left instanceof Long && right instanceof Integer) {
      return ((Long) left) | ((Integer) right);
    } else if (left instanceof Integer && right instanceof Long) {
      return ((Integer) left) | ((Long) right);
    } else if (left instanceof Integer && right instanceof Integer) {
      return ((Integer) left) | ((Integer) right);
    }
    return null;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy