//
// This file is part of the prose package.
//
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Original Code is prose.
//
// The Initial Developer of the Original Code is Angela Nicoara. Portions
// created by Angela Nicoara are Copyright (C) 2004 Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: HotSwapLocalVariableMapper.java,v 1.1 2008/11/18 11:12:07 anicoara Exp $
// =====================================================================
//
package ch.ethz.inf.iks.jvmai.jvmdi;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import ch.ethz.jvmai.IllegalSignaturePatternException;
import ch.ethz.jvmai.IllegalRedefinitionAdviceException;
/**
* HotSwapLocalVariableMapper class.
* <P>
* Transforms the slot numbers of local variables in Method Redefinition Advices
* to the slot numbers that will be used in the redefined method.
* <P>
* Detects accesses to ANY and REST arguments in method redefine advice bodies.
* <P>
* Detects accesses self-references (this) in method redefine advice bodies.
*
* @version $Revision: 1.1 $
* @author Angela Nicoara
* @author Gerald Linhofer
*/
class HotSwapLocalVariableMapper {
//
// The slot numbers for method arguments are mapped by 'slotMap'.
// All other slot number are calculated by adding 'stddifference'
// to the original slot number.
//
// ERROR_CODE values are used to map arguments
// that shouldn't be accessed in advice bodies.
/// Index for the (implicit) this argument of member methods (may not be referenced in the advice code)
private final static int ERROR_CODE_THIS = -1;
/// Index for arguments of type 'ANY' (may not be referenced in the advice code)
private final static int ERROR_CODE_ANY = -2;
/// Index for arguments of type 'REST' (may not be referenced in the advice code)
private final static int ERROR_CODE_REST = -3;
/// Index for the first argument when a static method is redefined (may not be referenced in the advice code)
private final static int ERROR_CODE_STATIC_THIS = -4;
// Default difference between the old slot number of a local variable
// and the new one. This is used to transform all local variable indexes
// except that, which are used for method arguments.
private int defaultOffset;
// map holding the mappings for the first local variables (usually the arguments of a method)
private int[] slotMap;
// advice method, just to print more significant error messages.
private final Method adviceMethod;
// target method
private final Method targetMethod;
/**
* Creates a <CODE>HotSwapLocalVariableMapper</CODE> for a method
* redefinition advice redefining <CODE>target</CODE>.
*
* @param advice method that will redefine <CODE>target</CODE>
* @param target method that will be redefined.
*
* @throws JVMAIRuntimeException parameter lists can not be matched
*/
public HotSwapLocalVariableMapper(Method advice, Method target) {
adviceMethod = advice;
targetMethod = target;
init(advice.getParameterTypes(), target.getParameterTypes());
// System.out.println("!!!Slot Mappings from " +advice + " to " + target );
// System.out.println(" std difference: " + stddifference );
// System.out.println( Arrays.toString(slotMap) );
}
/**
* Initializes <CODE>HotSwapLocalVariableMapper</CODE>.
* This sets the fields <CODE>stddifference</CODE> and <CODE>slotMap</CODE>.
*
* @param adviceParam paramter list of the advice.
* @param targetParam paramter list of the method beeing redefined by the advice.
*/
private void init(Class[] adviceParam, Class[] targetParam) {
if(0 == adviceParam.length) {
// a. advice has no parameters: METHOD_ARGS()
// slightly faster to do this here than to use 'createSlotMap()'
// a.1. invalidate 'this'
slotMap = new int[]{ERROR_CODE_THIS};
// a.2. increment the slot numbers for local variables,
// so that they dont overwrite the parameters.
defaultOffset = sizeOfParameterList(targetParam);
}
else if("ch.ethz.prose.crosscut.REST".equals(adviceParam[adviceParam.length-1].getName())) {
// b. advice last paramter is 'REST': METHOD_ARGS(...,REST)
// b.1. create slot mapping for all parameters except the last one (which is of type 'REST')
createSlotMap(adviceParam, targetParam, adviceParam.length - 1);
// b.2. invalidate access to 'REST'
slotMap[slotMap.length - 1] = ERROR_CODE_REST;
}
else if(targetParam.length + 1 == adviceParam.length) {
// c. advice defines the same number of arguments (plus one additional
// for 'this') as target
// c.1. create slot mapping for all all parameters
createSlotMap(adviceParam, targetParam, adviceParam.length);
}
else
// d. advice defines incompatible arguments (incompatible number of arguments)
throw new IllegalSignaturePatternException("Cannot map the paramter list of " + adviceMethod + " for " + targetMethod);
}
/**
* Initializes the field <CODE>slotMap</CODE>.
* <P>
* slotMap is used to translate slot numbers in cases where 'ANY' stands for
* a parameter which requires more than one slot in the local variable table
* (parameters of type <CODE>long</CODE> and <CODE>double</CODE>).
* <P>
* Slot map may be used to detect accesses to <CODE>ANY</CODE> and <CODE>REST</CODE>
* arguments in method redefine advice bodies.
* <P>
* slotMap may detect accesses to 'this' in the body of a method redefine advice.
* <P>
* NOTE: THIS METHOD MAY ONLY BE CALLED IF THE ADVICE WAS DEFINED WITH A NON EMPTY
* PARAMETER LIST.
*
* @param adviceParam parameter list of the advice.
* @param targetParam parameter list of the method being redefined by the advice.
* @param end last parameter to process, the mappings for all other parameters will be left empty.
*/
private void createSlotMap(Class[] adviceParam, Class[] targetParam, int end) {
// 0. create the map (+1 for the explicit 'this' parameter)
final int maxAdviceArgSlot = sizeOfParameterList(adviceParam);
defaultOffset = sizeOfParameterList(targetParam) - maxAdviceArgSlot;
slotMap = new int[maxAdviceArgSlot + 1];
int newSlot = 0; // slot for use in the redefined method
int oldSlot = 0; // slot used in the advice
// 1. Invalidate references to 'this' (slotMap[0])
slotMap[oldSlot++] = ERROR_CODE_THIS;
// 2. Handle the first explicit parameter.
// It represents the methods owner, there's
// no corresponding parameter in targets parameter
// list, so it may not be processed like the other
// parameters, which denote arguments of the target
// method
// 2.1. If the advice has no explicit parameters, we are done
if(0 == adviceParam.length) {
return;
}
if(Modifier.isStatic(targetMethod.getModifiers())) {
// 2.2.a. Invalidate accesses to the first parameter for static
// target methods (static methods have no 'this' to which
// this parameter may be mapped).
slotMap[oldSlot++] = ERROR_CODE_STATIC_THIS;
defaultOffset--;
}
else {
if("ch.ethz.prose.crosscut.ANY".equals(adviceParam[0].getName()))
// 2.2.b. Invalidate accesses to the first parameter, because mapping
// of 'ANY' to 'this' will not work.
slotMap[oldSlot++] = ERROR_CODE_ANY;
else
// 2.2.c. Map the first explicit parameter to 'this'
slotMap[oldSlot++] = 0;
newSlot++;
}
// assert(2 == oldSlot); // the first two elements of the slot map are inserted.
// 3. calculate the slot number for each parameter
for(int i = 0; i < end-1; i++) {
// 'i' is the index for 'targetParam', while 'i+1' is used for 'adviceParam'.
// The slot number are not the same like the index in the parameter list,
// because some types (long and double) need more than just one slot.
// 3.1. set the slot
if("ch.ethz.prose.crosscut.ANY".equals(adviceParam[i+1].getName())) {
// 3.1.a. invalidate access to 'ANY'
slotMap[oldSlot] = ERROR_CODE_ANY;
}
else if("ch.ethz.prose.crosscut.REST".equals(adviceParam[i+1].getName())) {
// 3.1.b. 'REST' is only allowed as last parameter
System.err.println("ERROR: can not apply " + adviceMethod);
System.err.println(" 'REST' is only allowed as last argument of an advice, but not before.");
throw new IllegalSignaturePatternException("'REST' may only be used as last parameter of an advice but not as in: " + adviceMethod);
}
else
// 3.1.c. map acces to the parameter (that is not of type 'ANY' or 'REST')
slotMap[oldSlot] = newSlot;
// 3.2. increments the slot numbers
int d = sizeOfVariable(adviceParam[i+1]);
// 3.3. just in case some optimizing compiler may reuse
// the slots used for the parameters.
if(2 == d && (slotMap.length > oldSlot + 2))
// insert also a mapping for the second slot, if a variable needs more than one.
slotMap[oldSlot + 1] = newSlot + 1;
// 3.4. increment slot numbers
oldSlot += d;
newSlot += sizeOfVariable(targetParam[i]);
}
}
/**
* Returns the number of slots that a variable of type <CODE>type</CODE>
* uses in the local variable table.
*
* @param type
* @return number of slots required for a local variable of type <CODE>type</CODE>.
*/
private final static int sizeOfVariable(Class type) {
int result;
// only primitives of type long and double use two slots, all other types
// need only one.
if(type.isPrimitive() && (Long.TYPE.equals(type) || Double.TYPE.equals(type)))
result = 2;
else
result = 1;
return result;
}
/**
* Returns the number of slots that the parameter list <CODE>params</CODE>
* uses in the local variable table.
*
* @param params parameter list
* @return number of slots used by <CODE>params</CODE>
*/
private final static int sizeOfParameterList(Class[] params) {
int result = 0;
for(int i = 0; i < params.length; i++)
result += sizeOfVariable(params[i]);
return result;
}
/**
* Returns the slot number (variable index) that must
* be used in a redefined method instead of the slot
* number defined in the advice (<CODE>oldSlot</CODE>).
*
* @param oldSlot slot used by the advice
* @return slot used by the redefined method
* @throws IllegalRedefinitionAdviceException if the slot is used for
* a local variable that may not be accessed in an advice body
*/
public int getNewSlot(int oldSlot) {
int result;
// 1. get the new slot number
if(slotMap.length > oldSlot)
// 1.a. Slot number for an argument
result = slotMap[oldSlot];
else
// 1.b. Slot number for a variable defined in the method body.
result = oldSlot + defaultOffset;
// 2. check for illegal slots (illegal local variable access)
if(0 > result) {
switch(result) {
case ERROR_CODE_THIS:
throw new IllegalRedefinitionAdviceException("No (implicit) `this' usage allowed in advice method: " + adviceMethod);
case ERROR_CODE_ANY:
throw new IllegalRedefinitionAdviceException("No `ANY' usage allowed in advice method: " + adviceMethod);
case ERROR_CODE_REST:
throw new IllegalRedefinitionAdviceException("No `REST' usage allowed in advice method: " + adviceMethod);
case ERROR_CODE_STATIC_THIS:
throw new IllegalRedefinitionAdviceException("No usage of the first parameter allowed in advice methods that redefine static methods: " + adviceMethod);
default:
throw new IllegalRedefinitionAdviceException("Illegal local variable access (unknown error code: " + result + ") in advice method: " + adviceMethod);
}
}
return result;
}
/**
* Returns the number of local variable slots (<CODE>MaxLocals</CODE>) used for the
* redefined method.
* <P>
* NOTE: 'proceed()' statements may require additional local variables.
*
* @param oldMaxLocals <CODE>MaxLocals</CODE> of the method redefinition advice.
* @return <CODE>MaxLocals</CODE> for the redefined method.
*/
public int getNewMaxLocals(int oldMaxLocals) {
int retVal = oldMaxLocals + defaultOffset;
if(0 > retVal)
retVal = 0;
return retVal;
}
}