blob: e510381682275c923feff1f921549ee79a05da55 [file] [log] [blame]
/*
* Copyright 2003-2007 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 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<PsiField> m_synchronizedAccesses =
new HashSet<PsiField>(2);
private final Set<PsiField> m_unsynchronizedAccesses =
new HashSet<PsiField>(2);
private final Set<PsiMethod> methodsAlwaysSynchronized =
new HashSet<PsiMethod>();
private final Set<PsiMethod> methodsNotAlwaysSynchronized =
new HashSet<PsiMethod>();
private final Set<PsiMethod> unusedMethods = new HashSet<PsiMethod>();
private final Set<PsiMethod> usedMethods = new HashSet<PsiMethod>();
private boolean m_inInitializer = false;
private boolean m_inSynchronizedContext = false;
private boolean privateMethodUsagesCalculated = false;
private final boolean countGettersAndSetters;
VariableAccessVisitor(PsiClass aClass, boolean countGettersAndSetters) {
super();
this.aClass = aClass;
this.countGettersAndSetters = countGettersAndSetters;
}
@Override
public void visitClass(PsiClass classToVisit) {
calculatePrivateMethodUsagesIfNecessary();
super.visitClass(classToVisit);
}
@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 visitMethod(@NotNull PsiMethod method) {
if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
if (unusedMethods.contains(method)) {
return;
}
}
final boolean methodIsSynchonized =
method.hasModifierProperty(PsiModifier.SYNCHRONIZED)
|| methodIsAlwaysUsedSynchronized(method);
boolean wasInSync = false;
if (methodIsSynchonized) {
wasInSync = m_inSynchronizedContext;
m_inSynchronizedContext = true;
}
final boolean isConstructor = method.isConstructor();
if (isConstructor) {
m_inInitializer = true;
}
super.visitMethod(method);
if (methodIsSynchonized) {
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<PsiMethod> privateMethods = findPrivateMethods();
final HashMap<PsiMethod, Collection<PsiReference>> referenceMap =
buildReferenceMap(privateMethods);
determineUsedMethods(privateMethods, referenceMap);
determineUsageMap(referenceMap);
privateMethodUsagesCalculated = true;
}
private void determineUsageMap(HashMap<PsiMethod,
Collection<PsiReference>> referenceMap) {
final Set<PsiMethod> remainingMethods =
new HashSet<PsiMethod>(usedMethods);
boolean stabilized = false;
while (!stabilized) {
stabilized = true;
final Set<PsiMethod> methodsDeterminedThisPass =
new HashSet<PsiMethod>();
for (PsiMethod method : remainingMethods) {
final Collection<PsiReference> 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<PsiMethod> privateMethods,
HashMap<PsiMethod, Collection<PsiReference>> referenceMap) {
final Set<PsiMethod> remainingMethods =
new HashSet<PsiMethod>(privateMethods);
boolean stabilized = false;
while (!stabilized) {
stabilized = true;
final Set<PsiMethod> methodsDeterminedThisPass =
new HashSet<PsiMethod>();
for (PsiMethod method : remainingMethods) {
final Collection<PsiReference> 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<PsiMethod, Collection<PsiReference>>
buildReferenceMap(Set<PsiMethod> privateMethods) {
final HashMap<PsiMethod, Collection<PsiReference>> referenceMap =
new HashMap();
for (PsiMethod method : privateMethods) {
final SearchScope scope = method.getUseScope();
final Collection<PsiReference> references =
ReferencesSearch.search(method, scope).findAll();
referenceMap.put(method, references);
}
return referenceMap;
}
private Set<PsiMethod> findPrivateMethods() {
final Set<PsiMethod> privateMethods = new HashSet<PsiMethod>();
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<PsiField> getInappropriatelyAccessedFields() {
final Set<PsiField> out =
new HashSet<PsiField>(m_synchronizedAccesses);
out.retainAll(m_unsynchronizedAccesses);
return out;
}
}