/*
* Copyright (c) 2004, Marco Petris
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Marco Petris nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.petris.dynamicaspects.wrapper;
import java.util.Iterator;
import java.util.List;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.BasicType;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.generic.Type;
import de.petris.dynamicaspects.util.InstructionSearcher;
import de.petris.dynamicaspects.util.LVTTRemover;
import de.petris.dynamicaspects.util.Logger;
import de.petris.dynamicaspects.util.Reflection;
/**
* This method patcher de-/installs the {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}s
* around the matching target calls within a method.
*
* @author Marco Petris
* @see de.petris.dynamicaspects.wrapper.CallMapper
* @see de.petris.dynamicaspects.wrapper.CallWrapper
* @see de.petris.dynamicaspects.BeforeAfterAdvice
* @see de.petris.dynamicaspects.WeaveType
*/
public class CallWrapperMethodPatcher implements Constants {
// the type of the CallWrapper
private final static Type CALLWRAPPERTYPE =
new ObjectType( CallWrapper.class.getName() );
private int methodIdx; // index of the method to be patched
private MethodGen mGen; // method generator for this method
private ConstantPoolGen constPoolGen; // the constant pool generator
// the joinpoint pattern for the target calls within this method
private String joinpointPattern;
private List<CallMatch> targets; // the list of the matching calls
private InstructionFactory factory;
private InstructionList iList;
/**
* Creates an instance of this class.
*
* @param methodIdx the index of the method to be patched
* @param mGen method generator for this method
* @param constPoolGen the constant pool generator
* @param joinpointPattern the joinpoint pattern for
* the target calls within this method
* @param targets the list of the matching calls
*/
public CallWrapperMethodPatcher(
int methodIdx, MethodGen mGen,
ConstantPoolGen constPoolGen,
String joinpointPattern,
List<CallMatch> targets ) {
this.methodIdx = methodIdx;
this.mGen = mGen;
this.joinpointPattern = joinpointPattern;
this.targets = targets;
this.constPoolGen = constPoolGen;
this.factory = new InstructionFactory( constPoolGen );
this.iList = mGen.getInstructionList();
}
/**
* Installs the {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}.
*
* @return the patched method.
*/
public Method install() {
patchMethod();
mGen.setMaxStack();
mGen.setMaxLocals();
Method newMethod = mGen.getMethod();
LVTTRemover.removeLVTTs( newMethod, constPoolGen );
iList.dispose();
return newMethod;
}
/**
* Deinstalls the {@link de.petris.dynamicaspects.wrapper.CallWrapper CallWrapper}.
* @return the unpatched method
*/
public Method deinstall() {
unpatchMethod();
mGen.setMaxStack();
mGen.setMaxLocals();
Method newMethod = mGen.getMethod();
LVTTRemover.removeLVTTs( newMethod, constPoolGen );
iList.dispose();
return newMethod;
}
/**
* Installs the wrapper for all the target calls within this method.
*/
private void patchMethod() {
InstructionSearcher aFinder =
new InstructionSearcher( constPoolGen, iList );
InstructionHandle first = null;
// respect the super() or this(...)-call at the
// beginning of constructors
if( mGen.getName().equals( CONSTRUCTOR_NAME ) ) {
first = aFinder.lookUpSuperConstructorCall().getNext();
}
else {
first = iList.getStart();
}
CallWrapperVariableHandler vHandler =
new CallWrapperVariableHandler(
mGen, iList,
factory, constPoolGen,
joinpointPattern );
// create the wrapper
int callWrapperIdx =
vHandler.createCallWrapper(
first, methodIdx );
Iterator<CallMatch> cmIter = targets.iterator();
// wrap each target
while( cmIter.hasNext() ) {
CallMatch curMatch = cmIter.next();
// BEFORE - call
int argListVarIdx =
vHandler.createArgumentList(
curMatch.getCallInstructionHandle(), curMatch );
// load the callwrapper
iList.insert( curMatch.getCallInstructionHandle(),
InstructionFactory.createLoad(
CALLWRAPPERTYPE, callWrapperIdx ) );
// load parameter for before()-call
iList.insert(
curMatch.getCallInstructionHandle(),
InstructionFactory.createLoad(
CallWrapperVariableHandler.ARGLIST_TYPE,
argListVarIdx ) );
// call before()
iList.insert(
curMatch.getCallInstructionHandle(),
factory.createInvoke(
CallWrapper.class.getName(),
Wrapper.getBeforeName(),
Type.VOID,
new Type[] {
CallWrapperVariableHandler.ARGLIST_TYPE },
INVOKEVIRTUAL ) );
// restore the arguments on the operand stack
vHandler.reloadArguments(
curMatch.getCallInstructionHandle(),
argListVarIdx, curMatch.getCallInfo() );
// AFTER - call
InstructionHandle appendHandle = iList.append(
curMatch.getCallInstructionHandle(),
InstructionFactory.createLoad(
CALLWRAPPERTYPE, callWrapperIdx ) );
// call after() for void-target
if( curMatch.getCallInfo().getReturnType().equals( Type.VOID ) ) {
appendHandle = iList.append(
appendHandle,
factory.createInvoke(
CallWrapper.class.getName(),
Wrapper.getAfterVoidName(),
Type.VOID, Type.NO_ARGS,
INVOKEVIRTUAL ) );
}
else {
// use the return value on the operand stack for after() call
if( Reflection.isCat2Type(
curMatch.getCallInfo().getReturnType() ) ) {
// duplicate the wrapper variable on the stack
// and store it below the cat2-returnvalue
appendHandle = iList.append(
appendHandle,
InstructionConstants.DUP_X2 );
// remove the old wrapper variable from the stack
appendHandle = iList.append(
appendHandle,
InstructionConstants.POP );
}
else {
// simple switch of the wrapper and the return value
// which becomes the argument of the wrapper-after()-call
appendHandle = iList.append(
appendHandle,
InstructionConstants.SWAP );
}
// call after()
appendHandle = iList.append(
appendHandle,
factory.createInvoke(
CallWrapper.class.getName(),
Wrapper.getAfterName(),
Wrapper.getAfterParamType(
curMatch.getCallInfo().getReturnType() ),
new Type[] {
Wrapper.getAfterParamType(
curMatch.getCallInfo().getReturnType() ) },
INVOKEVIRTUAL ) );
// cast the return type of the after()-call if necessary
if( !(curMatch.getCallInfo().
getReturnType() instanceof BasicType) ) {
appendHandle = iList.append(
appendHandle,
factory.createCast(
Type.OBJECT,
curMatch.getCallInfo().getReturnType() ) );
}
}
// EXCEPTION - handler
// jump after the exceptionhandler
appendHandle =
iList.append(
appendHandle, InstructionFactory.createBranchInstruction(
GOTO, appendHandle.getNext() ) );
InstructionHandle endHandling = appendHandle;
// load the wrapper
appendHandle = iList.append(
appendHandle,
InstructionFactory.createLoad(
CALLWRAPPERTYPE, callWrapperIdx ) );
InstructionHandle handler = appendHandle;
// use the excpetion as an argument for afterException()-call
appendHandle = iList.append(
appendHandle,
InstructionConstants.SWAP );
// method returns void?
appendHandle = iList.append(
appendHandle,
new PUSH( constPoolGen,
curMatch.getCallInfo().getReturnType().equals(
Type.VOID ) ) );
// call afterException()
appendHandle = iList.append(
appendHandle,
factory.createInvoke(
CallWrapper.class.getName(),
Wrapper.getAfterExceptionName(),
Type.THROWABLE,
new Type[] {
Type.THROWABLE, Type.BOOLEAN },
INVOKEVIRTUAL ) );
//todo: this should be configurable to install an aspect-exception-handler:
// rethrow the exception
appendHandle =
iList.append( appendHandle, InstructionConstants.ATHROW );
appendHandle =
iList.append( appendHandle, InstructionConstants.RETURN );
// register excpeptionhandler
mGen.addExceptionHandler(
curMatch.getCallInstructionHandle(), endHandling, handler,
Type.THROWABLE );
}
}
/**
* Removes the wrapper from all the target calls within this method.
*/
private void unpatchMethod() {
InstructionSearcher aFinder =
new InstructionSearcher( constPoolGen, iList );
CallWrapperVariableHandler vHandler =
new CallWrapperVariableHandler(
mGen, iList,
factory, constPoolGen,
joinpointPattern );
// the index of the wrapper to be deinstalled
int callWrapperIdx = vHandler.getCallWrapperVariableIndex();
// get the loads of this wrapper variable
aFinder.setCallWrapperLoads( callWrapperIdx, targets );
Iterator<CallMatch> cmIter = targets.iterator();
// loop over the target calls
while( cmIter.hasNext() ) {
// REMOVE before()-call
// get the argumentList variable
int curArgListVarIdx =
vHandler.getNextArgumentListVariableIndex();
InstructionHandle curArgListInit =
aFinder.lookUpArgumentListInit( curArgListVarIdx );
CallMatch curCallMatch = cmIter.next();
InstructionHandle beforeLoad =
curCallMatch.getBeforeLoad();
try {
iList.delete( curArgListInit,
vHandler.getLastReloadInstruction(
beforeLoad.getNext().getNext(),
curCallMatch.getCallInfo() ) );
}
catch( TargetLostException tle ) {
Logger.debug( "removing before()-calls: %s", tle.toString() );
}
// REMOVE after-call
InstructionHandle afterLoad =
curCallMatch.getAfterLoad();
try {
iList.delete( afterLoad,
getLastAfterInstruction(
afterLoad, curCallMatch.getCallInfo() ) );
}
catch( TargetLostException tle ) {
Logger.debug( "removing after()-calls: %s", tle.toString() );
}
// REMOVE Exception-handler
InstructionHandle afterExceptionLoad =
curCallMatch.getAfterExceptionLoad();
removeExceptionHandler( afterExceptionLoad );
try {
iList.delete( afterExceptionLoad.getPrev(),
afterExceptionLoad.getNext().getNext().
getNext().getNext().getNext() );
}
catch( TargetLostException tle ) {
Logger.debug(
"removing afterException()-calls: %s", tle.toString() );
}
}
// remove the creation of the wrapper variable
List<InstructionHandle> callWrapperStores =
aFinder.lookUpCallWrapperStores( callWrapperIdx );
try {
iList.delete(
callWrapperStores.get(0).getPrev(),
callWrapperStores.get(1) );
}
catch( TargetLostException tle ) {
Logger.debug(
"removing call wrapper variable creation: %s",
tle.toString() );
}
vHandler.removeVariables();
updateLocalVariableTable();
}
/**
* Updates the local variable table.
*/
private void updateLocalVariableTable() {
// save all other local variables
LocalVariableGen[] localVars = mGen.getLocalVariables();
// clear the local variables table
mGen.removeLocalVariables();
// add all other local variables with corrected end position
for( int localVarIdx=localVars.length-1;
localVarIdx>=0; localVarIdx-- ) {
// correct the end position
InstructionHandle end = localVars[localVarIdx].getEnd();
if( ( end == null )
|| ( end.getPosition() > iList.getEnd().getPosition() ) ) {
end = iList.getEnd();
}
// add the variable
mGen.addLocalVariable(
localVars[localVarIdx].getName(),
localVars[localVarIdx].getType(),
localVarIdx, localVars[localVarIdx].getStart(), end );
}
}
/**
* Removes the exception handler.
*
* @param handler the handler to be removed.
*/
private void removeExceptionHandler( InstructionHandle handler ) {
for( CodeExceptionGen ceg : mGen.getExceptionHandlers() ) {
if( ceg.getHandlerPC().equals(
handler ) ) {
mGen.removeExceptionHandler( ceg );
break;
}
}
}
/**
* Returns the last instruction of the after()-call
*
* @param afterLoad the load position of the wrapper variable
* @param callInfo information about the target call
* @return the last instruction of the after()-call
*/
private InstructionHandle getLastAfterInstruction(
InstructionHandle afterLoad, CallInfo callInfo ) {
InstructionHandle lastAfterInstruction = afterLoad;
if( callInfo.getReturnType().equals( Type.VOID ) ) {
// only the after()-invoke
lastAfterInstruction = lastAfterInstruction.getNext();
}
else {
if( Reflection.isCat2Type(
callInfo.getReturnType() ) ) {
// cat2Type stack manipulation
lastAfterInstruction =
lastAfterInstruction.getNext().getNext();
}
else {
// cat1Type stack manipulation
lastAfterInstruction = lastAfterInstruction.getNext();
}
// invoke after()
lastAfterInstruction = lastAfterInstruction.getNext();
// did we need a typecast?
if( !(callInfo.getReturnType() instanceof BasicType) ) {
lastAfterInstruction = lastAfterInstruction.getNext();
}
}
return lastAfterInstruction;
}
}