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

com.siyeh.ig.threading.VariableAccessVisitor 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 2003-2015 Dave Griffith, 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.threading;

import com.intellij.psi.*;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.HashMap;
import com.siyeh.ig.psiutils.SynchronizationUtil;
import org.jetbrains.annotations.NotNull;

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

class VariableAccessVisitor extends JavaRecursiveElementVisitor {

  private final PsiClass aClass;
  private final Set m_synchronizedAccesses =
    new HashSet(2);
  private final Set m_unsynchronizedAccesses =
    new HashSet(2);
  private final Set methodsAlwaysSynchronized =
    new HashSet();
  private final Set methodsNotAlwaysSynchronized =
    new HashSet();
  private final Set unusedMethods = new HashSet();
  private final Set usedMethods = new HashSet();
  private boolean m_inInitializer = false;
  private boolean m_inSynchronizedContext = false;
  private boolean privateMethodUsagesCalculated = false;
  private final boolean countGettersAndSetters;

  VariableAccessVisitor(PsiClass aClass, boolean countGettersAndSetters) {
    this.aClass = aClass;
    this.countGettersAndSetters = countGettersAndSetters;
  }

  @Override
  public void visitClass(PsiClass classToVisit) {
    calculatePrivateMethodUsagesIfNecessary();
    final boolean wasInSync = m_inSynchronizedContext;
    if (!classToVisit.equals(aClass)) {
      m_inSynchronizedContext = false;
    }
    super.visitClass(classToVisit);
    m_inSynchronizedContext = wasInSync;
  }

  @Override
  public void visitLambdaExpression(PsiLambdaExpression expression) {
    final boolean wasInSync = m_inSynchronizedContext;
    m_inSynchronizedContext = false;
    super.visitLambdaExpression(expression);
    m_inSynchronizedContext = wasInSync;
  }

  @Override
  public void visitReferenceExpression(@NotNull PsiReferenceExpression ref) {
    super.visitReferenceExpression(ref);
    final PsiExpression qualifier = ref.getQualifierExpression();
    if (qualifier != null && !(qualifier instanceof PsiThisExpression)) {
      return;
    }
    final PsiElement element = ref.resolve();
    if (!(element instanceof PsiField)) {
      return;
    }
    if (m_inInitializer) {
    }
    else if (m_inSynchronizedContext) {
      m_synchronizedAccesses.add((PsiField)element);
    }
    else if (ref.getParent() instanceof PsiSynchronizedStatement) {
      //covers the very specific case of a field reference being directly
      // used as a lock
      m_synchronizedAccesses.add((PsiField)element);
    }
    else {
      m_unsynchronizedAccesses.add((PsiField)element);
    }
  }

  @Override
  public void visitMethodCallExpression(PsiMethodCallExpression expression) {
    super.visitMethodCallExpression(expression);
    if (!countGettersAndSetters) {
      return;
    }
    final PsiReferenceExpression methodExpression =
      expression.getMethodExpression();
    final PsiExpression qualifier =
      methodExpression.getQualifierExpression();
    if (qualifier != null && !(qualifier instanceof PsiThisExpression)) {
      return;
    }
    final PsiMethod method = (PsiMethod)methodExpression.resolve();
    PsiField field = PropertyUtil.getFieldOfGetter(method);
    if (field == null) {
      field = PropertyUtil.getFieldOfSetter(method);
    }
    if (field == null) {
      return;
    }
    if (m_inInitializer) {
    }
    else if (m_inSynchronizedContext) {
      m_synchronizedAccesses.add(field);
    }
    else {
      m_unsynchronizedAccesses.add(field);
    }
  }

  @Override
  public void visitCodeBlock(PsiCodeBlock block) {
    final boolean wasInSync = m_inSynchronizedContext;
    if (block.getParent() instanceof PsiSynchronizedStatement) {
      m_inSynchronizedContext = true;
    }
    super.visitCodeBlock(block);
    m_inSynchronizedContext = wasInSync;
  }

  @Override
  public void visitAssertStatement(PsiAssertStatement statement) {
    final PsiExpression condition = statement.getAssertCondition();
    if (SynchronizationUtil.isCallToHoldsLock(condition)) {
      m_inSynchronizedContext = true;
    }
    super.visitAssertStatement(statement);
  }

  @Override
  public void visitMethod(@NotNull PsiMethod method) {
    if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
      if (unusedMethods.contains(method)) {
        return;
      }
    }
    final boolean methodIsSynchronized =
      method.hasModifierProperty(PsiModifier.SYNCHRONIZED)
      || methodIsAlwaysUsedSynchronized(method);
    boolean wasInSync = false;
    if (methodIsSynchronized) {
      wasInSync = m_inSynchronizedContext;
      m_inSynchronizedContext = true;
    }
    final boolean isConstructor = method.isConstructor();
    if (isConstructor) {
      m_inInitializer = true;
    }
    super.visitMethod(method);
    if (methodIsSynchronized) {
      m_inSynchronizedContext = wasInSync;
    }
    if (isConstructor) {
      m_inInitializer = false;
    }
  }

  private boolean methodIsAlwaysUsedSynchronized(PsiMethod method) {
    if (!method.hasModifierProperty(PsiModifier.PRIVATE)) {
      return false;
    }
    return methodsAlwaysSynchronized.contains(method);
  }

  private void calculatePrivateMethodUsagesIfNecessary() {
    if (privateMethodUsagesCalculated) {
      return;
    }
    final Set privateMethods = findPrivateMethods();
    final HashMap> referenceMap =
      buildReferenceMap(privateMethods);
    determineUsedMethods(privateMethods, referenceMap);
    determineUsageMap(referenceMap);
    privateMethodUsagesCalculated = true;
  }

  private void determineUsageMap(HashMap> referenceMap) {
    final Set remainingMethods =
      new HashSet(usedMethods);
    boolean stabilized = false;
    while (!stabilized) {
      stabilized = true;
      final Set methodsDeterminedThisPass =
        new HashSet();
      for (PsiMethod method : remainingMethods) {
        final Collection references =
          referenceMap.get(method);
        boolean areAllReferencesSynchronized = true;
        for (PsiReference reference : references) {
          if (isKnownToBeUsed(reference)) {
            if (isInKnownUnsynchronizedContext(reference)) {
              methodsNotAlwaysSynchronized.add(method);
              methodsDeterminedThisPass.add(method);
              areAllReferencesSynchronized = false;
              stabilized = false;
              break;
            }
            if (!isInKnownSynchronizedContext(reference)) {
              areAllReferencesSynchronized = false;
            }
          }
        }
        if (areAllReferencesSynchronized &&
            unusedMethods.contains(method)) {
          methodsAlwaysSynchronized.add(method);
          methodsDeterminedThisPass.add(method);
          stabilized = false;
        }
      }
      remainingMethods.removeAll(methodsDeterminedThisPass);
    }
    methodsAlwaysSynchronized.addAll(remainingMethods);
  }

  private void determineUsedMethods(
    Set privateMethods,
    HashMap> referenceMap) {
    final Set remainingMethods =
      new HashSet(privateMethods);
    boolean stabilized = false;
    while (!stabilized) {
      stabilized = true;
      final Set methodsDeterminedThisPass =
        new HashSet();
      for (PsiMethod method : remainingMethods) {
        final Collection references =
          referenceMap.get(method);
        for (PsiReference reference : references) {
          if (isKnownToBeUsed(reference)) {
            usedMethods.add(method);
            methodsDeterminedThisPass.add(method);
            stabilized = false;
          }
        }
      }
      remainingMethods.removeAll(methodsDeterminedThisPass);
    }
    unusedMethods.addAll(remainingMethods);
  }

  private static HashMap>
  buildReferenceMap(Set privateMethods) {
    final HashMap> referenceMap =
      new HashMap();
    for (PsiMethod method : privateMethods) {
      final SearchScope scope = method.getUseScope();
      final Collection references =
        ReferencesSearch.search(method, scope).findAll();
      referenceMap.put(method, references);
    }
    return referenceMap;
  }

  private Set findPrivateMethods() {
    final Set privateMethods = new HashSet();
    final PsiMethod[] methods = aClass.getMethods();
    for (PsiMethod method : methods) {
      if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
        privateMethods.add(method);
      }
    }
    return privateMethods;
  }

  private boolean isKnownToBeUsed(PsiReference reference) {
    final PsiElement element = reference.getElement();

    final PsiMethod method =
      PsiTreeUtil.getParentOfType(element, PsiMethod.class);
    if (method == null) {
      return true;
    }
    if (!method.hasModifierProperty(PsiModifier.PRIVATE)) {
      return true;
    }
    return usedMethods.contains(method);
  }

  private boolean isInKnownSynchronizedContext(PsiReference reference) {
    final PsiElement element = reference.getElement();
    if (PsiTreeUtil.getParentOfType(element,
                                    PsiSynchronizedStatement.class) != null) {
      return true;
    }
    final PsiMethod method =
      PsiTreeUtil.getParentOfType(element, PsiMethod.class);
    if (method == null) {
      return false;
    }
    if (method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
      return true;
    }
    if (methodsAlwaysSynchronized.contains(method)) {
      return true;
    }
    return !methodsNotAlwaysSynchronized.contains(method);
  }

  private boolean isInKnownUnsynchronizedContext(PsiReference reference) {
    final PsiElement element = reference.getElement();
    if (PsiTreeUtil.getParentOfType(element,
                                    PsiSynchronizedStatement.class) != null) {
      return false;
    }
    final PsiMethod method =
      PsiTreeUtil.getParentOfType(element, PsiMethod.class);
    if (method == null) {
      return true;
    }
    if (method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
      return false;
    }
    if (!method.hasModifierProperty(PsiModifier.PRIVATE)) {
      return true;
    }
    if (methodsAlwaysSynchronized.contains(method)) {
      return false;
    }
    return methodsNotAlwaysSynchronized.contains(method);
  }

  @Override
  public void visitClassInitializer(@NotNull PsiClassInitializer initializer) {
    m_inInitializer = true;
    super.visitClassInitializer(initializer);
    m_inInitializer = false;
  }

  @Override
  public void visitField(@NotNull PsiField field) {
    m_inInitializer = true;
    super.visitField(field);
    m_inInitializer = false;
  }

  public Set getInappropriatelyAccessedFields() {
    final Set out =
      new HashSet(m_synchronizedAccesses);
    out.retainAll(m_unsynchronizedAccesses);
    return out;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy