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

com.siyeh.ig.bugs.MismatchedStringBuilderQueryUpdateInspection Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition java-analysis-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2011-2014 Bas Leijdekkers
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.siyeh.ig.bugs;

import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.util.HashSet;
import java.util.Set;

public class MismatchedStringBuilderQueryUpdateInspection extends BaseInspection {

  @NonNls
  private static final Set returnSelfNames = new HashSet();

  static {
    returnSelfNames.add("append");
    returnSelfNames.add("appendCodePoint");
    returnSelfNames.add("delete");
    returnSelfNames.add("deleteCharAt");
    returnSelfNames.add("insert");
    returnSelfNames.add("replace");
    returnSelfNames.add("reverse");
  }

  @Override
  @NotNull
  public String getID() {
    return "MismatchedQueryAndUpdateOfStringBuilder";
  }

  @Nls
  @NotNull
  @Override
  public String getDisplayName() {
    return InspectionGadgetsBundle.message(
      "mismatched.string.builder.query.update.display.name");
  }

  @NotNull
  @Override
  protected String buildErrorString(Object... infos) {
    final boolean updated = ((Boolean)infos[0]).booleanValue();
    final PsiType type = (PsiType)infos[1]; //"StringBuilder";
    if (updated) {
      return InspectionGadgetsBundle.message("mismatched.string.builder.updated.problem.descriptor", type.getPresentableText());
    }
    else {
      return InspectionGadgetsBundle.message("mismatched.string.builder.queried.problem.descriptor", type.getPresentableText());
    }
  }

  @Override
  public boolean isEnabledByDefault() {
    return true;
  }

  @Override
  public boolean runForWholeFile() {
    return true;
  }

  @Override
  public BaseInspectionVisitor buildVisitor() {
    return new MismatchedQueryAndUpdateOfStringBuilderVisitor();
  }

  private static class MismatchedQueryAndUpdateOfStringBuilderVisitor extends BaseInspectionVisitor {

    @Override
    public void visitField(PsiField field) {
      super.visitField(field);
      if (!field.hasModifierProperty(PsiModifier.PRIVATE)) {
        return;
      }
      final PsiClass containingClass = PsiUtil.getTopLevelClass(field);
      if (!checkVariable(field, containingClass)) {
        return;
      }
      final boolean queried = stringBuilderContentsAreQueried(field, containingClass);
      final boolean updated = stringBuilderContentsAreUpdated(field, containingClass);
      if (queried == updated) {
        return;
      }
      registerFieldError(field, Boolean.valueOf(updated), field.getType());
    }

    @Override
    public void visitLocalVariable(PsiLocalVariable variable) {
      super.visitLocalVariable(variable);
      final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
      if (!checkVariable(variable, codeBlock)) {
        return;
      }
      final boolean queried = stringBuilderContentsAreQueried(variable, codeBlock);
      final boolean updated = stringBuilderContentsAreUpdated(variable, codeBlock);
      if (queried == updated) {
        return;
      }
      registerVariableError(variable, Boolean.valueOf(updated), variable.getType());
    }

    private static boolean checkVariable(PsiVariable variable, PsiElement context) {
      if (context == null) {
        return false;
      }
      if (!TypeUtils.variableHasTypeOrSubtype(variable, CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER)) {
        return false;
      }
      if (VariableAccessUtils.variableIsAssigned(variable, context)) {
        return false;
      }
      if (VariableAccessUtils.variableIsAssignedFrom(variable, context)) {
        return false;
      }
      if (VariableAccessUtils.variableIsReturned(variable, context)) {
        return false;
      }
      if (VariableAccessUtils.variableIsPassedAsMethodArgument(variable, context)) {
        return false;
      }
      return !VariableAccessUtils.variableIsUsedInArrayInitializer(variable, context);
    }

    private static boolean stringBuilderContentsAreUpdated(
      PsiVariable variable, PsiElement context) {
      final PsiExpression initializer = variable.getInitializer();
      if (initializer != null && !isDefaultConstructorCall(initializer)) {
        return true;
      }
      return isStringBuilderUpdated(variable, context);
    }

    private static boolean stringBuilderContentsAreQueried(PsiVariable variable, PsiElement context) {
      return isStringBuilderQueried(variable, context);
    }

    private static boolean isDefaultConstructorCall(PsiExpression initializer) {
      if (!(initializer instanceof PsiNewExpression)) {
        return false;
      }
      final PsiNewExpression newExpression = (PsiNewExpression)initializer;
      final PsiJavaCodeReferenceElement classReference = newExpression.getClassReference();
      if (classReference == null) {
        return false;
      }
      final PsiElement target = classReference.resolve();
      if (!(target instanceof PsiClass)) {
        return false;
      }
      final PsiClass aClass = (PsiClass)target;
      final String qualifiedName = aClass.getQualifiedName();
      if (!CommonClassNames.JAVA_LANG_STRING_BUILDER.equals(qualifiedName) &&
          !CommonClassNames.JAVA_LANG_STRING_BUFFER.equals(qualifiedName)) {
        return false;
      }
      final PsiExpressionList argumentList = newExpression.getArgumentList();
      if (argumentList == null) {
        return false;
      }
      final PsiExpression[] arguments = argumentList.getExpressions();
      if (arguments.length == 0) {
        return true;
      }
      final PsiExpression argument = arguments[0];
      final PsiType argumentType = argument.getType();
      return PsiType.INT.equals(argumentType);
    }
  }

  public static boolean isStringBuilderUpdated(PsiVariable variable, PsiElement context) {
    final StringBuilderUpdateCalledVisitor visitor = new StringBuilderUpdateCalledVisitor(variable);
    context.accept(visitor);
    return visitor.isUpdated();
  }

  private static class StringBuilderUpdateCalledVisitor extends JavaRecursiveElementVisitor {

    @NonNls
    private static final Set updateNames = new HashSet();

    static {
      updateNames.add("append");
      updateNames.add("appendCodePoint");
      updateNames.add("delete");
      updateNames.add("delete");
      updateNames.add("deleteCharAt");
      updateNames.add("insert");
      updateNames.add("replace");
      updateNames.add("setCharAt");
    }

    private final PsiVariable variable;
    boolean updated = false;

    public StringBuilderUpdateCalledVisitor(PsiVariable variable) {
      this.variable = variable;
    }

    public boolean isUpdated() {
      return updated;
    }

    @Override
    public void visitMethodCallExpression(PsiMethodCallExpression expression) {
      if (updated) {
        return;
      }
      super.visitMethodCallExpression(expression);
      checkReferenceExpression(expression.getMethodExpression());
    }

    @Override
    public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) {
      if (updated) {
        return;
      }
      super.visitMethodReferenceExpression(expression);
      checkReferenceExpression(expression);
    }

    private void checkReferenceExpression(PsiReferenceExpression methodExpression) {
      final String name = methodExpression.getReferenceName();
      if (!updateNames.contains(name)) {
        return;
      }
      final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
      if (hasReferenceToVariable(variable, qualifierExpression)) {
        updated = true;
      }
    }
  }

  public static boolean isStringBuilderQueried(PsiVariable variable, PsiElement context) {
    final StringBuilderQueryCalledVisitor visitor = new StringBuilderQueryCalledVisitor(variable);
    context.accept(visitor);
    return visitor.isQueried();
  }

  private static class StringBuilderQueryCalledVisitor extends JavaRecursiveElementVisitor {

    @NonNls
    private static final Set queryNames = new HashSet();

    static {
      queryNames.add("toString");
      queryNames.add("indexOf");
      queryNames.add("lastIndexOf");
      queryNames.add("capacity");
      queryNames.add("charAt");
      queryNames.add("codePointAt");
      queryNames.add("codePointBefore");
      queryNames.add("codePointCount");
      queryNames.add("equals");
      queryNames.add("getChars");
      queryNames.add("hashCode");
      queryNames.add("length");
      queryNames.add("offsetByCodePoints");
      queryNames.add("subSequence");
      queryNames.add("substring");
    }

    private final PsiVariable variable;
    private boolean queried = false;

    private StringBuilderQueryCalledVisitor(PsiVariable variable) {
      this.variable = variable;
    }

    public boolean isQueried() {
      return queried;
    }

    @Override
    public void visitElement(@NotNull PsiElement element) {
      if (queried) {
        return;
      }
      super.visitElement(element);
    }

    @Override
    public void visitReferenceExpression(PsiReferenceExpression expression) {
      if (queried) {
        return;
      }
      super.visitReferenceExpression(expression);
      final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression);
      if (!(parent instanceof PsiPolyadicExpression)) {
        return;
      }
      final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent;
      final IElementType tokenType = polyadicExpression.getOperationTokenType();
      if (!JavaTokenType.PLUS.equals(tokenType)) {
        return;
      }
      final PsiElement target = expression.resolve();
      if (!variable.equals(target)) {
        return;
      }
      final PsiType type = polyadicExpression.getType();
      if (type == null || !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
        return;
      }
      queried = true;
    }

    @Override
    public void visitMethodCallExpression(PsiMethodCallExpression expression) {
      if (queried) {
        return;
      }
      super.visitMethodCallExpression(expression);
      final PsiReferenceExpression methodExpression = expression.getMethodExpression();
      final String name = methodExpression.getReferenceName();
      final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
      if (!queryNames.contains(name)) {
        if (returnSelfNames.contains(name) && hasReferenceToVariable(variable, qualifierExpression) && isVariableValueUsed(expression)) {
          queried = true;
        }
        return;
      }
      if (hasReferenceToVariable(variable, qualifierExpression)) {
        queried = true;
      }
    }
  }

  private static boolean isVariableValueUsed(PsiExpression expression) {
    final PsiElement parent = expression.getParent();
    if (parent instanceof PsiParenthesizedExpression) {
      final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression)parent;
      return isVariableValueUsed(parenthesizedExpression);
    }
    else if (parent instanceof PsiTypeCastExpression) {
      final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)parent;
      return isVariableValueUsed(typeCastExpression);
    }
    else if (parent instanceof PsiReturnStatement) {
      return true;
    }
    else if (parent instanceof PsiExpressionList) {
      final PsiElement grandParent = parent.getParent();
      if (grandParent instanceof PsiMethodCallExpression) {
        return true;
      }
    }
    else if (parent instanceof PsiArrayInitializerExpression) {
      return true;
    }
    else if (parent instanceof PsiAssignmentExpression) {
      final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent;
      final PsiExpression rhs = assignmentExpression.getRExpression();
      return expression.equals(rhs);
    }
    else if (parent instanceof PsiVariable) {
      final PsiVariable variable = (PsiVariable)parent;
      final PsiExpression initializer = variable.getInitializer();
      return expression.equals(initializer);
    }
    return false;
  }

  private static boolean hasReferenceToVariable(PsiVariable variable, PsiElement element) {
    if (element instanceof PsiReferenceExpression) {
      final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)element;
      final PsiElement target = referenceExpression.resolve();
      if (variable.equals(target)) {
        return true;
      }
    }
    else if (element instanceof PsiParenthesizedExpression) {
      final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression)element;
      final PsiExpression expression = parenthesizedExpression.getExpression();
      return hasReferenceToVariable(variable, expression);
    }
    else if (element instanceof PsiMethodCallExpression) {
      final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
      final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
      final String name = methodExpression.getReferenceName();
      if (returnSelfNames.contains(name)) {
        return hasReferenceToVariable(variable, methodExpression.getQualifierExpression());
      }
    }
    else if (element instanceof PsiConditionalExpression) {
      final PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)element;
      final PsiExpression thenExpression = conditionalExpression.getThenExpression();
      if (hasReferenceToVariable(variable, thenExpression)) {
        return true;
      }
      final PsiExpression elseExpression = conditionalExpression.getElseExpression();
      return hasReferenceToVariable(variable, elseExpression);
    }
    return false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy