/*
* 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.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ConstantCP;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.CPInstruction;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.util.InstructionFinder;
import de.petris.dynamicaspects.AspectException;
import de.petris.dynamicaspects.wrapper.CallInfo;
import de.petris.dynamicaspects.wrapper.CallMatch;
/**
* A searcher of instructions.
*
* @author Marco Petris
*/
public class InstructionSearcher implements Constants {
private ConstantPoolGen constPoolGen;
private InstructionList iList;
/**
* Constructor.
*
* @param constPoolGen the Constant Pool Generator
* which belongs to the instruction list.
* @param iList the instruction list to search in.
*/
public InstructionSearcher( ConstantPoolGen constPoolGen, InstructionList iList ) {
this.constPoolGen = constPoolGen;
this.iList = iList;
}
/**
* Returns a handle to the super/this(...) constructor call.
*
* @return the handle to the super/this(...) constructor call.
*/
public InstructionHandle lookUpSuperConstructorCall() {
// construct a finder
InstructionFinder finder =
new InstructionFinder( iList );
// do the search
Iterator iter
= finder.search(
Constants.OPCODE_NAMES[Constants.INVOKESPECIAL] );
if( iter.hasNext() ) {
InstructionHandle[] ih
= (InstructionHandle[])iter.next();
return ih[0];
}
throw new AspectException(
"unexpected situation: " +
"expected super() or this(...) statement but found none!" );
}
/**
* Returns a list of {@link CallMatch CallMatch}es matching the given
* pattern of method invokes/calls.
*
* @param invokePattern the pattern for the method calls
* @return a list of matches
*/
public List<CallMatch> lookUpMethodCall( Pattern invokePattern ) {
// construct a finder
InstructionFinder finder =
new InstructionFinder( iList );
// search all invokes
Iterator iter
= finder.search(
Constants.OPCODE_NAMES[Constants.INVOKEINTERFACE]
+ "|"
+ Constants.OPCODE_NAMES[Constants.INVOKESPECIAL]
+ "|"
+ Constants.OPCODE_NAMES[Constants.INVOKESTATIC]
+ "|"
+ Constants.OPCODE_NAMES[Constants.INVOKEVIRTUAL]
);
// the return list
List<CallMatch> rc = new ArrayList<CallMatch>();
// loop over the search results
while( iter.hasNext() ) {
InstructionHandle[] ih
= (InstructionHandle[])iter.next();
// construct a call info of the invoke
CallInfo ci = createCallInfo( ih[0] );
// does this invoke match our pattern?
if( invokePattern.matcher( ci.getMethodDeclaration() ).matches() ) {
Logger.debug( "%s matching",
ci.getMethodDeclaration() );
rc.add(
new CallMatch( ih[0], ci ) );
}
else {
Logger.debug( "%s not matching",
ci.getMethodDeclaration() );
}
}
return rc;
}
/**
* Builds a {@link de.petris.dynamicaspects.Wrapper.CallInfo CallInfo} from the
* InstructionHandle of the method call.
*
* @param invoke the InstructionHandle of the method call.
* @return the callinfo containing details about the method called.
*/
private CallInfo createCallInfo( InstructionHandle invoke ) {
CPInstruction methodCall =
(CPInstruction)invoke.getInstruction();
// get infos from the constant pool:
ConstantCP methodConst =
(ConstantCP)constPoolGen.getConstant(
methodCall.getIndex() );
ConstantClass classConst =
(ConstantClass)constPoolGen.getConstant(
methodConst.getClassIndex() );
ConstantNameAndType ntConst =
(ConstantNameAndType)constPoolGen.getConstant(
methodConst.getNameAndTypeIndex() );
return new CallInfo(
((ConstantUtf8)constPoolGen.getConstant(
classConst.getNameIndex() )).getBytes(),
ntConst.getName( constPoolGen.getConstantPool() ),
ntConst.getSignature( constPoolGen.getConstantPool() ) );
}
/**
* Looks up a method call according to the given arguments.
*
* @param classname the name of the class the method belongs to
* @param name the name of the method
* @param signature the signature of the method
* @return a list of InstructionHandles matching calls the given method
*/
public List<InstructionHandle> lookUpMethodCall(
String classname, String name, String signature ) {
// get the index of the method
int idx =
constPoolGen.lookupMethodref(
classname, name, signature );
// construct a finder
InstructionFinder finder =
new InstructionFinder( iList );
// do the search
Iterator iter
= finder.search(
Constants.OPCODE_NAMES[Constants.INVOKEVIRTUAL],
new MethodCallConstraint( idx ) );
// the return list
List<InstructionHandle> rc = new ArrayList<InstructionHandle>();
// loop over the search results
while( iter.hasNext() ) {
rc.add( ((InstructionHandle[])iter.next())[0] );
}
return rc;
}
/**
* Look up all return statments.
*
* @return a list of return statments.
*/
public List<InstructionHandle> lookUpReturnStatements() {
return lookUpReturnStatements( false );
}
/**
* Look up all return statement except the last one.
*
* @return a list of return statements
*/
public List<InstructionHandle> lookUpReturnStatementsSkipLast() {
return lookUpReturnStatements( true );
}
/**
* Look up all return statments.
*
* @param skipLast if true the last found return statement
* is skipped from the list.
* @return a list of return statments.
*/
private List<InstructionHandle> lookUpReturnStatements( boolean skipLast ) {
// construct a finder
InstructionFinder finder =
new InstructionFinder( iList );
// do the search
Iterator iter
= finder.search( "IRETURN|LRETURN|FRETURN|DRETURN|ARETURN|RETURN" );
List<InstructionHandle> rc = new ArrayList<InstructionHandle>();
while( iter.hasNext() ) {
InstructionHandle[] ih
= (InstructionHandle[])iter.next();
rc.add( ih[0] );
}
if( skipLast ) {
rc.remove( rc.size()-1 );
}
return rc;
}
/**
* Looks up the initialization point of the argumentlist with the given index.
*
* @param argListVarIdx the local variable index of the argument list
* @return the InstructionHandle of the initialization
*/
public InstructionHandle lookUpArgumentListInit( int argListVarIdx ) {
// do the search
Iterator iter =
searchStoreInstruction( argListVarIdx, "ACONST_NULL", "", 1 );
if( iter.hasNext() ) {
return ((InstructionHandle[])iter.next())[0];
}
throw new AspectException(
"unexpected situation: could not find " +
" initialization of argument info with idx: "
+ ((Integer)argListVarIdx).toString() );
}
/**
* Sets the load points of the call wrapper at the corresponding
* {@link de.petris.dynamicaspects.wrapper.CallMatch CallMatch}.
* Each call match leads to a wrapper which handles the aspect
* execution at the call match. There have to be three loads of the wrapper:
* a load for the before()-call, a load for the after()-call and a load for the
* afterException()-call.
* We need to know these loadpoints to remove the wrapper.
*
* @param wrapperVarIdx the local variable index of the wrapper
* @param callMatches a list of call matches
*/
public void setCallWrapperLoads(
int wrapperVarIdx, List<CallMatch> callMatches ) {
// do the search
Iterator iter =
searchLoadInstruction( wrapperVarIdx, "", "", 0 );
Iterator<CallMatch> callMatchesIterator = callMatches.iterator();
while( iter.hasNext() ) {
InstructionHandle[] beforeLoad
= (InstructionHandle[])iter.next();
InstructionHandle[] afterLoad
= (InstructionHandle[])iter.next();
InstructionHandle[] afterExcpetionLoad
= (InstructionHandle[])iter.next();
callMatchesIterator.next().setLoadInstructions(
beforeLoad[0], afterLoad[0], afterExcpetionLoad[0] );
}
}
/**
* Looks up the stores of the wrapper variable. There a two stores of the
* wrapper: a null-init-store and a store after the the constructor call.
* We need these stores to remove the init of the wrapper variable.
*
* @param wrapperVarIdx the local variable index of the wrapper
* @return a list of InstructionHandles of the stores
*/
public List<InstructionHandle> lookUpCallWrapperStores( int wrapperVarIdx ) {
// do the search
Iterator iter =
searchStoreInstruction( wrapperVarIdx, "", "", 0 );
List<InstructionHandle> rc = new ArrayList<InstructionHandle>();
while( iter.hasNext() ) {
InstructionHandle[] ih
= (InstructionHandle[])iter.next();
rc.add( ih[0] );
}
return rc;
}
/**
* Searches load-instructions.
*
* @param varIdx the variable index of the variable which will be loaded
* @param prefix a prefix search string
* @param postfix a postfix search string
* @param instructionIdx the index of the instruction in the result list.
* @return an iterator of the search-results.
*/
private Iterator searchLoadInstruction(
int varIdx, String prefix, String postfix, int instructionIdx ) {
InstructionFinder finder =
new InstructionFinder( iList );
if( varIdx > 3 ) { // a constraint checks the variable index
return finder.search(
prefix + " " +
"ALOAD " + postfix,
new LoadStoreConstraint( varIdx, instructionIdx ) );
}
else { // the variable index is included in the instruction
return finder.search(
prefix + " "
+ new ALOAD( varIdx ).toString( false )
+ " " + postfix );
}
}
/**
* Searches store-instructions.
*
* @param varIdx the variable index of the variable which will be stored
* @param prefix a prefix search string
* @param postfix a postfix search string
* @param instructionIdx the index of the instruction in the result list.
* @return an iterator of the search-results.
*/
private Iterator searchStoreInstruction(
int varIdx, String prefix, String postfix, int instructionIdx ) {
InstructionFinder finder =
new InstructionFinder( iList );
if( varIdx > 3 ) { // a constraint checks the variable index
return finder.search(
prefix + " " +
"ASTORE " + postfix,
new LoadStoreConstraint( varIdx, instructionIdx ) );
}
else { // the variable index is included in the instruction
return finder.search(
prefix + " "
+ new ASTORE( varIdx ).toString( false )
+ " " + postfix );
}
}
/**
* A constraint which checks the local variable index
* of a load- or store-instruction. We want a specific load or store so we
* need to check the variable index which is not included in the instruction
* for load/stores with indices higher than 3, but given as an argument:
* ASTORE_2 but ASTORE 5
*
* @author Marco Petris
*/
private static class LoadStoreConstraint
implements InstructionFinder.CodeConstraint {
private int targetIdx;
private int instructionIdx;
/**
* Constructs an instance of this constraint.
*
* @param targetIdx the index of the local variable
* @param instructionIdx the index of the load/store-instruction
* in the result list
*/
public LoadStoreConstraint( int targetIdx, int instructionIdx ) {
this.targetIdx = targetIdx;
this.instructionIdx = instructionIdx;
}
/* (non-Javadoc)
* @see org.apache.bcel.util.InstructionFinder$CodeConstraint#checkCode(org.apache.bcel.generic.InstructionHandle[])
*/
public boolean checkCode( InstructionHandle[] match ) {
if( ( match[instructionIdx].getInstruction()
instanceof LocalVariableInstruction )
&&
( ((LocalVariableInstruction)match[instructionIdx].
getInstruction()).getIndex() == targetIdx ) ) {
return true;
}
return false;
}
}
/**
* A constraint which checks the constant pool index of the target instance
* of the method call.
*
* @author Marco Petris
*/
private static class MethodCallConstraint
implements InstructionFinder.CodeConstraint {
private int targetIdx;
/**
* Constructs an instance of this constraint.
*
* @param targetIdx the constant pool index of the target instance
*/
public MethodCallConstraint( int targetIdx ) {
this.targetIdx = targetIdx;
}
/* (non-Javadoc)
* @see org.apache.bcel.util.InstructionFinder$CodeConstraint#checkCode(org.apache.bcel.generic.InstructionHandle[])
*/
public boolean checkCode( InstructionHandle[] match ) {
if( ((CPInstruction)match[0].
getInstruction()).getIndex() == targetIdx ) {
return true;
}
return false;
}
}
}