/*
* fb-contrib - Auxiliary detectors for Java programs
* Copyright (C) 2005-2017 Dave Brosius
*
* 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 com.mebigfatguy.fbcontrib.detect;
import java.util.BitSet;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.RegisterUtils;
import com.mebigfatguy.fbcontrib.utils.SignatureBuilder;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.TernaryPatcher;
import com.mebigfatguy.fbcontrib.utils.ToString;
import com.mebigfatguy.fbcontrib.utils.Values;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;
/**
* looks for creation of arrays, that are not populated before being returned for a method. While it is possible that the method that called this method will do
* the work of populated the array, it seems odd that this would be the case.
*/
@CustomUserValue
public class SuspiciousUninitializedArray extends BytecodeScanningDetector {
private static JavaClass THREAD_LOCAL_CLASS;
private static final String INITIAL_VALUE = "initialValue";
static {
try {
THREAD_LOCAL_CLASS = Repository.lookupClass(ThreadLocal.class);
} catch (ClassNotFoundException e) {
THREAD_LOCAL_CLASS = null;
}
}
private final BugReporter bugReporter;
private OpcodeStack stack;
private String returnArraySig;
private BitSet uninitializedRegs;
/**
* constructs a SUA detector given the reporter to report bugs on
*
* @param bugReporter
* the sync of bug reports
*/
public SuspiciousUninitializedArray(BugReporter bugReporter) {
this.bugReporter = bugReporter;
}
/**
* overrides the visitor to reset the stack
*
* @param classContext
* the context object of the currently parsed class
*/
@Override
public void visitClassContext(ClassContext classContext) {
try {
stack = new OpcodeStack();
uninitializedRegs = new BitSet();
super.visitClassContext(classContext);
} finally {
stack = null;
uninitializedRegs = null;
}
}
/**
* overrides the visitor to check to see if the method returns an array, and if so resets the stack for this method.
*
* @param obj
* the context object for the currently parsed code block
*/
@Override
public void visitCode(Code obj) {
Method m = getMethod();
if (m.isSynthetic()) {
return;
}
String sig = m.getSignature();
int sigPos = sig.indexOf(")[");
if (sigPos < 0) {
return;
}
if (INITIAL_VALUE.equals(m.getName())) {
try {
if ((THREAD_LOCAL_CLASS == null) || getClassContext().getJavaClass().instanceOf(THREAD_LOCAL_CLASS)) {
return;
}
} catch (ClassNotFoundException e) {
bugReporter.reportMissingClass(e);
return;
}
}
stack.resetForMethodEntry(this);
returnArraySig = sig.substring(sigPos + 1);
uninitializedRegs.clear();
super.visitCode(obj);
}
/**
* overrides the visitor to annotate new array creation with a user value that denotes it as being uninitialized, and then if the array is populated to
* remove that user value. It then finds return values that have uninitialized arrays. byte arrays are not collected as creating a blank byte array is
* probably a reasonably normal occurance.
*
* @param seen
* the context parameter of the currently parsed op code
*/
@Override
public void sawOpcode(int seen) {
SUAUserValue userValue = null;
try {
stack.precomputation(this);
switch (seen) {
case NEWARRAY: {
if (!isTOS0()) {
int typeCode = getIntConstant();
if ((typeCode != Constants.T_BYTE) && returnArraySig.equals(SignatureUtils.toArraySignature(SignatureUtils.getTypeCodeSignature(typeCode)))) {
userValue = SUAUserValue.UNINIT_ARRAY;
}
}
}
break;
case ANEWARRAY: {
if (!isTOS0()) {
String sig = SignatureUtils.toArraySignature(getClassConstantOperand());
if (returnArraySig.equals(sig)) {
userValue = SUAUserValue.UNINIT_ARRAY;
}
}
}
break;
case MULTIANEWARRAY: {
if (returnArraySig.equals(getClassConstantOperand())) {
userValue = SUAUserValue.UNINIT_ARRAY;
}
}
break;
case INVOKEVIRTUAL:
case INVOKEINTERFACE:
case INVOKESPECIAL:
case INVOKESTATIC:
case INVOKEDYNAMIC: {
String methodSig = getSigConstantOperand();
List<String> types = SignatureUtils.getParameterSignatures(methodSig);
for (int t = 0; t < types.size(); t++) {
String parmSig = types.get(t);
if (returnArraySig.equals(parmSig) || Values.SIG_JAVA_LANG_OBJECT.equals(parmSig)
|| SignatureBuilder.SIG_OBJECT_ARRAY.equals(parmSig)) {
int parmIndex = types.size() - t - 1;
if (stack.getStackDepth() > parmIndex) {
OpcodeStack.Item item = stack.getStackItem(parmIndex);
SUAUserValue uv = (SUAUserValue) item.getUserValue();
if (uv != null) {
int reg;
if (uv.isRegister()) {
reg = uv.getRegister();
} else {
reg = item.getRegisterNumber();
}
item.setUserValue(null);
if (reg >= 0) {
uninitializedRegs.clear(reg);
}
}
}
}
}
}
break;
case AALOAD: {
if (stack.getStackDepth() >= 2) {
OpcodeStack.Item item = stack.getStackItem(1);
SUAUserValue uv = (SUAUserValue) item.getUserValue();
if ((uv != null) && (uv.isUnitializedArray())) {
userValue = new SUAUserValue(item.getRegisterNumber());
}
}
}
break;
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE: {
if (stack.getStackDepth() >= 3) {
OpcodeStack.Item item = stack.getStackItem(2);
SUAUserValue uv = (SUAUserValue) item.getUserValue();
int reg;
if ((uv != null) && uv.isRegister()) {
reg = uv.getRegister();
} else {
reg = item.getRegisterNumber();
}
item.setUserValue(null);
if (reg >= 0) {
uninitializedRegs.clear(reg);
}
} else {
// error condition - stack isn't right
uninitializedRegs.clear();
}
}
break;
case ASTORE:
case ASTORE_0:
case ASTORE_1:
case ASTORE_2:
case ASTORE_3: {
int reg = RegisterUtils.getAStoreReg(this, seen);
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
SUAUserValue uv = (SUAUserValue) item.getUserValue();
uninitializedRegs.set(reg, (uv != null) && (uv.isUnitializedArray()));
} else {
uninitializedRegs.clear(reg);
}
}
break;
case ALOAD:
case ALOAD_0:
case ALOAD_1:
case ALOAD_2:
case ALOAD_3: {
int reg = RegisterUtils.getALoadReg(this, seen);
if (uninitializedRegs.get(reg)) {
userValue = SUAUserValue.UNINIT_ARRAY;
}
}
break;
case PUTFIELD: {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
item.setUserValue(null);
int reg = item.getRegisterNumber();
if (reg >= 0) {
uninitializedRegs.clear(reg);
}
}
}
break;
case ARETURN: {
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
SUAUserValue uv = (SUAUserValue) item.getUserValue();
if ((uv != null) && (uv.isUnitializedArray())) {
bugReporter.reportBug(new BugInstance(this, BugType.SUA_SUSPICIOUS_UNINITIALIZED_ARRAY.name(), NORMAL_PRIORITY).addClass(this)
.addMethod(this).addSourceLine(this));
}
}
}
break;
default:
break;
}
} finally
{
TernaryPatcher.pre(stack, seen);
stack.sawOpcode(this, seen);
TernaryPatcher.post(stack, seen);
if (stack.getStackDepth() > 0) {
OpcodeStack.Item item = stack.getStackItem(0);
item.setUserValue(userValue);
}
}
}
private boolean isTOS0() {
if (stack.getStackDepth() == 0) {
return false;
}
OpcodeStack.Item item = stack.getStackItem(0);
return item.mustBeZero();
}
static final class SUAUserValue {
enum SUAUserValueType {
REGISTER, UNINIT_ARRAY
};
public static final SUAUserValue UNINIT_ARRAY = new SUAUserValue();
private SUAUserValueType type;
private int reg;
private SUAUserValue() {
this.type = SUAUserValueType.UNINIT_ARRAY;
reg = -1;
}
public SUAUserValue(int register) {
this.type = SUAUserValueType.REGISTER;
reg = register;
}
public boolean isUnitializedArray() {
return type == SUAUserValueType.UNINIT_ARRAY;
}
public boolean isRegister() {
return type == SUAUserValueType.REGISTER;
}
public int getRegister() {
return reg;
}
@Override
public String toString() {
return ToString.build(this);
}
}
}