
edu.umd.cs.findbugs.detect.ResourceInMultipleThreadsDetector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* SpotBugs - Find bugs in Java programs
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.detect;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.BootstrapMethods;
import org.apache.bcel.classfile.ConstantInvokeDynamic;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.bcel.BCELUtil;
import edu.umd.cs.findbugs.bcel.OpcodeStackDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.util.BootstrapMethodsUtil;
import edu.umd.cs.findbugs.util.CollectionAnalysis;
import edu.umd.cs.findbugs.util.MethodAnalysis;
import edu.umd.cs.findbugs.util.MutableClasses;
public class ResourceInMultipleThreadsDetector extends OpcodeStackDetector {
private static final class FieldData {
private boolean modified = false;
private boolean onlySynchronized = true;
private boolean onlyPutField = true;
private final Map> methodBugs = new HashMap<>();
}
private final BugReporter bugReporter;
private final Set synchronizedCollectionTypedFields = new HashSet<>();
private final Set methodsUsedInThreads = new HashSet<>();
private final Map fieldsUsedInThreads = new HashMap<>();
private boolean synchronizedBlock = false;
private boolean firstPass = true;
public ResourceInMultipleThreadsDetector(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
@Override
public void visit(JavaClass obj) {
resetState();
for (Method m : obj.getMethods()) {
doVisitMethod(m);
}
firstPass = false;
}
@Override
public void visit(Method method) {
synchronizedBlock = method.isSynchronized();
}
@Override
public void sawOpcode(int seen) {
if (seen == Const.MONITORENTER) {
synchronizedBlock = true;
} else if (seen == Const.MONITOREXIT) {
synchronizedBlock = false;
}
if (firstPass) {
collectMethodsUsedInThreads(seen);
} else {
try {
collectFieldsUsedInThreads(seen);
} catch (CheckedAnalysisException e) {
bugReporter.logError(String.format("Detector %s caught exception while analyzing class %s", getClass().getName(), getClassName()), e);
}
}
}
private void collectMethodsUsedInThreads(int seen) {
if (seen == Const.INVOKEDYNAMIC && getStack().getStackDepth() > 1
&& "Ljava/lang/Thread;".equals(getStack().getStackItem(1).getSignature())
&& !isJavaRuntimeMethod()) {
getMethodFromBootstrap(getThisClass(), (ConstantInvokeDynamic) getConstantRefOperand()).ifPresent(methodsUsedInThreads::add);
} else if ((seen == Const.INVOKEVIRTUAL || seen == Const.INVOKEINTERFACE || seen == Const.INVOKESPECIAL || seen == Const.INVOKESTATIC)
&& getXMethodOperand() != null && methodsUsedInThreads.contains(getMethodDescriptor())
&& getClassDescriptor().equals(getXMethodOperand().getClassDescriptor())) {
methodsUsedInThreads.add(getMethodDescriptorOperand());
}
}
/**
* Ignore a special case where a Thread is passed to the {@code java.lang.Runtime} class,
* so it is used as a shutdown hook.
*
* @return {@code true} if the Thread is passed to the {@code java.lang.Runtime} class, {@code false} otherwise
*/
private boolean isJavaRuntimeMethod() {
return IntStream.range(0, getStack().getStackDepth())
.mapToObj(getStack()::getStackItem)
.map(OpcodeStack.Item::getReturnValueOf)
.filter(Objects::nonNull)
.anyMatch(method -> "java.lang.Runtime".equals(method.getClassName()));
}
private Optional getMethodFromBootstrap(JavaClass javaClass, ConstantInvokeDynamic constDyn) {
for (Attribute attr : javaClass.getAttributes()) {
if (attr instanceof BootstrapMethods) {
Optional method = BootstrapMethodsUtil.getMethodFromBootstrap((BootstrapMethods) attr,
constDyn.getBootstrapMethodAttrIndex(), getConstantPool(), javaClass);
if (method.isPresent()) {
return Optional.of(BCELUtil.getMethodDescriptor(javaClass, method.get()));
}
}
}
return Optional.empty();
}
private void collectFieldsUsedInThreads(int seen) throws CheckedAnalysisException {
if ((seen == Const.PUTFIELD || seen == Const.PUTSTATIC) && getStack().getStackDepth() > 0
&& !MethodAnalysis.isDuplicatedLocation(getMethodDescriptor(), getPC())
&& methodsUsedInThreads.contains(getMethodDescriptor())) {
OpcodeStack.Item stackItem = getStack().getStackItem(0);
if (stackItem.getReturnValueOf() != null && CollectionAnalysis.isSynchronizedCollection(stackItem.getReturnValueOf())) {
synchronizedCollectionTypedFields.add(getXFieldOperand());
} else if (!isAtomicTypedField(getXFieldOperand())
&& !(Const.CONSTRUCTOR_NAME.equals(getMethodName()) || Const.STATIC_INITIALIZER_NAME.equals(getMethodName()))) {
createOrUpdateFieldData(getXFieldOperand(), true, getMethod(), getXMethodOperand());
}
} else if ((seen == Const.INVOKEVIRTUAL || seen == Const.INVOKEINTERFACE || seen == Const.INVOKESPECIAL || seen == Const.INVOKESTATIC)
&& getXMethodOperand() != null && getStack().getStackDepth() > 0
&& !MethodAnalysis.isDuplicatedLocation(getMethodDescriptor(), getPC())
&& methodsUsedInThreads.contains(getMethodDescriptor())) {
// The field is accessed always be the last item in the stack, because the earlier elements are the arguments
XField xField = getStack().getStackItem(getStack().getStackDepth() - 1).getXField();
if (xField != null && !isAtomicTypedField(xField)) {
createOrUpdateFieldData(xField, false, getMethod(), getXMethodOperand());
}
}
}
private boolean isAtomicTypedField(XField xField) {
return xField.getSignature().contains("java/util/concurrent/atomic") || synchronizedCollectionTypedFields.contains(xField);
}
private void createOrUpdateFieldData(XField xField, boolean putfield, Method method, XMethod xMethod) {
BugInstance bug = new BugInstance(this, "AT_UNSAFE_RESOURCE_ACCESS_IN_THREAD", LOW_PRIORITY)
.addClassAndMethod(this)
.addSourceLine(this)
.addField(xField);
if (!putfield) {
bug.addCalledMethod(this);
}
FieldData data = fieldsUsedInThreads.computeIfAbsent(xField, value -> new FieldData());
data.methodBugs.computeIfAbsent(method, value -> new HashSet<>()).add(bug);
data.onlySynchronized &= synchronizedBlock;
data.onlyPutField &= putfield;
data.modified |= putfield || MutableClasses.looksLikeASetter(xMethod.getName());
}
@Override
public void visitAfter(JavaClass javaClass) {
super.visit(javaClass);
fieldsUsedInThreads.entrySet().stream()
.filter(entry -> isBug(entry.getValue()))
.flatMap(entry -> entry.getValue().methodBugs.values().stream().flatMap(Set::stream))
.collect(Collectors.toSet())
.forEach(bugReporter::reportBug);
}
/**
* A bug is reported if the field is modified in multiple methods, it is not only accessed in synchronized blocks,
* and it is not a synchronized collection or an atomic typed field.
*
* @param data the field data
* @return {@code true} if the field is a bug, {@code false} otherwise
*/
private static boolean isBug(FieldData data) {
return data.modified && !data.onlySynchronized && data.methodBugs.size() > 1 && !data.onlyPutField;
}
private void resetState() {
firstPass = true;
synchronizedCollectionTypedFields.clear();
methodsUsedInThreads.clear();
fieldsUsedInThreads.clear();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy