//
// 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 Developers of the Original Code are Andrei Popovici and
// Angela Nicoara. Portions created by Andrei Popovici and Angela Nicoara
// are Copyright (C) 2002 Andrei Popovici and Angela Nicoara.
// All Rights Reserved.
//
// Contributor(s):
// $Id: AspectInterfaceImpl.java,v 1.5 2008/11/18 11:09:31 anicoara Exp $
// =====================================================================
//
package ch.ethz.inf.iks.jvmai.jvmdi;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
// used packages/classes
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.io.InputStream;
import java.io.IOException;
import ch.ethz.jvmai.JoinPointHook;
import ch.ethz.jvmai.JVMAIRuntimeException;
import ch.ethz.jvmai.JVMAspectInterface;
import ch.ethz.jvmai.WatchAlreadySetException;
import ch.ethz.jvmai.WatchNotSetException;
import ch.ethz.jvmai.NotInitializedException;
import ch.ethz.jvmai.CannotSetWatchException;
import ch.ethz.jvmai.SunBCELRepositoryInterface;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.util.Repository;
import org.apache.bcel.util.ClassLoaderRepository;
/**
* Interface AspectInterfaceImpl represents the aspect-interface to the jvmai-system.
* It's needed for initialization of the jvmai-system, management of joinpoint-watches and
* for enable or disable event-notification.
* <p>
* An instance of this interface can be obtained by calling the method
* <code>getAspectInterface()</code> of a jvmai-provider (class <code>DebuggerProvider</code>).
* <p>
* <h3>Implementation</h3>
* The implementation of this aspect interface is based on the debugger interface of the JVM.
* To receive notifications from the JVM, it uses the native code available in this class.
* <p>
*
* <h3>Bugs</h3>
* The JVMDI is not <em> consistent </em> over several J2SDK versions.
* <ul>
* <li> Startup ignores the openword/closed world assumption and the list of prefixes.
* <li> Under JDK 1.4.*, when throwing an exception within the notification, exception
* events are wrongly sent to the debugger
* <li> Under JDK 1.4.*, the 'GetThreadDepth' JVMDI function (name may be wrong),
* does not always work correctly. Consequently, the current implementation (as of prose 0.18.0)
* relies on the <em>height</em> of a frame relative to the frame which received
* the debugger notification.
* <li> Under JDK 1.4.*, the 'getBytecode' method exported by JVMDI works non-deterministically.
* (first time it works, subsequent times it generates illegal opcodes). The workaround
* uses BCEL and the class loader. However, this may incur performance penalties.
* </ul>
*
* @version $Revision: 1.5 $
* @author Andrei Popovici
* @author Angela Nicoara
*/
public class AspectInterfaceImpl implements JVMAspectInterface, SunBCELRepositoryInterface {
/**
* Caches BCEL class definitions, which where fetched to analyze or
* redefine java classes.
*/
protected static Repository classRepository = null;//SyntheticRepository.getInstance();
// load shared library upon class loading.
static {
Class x=java.lang.RuntimeException.class;
try {
// 1. assume we are an extension
System.loadLibrary("prosevm");
}
catch (java.lang.UnsatisfiedLinkError notInstalled) {
// 2. no extension, we are in the development tree
try {
System.load(System.getProperty("ch.ethz.inf.project.home") +
System.getProperty("file.separator","/") +
"lib" +
System.getProperty("file.separator","/") +
System.getProperty("os.arch") +
System.getProperty("file.separator","/") +
System.mapLibraryName("prosevm"));
}
catch (java.lang.UnsatisfiedLinkError notInProjectLib) {
try {
String path=System.getProperty("ch.ethz.inf.project.home") +
System.getProperty("file.separator","/") +
"site" +
System.getProperty("file.separator","/") +
"lib" +
System.getProperty("file.separator","/") +
System.getProperty("os.arch") +
System.getProperty("file.separator","/") +
System.mapLibraryName("prosevm");
System.load(path);
}
catch (java.lang.UnsatisfiedLinkError noProse) {
throw new RuntimeException(noProse.toString());
}
}
}
}
// for JDK 1.5 or above:
//Map<Field,Object> fieldAccessMap;
//Map<Field,Object> fieldModificationMap;
//Map<java.lang.reflect.Member,Object> methodExecutionMap;
//Map<Class,Object> exceptionThrowMap;
//Map<Class,Object> exceptionCatchMap;
// for JDK < 1.5.0:
Map fieldAccessMap;
Map fieldModificationMap;
Map methodExecutionMap;
Map exceptionThrowMap;
Map exceptionCatchMap;
protected static JoinPointHook hook = null;
public boolean isInitialized = false; // FIXME; must be made not-public
static class CompoundAopTag {
Object entryTag = null;
Object exitTag = null;
}
static ThreadLocal cflows = new ThreadLocal() {
public Object initialValue() {
synchronized(AspectInterfaceImpl.class) {
return new ControlFlow();
}
}
};
private ControlFlow getCflow() {
ControlFlow crtFlow = (ControlFlow)cflows.get();
if (crtFlow == null) {
throw new Error("AspectInterface.getCflow: cflow should be always non-null\n");
}
return crtFlow;
}
private ClassLoader contingencyLoader = null;
/**
* Initializes the underlying jvmai system.
* <p>
* In addition, this method takes a list of java package-names and a
* boolean indicating how to interpret this prefixes. The prefixes are
* used by the jvmai system to determine the set of classes being
* relevant to the system at all.
* Depending on the value of <code>openWorldAssumption</code>, prefixes
* are interpreted as followed:
* <p>
* <code>openWorldAssumption == true</code><br>
* The jvmai-system is instructed to treat ALL classes as relevant,
* EXCEPT the ones contained in a packages starting with one of the
* supplied prefixes in <code>packagePrefixes</code>
* <p>
* <code>openWorldAssumption == false</code><br>
* The jvmai-system is instructed to treat as relevant ONLY the classes
* contained in a packages starting with one of the supplied prefixes
* in <code>packagePrefixes</code>
*
* @param packagePrefixes List of prefixes for java package-names.
* @param openWorldAssumption Specifies how the prefixes are interpreted.
* @exception StartupException Use <code>StartupException.getMessage()</code> to get a detailed description
*/
public synchronized void startup(String[] packagePrefixes, boolean openWorldAssumption) {
// make sure all classes that are addressed during event notification are loaded already here
Class toload;
toload=ch.ethz.inf.iks.jvmai.jvmdi.AbsentInformationException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.AspectInterfaceImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.AspectInterfaceImpl.CompoundAopTag.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.CodeJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.CodeSignatureImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ControlFlow.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.DebuggerProvider.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ExceptionJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ExceptionCatchJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.FieldAccessJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.FieldJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.FieldModificationJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.FieldSignatureImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.InvalidObjectException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.InvalidVmStateException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ItemManipulationException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.JoinPointContext.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.JoinPointLocation.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.MethodExecutionJoinPointImpl.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ProseVmException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.SignatureFormatException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.StackFrameException.class;
toload=ch.ethz.inf.iks.jvmai.jvmdi.ThreadStateException.class;
toload=ch.ethz.jvmai.CannotSetWatchException.class;
toload=ch.ethz.jvmai.CatchClauseSignature.class;
toload=ch.ethz.jvmai.ClassSpecific.class;
toload=ch.ethz.jvmai.CodeJoinPoint.class;
toload=ch.ethz.jvmai.ExceptionJoinPoint.class;
toload=ch.ethz.jvmai.ExceptionCatchJoinPoint.class;
toload=ch.ethz.jvmai.FieldAccessJoinPoint.class;
toload=ch.ethz.jvmai.FieldJoinPoint.class;
toload=ch.ethz.jvmai.FieldModificationJoinPoint.class;
toload=ch.ethz.jvmai.FieldSignature.class;
toload=ch.ethz.jvmai.InvalidIdException.class;
toload=ch.ethz.jvmai.JVMAIException.class;
toload=ch.ethz.jvmai.JVMAIRuntimeException.class;
toload=ch.ethz.jvmai.JVMAspectInterface.class;
toload=ch.ethz.jvmai.JoinPoint.class;
toload=ch.ethz.jvmai.JoinPointHook.class;
toload=ch.ethz.jvmai.JoinPointStaticPart.class;
toload=ch.ethz.jvmai.MethodEntryJoinPoint.class;
toload=ch.ethz.jvmai.MethodExitJoinPoint.class;
toload=ch.ethz.jvmai.MethodSignature.class;
toload=ch.ethz.jvmai.NotInitializedException.class;
toload=ch.ethz.jvmai.Provider.class;
toload=ch.ethz.jvmai.Signature.class;
toload=ch.ethz.jvmai.StartupException.class;
toload=ch.ethz.jvmai.WatchAlreadySetException.class;
toload=ch.ethz.jvmai.WatchNotSetException.class;
Object crtClow=cflows.get();
// JDK 1.5 or above
// methodExecutionMap = new HashMap<java.lang.reflect.Member,Object>();
// fieldAccessMap = new HashMap<Field,Object>();
// fieldModificationMap = new HashMap<Field,Object>();
// exceptionThrowMap = new HashMap<Class,Object>();
// exceptionCatchMap = new HashMap<Class,Object>();
// JDK < 1.5
methodExecutionMap = new HashMap();
fieldAccessMap = new HashMap();
fieldModificationMap = new HashMap();
exceptionThrowMap = new HashMap();
exceptionCatchMap = new HashMap();
if (contingencyLoader == null) {
Iterator i = doGetClasses().iterator();
while(i.hasNext() && contingencyLoader == null) {
Class cls=(Class)i.next();
contingencyLoader=cls.getClassLoader();
}
}
classRepository = new ClassLoaderRepository( contingencyLoader );
doStartup(packagePrefixes,openWorldAssumption);
isInitialized = true;
}
/**
* Adds a class file to BCELs repository. This is only required for remote class files,
* which may not be found in the local class path.
* <P>
* Used by {@link ch.ethz.prose.tools.RemoteAspectManagerImpl} to add <CODE>
* RedefineCut</CODE>s to a remote aspect interface.
* <P>
* If the class file can not be readed or added to the repository an error message
* will be written to stdout, but no exception will be thrown to notify the caller.
*
* @param definition the raw class file bytes wrapped in an <CODE>InputStream</CODE>
* @param name (full qualified) class name in binary notation (p.a. <CODE>ch.ethz.prose.LocalAspectManager</CODE>)
*/
public void addBCELClassDefiniton( InputStream definition, String name ) {
if(isClassLoaded(name)) return;
//System.err.println("AspectInterfaceImpl - addBCELClassDefiniton CLASS NAME = " + name);
//System.err.println("AspectInterfaceImpl - addBCELClassDefiniton => CLASS = " + definition.getClass().getName());
ClassParser cp = new ClassParser(definition,name);
try{ classRepository.storeClass( cp.parse() ); }
catch(IOException e) { System.err.println("AspectInterfaceImpl.addBCELClassDefinition(): could not add class definition for " + name); }
}
/**
* Checks if the class is already into the PROSE BCEL repository.
*/
public boolean isClassLoaded(String name) {
return null != classRepository.findClass(name);
}
/**
* Sets a JoinPointHook as listener for jvmai-events.
* Whenever a watched joinpoint is reached or a class
* is loaded into the virtual machine, this JoinPointHook
* is notified by the aspect-interface. As long as no
* JoinPointHook is set (or after setting <code>null</code>),
* processing of joinpoint- and classload-events is disabled
* in the jvmai-system.
*
* @param jpHook JoinPointHook to set as listener.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
*/
public void setJoinPointHook(JoinPointHook jpHook) {
if (!isInitialized)
{
throw new NotInitializedException("AspectInterfaceImpl.setJoinPointHook: JVMAI not initialized");
}
hook = jpHook;
}
public void teardown() {
isInitialized=false;
fieldAccessMap = null;
fieldModificationMap = null;
methodExecutionMap = null;
exceptionThrowMap = null;
classRepository.clear();
doTeardown();
}
private void setWatchPrecondition(Object arg, Object aopTag) {
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.setWatch: jvmai not initialized");
if (arg == null)
throw new NullPointerException("JVMAspectInterface.setWatch: null argument parameter");
if (aopTag == null)
throw new IllegalArgumentException("JVMAspectInterface.setWatch: null aopTag value");
}
/**
* Sets a watch on a field access joinpoint.
*
* @param f the field to be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no field with id <code>fieldId</code> in class <code>cls</code>.
* @exception CannotSetWatchException ?
* @exception WatchAlreadySetException There already exists a access-watch on the field.
*/
public void setFieldAccessWatch(Field f, Object aopTag) {
setWatchPrecondition(f,aopTag);
synchronized(fieldAccessMap) {
try {
doSetFieldAccessWatch(f.getDeclaringClass(),f,aopTag);
fieldAccessMap.put(f,aopTag);
}
catch (NullPointerException e)
{ e.printStackTrace();}
}
}
/**
* Clears a watch on a field access joinpoint.
*
* @param field the field beeing watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no field with id <code>fieldId</code> in class <code>cls</code>.
* @exception WatchNotSetException There exists no access-watch on the field.
*/
public void clearFieldAccessWatch(Field field) {
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearFieldAccessWatch: jvmai not initialized");
if (field == null)
throw new NullPointerException("JVMAspectInterface.clearFieldAccessWatch: null cls parameter");
synchronized(fieldAccessMap) {
doClearFieldAccessWatch(field.getDeclaringClass(),field);
fieldAccessMap.remove(field);
}
}
/**
* Sets a watch on a field modification joinpoint.
*
* @param field the field to be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exist no field with id <code>fieldId</code> in class <code>cls</code>.
* @exception CannotSetWatchException ?
* @exception WatchAlreadySetException There already exists a modification-watch on the field.
*/
public void setFieldModificationWatch(Field field, Object aopTag) {
setWatchPrecondition(field,aopTag);
synchronized(fieldModificationMap) {
doSetFieldModificationWatch(field.getDeclaringClass(),field,aopTag);
fieldModificationMap.put(field,aopTag);
}
}
/**
* Clears a watch on a field modification joinpoint.
*
* @param field the field beeing watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no field with id <code>fieldId</code> in class <code>cls</code>.
* @exception WatchNotSetException There exists no modification-watch on the field.
*/
public void clearFieldModificationWatch(Field field) {
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearFielddModificationWatch: jvmai not initialized");
synchronized(fieldModificationMap) {
doClearFieldModificationWatch(field.getDeclaringClass(),field);
fieldModificationMap.remove(field);
}
}
// workaround for bug in 1.4.1. Subsequent invocations
// to 'getByteCode' delivers *something else* than
// the original bytecode. As a consequence, we
// cache the end locations in a map, to avoid
// asking the vm twice to obtain the bytecodes for a method.
// private HashMap<Method,List> endLocationsMap = new HashMap<Method,List>(); //for JDK 1.5.0
private HashMap endLocationsMap = new HashMap();
/// returns a list of Integer values with the bci of locations
private List returnLocations(Method m) {
// use the cache, if possible
List endLocations = (List)endLocationsMap.get(m);
if (endLocations != null)
return endLocations;
else
endLocations = new java.util.Vector();
// obtain the bytecode for the method
InstructionList iList = null;
byte[] code = null;
try {
code = doGetByteCode(m.getDeclaringClass(),m);
iList=new InstructionList(code);
}
catch (Throwable e) {
code = doGetByteCodeWithoutJvmdi(m);
iList= new InstructionList(code);
}
// search the bytecode for the method for 'return' bytecodes
InstructionHandle[] instructions = iList.getInstructionHandles();
for (int i=0; i < instructions.length; i++) {
if (instructions[i].getInstruction() instanceof ReturnInstruction)
endLocations.add(new Integer(instructions[i].getPosition()));
}
// treat special case of empty methods
if ( (code.length == 1 || code.length == 0) && (!endLocations.contains(new Integer(0)))) {
endLocations.add(new Integer(0));
}
endLocationsMap.put(m,endLocations);
return endLocations;
}
private byte[] doGetByteCodeWithoutJvmdi (Method m) {
ClassLoader cl;
cl= m.getDeclaringClass().getClassLoader();
if (cl == null)
cl = contingencyLoader;
String fileName = m.getDeclaringClass().getName().replace('.','/') + ".class";
URL resource = cl.getResource(fileName);
InputStream classStream = cl.getResourceAsStream(fileName);
try {
ClassParser cparser=new ClassParser(classStream,fileName);
JavaClass parsedClass = cparser.parse();
org.apache.bcel.classfile.Method[] methods = parsedClass.getMethods();
org.apache.bcel.classfile.Method javaMethod = null;
for (int i = 0; i < methods.length && javaMethod == null; i++) {
if (methods[i].getName().equals(m.getName()) &&
JNIUtil.jniSignature(m).equals(methods[i].getSignature())) {
javaMethod=methods[i];
}
}
classStream.close();
if (javaMethod != null) {
return javaMethod.getCode().getCode();
}
else {
throw new Error("could not find method in code");
}
}
catch (IOException cannotAccessClassCode) {
throw new RuntimeException(cannotAccessClassCode.toString());
}
}
/**
* Sets a watch on a method entry joinpoint.
*
* @param m the method to be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no method with id <code>methodId</code> in class <code>cls</code>.
* @exception CannotSetWatchException The method must not be abstract or native.
* @exception WatchAlreadySetException There already exists a entry-watch on the method.
*/
public synchronized void setMethodEntryWatch(Method m, Object aopTag) {
// preconditions
setWatchPrecondition(m,aopTag);
if ((m.getModifiers() & (Modifier.ABSTRACT | Modifier.NATIVE)) != 0)
throw new CannotSetWatchException("JVMAspectInterface.setMethodEntryWatch: cannot set watches on interfaces");
// 1. build the aop tag (if not existent); check, if existent the invariant
CompoundAopTag executionTag = (CompoundAopTag)methodExecutionMap.get(m);
if (executionTag == null)
executionTag = new CompoundAopTag();
if (executionTag.entryTag != null)
throw new WatchAlreadySetException("JVMAspectInterface.setMethodEntryWatch:" + m);
else
executionTag.entryTag=aopTag;
// if the exit tag is set and 'bci=0' is an end location, we use the
// execution tag; otherwise we use our own tag
CompoundAopTag tagToBeSet;
boolean tagIsCommonToEntryExit = false;
if (returnLocations(m).contains(new Integer(0))) {
tagToBeSet=executionTag;
tagIsCommonToEntryExit = true;
}
else {
tagToBeSet=new CompoundAopTag();
tagToBeSet.entryTag=aopTag;
tagToBeSet.exitTag=null;
tagIsCommonToEntryExit = false;
}
// 2. activate the location watch
try {
doSetLocationWatch(m.getDeclaringClass(),m,0,tagToBeSet);
}
catch (WatchAlreadySetException e) {
if (!(tagIsCommonToEntryExit && executionTag.exitTag!=null))
throw new Error("JVMAspectInteface.setMethodEntryWatch:" +
"JMD reports watch already set, but the exit tag is null");
}
methodExecutionMap.put(m,executionTag);
}
/**
* Clears a watch on a method entry joinpoint.
*
* @param m the method beeing watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no method with id <code>methodId</code> in class <code>cls</code>.
* @exception WatchNotSetException There exists no entry-watch on the method.
*/
public synchronized void clearMethodEntryWatch(Method m) {
// preconditions
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearMethodEntryWatch: jvmai not initialized");
if (m == null)
throw new NullPointerException("JVMAspectInterface.clearMethodEntryWatch: null m parameter");
if ((m.getModifiers() & (Modifier.ABSTRACT | Modifier.NATIVE)) != 0)
throw new CannotSetWatchException("JVMAspectInterface.setMethodEntryWatch: cannot clear watches on interfaces");
// 1. first check whether we have anything to delete
CompoundAopTag oldTag = (CompoundAopTag) methodExecutionMap.get(m);
if (oldTag == null) {
throw new WatchNotSetException("JVMAspectInterface.clearMethodEntryWatch");
}
// 2. then we delete the aopTag
if (oldTag.entryTag == null)
throw new WatchNotSetException("JVMAspectInterface.clearMethodEntryWatch:" + m);
else
oldTag.entryTag = null;
// 3. we actually remove the watch if
// - no method exit tag is set OR
// - '0' is not an end location
if (oldTag.exitTag == null || (!returnLocations(m).contains(new Integer(0))) ) {
doClearLocationWatch(m.getDeclaringClass(),m,0);
}
// 4. clear the tag map, if noboy is interested in this method
if (oldTag.exitTag == null)
methodExecutionMap.remove(m);
}
/**
* Sets a watch on a constructor joinpoint.
*
* @param m the constructor to be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
*/
public void setConstructorWatch(Constructor m, Object aopTag) {
throw new JVMAIRuntimeException("not implemented");
}
/**
* Clears a watch on a constructor joinpoint.
*
* @param m the constructor beeing watched.
*/
public void clearConstructorWatch(Constructor m) {
throw new JVMAIRuntimeException("not implemented");
}
// remove the map only if there are no breakpoints left in the exits;
/**
* Sets a watch on a method exit joinpoint.
*
* @param m the method beeing watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no method with id <code>methodId</code> in class <code>cls</code>.
* @exception CannotSetWatchException The method must not be abstract or native.
* @exception WatchAlreadySetException There already exists a exit-watch on the method.
*/
public synchronized void setMethodExitWatch(Method m, Object aopTag) {
// preconditions
setWatchPrecondition(m,aopTag);
if ((m.getModifiers() & (Modifier.ABSTRACT | Modifier.NATIVE)) != 0)
throw new CannotSetWatchException("JVMAspectInterface.setMethodExitWatch: cannot set watches on interfaces");
// 1. check whether it is already set, and then set the tag
CompoundAopTag compoundTag = (CompoundAopTag)methodExecutionMap.get(m);
if (compoundTag == null) {
compoundTag = new CompoundAopTag();
methodExecutionMap.put(m,compoundTag);
}
if (compoundTag.exitTag != null)
throw new WatchAlreadySetException("JVMAspectInteface.setMethodExitWatch" + m);
compoundTag.exitTag = aopTag;
// 2. find out the end locations of this method (everywhere were 'return' exists + '0' if
// the method is empty
List endLocations = returnLocations(m);
// 3. iterate over locations and set the watch
Iterator j = endLocations.iterator();
while (j.hasNext()) {
CompoundAopTag tagToBeSet;
int bci = ((Integer)(j.next())).intValue();
if (bci == 0) {
// if this return location is ALSO an entry location, we use a (X,Y) compound tag
tagToBeSet=compoundTag;
}
else {
// if this return location is NOT an entry location, we use a (null,Y) compound tag
tagToBeSet=new CompoundAopTag();
tagToBeSet.entryTag=null;
tagToBeSet.exitTag=aopTag;
}
try {
doSetLocationWatch(m.getDeclaringClass(),m,bci,tagToBeSet);
}
catch (WatchAlreadySetException entryWatchOnZeroLengthMethod) {
// this exception is ok only if the return location is a entry location, too,
// and the entry location is already set.
if (compoundTag.entryTag != null && bci == 0)
{}// everyhting is fine
else {
// this is an error, it should have been caputred earlier
Error x =new Error("JVMAspectInterface.setMethodEntryWatch: " +
" watch set on exit in spite of non-zero length method:" + m);
x.printStackTrace();
throw x;
}
}
}
}
/**
* Clears a watch on a method exit joinpoint.
*
* @param m the method beeing watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception InvalidIdException There exists no method with id <code>methodId</code> in class <code>cls</code>.
* @exception WatchNotSetException There exists no exit-watch on the method.
*/
public synchronized void clearMethodExitWatch(Method m) {
// preconditions
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearMethodExitWatch: jvmai not initialized");
if (m == null)
throw new NullPointerException("JVMAspectInterface.clearMethodExitWatch: null Method parameter");
if (m.getDeclaringClass().isInterface())
throw new WatchNotSetException("JVMAspectInterface.clearMethodExitWatch: cannot clear watches on interfaces");
// 1. obtain the tag to delete
CompoundAopTag compoundTag = (CompoundAopTag)methodExecutionMap.get(m);
if (compoundTag == null || compoundTag.exitTag == null) {
throw new WatchNotSetException("JVMAspectInterface.clearMethodExitWatch: " + m);
}
compoundTag.exitTag = null;
// 2. iterate over the 'return' locations and delete the watches
Iterator j = returnLocations(m).iterator();
while (j.hasNext()) {
int bci = ((Integer)(j.next())).intValue();
if (bci != 0 || (compoundTag.entryTag == null)) {
doClearLocationWatch(m.getDeclaringClass(),m,bci);
}
}
// 3. if nobody is interested in this method, delete the compound tag, too
if (compoundTag.entryTag == null)
methodExecutionMap.remove(m);
}
/**
* Sets a watch on a exception throw joinpoint.
*
* @param cls Exception Class that should be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception WatchAlreadySetException There already exists a exceptionThrow-watch at this point.
*/
public void setExceptionThrowWatch(Class cls, Object aopTag) {
setWatchPrecondition(cls,aopTag);
synchronized(exceptionThrowMap) {
doSetExceptionWatch(cls,aopTag);
exceptionThrowMap.put(cls, aopTag);
}
}
/**
* Clears a watch on a exception throw joinpoint.
*
* @param cls Exception Class that should be watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception WatchNotSetException There exists no exceptionThrow-watch at this point.
*/
public void clearExceptionThrowWatch(Class cls) {
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearExceptionThrowWatch: jvmai not initialized");
if (cls == null)
throw new NullPointerException("JVMAspectInterface.clearExceptionThrowWatch: null cls parameter");
synchronized(exceptionThrowMap) {
doClearExceptionWatch(cls);
if (exceptionThrowMap.containsKey(cls))
exceptionThrowMap.remove(cls);
else
throw new WatchNotSetException();
}
}
/**
* Sets a watch on a exception catch joinpoint.
*
* @param cls Exception Class that should be watched.
* @param aopTag A user-defined object saved with this watch.
* When the joinpoint is reached, this object is
* passed to the JoinPointHook as part of the
* JoinPoint-instance. This object may be <code>null</code>.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception WatchAlreadySetException There already exists a exceptionCatch-watch at this point.
*/
public void setExceptionCatchWatch(Class cls, Object aopTag) {
setWatchPrecondition(cls,aopTag);
synchronized(exceptionCatchMap) {
doSetExceptionCatchWatch(cls,aopTag);
exceptionCatchMap.put(cls, aopTag);
}
}
/**
* Clears a watch on a exception catch joinpoint.
*
* @param cls Exception Class that should be watched.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>cls</code> is <code>null</code>.
* @exception WatchNotSetException There exists no exceptionCatch-watch at this point.
*/
public void clearExceptionCatchWatch(Class cls) {
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.clearExceptionCatchWatch: jvmai not initialized");
if (cls == null)
throw new NullPointerException("JVMAspectInterface.clearExceptionCatchWatch: null cls parameter");
synchronized(exceptionCatchMap) {
doClearExceptionCatchWatch(cls);
if (exceptionCatchMap.containsKey(cls))
exceptionCatchMap.remove(cls);
else
throw new WatchNotSetException();
}
}
/**
* Suspend notification regarding the specified thread.
* Successive calls to <code>suspendNotification</code>
* have to be balanced by (at least) the same number of calls to
* <code>resumeNotification</code>, or the jvmai-system
* will not resume notification.
*
* @param thread Thread for which to disable notification.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>thread</code> is <code>null</code>.
*/
public void suspendNotification(Thread thread) {
// jvmdi implementation
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.resumeNotification: jvmai not initialized");
doSuspendNotification(thread);
}
/**
* Resumes notification regarding the specified thread.
* Successive calls to <code>resumeNotification</code>
* without preceding calls to <code>suspendNotification</code>
* will be ignored by the jvmai-system.
*
* @param thread Thread for which to reenable notification.
* @exception NotInitializedException Aspect-interface has not been initialized yet. Call <code>setup</code> first.
* @exception NullPointerException <code>thread</code> is <code>null</code>.
*/
public void resumeNotification(Thread thread) {
// jvmdi implementation
if (!isInitialized)
throw new NotInitializedException("JVMAspectInterface.resumeNotification: jvmai not initialized");
doResumeNotification(thread);
}
public List getLoadedClasses() {
return doGetClasses();
}
//private void doOnClassLoad(Class cls) //BEFORE-delete
protected void doOnClassLoad(Class cls) { //CORECT-BUGFIX ANGY - both methods (from AspectInterfaceImpl and HotswapAspectInterfaceImpl) need to have the same modifiers = have to be PROTECTED
if (hook==null)
return;
hook.onClassLoad(cls);
}
private void doOnMethodExecution(MethodExecutionJoinPointImpl jp) {
// find out whether it is entry, exit, or both
if (hook==null)
return;
Method method = jp.getMethod();
CompoundAopTag compoundTag = (CompoundAopTag)jp.getAopTag();
if (compoundTag.entryTag!=null) {
jp.aopTag = compoundTag.entryTag;
jp.setKind(true);
hook.onMethodEntry(jp);
}
if (compoundTag.exitTag != null) {
jp.aopTag = compoundTag.exitTag;
jp.setKind(false);
hook.onMethodExit(jp);
}
}
private void doOnFieldAccess(FieldAccessJoinPointImpl jp) {
if (hook==null)
return;
hook.onFieldAccess(jp);
}
private void doOnFieldModification(FieldModificationJoinPointImpl jp) {
if (hook==null)
return;
hook.onFieldModification(jp);
}
private void doOnExceptionThrow(ExceptionJoinPointImpl jp) {
if (hook==null) return;
try {
hook.onExceptionThrow(jp);
} catch(Throwable e) {
System.err.println( "doOnExceptionThrow throws " + e.getClass().getName() + ": " + e.getMessage() );
StackTraceElement[] stackTrace = e.getStackTrace();
for( int i = 0; i < stackTrace.length; i++) {
System.err.println(" " + stackTrace[i]);
}
}
}
private void doOnExceptionCatch(ExceptionCatchJoinPointImpl jp) {
if (hook==null) return;
try {
hook.onExceptionCatch(jp);
} catch (Throwable x) {
System.err.println("doOnExceptionCatch throws" + x.getClass().getName() + ": " + x.getMessage() );
}
}
private native void doStartup(Object[] prefixes, boolean openWorld);
private native void doTeardown();
private native void doSetLocationWatch(Class c, Method m, int bci,Object tag);
private native void doClearLocationWatch(Class c, Method m, int bci);
private native void doSetFieldAccessWatch(Class c, Field f,Object tag);
private native void doClearFieldAccessWatch(Class c, Field f);
private native void doSetFieldModificationWatch(Class c, Field f, Object tag);
private native void doClearFieldModificationWatch(Class c, Field f);
private native void doSetExceptionWatch(Class throwableClass, Object tag);
private native void doClearExceptionWatch(Class trowableClass);
private native void doSetExceptionCatchWatch(Class throwableClass, Object tag);
private native void doClearExceptionCatchWatch(Class trowableClass);
private native byte [] doGetByteCode(Class c, Method m);
private native List doGetClasses();
private native void doSuspendNotification(Thread t);
private native void doResumeNotification(Thread t);
}