edu.umd.cs.findbugs.ba.InnerClassAccessMap 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!
/*
* Bytecode Analysis Framework
* Copyright (C) 2003,2004 University of Maryland
*
* 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.ba;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import edu.umd.cs.findbugs.util.ClassName;
import org.apache.bcel.Const;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantFieldref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKESTATIC;
import edu.umd.cs.findbugs.SystemProperties;
/**
* Determine which methods are accessors used by inner classes to access fields
* in their enclosing classes. This has been tested with javac from the Sun JDK
* 1.4.x, but will probably not work with other source to bytecode compilers.
*
*
* The instance of InnerClassAccessMap should be retrieved from the
* AnalysisContext.
*
*
* @author David Hovemeyer
* @see InnerClassAccess
*/
public class InnerClassAccessMap {
private static final boolean DEBUG = SystemProperties.getBoolean("icam.debug");
/*
* ----------------------------------------------------------------------
* Fields
* ----------------------------------------------------------------------
*/
/**
* Map of class names to maps of method names to InnerClassAccess objects
* representing access methods.
*/
private final Map> classToAccessMap;
/*
* ----------------------------------------------------------------------
* Public interface
* ----------------------------------------------------------------------
*/
/**
* Create an instance.
*
* @return a new instance of InnerClassAccessMap
*/
public static InnerClassAccessMap create() {
return new InnerClassAccessMap();
}
/**
* Get the InnerClassAccess in given class with the given method name.
*
* @param className
* the name of the class
* @param methodName
* the name of the access method
* @return the InnerClassAccess object for the method, or null if the method
* doesn't seem to be an inner class access
*/
public InnerClassAccess getInnerClassAccess(String className, String methodName) throws ClassNotFoundException {
Map map = getAccessMapForClass(className);
return map.get(methodName);
}
/**
* Get the inner class access object for given invokestatic instruction.
* Returns null if the called method is not an inner class access.
*
* @param inv
* the invokestatic instruction
* @param cpg
* the ConstantPoolGen for the method
* @return the InnerClassAccess, or null if the call is not an inner class
* access
*/
public InnerClassAccess getInnerClassAccess(INVOKESTATIC inv, ConstantPoolGen cpg) throws ClassNotFoundException {
String methodName = inv.getMethodName(cpg);
if (methodName.startsWith("access$")) {
String className = inv.getClassName(cpg);
return getInnerClassAccess(className, methodName);
}
return null;
}
/**
* Clear the cache.
*/
public void clearCache() {
classToAccessMap.clear();
}
/*
* ----------------------------------------------------------------------
* Implementation
* ----------------------------------------------------------------------
*/
/**
* Constructor.
*/
private InnerClassAccessMap() {
this.classToAccessMap = new HashMap<>(3);
}
/**
* Convert byte to unsigned int.
*/
private static int toInt(byte b) {
int value = b & 0x7F;
if ((b & 0x80) != 0) {
value |= 0x80;
}
return value;
}
/**
* Get an unsigned 16 bit constant pool index from a byte array.
*/
private static int getIndex(byte[] instructionList, int index) {
return (toInt(instructionList[index + 1]) << 8) | toInt(instructionList[index + 2]);
}
/*
private static class LookupFailure extends RuntimeException {
private static final long serialVersionUID = 1L;
private final ClassNotFoundException exception;
public LookupFailure(ClassNotFoundException exception) {
this.exception = exception;
}
public ClassNotFoundException getException() {
return exception;
}
}
*/
/**
* Callback to scan an access method to determine what field it accesses,
* and whether the field is loaded or stored.
*/
private static class InstructionCallback implements BytecodeScanner.Callback {
private final JavaClass javaClass;
private final String methodName;
private final String methodSig;
private final byte[] instructionList;
private InnerClassAccess access;
private int accessCount;
/**
* Constructor.
*
* @param javaClass
* the class containing the access method
* @param methodName
* the name of the access method
* @param methodSig
* the signature of the access method
* @param instructionList
* the bytecode of the method
*/
public InstructionCallback(JavaClass javaClass, String methodName, String methodSig, byte[] instructionList) {
this.javaClass = javaClass;
this.methodName = methodName;
this.methodSig = methodSig;
this.instructionList = instructionList;
this.access = null;
this.accessCount = 0;
}
@Override
public void handleInstruction(int opcode, int index) {
switch (opcode) {
case Const.GETFIELD:
case Const.PUTFIELD:
setField(getIndex(instructionList, index), false, opcode == Const.GETFIELD);
break;
case Const.GETSTATIC:
case Const.PUTSTATIC:
setField(getIndex(instructionList, index), true, opcode == Const.GETSTATIC);
break;
default:
break;
}
}
/**
* Get the InnerClassAccess object representing the method.
*
* @return the InnerClassAccess, or null if the method was not found to
* be a simple load or store in the expected form
*/
public InnerClassAccess getAccess() {
return access;
}
/**
* Called to indicate that a field load or store was encountered.
*
* @param cpIndex
* the constant pool index of the fieldref
* @param isStatic
* true if it is a static field access
* @param isLoad
* true if the access is a load
*/
private void setField(int cpIndex, boolean isStatic, boolean isLoad) {
// We only allow one field access for an accessor method.
accessCount++;
if (accessCount != 1) {
access = null;
return;
}
ConstantPool cp = javaClass.getConstantPool();
ConstantFieldref fieldref = (ConstantFieldref) cp.getConstant(cpIndex);
ConstantClass cls = (ConstantClass) cp.getConstant(fieldref.getClassIndex());
String className = ClassName.toDottedClassName(cls.getBytes(cp));
ConstantNameAndType nameAndType = (ConstantNameAndType) cp.getConstant(fieldref.getNameAndTypeIndex());
String fieldName = nameAndType.getName(cp);
String fieldSig = nameAndType.getSignature(cp);
XField xfield = Hierarchy.findXField(className, fieldName, fieldSig, isStatic);
if (xfield != null && xfield.isStatic() == isStatic && isValidAccessMethod(methodSig, xfield, isLoad)) {
access = new InnerClassAccess(methodName, methodSig, xfield, isLoad);
}
}
/**
* Determine if the method appears to be an accessor of the expected
* form. This has only been tested with the Sun JDK 1.4 javac
* (definitely) and jikes 1.18 (I think).
*
* @param methodSig
* the method's signature
* @param field
* the field accessed by the method
* @param isLoad
* true if the access is a load
*/
private boolean isValidAccessMethod(String methodSig, XField field, boolean isLoad) {
// Get the method parameters and return type
// (as they appear in the method signature).
int paramsEnd = methodSig.indexOf(')');
if (paramsEnd < 0) {
return false;
}
String methodParams = methodSig.substring(0, paramsEnd + 1);
String methodReturnType = methodSig.substring(paramsEnd + 1);
// Figure out what the expected method parameters should be
StringBuilder buf = new StringBuilder();
buf.append('(');
if (!field.isStatic()) {
String classSig = "L" + ClassName.toSlashedClassName(javaClass.getClassName()) + ";";
buf.append(classSig); // the OuterClass.this reference
}
if (!isLoad) {
buf.append(field.getSignature()); // the value being stored
}
buf.append(')');
String expectedMethodParams = buf.toString();
// See if params match
if (!methodParams.equals(expectedMethodParams)) {
if (DEBUG) {
System.out.println("In " + javaClass.getClassName() + "." + methodName + " expected params "
+ expectedMethodParams + ", saw " + methodParams);
System.out.println(isLoad ? "LOAD" : "STORE");
}
return false;
}
// Return type can be either the type of the field, or void.
if (!"V".equals(methodReturnType) && !methodReturnType.equals(field.getSignature())) {
if (DEBUG) {
System.out.println("In " + javaClass.getClassName() + "." + methodName + " expected return type V or "
+ field.getSignature() + ", saw " + methodReturnType);
System.out.println(isLoad ? "LOAD" : "STORE");
}
return false;
}
return true;
}
}
/**
* Return a map of inner-class member access method names to the fields that
* they access for given class name.
*
* @param className
* the name of the class
* @return map of access method names to the fields they access
*/
private Map getAccessMapForClass(String className) throws ClassNotFoundException {
Map map = classToAccessMap.get(className);
if (map == null) {
map = new HashMap<>(3);
if (!className.startsWith("[")) {
JavaClass javaClass = Repository.lookupClass(className);
Method[] methodList = javaClass.getMethods();
for (Method method : methodList) {
String methodName = method.getName();
if (!methodName.startsWith("access$")) {
continue;
}
Code code = method.getCode();
if (code == null) {
continue;
}
if (DEBUG) {
System.out.println("Analyzing " + className + "." + method.getName()
+ " as an inner-class access method...");
}
byte[] instructionList = code.getCode();
String methodSig = method.getSignature();
InstructionCallback callback = new InstructionCallback(javaClass, methodName, methodSig, instructionList);
// try {
new BytecodeScanner().scan(instructionList, callback);
// } catch (LookupFailure lf) {
// throw lf.getException();
// }
InnerClassAccess access = callback.getAccess();
if (DEBUG) {
System.out.println((access != null ? "IS" : "IS NOT") + " an inner-class access method");
}
if (access != null) {
map.put(methodName, access);
}
}
}
if (map.size() == 0) {
map = Collections.emptyMap();
} else {
map = new HashMap<>(map);
}
classToAccessMap.put(className, map);
}
return map;
}
}