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

edu.umd.cs.findbugs.ba.InnerClassAccessMap Maven / Gradle / Ivy

There is a newer version: 4.8.6
Show newest version
/*
 * 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 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 = cls.getBytes(cp).replace('/', '.'); 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" + javaClass.getClassName().replace('.', '/') + ";"; 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy