/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *     PARC     initial implementation
 *     Alexandre Vasseur    support for @AJ aspects
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.Field;
import org.aspectj.apache.bcel.generic.ArrayType;
import org.aspectj.apache.bcel.generic.BranchHandle;
import org.aspectj.apache.bcel.generic.FieldInstruction;
import org.aspectj.apache.bcel.generic.INVOKEINTERFACE;
import org.aspectj.apache.bcel.generic.Instruction;
import org.aspectj.apache.bcel.generic.InstructionBranch;
import org.aspectj.apache.bcel.generic.InstructionConstants;
import org.aspectj.apache.bcel.generic.InstructionFactory;
import org.aspectj.apache.bcel.generic.InstructionHandle;
import org.aspectj.apache.bcel.generic.InstructionLV;
import org.aspectj.apache.bcel.generic.InstructionList;
import org.aspectj.apache.bcel.generic.InstructionTargeter;
import org.aspectj.apache.bcel.generic.InvokeInstruction;
import org.aspectj.apache.bcel.generic.LineNumberTag;
import org.aspectj.apache.bcel.generic.LocalVariableTag;
import org.aspectj.apache.bcel.generic.MULTIANEWARRAY;
import org.aspectj.apache.bcel.generic.ObjectType;
import org.aspectj.apache.bcel.generic.TargetLostException;
import org.aspectj.apache.bcel.generic.Type;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.AjcMemberMaker;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.ConcreteTypeMunger;
import org.aspectj.weaver.IntMap;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.MemberImpl;
import org.aspectj.weaver.NameMangler;
import org.aspectj.weaver.NewConstructorTypeMunger;
import org.aspectj.weaver.NewFieldTypeMunger;
import org.aspectj.weaver.NewMethodTypeMunger;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Var;
import org.aspectj.weaver.patterns.AbstractPatternNodeVisitor;
import org.aspectj.weaver.patterns.AndPointcut;
import org.aspectj.weaver.patterns.NotPointcut;
import org.aspectj.weaver.patterns.OrPointcut;
import org.aspectj.weaver.patterns.ThisOrTargetPointcut;


/*
 * Some fun implementation stuff:
 *
 *   * expressionKind advice is non-execution advice
 *     * may have a target.
 *     * if the body is extracted, it will be extracted into
 *       a static method.  The first argument to the static
 *       method is the target
 *     * advice may expose a this object, but that's the advice's
 *       consideration, not ours.  This object will NOT be cached in another
 *       local, but will always come from frame zero.
 *
 *   * non-expressionKind advice is execution advice
 *     * may have a this.
 *     * target is same as this, and is exposed that way to advice
 *       (i.e., target will not be cached, will always come from frame zero)
 *     * if the body is extracted, it will be extracted into a method
 *       with same static/dynamic modifier as enclosing method.  If non-static,
 *       target of callback call will be this.
 *
 *   * because of these two facts, the setup of the actual arguments (including
 *     possible target) callback method is the same for both kinds of advice:
 *     push the targetVar, if it exists (it will not exist for advice on static
 *     things), then push all the argVars.
 *
 * Protected things:
 *
 *   * the above is sufficient for non-expressionKind advice for protected things,
 *     since the target will always be this.
 *
 *   * For expressionKind things, we have to modify the signature of the callback
 *     method slightly.  For non-static expressionKind things, we modify
 *     the first argument of the callback method NOT to be the type specified
 *     by the method/field signature (the owner), but rather we type it to
 *     the currentlyEnclosing type. We are guaranteed this will be fine,
 *     since the verifier verifies that the target is a subtype of the currently
 *     enclosingType.
 *
 * Worries:
 *
 *    * ConstructorCalls will be weirder than all of these, since they
 *      supposedly don't have a target (according to AspectJ), but they clearly
 *      do have a target of sorts, just one that needs to be pushed on the stack,
 *      dupped, and not touched otherwise until the constructor runs.
 *
 * @author Jim Hugunin
 * @author Erik Hilsdale
 *
 */

public class BcelShadow extends Shadow {

	private static final String[] NoDeclaredExceptions = new String[0];

	private ShadowRange range;
	private final BcelWorld world;
	private final LazyMethodGen enclosingMethod;

	// TESTING this will tell us if the optimisation succeeded *on the last shadow processed*
	public static boolean appliedLazyTjpOptimization;

	// Some instructions have a target type that will vary
	// from the signature (pr109728) (1.4 declaring type issue)
	private String actualInstructionTargetType;

	/**
	 * This generates an unassociated shadow, rooted in a particular method but not rooted to any particular point in the code. It
	 * should be given to a rooted ShadowRange in the {@link ShadowRange#associateWithShadow(BcelShadow)} method.
	 */
	public BcelShadow(BcelWorld world, Kind kind, Member signature, LazyMethodGen enclosingMethod, BcelShadow enclosingShadow) {
		super(kind, signature, enclosingShadow);
		this.world = world;
		this.enclosingMethod = enclosingMethod;
	}

	// ---- copies all state, including Shadow's mungers...

	public BcelShadow copyInto(LazyMethodGen recipient, BcelShadow enclosing) {
		BcelShadow s = new BcelShadow(world, getKind(), getSignature(), recipient, enclosing);
		if (mungers.size() > 0) {
			List<ShadowMunger> src = mungers;
			if (s.mungers == Collections.EMPTY_LIST) {
				s.mungers = new ArrayList<>();
			}
			List<ShadowMunger> dest = s.mungers;
			for (ShadowMunger shadowMunger : src) {
				dest.add(shadowMunger);
			}
		}
		return s;
	}

	// ---- overridden behaviour

	@Override
	public World getIWorld() {
		return world;
	}

	// see comment in deleteNewAndDup
	// } else if (inst.opcode == Constants.DUP_X2) {
	// // This code seen in the wild (by Brad):
	// // 40: new #12; //class java/lang/StringBuffer
	// // STACK: STRINGBUFFER
	// // 43: dup
	// // STACK: STRINGBUFFER/STRINGBUFFER
	// // 44: aload_0
	// // STACK: STRINGBUFFER/STRINGBUFFER/THIS
	// // 45: dup_x2
	// // STACK: THIS/STRINGBUFFER/STRINGBUFFER/THIS
	// // 46: getfield #36; //Field value:Ljava/lang/String;
	// // STACK: THIS/STRINGBUFFER/STRINGBUFFER/STRING<value>
	// // 49: invokestatic #37; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
	// // STACK: THIS/STRINGBUFFER/STRINGBUFFER/STRING
	// // 52: invokespecial #19; //Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
	// // STACK: THIS/STRINGBUFFER
	// // 55: aload_1
	// // STACK: THIS/STRINGBUFFER/LOCAL1
	// // 56: invokevirtual #22; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
	// // STACK: THIS/STRINGBUFFER
	// // 59: invokevirtual #34; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
	// // STACK: THIS/STRING
	// // 62: putfield #36; //Field value:Ljava/lang/String;
	// // STACK: <empty>
	// // 65: return
	//
	// // if we attempt to match on the ctor call to StringBuffer.<init> then we get into trouble.
	// // if we simply delete the new/dup pair without fixing up the dup_x2 then the dup_x2 will fail due to there
	// // not being 3 elements on the stack for it to work with. The fix *in this situation* is to change it to
	// // a simple 'dup'
	//
	// // this fix is *not* very clean - but a general purpose decent solution will take much longer and this
	// // bytecode sequence has only been seen once in the wild.
	// ih.setInstruction(InstructionConstants.DUP);

	/**
	 * The new/dup (or new/dup_x1/swap) are removed and will be readded later (after the advice call) by the caller of this method.
	 * The groovy compiler produces unusual code where the new/dup isn't visible (when making a this() call from an existing ctor),
	 * an aload_0 is used to load the uninitialized object (as an example see the ctors in grails.util.BuildSettings).
	 *
	 * @return true if managed to remove them
	 */
	private boolean deleteNewAndDup() {
		final ConstantPool cpool = getEnclosingClass().getConstantPool();
		int depth = 1;
		InstructionHandle ih = range.getStart();

		// Go back from where we are looking for 'NEW' that takes us to a stack depth of 0. INVOKESPECIAL <init>
		while (ih != null) {
			Instruction inst = ih.getInstruction();
			if (inst.opcode == Constants.INVOKESPECIAL && ((InvokeInstruction) inst).getName(cpool).equals("<init>")) {
				depth++;
			} else if (inst.opcode == Constants.NEW) {
				depth--;
				if (depth == 0) {
					break;
				}
				// need a testcase to show this can really happen in a modern compiler - removed due to 315398 - moved this out to
				// comment proceeding this method:

			}
			ih = ih.getPrev();
		}
		if (ih == null) {
			return false;
		}
		// now IH points to the NEW. We're followed by the DUP, and that is followed
		// by the actual instruction we care about.
		InstructionHandle newHandle = ih;
		InstructionHandle endHandle = newHandle.getNext();
		InstructionHandle nextHandle;
		if (endHandle.getInstruction().opcode == Constants.DUP) {
			nextHandle = endHandle.getNext();
			retargetFrom(newHandle, nextHandle);
			retargetFrom(endHandle, nextHandle);
		} else if (endHandle.getInstruction().opcode == Constants.DUP_X1) {
			InstructionHandle dupHandle = endHandle;
			endHandle = endHandle.getNext();
			nextHandle = endHandle.getNext();
			boolean skipEndRepositioning = false;
			if (endHandle.getInstruction().opcode == Constants.SWAP) {
			} else if (endHandle.getInstruction().opcode == Constants.IMPDEP1) {
				skipEndRepositioning = true; // pr186884
			} else {
				// XXX see next XXX comment
				throw new RuntimeException("Unhandled kind of new " + endHandle);
			}
			// Now make any jumps to the 'new', the 'dup' or the 'end' now target the nextHandle
			retargetFrom(newHandle, nextHandle);
			retargetFrom(dupHandle, nextHandle);
			if (!skipEndRepositioning) {
				retargetFrom(endHandle, nextHandle);
			}
		} else {
			endHandle = newHandle;
			nextHandle = endHandle.getNext();
			retargetFrom(newHandle, nextHandle);
			// add a POP here... we found a NEW w/o a dup or anything else, so
			// we must be in statement context.
			getRange().insert(InstructionConstants.POP, Range.OutsideAfter);
		}
		// assert (dupHandle.getInstruction() instanceof DUP);

		try {
			range.getBody().delete(newHandle, endHandle);
		} catch (TargetLostException e) {
			throw new BCException("shouldn't happen");
		}
		return true;
	}

	private void retargetFrom(InstructionHandle old, InstructionHandle fresh) {
		for (InstructionTargeter targeter : old.getTargetersCopy()) {
			if (targeter instanceof ExceptionRange) {
				ExceptionRange it = (ExceptionRange) targeter;
				it.updateTarget(old, fresh, it.getBody());
			} else {
				targeter.updateTarget(old, fresh);
			}
		}
	}

	// records advice that is stopping us doing the lazyTjp optimization
	private List<BcelAdvice> badAdvice = null;

	public void addAdvicePreventingLazyTjp(BcelAdvice advice) {
		if (badAdvice == null) {
			badAdvice = new ArrayList<>();
		}
		badAdvice.add(advice);
	}

	@Override
	protected void prepareForMungers() {
		// if we're a constructor call, we need to remove the new:dup or the new:dup_x1:swap,
		// and store all our arguments on the frame.

		// ??? This is a bit of a hack (for the Java langauge). We do this because
		// we sometime add code "outsideBefore" when dealing with weaving join points. We only
		// do this for exposing state that is on the stack. It turns out to just work for
		// everything except for constructor calls and exception handlers. If we were to clean
		// this up, every ShadowRange would have three instructionHandle points, the start of
		// the arg-setup code, the start of the running code, and the end of the running code.
		boolean deletedNewAndDup = true;
		if (getKind() == ConstructorCall) {
			if (!world.isJoinpointArrayConstructionEnabled() || !this.getSignature().getDeclaringType().isArray()) {
				deletedNewAndDup = deleteNewAndDup(); // no new/dup for new array construction
			}
			initializeArgVars();
		} else if (getKind() == PreInitialization) { // pr74952
			ShadowRange range = getRange();
			range.insert(InstructionConstants.NOP, Range.InsideAfter);
		} else if (getKind() == ExceptionHandler) {

			ShadowRange range = getRange();
			InstructionList body = range.getBody();
			InstructionHandle start = range.getStart();

			// Create a store instruction to put the value from the top of the
			// stack into a local variable slot. This is a trimmed version of
			// what is in initializeArgVars() (since there is only one argument
			// at a handler jp and only before advice is supported) (pr46298)
			argVars = new BcelVar[1];
			// int positionOffset = (hasTarget() ? 1 : 0) + ((hasThis() && !getKind().isTargetSameAsThis()) ? 1 : 0);
			UnresolvedType tx = getArgType(0);
			argVars[0] = genTempVar(tx, "ajc$arg0");
			InstructionHandle insertedInstruction = range.insert(argVars[0].createStore(getFactory()), Range.OutsideBefore);

			// Now the exception range starts just after our new instruction.
			// The next bit of code changes the exception range to point at
			// the store instruction
			for (InstructionTargeter t : start.getTargetersCopy()) {
				if (t instanceof ExceptionRange) {
					ExceptionRange er = (ExceptionRange) t;
					er.updateTarget(start, insertedInstruction, body);
				}
			}
		}

		// now we ask each munger to request our state
		isThisJoinPointLazy = true;// world.isXlazyTjp(); // lazy is default now

		badAdvice = null;
		for (ShadowMunger munger : mungers) {
			munger.specializeOn(this);
		}

		initializeThisJoinPoint();

		if (thisJoinPointVar != null && !isThisJoinPointLazy && badAdvice != null && badAdvice.size() > 1) {
			// something stopped us making it a lazy tjp
			// can't build tjp lazily, no suitable test...
			int valid = 0;
			for (BcelAdvice element : badAdvice) {
				ISourceLocation sLoc = element.getSourceLocation();
				if (sLoc != null && sLoc.getLine() > 0) {
					valid++;
				}
			}
			if (valid != 0) {
				ISourceLocation[] badLocs = new ISourceLocation[valid];
				int i = 0;
				for (BcelAdvice element : badAdvice) {
					ISourceLocation sLoc = element.getSourceLocation();
					if (sLoc != null && sLoc.getLine() > 0) {
						badLocs[i++] = sLoc;
					}
				}
				world.getLint().multipleAdviceStoppingLazyTjp
						.signal(new String[] { this.toString() }, getSourceLocation(), badLocs);
			}
		}
		badAdvice = null;

		// If we are an expression kind, we require our target/arguments on the stack
		// before we do our actual thing. However, they may have been removed
		// from the stack as the shadowMungers have requested state.
		// if any of our shadowMungers requested either the arguments or target,
		// the munger will have added code
		// to pop the target/arguments into temporary variables, represented by
		// targetVar and argVars. In such a case, we must make sure to re-push the
		// values.

		// If we are nonExpressionKind, we don't expect arguments on the stack
		// so this is moot. If our argVars happen to be null, then we know that
		// no ShadowMunger has squirrelled away our arguments, so they're still
		// on the stack.
		InstructionFactory fact = getFactory();
		if (getKind().argsOnStack() && argVars != null) {

			// Special case first (pr46298). If we are an exception handler and the instruction
			// just after the shadow is a POP then we should remove the pop. The code
			// above which generated the store instruction has already cleared the stack.
			// We also don't generate any code for the arguments in this case as it would be
			// an incorrect aload.
			if (getKind() == ExceptionHandler && range.getEnd().getNext().getInstruction().equals(InstructionConstants.POP)) {
				// easier than deleting it ...
				range.getEnd().getNext().setInstruction(InstructionConstants.NOP);
			} else {
				range.insert(BcelRenderer.renderExprs(fact, world, argVars), Range.InsideBefore);
				if (targetVar != null) {
					range.insert(BcelRenderer.renderExpr(fact, world, targetVar), Range.InsideBefore);
				}
				if (getKind() == ConstructorCall) {
					if (!world.isJoinpointArrayConstructionEnabled() || !this.getSignature().getDeclaringType().isArray()) {
						if (deletedNewAndDup) { // if didnt delete them, dont insert any!
							range.insert(InstructionFactory.createDup(1), Range.InsideBefore);
							range.insert(fact.createNew((ObjectType) BcelWorld.makeBcelType(getSignature().getDeclaringType())),
									Range.InsideBefore);
						}
					}
				}
			}
		}
	}

	// ---- getters

	public ShadowRange getRange() {
		return range;
	}

	public void setRange(ShadowRange range) {
		this.range = range;
	}

	private int sourceline = -1;

	public int getSourceLine() {
		// if the kind of join point for which we are a shadow represents
		// a method or constructor execution, then the best source line is
		// the one from the enclosingMethod declarationLineNumber if available.
		if (sourceline != -1) {
			return sourceline;
		}
		Kind kind = getKind();
		if ((kind == MethodExecution) || (kind == ConstructorExecution) || (kind == AdviceExecution)
				|| (kind == StaticInitialization) || (kind == PreInitialization) || (kind == Initialization)) {
			if (getEnclosingMethod().hasDeclaredLineNumberInfo()) {
				sourceline = getEnclosingMethod().getDeclarationLineNumber();
				return sourceline;
			}
		}

		if (range == null) {
			if (getEnclosingMethod().hasBody()) {
				sourceline = Utility.getSourceLine(getEnclosingMethod().getBody().getStart());
				return sourceline;
			} else {
				sourceline = 0;
				return sourceline;
			}
		}
		sourceline = Utility.getSourceLine(range.getStart());
		if (sourceline < 0) {
			sourceline = 0;
		}
		return sourceline;
	}

	@Override
	public ResolvedType getEnclosingType() {
		return getEnclosingClass().getType();
	}

	public LazyClassGen getEnclosingClass() {
		return enclosingMethod.getEnclosingClass();
	}

	public BcelWorld getWorld() {
		return world;
	}

	// ---- factory methods

	public static BcelShadow makeConstructorExecution(BcelWorld world, LazyMethodGen enclosingMethod,
			InstructionHandle justBeforeStart) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, ConstructorExecution, world.makeJoinPointSignatureFromMethod(enclosingMethod,
				Member.CONSTRUCTOR), enclosingMethod, null);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, justBeforeStart.getNext()), Range.genEnd(body));
		return s;
	}

	public static BcelShadow makeStaticInitialization(BcelWorld world, LazyMethodGen enclosingMethod) {
		InstructionList body = enclosingMethod.getBody();
		// move the start past ajc$preClinit
		InstructionHandle clinitStart = body.getStart();
		if (clinitStart.getInstruction() instanceof InvokeInstruction) {
			InvokeInstruction ii = (InvokeInstruction) clinitStart.getInstruction();
			if (ii.getName(enclosingMethod.getEnclosingClass().getConstantPool()).equals(NameMangler.AJC_PRE_CLINIT_NAME)) {
				clinitStart = clinitStart.getNext();
			}
		}

		InstructionHandle clinitEnd = body.getEnd();

		// XXX should move the end before the postClinit, but the return is then tricky...
		// if (clinitEnd.getInstruction() instanceof InvokeInstruction) {
		// InvokeInstruction ii = (InvokeInstruction)clinitEnd.getInstruction();
		// if (ii.getName(enclosingMethod.getEnclosingClass().getConstantPool()).equals(NameMangler.AJC_POST_CLINIT_NAME)) {
		// clinitEnd = clinitEnd.getPrev();
		// }
		// }

		BcelShadow s = new BcelShadow(world, StaticInitialization, world.makeJoinPointSignatureFromMethod(enclosingMethod,
				Member.STATIC_INITIALIZATION), enclosingMethod, null);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, clinitStart), Range.genEnd(body, clinitEnd));
		return s;
	}

	/**
	 * Make the shadow for an exception handler. Currently makes an empty shadow that only allows before advice to be woven into it.
	 */

	public static BcelShadow makeExceptionHandler(BcelWorld world, ExceptionRange exceptionRange, LazyMethodGen enclosingMethod,
			InstructionHandle startOfHandler, BcelShadow enclosingShadow) {
		InstructionList body = enclosingMethod.getBody();
		UnresolvedType catchType = exceptionRange.getCatchType();
		UnresolvedType inType = enclosingMethod.getEnclosingClass().getType();

		ResolvedMemberImpl sig = MemberImpl.makeExceptionHandlerSignature(inType, catchType);
		sig.setParameterNames(new String[] { findHandlerParamName(startOfHandler) });

		BcelShadow s = new BcelShadow(world, ExceptionHandler, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		InstructionHandle start = Range.genStart(body, startOfHandler);
		InstructionHandle end = Range.genEnd(body, start);

		r.associateWithTargets(start, end);
		exceptionRange.updateTarget(startOfHandler, start, body);
		return s;
	}

	private static String findHandlerParamName(InstructionHandle startOfHandler) {
		if (startOfHandler.getInstruction().isStoreInstruction() && startOfHandler.getNext() != null) {
			int slot = startOfHandler.getInstruction().getIndex();
			// System.out.println("got store: " + startOfHandler.getInstruction() + ", " + index);
			for (InstructionTargeter targeter : startOfHandler.getNext().getTargeters()) {
				if (targeter instanceof LocalVariableTag) {
					LocalVariableTag t = (LocalVariableTag) targeter;
					if (t.getSlot() == slot) {
						return t.getName();
					}
				}
			}
		}

		return "<missing>";
	}

	/** create an init join point associated w/ an interface in the body of a constructor */

	public static BcelShadow makeIfaceInitialization(BcelWorld world, LazyMethodGen constructor,
			Member interfaceConstructorSignature) {
		// this call marks the instruction list as changed
		constructor.getBody();
		// UnresolvedType inType = constructor.getEnclosingClass().getType();
		BcelShadow s = new BcelShadow(world, Initialization, interfaceConstructorSignature, constructor, null);
		// s.fallsThrough = true;
		// ShadowRange r = new ShadowRange(body);
		// r.associateWithShadow(s);
		// InstructionHandle start = Range.genStart(body, handle);
		// InstructionHandle end = Range.genEnd(body, handle);
		//
		// r.associateWithTargets(start, end);
		return s;
	}

	public void initIfaceInitializer(InstructionHandle end) {
		final InstructionList body = enclosingMethod.getBody();
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(this);
		InstructionHandle nop = body.insert(end, InstructionConstants.NOP);

		r.associateWithTargets(Range.genStart(body, nop), Range.genEnd(body, nop));
	}

	// public static BcelShadow makeIfaceConstructorExecution(
	// BcelWorld world,
	// LazyMethodGen constructor,
	// InstructionHandle next,
	// Member interfaceConstructorSignature)
	// {
	// // final InstructionFactory fact = constructor.getEnclosingClass().getFactory();
	// InstructionList body = constructor.getBody();
	// // UnresolvedType inType = constructor.getEnclosingClass().getType();
	// BcelShadow s =
	// new BcelShadow(
	// world,
	// ConstructorExecution,
	// interfaceConstructorSignature,
	// constructor,
	// null);
	// s.fallsThrough = true;
	// ShadowRange r = new ShadowRange(body);
	// r.associateWithShadow(s);
	// // ??? this may or may not work
	// InstructionHandle start = Range.genStart(body, next);
	// //InstructionHandle end = Range.genEnd(body, body.append(start, fact.NOP));
	// InstructionHandle end = Range.genStart(body, next);
	// //body.append(start, fact.NOP);
	//
	// r.associateWithTargets(start, end);
	// return s;
	// }

	/**
	 * Create an initialization join point associated with a constructor, but not with any body of code yet. If this is actually
	 * matched, its range will be set when we inline self constructors.
	 *
	 * @param constructor The constructor starting this initialization.
	 */
	public static BcelShadow makeUnfinishedInitialization(BcelWorld world, LazyMethodGen constructor) {
		BcelShadow ret = new BcelShadow(world, Initialization, world.makeJoinPointSignatureFromMethod(constructor,
				Member.CONSTRUCTOR), constructor, null);
		if (constructor.getEffectiveSignature() != null) {
			ret.setMatchingSignature(constructor.getEffectiveSignature().getEffectiveSignature());
		}
		return ret;
	}

	public static BcelShadow makeUnfinishedPreinitialization(BcelWorld world, LazyMethodGen constructor) {
		BcelShadow ret = new BcelShadow(world, PreInitialization, world.makeJoinPointSignatureFromMethod(constructor,
				Member.CONSTRUCTOR), constructor, null);
		if (constructor.getEffectiveSignature() != null) {
			ret.setMatchingSignature(constructor.getEffectiveSignature().getEffectiveSignature());
		}
		return ret;
	}

	public static BcelShadow makeMethodExecution(BcelWorld world, LazyMethodGen enclosingMethod, boolean lazyInit) {
		if (!lazyInit) {
			return makeMethodExecution(world, enclosingMethod);
		}

		BcelShadow s = new BcelShadow(world, MethodExecution, enclosingMethod.getMemberView(), enclosingMethod, null);

		return s;
	}

	public void init() {
		if (range != null) {
			return;
		}

		final InstructionList body = enclosingMethod.getBody();
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(this);
		r.associateWithTargets(Range.genStart(body), Range.genEnd(body));
	}

	public static BcelShadow makeMethodExecution(BcelWorld world, LazyMethodGen enclosingMethod) {
		return makeShadowForMethod(world, enclosingMethod, MethodExecution, enclosingMethod.getMemberView());
	}

	public static BcelShadow makeShadowForMethod(BcelWorld world, LazyMethodGen enclosingMethod, Shadow.Kind kind, Member sig) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, kind, sig, enclosingMethod, null);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(// OPTIMIZE this occurs lots of times for all jp kinds...
				Range.genStart(body), Range.genEnd(body));
		return s;
	}

	public static BcelShadow makeAdviceExecution(BcelWorld world, LazyMethodGen enclosingMethod) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, AdviceExecution,
				world.makeJoinPointSignatureFromMethod(enclosingMethod, Member.ADVICE), enclosingMethod, null);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body), Range.genEnd(body));
		return s;
	}

	// constructor call shadows are <em>initially</em> just around the
	// call to the constructor. If ANY advice gets put on it, we move
	// the NEW instruction inside the join point, which involves putting
	// all the arguments in temps.
	public static BcelShadow makeConstructorCall(BcelWorld world, LazyMethodGen enclosingMethod, InstructionHandle callHandle,
			BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();

		Member sig = world.makeJoinPointSignatureForMethodInvocation(enclosingMethod.getEnclosingClass(),
				(InvokeInstruction) callHandle.getInstruction());

		BcelShadow s = new BcelShadow(world, ConstructorCall, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, callHandle), Range.genEnd(body, callHandle));
		retargetAllBranches(callHandle, r.getStart());
		return s;
	}

	public static BcelShadow makeArrayConstructorCall(BcelWorld world, LazyMethodGen enclosingMethod,
			InstructionHandle arrayInstruction, BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		Member sig = world.makeJoinPointSignatureForArrayConstruction(enclosingMethod.getEnclosingClass(), arrayInstruction);
		BcelShadow s = new BcelShadow(world, ConstructorCall, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, arrayInstruction), Range.genEnd(body, arrayInstruction));
		retargetAllBranches(arrayInstruction, r.getStart());
		return s;
	}

	public static BcelShadow makeMonitorEnter(BcelWorld world, LazyMethodGen enclosingMethod, InstructionHandle monitorInstruction,
			BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		Member sig = world.makeJoinPointSignatureForMonitorEnter(enclosingMethod.getEnclosingClass(), monitorInstruction);
		BcelShadow s = new BcelShadow(world, SynchronizationLock, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, monitorInstruction), Range.genEnd(body, monitorInstruction));
		retargetAllBranches(monitorInstruction, r.getStart());
		return s;
	}

	public static BcelShadow makeMonitorExit(BcelWorld world, LazyMethodGen enclosingMethod, InstructionHandle monitorInstruction,
			BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		Member sig = world.makeJoinPointSignatureForMonitorExit(enclosingMethod.getEnclosingClass(), monitorInstruction);
		BcelShadow s = new BcelShadow(world, SynchronizationUnlock, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, monitorInstruction), Range.genEnd(body, monitorInstruction));
		retargetAllBranches(monitorInstruction, r.getStart());
		return s;
	}

	// see pr77166
	// public static BcelShadow makeArrayLoadCall(
	// BcelWorld world,
	// LazyMethodGen enclosingMethod,
	// InstructionHandle arrayInstruction,
	// BcelShadow enclosingShadow)
	// {
	// final InstructionList body = enclosingMethod.getBody();
	// Member sig = world.makeJoinPointSignatureForArrayLoad(enclosingMethod.getEnclosingClass(),arrayInstruction);
	// BcelShadow s =
	// new BcelShadow(
	// world,
	// MethodCall,
	// sig,
	// enclosingMethod,
	// enclosingShadow);
	// ShadowRange r = new ShadowRange(body);
	// r.associateWithShadow(s);
	// r.associateWithTargets(
	// Range.genStart(body, arrayInstruction),
	// Range.genEnd(body, arrayInstruction));
	// retargetAllBranches(arrayInstruction, r.getStart());
	// return s;
	// }

	public static BcelShadow makeMethodCall(BcelWorld world, LazyMethodGen enclosingMethod, InstructionHandle callHandle,
			BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, MethodCall, world.makeJoinPointSignatureForMethodInvocation(
				enclosingMethod.getEnclosingClass(), (InvokeInstruction) callHandle.getInstruction()), enclosingMethod,
				enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, callHandle), Range.genEnd(body, callHandle));
		retargetAllBranches(callHandle, r.getStart());
		return s;
	}

	public static BcelShadow makeShadowForMethodCall(BcelWorld world, LazyMethodGen enclosingMethod, InstructionHandle callHandle,
			BcelShadow enclosingShadow, Kind kind, ResolvedMember sig) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, kind, sig, enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, callHandle), Range.genEnd(body, callHandle));
		retargetAllBranches(callHandle, r.getStart());
		return s;
	}

	public static BcelShadow makeFieldGet(BcelWorld world, ResolvedMember field, LazyMethodGen enclosingMethod,
			InstructionHandle getHandle, BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, FieldGet, field,
		// BcelWorld.makeFieldSignature(
		// enclosingMethod.getEnclosingClass(),
		// (FieldInstruction) getHandle.getInstruction()),
				enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, getHandle), Range.genEnd(body, getHandle));
		retargetAllBranches(getHandle, r.getStart());
		return s;
	}

	public static BcelShadow makeFieldSet(BcelWorld world, ResolvedMember field, LazyMethodGen enclosingMethod,
			InstructionHandle setHandle, BcelShadow enclosingShadow) {
		final InstructionList body = enclosingMethod.getBody();
		BcelShadow s = new BcelShadow(world, FieldSet, field,
		// BcelWorld.makeFieldJoinPointSignature(
		// enclosingMethod.getEnclosingClass(),
		// (FieldInstruction) setHandle.getInstruction()),
				enclosingMethod, enclosingShadow);
		ShadowRange r = new ShadowRange(body);
		r.associateWithShadow(s);
		r.associateWithTargets(Range.genStart(body, setHandle), Range.genEnd(body, setHandle));
		retargetAllBranches(setHandle, r.getStart());
		return s;
	}

	public static void retargetAllBranches(InstructionHandle from, InstructionHandle to) {
		for (InstructionTargeter source : from.getTargetersCopy()) {
			if (source instanceof InstructionBranch) {
				source.updateTarget(from, to);
			}
		}
	}

	// // ---- type access methods
	// private ObjectType getTargetBcelType() {
	// return (ObjectType) BcelWorld.makeBcelType(getTargetType());
	// }
	// private Type getArgBcelType(int arg) {
	// return BcelWorld.makeBcelType(getArgType(arg));
	// }

	// ---- kinding

	/**
	 * If the end of my range has no real instructions following then my context needs a return at the end.
	 */
	public boolean terminatesWithReturn() {
		return getRange().getRealNext() == null;
	}

	/**
	 * Is arg0 occupied with the value of this
	 */
	public boolean arg0HoldsThis() {
		if (getKind().isEnclosingKind()) {
			return !Modifier.isStatic(getSignature().getModifiers());
		} else if (enclosingShadow == null) {
			// XXX this is mostly right
			// this doesn't do the right thing for calls in the pre part of introduced constructors.
			return !enclosingMethod.isStatic();
		} else {
			return ((BcelShadow) enclosingShadow).arg0HoldsThis();
		}
	}

	// ---- argument getting methods

	private BcelVar thisVar = null;
	private BcelVar targetVar = null;
	private BcelVar[] argVars = null;
	private Map<ResolvedType, AnnotationAccessVar> kindedAnnotationVars = null;
	private Map<ResolvedType, TypeAnnotationAccessVar> thisAnnotationVars = null;
	private Map<ResolvedType, TypeAnnotationAccessVar> targetAnnotationVars = null;
	// private Map/* <UnresolvedType,BcelVar> */[] argAnnotationVars = null;
	private Map<ResolvedType, AnnotationAccessVar> withinAnnotationVars = null;
	private Map<ResolvedType, AnnotationAccessVar> withincodeAnnotationVars = null;
	private boolean allArgVarsInitialized = false;

	// If in annotation style and the relevant advice is using PJP then this will
	// be set to true when the closure variable is initialized - if it gets set
	// (which means link() has been called) then we will need to call unlink()
	// after the code has been run.
	boolean closureVarInitialized = false;

	@Override
	public Var getThisVar() {
		if (!hasThis()) {
			throw new IllegalStateException("no this");
		}
		initializeThisVar();
		return thisVar;
	}

	@Override
	public Var getThisAnnotationVar(UnresolvedType forAnnotationType) {
		if (!hasThis()) {
			throw new IllegalStateException("no this");
		}
		initializeThisAnnotationVars(); // FIXME asc Why bother with this if we always return one?
		// Even if we can't find one, we have to return one as we might have this annotation at runtime
		Var v = thisAnnotationVars.get(forAnnotationType);
		if (v == null) {
			v = new TypeAnnotationAccessVar(forAnnotationType.resolve(world), (BcelVar) getThisVar());
		}
		return v;
	}

	@Override
	public Var getTargetVar() {
		if (!hasTarget()) {
			throw new IllegalStateException("no target");
		}
		initializeTargetVar();
		return targetVar;
	}

	@Override
	public Var getTargetAnnotationVar(UnresolvedType forAnnotationType) {
		if (!hasTarget()) {
			throw new IllegalStateException("no target");
		}
		initializeTargetAnnotationVars(); // FIXME asc why bother with this if we always return one?
		Var v = targetAnnotationVars.get(forAnnotationType);
		// Even if we can't find one, we have to return one as we might have this annotation at runtime
		if (v == null) {
			v = new TypeAnnotationAccessVar(forAnnotationType.resolve(world), (BcelVar) getTargetVar());
		}
		return v;
	}

	@Override
	public Var getArgVar(int i) {
		ensureInitializedArgVar(i);
		return argVars[i];
	}

	@Override
	public Var getArgAnnotationVar(int i, UnresolvedType forAnnotationType) {
		return new TypeAnnotationAccessVar(forAnnotationType.resolve(world), (BcelVar) getArgVar(i));
		// initializeArgAnnotationVars();
		//
		// Var v = (Var) argAnnotationVars[i].get(forAnnotationType);
		// if (v == null) {
		// v = new TypeAnnotationAccessVar(forAnnotationType.resolve(world), (BcelVar) getArgVar(i));
		// }
		// return v;
	}

	@Override
	public Var getKindedAnnotationVar(UnresolvedType forAnnotationType) {
		initializeKindedAnnotationVars();
		return kindedAnnotationVars.get(forAnnotationType);
	}

	@Override
	public Var getWithinAnnotationVar(UnresolvedType forAnnotationType) {
		initializeWithinAnnotationVars();
		return withinAnnotationVars.get(forAnnotationType);
	}

	@Override
	public Var getWithinCodeAnnotationVar(UnresolvedType forAnnotationType) {
		initializeWithinCodeAnnotationVars();
		return withincodeAnnotationVars.get(forAnnotationType);
	}

	// reflective thisJoinPoint support
	private BcelVar thisJoinPointVar = null;
	private boolean isThisJoinPointLazy;
	private int lazyTjpConsumers = 0;
	private BcelVar thisJoinPointStaticPartVar = null;

	// private BcelVar thisEnclosingJoinPointStaticPartVar = null;

	@Override
	public final Var getThisJoinPointStaticPartVar() {
		return getThisJoinPointStaticPartBcelVar();
	}

	@Override
	public final Var getThisEnclosingJoinPointStaticPartVar() {
		return getThisEnclosingJoinPointStaticPartBcelVar();
	}

	public void requireThisJoinPoint(boolean hasGuardTest, boolean isAround) {
		if (!isAround) {
			if (!hasGuardTest) {
				isThisJoinPointLazy = false;
			} else {
				lazyTjpConsumers++;
			}
		}
		// if (!hasGuardTest) {
		// isThisJoinPointLazy = false;
		// } else {
		// lazyTjpConsumers++;
		// }
		if (thisJoinPointVar == null) {
			thisJoinPointVar = genTempVar(UnresolvedType.forName("org.aspectj.lang.JoinPoint"));
		}
	}

	@Override
	public Var getThisJoinPointVar() {
		requireThisJoinPoint(false, false);
		return thisJoinPointVar;
	}

	void initializeThisJoinPoint() {
		if (thisJoinPointVar == null) {
			return;
		}

		if (isThisJoinPointLazy) {
			isThisJoinPointLazy = checkLazyTjp();
		}

		if (isThisJoinPointLazy) {
			appliedLazyTjpOptimization = true;
			createThisJoinPoint(); // make sure any state needed is initialized, but throw the instructions out

			if (lazyTjpConsumers == 1) {
				return; // special case only one lazyTjpUser
			}

			InstructionFactory fact = getFactory();
			InstructionList il = new InstructionList();
			il.append(InstructionConstants.ACONST_NULL);
			il.append(thisJoinPointVar.createStore(fact));
			range.insert(il, Range.OutsideBefore);
		} else {
			appliedLazyTjpOptimization = false;
			InstructionFactory fact = getFactory();
			InstructionList il = createThisJoinPoint();
			il.append(thisJoinPointVar.createStore(fact));
			range.insert(il, Range.OutsideBefore);
		}
	}

	private boolean checkLazyTjp() {
		// check for around advice
		for (ShadowMunger munger : mungers) {
			if (munger instanceof Advice) {
				if (((Advice) munger).getKind() == AdviceKind.Around) {
					if (munger.getSourceLocation() != null) { // do we know enough to bother reporting?
						if (world.getLint().canNotImplementLazyTjp.isEnabled()) {
							world.getLint().canNotImplementLazyTjp.signal(new String[]{toString()}, getSourceLocation(),
									new ISourceLocation[]{munger.getSourceLocation()});
						}
					}
					return false;
				}
			}
		}

		return true;
	}

	InstructionList loadThisJoinPoint() {
		InstructionFactory fact = getFactory();
		InstructionList il = new InstructionList();

		if (isThisJoinPointLazy) {
			// If we're lazy, build the join point right here.
			il.append(createThisJoinPoint());

			// Does someone else need it? If so, store it for later retrieval
			if (lazyTjpConsumers > 1) {
				il.append(thisJoinPointVar.createStore(fact));

				InstructionHandle end = il.append(thisJoinPointVar.createLoad(fact));

				il.insert(InstructionFactory.createBranchInstruction(Constants.IFNONNULL, end));
				il.insert(thisJoinPointVar.createLoad(fact));
			}
		} else {
			// If not lazy, its already been built and stored, just retrieve it
			thisJoinPointVar.appendLoad(il, fact);
		}

		return il;
	}

	InstructionList createThisJoinPoint() {
		InstructionFactory fact = getFactory();
		InstructionList il = new InstructionList();

		BcelVar staticPart = getThisJoinPointStaticPartBcelVar();
		staticPart.appendLoad(il, fact);
		if (hasThis()) {
			((BcelVar) getThisVar()).appendLoad(il, fact);
		} else {
			il.append(InstructionConstants.ACONST_NULL);
		}
		if (hasTarget()) {
			((BcelVar) getTargetVar()).appendLoad(il, fact);
		} else {
			il.append(InstructionConstants.ACONST_NULL);
		}

		switch (getArgCount()) {
		case 0:
			il.append(fact.createInvoke("org.aspectj.runtime.reflect.Factory", "makeJP", LazyClassGen.tjpType, new Type[] {
					LazyClassGen.staticTjpType, Type.OBJECT, Type.OBJECT }, Constants.INVOKESTATIC));
			break;
		case 1:
			((BcelVar) getArgVar(0)).appendLoadAndConvert(il, fact, world.getCoreType(ResolvedType.OBJECT));
			il.append(fact.createInvoke("org.aspectj.runtime.reflect.Factory", "makeJP", LazyClassGen.tjpType, new Type[] {
					LazyClassGen.staticTjpType, Type.OBJECT, Type.OBJECT, Type.OBJECT }, Constants.INVOKESTATIC));
			break;
		case 2:
			((BcelVar) getArgVar(0)).appendLoadAndConvert(il, fact, world.getCoreType(ResolvedType.OBJECT));
			((BcelVar) getArgVar(1)).appendLoadAndConvert(il, fact, world.getCoreType(ResolvedType.OBJECT));
			il.append(fact.createInvoke("org.aspectj.runtime.reflect.Factory", "makeJP", LazyClassGen.tjpType, new Type[] {
					LazyClassGen.staticTjpType, Type.OBJECT, Type.OBJECT, Type.OBJECT, Type.OBJECT }, Constants.INVOKESTATIC));
			break;
		default:
			il.append(makeArgsObjectArray());
			il.append(fact.createInvoke("org.aspectj.runtime.reflect.Factory", "makeJP", LazyClassGen.tjpType, new Type[] {
					LazyClassGen.staticTjpType, Type.OBJECT, Type.OBJECT, new ArrayType(Type.OBJECT, 1) }, Constants.INVOKESTATIC));
			break;
		}

		return il;
	}

	public BcelVar getThisJoinPointStaticPartBcelVar() {
		return getThisJoinPointStaticPartBcelVar(false);
	}

	@Override
	public BcelVar getThisAspectInstanceVar(ResolvedType aspectType) {
		return new AspectInstanceVar(aspectType);
	}

	/**
	 * Get the Var for the xxxxJpStaticPart, xxx = this or enclosing
	 *
	 * @param isEnclosingJp true to have the enclosingJpStaticPart
	 * @return
	 */
	public BcelVar getThisJoinPointStaticPartBcelVar(final boolean isEnclosingJp) {
		if (thisJoinPointStaticPartVar == null) {
			Field field = getEnclosingClass().getTjpField(this, isEnclosingJp);
			ResolvedType sjpType = null;
			if (world.isTargettingAspectJRuntime12()) { // TAG:SUPPORTING12: We didn't have different jpsp types in 1.2
				sjpType = world.getCoreType(UnresolvedType.JOINPOINT_STATICPART);
			} else {
				sjpType = isEnclosingJp ? world.getCoreType(UnresolvedType.JOINPOINT_ENCLOSINGSTATICPART) : world
						.getCoreType(UnresolvedType.JOINPOINT_STATICPART);
			}
			thisJoinPointStaticPartVar = new BcelFieldRef(sjpType, getEnclosingClass().getClassName(), field.getName());
			// getEnclosingClass().warnOnAddedStaticInitializer(this,munger.getSourceLocation());
		}
		return thisJoinPointStaticPartVar;
	}

	/**
	 * Get the Var for the enclosingJpStaticPart
	 *
	 * @return
	 */
	public BcelVar getThisEnclosingJoinPointStaticPartBcelVar() {
		if (enclosingShadow == null) {
			// the enclosing of an execution is itself
			return getThisJoinPointStaticPartBcelVar(true);
		} else {
			return ((BcelShadow) enclosingShadow).getThisJoinPointStaticPartBcelVar(true);
		}
	}

	// ??? need to better understand all the enclosing variants
	@Override
	public Member getEnclosingCodeSignature() {
		if (getKind().isEnclosingKind()) {
			return getSignature();
		} else if (getKind() == Shadow.PreInitialization) {
			// PreInit doesn't enclose code but its signature
			// is correctly the signature of the ctor.
			return getSignature();
		} else if (enclosingShadow == null) {
			return getEnclosingMethod().getMemberView();
		} else {
			return enclosingShadow.getSignature();
		}
	}

	public Member getRealEnclosingCodeSignature() {
		return enclosingMethod.getMemberView();
	}

	// public Member getEnclosingCodeSignatureForModel() {
	// if (getKind().isEnclosingKind()) {
	// return getSignature();
	// } else if (getKind() == Shadow.PreInitialization) {
	// // PreInit doesn't enclose code but its signature
	// // is correctly the signature of the ctor.
	// return getSignature();
	// } else if (enclosingShadow == null) {
	// return getEnclosingMethod().getMemberView();
	// } else {
	// if (enclosingShadow.getKind() == Shadow.MethodExecution && enclosingMethod.getEffectiveSignature() != null) {
	//
	// } else {
	// return enclosingShadow.getSignature();
	// }
	// }
	// }

	private InstructionList makeArgsObjectArray() {
		InstructionFactory fact = getFactory();
		BcelVar arrayVar = genTempVar(UnresolvedType.OBJECTARRAY);
		final InstructionList il = new InstructionList();
		int alen = getArgCount();
		il.append(Utility.createConstant(fact, alen));
		il.append(fact.createNewArray(Type.OBJECT, (short) 1));
		arrayVar.appendStore(il, fact);

		int stateIndex = 0;
		for (int i = 0, len = getArgCount(); i < len; i++) {
			arrayVar.appendConvertableArrayStore(il, fact, stateIndex, (BcelVar) getArgVar(i));
			stateIndex++;
		}
		arrayVar.appendLoad(il, fact);
		return il;
	}

	// ---- initializing var tables

	/*
	 * initializing this is doesn't do anything, because this is protected from side-effects, so we don't need to copy its location
	 */

	private void initializeThisVar() {
		if (thisVar != null) {
			return;
		}
		thisVar = new BcelVar(getThisType().resolve(world), 0);
		thisVar.setPositionInAroundState(0);
	}

	public void initializeTargetVar() {
		InstructionFactory fact = getFactory();
		if (targetVar != null) {
			return;
		}
		if (getKind().isTargetSameAsThis()) {
			if (hasThis()) {
				initializeThisVar();
			}
			targetVar = thisVar;
		} else {
			initializeArgVars(); // gotta pop off the args before we find the target
			UnresolvedType type = getTargetType();
			type = ensureTargetTypeIsCorrect(type);
			targetVar = genTempVar(type, "ajc$target");
			range.insert(targetVar.createStore(fact), Range.OutsideBefore);
			targetVar.setPositionInAroundState(hasThis() ? 1 : 0);
		}
	}

	/*
	 * PR 72528 This method double checks the target type under certain conditions. The Java 1.4 compilers seem to take calls to
	 * clone methods on array types and create bytecode that looks like clone is being called on Object. If we advise a clone call
	 * with around advice we extract the call into a helper method which we can then refer to. Because the type in the bytecode for
	 * the call to clone is Object we create a helper method with an Object parameter - this is not correct as we have lost the fact
	 * that the actual type is an array type. If we don't do the check below we will create code that fails java verification. This
	 * method checks for the peculiar set of conditions and if they are true, it has a sneak peek at the code before the call to see
	 * what is on the stack.
	 */
	public UnresolvedType ensureTargetTypeIsCorrect(UnresolvedType tx) {

		Member msig = getSignature();
		if (msig.getArity() == 0 && getKind() == MethodCall && msig.getName().charAt(0) == 'c' && tx.equals(ResolvedType.OBJECT)
				&& msig.getReturnType().equals(ResolvedType.OBJECT) && msig.getName().equals("clone")) {

			// Lets go back through the code from the start of the shadow
			InstructionHandle searchPtr = range.getStart().getPrev();
			while (Range.isRangeHandle(searchPtr) || searchPtr.getInstruction().isStoreInstruction()) { // ignore this instruction -
				// it doesnt give us the
				// info we want
				searchPtr = searchPtr.getPrev();
			}

			// A load instruction may tell us the real type of what the clone() call is on
			if (searchPtr.getInstruction().isLoadInstruction()) {
				LocalVariableTag lvt = LazyMethodGen.getLocalVariableTag(searchPtr, searchPtr.getInstruction().getIndex());
				if (lvt != null) {
					return UnresolvedType.forSignature(lvt.getType());
				}
			}
			// A field access instruction may tell us the real type of what the clone() call is on
			if (searchPtr.getInstruction() instanceof FieldInstruction) {
				FieldInstruction si = (FieldInstruction) searchPtr.getInstruction();
				Type t = si.getFieldType(getEnclosingClass().getConstantPool());
				return BcelWorld.fromBcel(t);
			}
			// A new array instruction obviously tells us it is an array type !
			if (searchPtr.getInstruction().opcode == Constants.ANEWARRAY) {
				// ANEWARRAY ana = (ANEWARRAY)searchPoint.getInstruction();
				// Type t = ana.getType(getEnclosingClass().getConstantPool());
				// Just use a standard java.lang.object array - that will work fine
				return BcelWorld.fromBcel(new ArrayType(Type.OBJECT, 1));
			}
			// A multi new array instruction obviously tells us it is an array type !
			if (searchPtr.getInstruction() instanceof MULTIANEWARRAY) {
				MULTIANEWARRAY ana = (MULTIANEWARRAY) searchPtr.getInstruction();
				// Type t = ana.getType(getEnclosingClass().getConstantPool());
				// t = new ArrayType(t,ana.getDimensions());
				// Just use a standard java.lang.object array - that will work fine
				return BcelWorld.fromBcel(new ArrayType(Type.OBJECT, ana.getDimensions()));
			}
			throw new BCException("Can't determine real target of clone() when processing instruction "
					+ searchPtr.getInstruction() + ".  Perhaps avoid selecting clone with your pointcut?");
		}
		return tx;
	}

	public void ensureInitializedArgVar(int argNumber) {
		if (allArgVarsInitialized || (argVars != null && argVars[argNumber] != null)) {
			return;
		}
		InstructionFactory fact = getFactory();
		int len = getArgCount();
		if (argVars == null) {
			argVars = new BcelVar[len];
		}

		// Need to initialize argument i
		int positionOffset = (hasTarget() ? 1 : 0) + ((hasThis() && !getKind().isTargetSameAsThis()) ? 1 : 0);

		if (getKind().argsOnStack()) {
			// Let's just do them all now since they are on the stack
			// we move backwards because we're popping off the stack
			for (int i = len - 1; i >= 0; i--) {
				UnresolvedType type = getArgType(i);
				BcelVar tmp = genTempVar(type, "ajc$arg" + i);
				range.insert(tmp.createStore(getFactory()), Range.OutsideBefore);
				int position = i;
				position += positionOffset;
				tmp.setPositionInAroundState(position);
				argVars[i] = tmp;
			}
			allArgVarsInitialized = true;
		} else {
			int index = 0;
			if (arg0HoldsThis()) {
				index++;
			}
			boolean allInited = true;
			for (int i = 0; i < len; i++) {
				UnresolvedType type = getArgType(i);
				if (i == argNumber) {
					argVars[argNumber] = genTempVar(type, "ajc$arg" + argNumber);
					range.insert(argVars[argNumber].createCopyFrom(fact, index), Range.OutsideBefore);
					argVars[argNumber].setPositionInAroundState(argNumber + positionOffset);
				}
				allInited = allInited && argVars[i] != null;
				index += type.getSize();
			}
			if (allInited && (argNumber + 1) == len) {
				allArgVarsInitialized = true;
			}
		}
	}

	/**
	 * Initialize all the available arguments at the shadow. This means creating a copy of them that we can then use for advice
	 * calls (the copy ensures we are not affected by other advice changing the values). This method initializes all arguments
	 * whereas the method ensureInitializedArgVar will only ensure a single argument is setup.
	 */
	public void initializeArgVars() {
		if (allArgVarsInitialized) {
			return;
		}
		InstructionFactory fact = getFactory();
		int len = getArgCount();
		if (argVars == null) {
			argVars = new BcelVar[len];
		}
		int positionOffset = (hasTarget() ? 1 : 0) + ((hasThis() && !getKind().isTargetSameAsThis()) ? 1 : 0);

		if (getKind().argsOnStack()) {
			// we move backwards because we're popping off the stack
			for (int i = len - 1; i >= 0; i--) {
				UnresolvedType type = getArgType(i);
				BcelVar tmp = genTempVar(type, "ajc$arg" + i);
				range.insert(tmp.createStore(getFactory()), Range.OutsideBefore);
				int position = i;
				position += positionOffset;
				tmp.setPositionInAroundState(position);
				argVars[i] = tmp;
			}
		} else {
			int index = 0;
			if (arg0HoldsThis()) {
				index++;
			}

			for (int i = 0; i < len; i++) {
				UnresolvedType type = getArgType(i);
				if (argVars[i] == null) {
					BcelVar tmp = genTempVar(type, "ajc$arg" + i);
					range.insert(tmp.createCopyFrom(fact, index), Range.OutsideBefore);
					argVars[i] = tmp;
					tmp.setPositionInAroundState(i + positionOffset);
				}
				index += type.resolve(world).getSize();
			}
		}
		allArgVarsInitialized = true;

	}

	public void initializeForAroundClosure() {
		initializeArgVars();
		if (hasTarget()) {
			initializeTargetVar();
		}
		if (hasThis()) {
			initializeThisVar();
			// System.out.println("initialized: " + this + " thisVar = " + thisVar);
		}
	}

	public void initializeThisAnnotationVars() {
		if (thisAnnotationVars != null) {
			return;
		}
		thisAnnotationVars = new HashMap<>();
		// populate..
	}

	public void initializeTargetAnnotationVars() {
		if (targetAnnotationVars != null) {
			return;
		}
		if (getKind().isTargetSameAsThis()) {
			if (hasThis()) {
				initializeThisAnnotationVars();
			}
			targetAnnotationVars = thisAnnotationVars;
		} else {
			targetAnnotationVars = new HashMap<>();
			ResolvedType[] rtx = this.getTargetType().resolve(world).getAnnotationTypes(); // what about annotations we havent
			// gotten yet but we will get in
			// subclasses?
			for (ResolvedType typeX : rtx) {
				targetAnnotationVars.put(typeX, new TypeAnnotationAccessVar(typeX, (BcelVar) getTargetVar()));
			}
			// populate.
		}
	}

	// public void initializeArgAnnotationVars() {
	// if (argAnnotationVars != null) {
	// return;
	// }
	// int numArgs = getArgCount();
	// argAnnotationVars = new Map[numArgs];
	// for (int i = 0; i < argAnnotationVars.length; i++) {
	// argAnnotationVars[i] = new HashMap();
	// // FIXME asc just delete this logic - we always build the Var on demand, as we don't know at weave time
	// // what the full set of annotations could be (due to static/dynamic type differences...)
	// }
	// }

	protected ResolvedMember getRelevantMember(ResolvedMember foundMember, Member relevantMember, ResolvedType relevantType) {
		if (foundMember != null) {
			return foundMember;
		}

		foundMember = getSignature().resolve(world);
		if (foundMember == null && relevantMember != null) {
			foundMember = relevantType.lookupMemberWithSupersAndITDs(relevantMember);
		}

		// check the ITD'd dooberries
		List<ConcreteTypeMunger> mungers = relevantType.resolve(world).getInterTypeMungers();
		for (ConcreteTypeMunger typeMunger : mungers) {
			if (typeMunger.getMunger() instanceof NewMethodTypeMunger || typeMunger.getMunger() instanceof NewConstructorTypeMunger) {
				ResolvedMember fakerm = typeMunger.getSignature();
				if (fakerm.getName().equals(getSignature().getName())
						&& fakerm.getParameterSignature().equals(getSignature().getParameterSignature())) {
					if (foundMember.getKind() == ResolvedMember.CONSTRUCTOR) {
						foundMember = AjcMemberMaker.interConstructor(relevantType, foundMember, typeMunger.getAspectType());
					} else {
						foundMember = AjcMemberMaker.interMethod(foundMember, typeMunger.getAspectType(), false);
						// ResolvedMember o = AjcMemberMaker.interMethodBody(fakerm, typeMunger.getAspectType());
						// // Object os = o.getAnnotations();
						// ResolvedMember foundMember2 = findMethod(typeMunger.getAspectType(), o);
						// Object os2 = foundMember2.getAnnotations();
						// int stop = 1;
						// foundMember = foundMember2;
						// foundMember = AjcMemberMaker.interMethod(foundMember, typeMunger.getAspectType());
					}
					// in the above.. what about if it's on an Interface? Can that happen?
					// then the last arg of the above should be true
					return foundMember;
				}
			}
		}
		return foundMember;
	}

	protected ResolvedType[] getAnnotations(ResolvedMember foundMember, Member relevantMember, ResolvedType relevantType) {
		if (foundMember == null) {
			// check the ITD'd dooberries
			List<ConcreteTypeMunger> mungers = relevantType.resolve(world).getInterTypeMungers();
			for (ConcreteTypeMunger typeMunger : mungers) {
				if (typeMunger.getMunger() instanceof NewMethodTypeMunger
						|| typeMunger.getMunger() instanceof NewConstructorTypeMunger) {
					ResolvedMember fakerm = typeMunger.getSignature();
					// if (fakerm.hasAnnotations())

					ResolvedMember ajcMethod = (getSignature().getKind() == ResolvedMember.CONSTRUCTOR ? AjcMemberMaker
							.postIntroducedConstructor(typeMunger.getAspectType(), fakerm.getDeclaringType(),
									fakerm.getParameterTypes()) : AjcMemberMaker.interMethodDispatcher(fakerm,
							typeMunger.getAspectType()));
					// AjcMemberMaker.interMethodBody(fakerm,typeMunger.getAspectType()));
					ResolvedMember rmm = findMethod(typeMunger.getAspectType(), ajcMethod);
					if (fakerm.getName().equals(getSignature().getName())
							&& fakerm.getParameterSignature().equals(getSignature().getParameterSignature())) {
						relevantType = typeMunger.getAspectType();
						foundMember = rmm;
						return foundMember.getAnnotationTypes();
					}
				}
			}
			// didn't find in ITDs, look in supers
			foundMember = relevantType.lookupMemberWithSupersAndITDs(relevantMember);
			if (foundMember == null) {
				throw new IllegalStateException("Couldn't find member " + relevantMember + " for type " + relevantType);
			}
		}
		return foundMember.getAnnotationTypes();
	}

	/**
	 * By determining what "kind" of shadow we are, we can find out the annotations on the appropriate element (method, field,
	 * constructor, type). Then create one BcelVar entry in the map for each annotation, keyed by annotation type.
	 */
	public void initializeKindedAnnotationVars() {
		if (kindedAnnotationVars != null) {
			return;
		}
		kindedAnnotationVars = new HashMap<>();

		ResolvedType[] annotations = null;
		Member shadowSignature = getSignature();
		Member annotationHolder = getSignature();
		ResolvedType relevantType = shadowSignature.getDeclaringType().resolve(world);

		if (relevantType.isRawType() || relevantType.isParameterizedType()) {
			relevantType = relevantType.getGenericType();
		}

		// Determine the annotations that are of interest
		if (getKind() == Shadow.StaticInitialization) {
			annotations = relevantType.resolve(world).getAnnotationTypes();
		} else if (getKind() == Shadow.MethodCall || getKind() == Shadow.ConstructorCall) {
			ResolvedMember foundMember = findMethod2(relevantType.resolve(world).getDeclaredMethods(), getSignature());
			annotations = getAnnotations(foundMember, shadowSignature, relevantType);
			annotationHolder = getRelevantMember(foundMember, shadowSignature, relevantType);
			relevantType = annotationHolder.getDeclaringType().resolve(world);
		} else if (getKind() == Shadow.FieldSet || getKind() == Shadow.FieldGet) {
			annotationHolder = findField(relevantType.getDeclaredFields(), getSignature());

			if (annotationHolder == null) {
				// check the ITD'd dooberries
				List<ConcreteTypeMunger> mungers = relevantType.resolve(world).getInterTypeMungers();
				for (ConcreteTypeMunger typeMunger : mungers) {
					if (typeMunger.getMunger() instanceof NewFieldTypeMunger) {
						ResolvedMember fakerm = typeMunger.getSignature();
						// if (fakerm.hasAnnotations())
						ResolvedMember ajcMethod = AjcMemberMaker.interFieldInitializer(fakerm, typeMunger.getAspectType());
						ResolvedMember rmm = findMethod(typeMunger.getAspectType(), ajcMethod);
						if (fakerm.equals(getSignature())) {
							relevantType = typeMunger.getAspectType();
							annotationHolder = rmm;
						}
					}
				}
			}
			annotations = ((ResolvedMember) annotationHolder).getAnnotationTypes();

		} else if (getKind() == Shadow.MethodExecution || getKind() == Shadow.ConstructorExecution
				|| getKind() == Shadow.AdviceExecution) {

			ResolvedMember foundMember = findMethod2(relevantType.getDeclaredMethods(), getSignature());
			annotations = getAnnotations(foundMember, shadowSignature, relevantType);
			annotationHolder = getRelevantMember(foundMember, annotationHolder, relevantType);
			UnresolvedType ut = annotationHolder.getDeclaringType();
			relevantType = ut.resolve(world);

		} else if (getKind() == Shadow.ExceptionHandler) {
			relevantType = getSignature().getParameterTypes()[0].resolve(world);
			annotations = relevantType.getAnnotationTypes();

		} else if (getKind() == Shadow.PreInitialization || getKind() == Shadow.Initialization) {
			ResolvedMember found = findMethod2(relevantType.getDeclaredMethods(), getSignature());
			annotations = found.getAnnotationTypes();
		}

		if (annotations == null) {
			// We can't have recognized the shadow - should blow up now to be on the safe side
			throw new BCException("Could not discover annotations for shadow: " + getKind());
		}

		for (ResolvedType annotationType : annotations) {
			AnnotationAccessVar accessVar = new AnnotationAccessVar(this, getKind(), annotationType.resolve(world), relevantType,
					annotationHolder, false);
			kindedAnnotationVars.put(annotationType, accessVar);
		}
	}

	private ResolvedMember findMethod2(ResolvedMember members[], Member sig) {
		String signatureName = sig.getName();
		String parameterSignature = sig.getParameterSignature();
		for (ResolvedMember member : members) {
			if (member.getName().equals(signatureName) && member.getParameterSignature().equals(parameterSignature)) {
				return member;
			}
		}
		return null;
	}

	private ResolvedMember findMethod(ResolvedType aspectType, ResolvedMember ajcMethod) {
		ResolvedMember decMethods[] = aspectType.getDeclaredMethods();
		for (ResolvedMember member : decMethods) {
			if (member.equals(ajcMethod)) {
				return member;
			}
		}
		return null;
	}

	private ResolvedMember findField(ResolvedMember[] members, Member lookingFor) {
		for (ResolvedMember member : members) {
			if (member.getName().equals(getSignature().getName()) && member.getType().equals(getSignature().getType())) {
				return member;
			}
		}
		return null;
	}

	public void initializeWithinAnnotationVars() {
		if (withinAnnotationVars != null) {
			return;
		}
		withinAnnotationVars = new HashMap<>();

		ResolvedType[] annotations = getEnclosingType().resolve(world).getAnnotationTypes();
		for (ResolvedType ann : annotations) {
			Kind k = Shadow.StaticInitialization;
			withinAnnotationVars.put(ann, new AnnotationAccessVar(this, k, ann, getEnclosingType(), null, true));
		}
	}

	public void initializeWithinCodeAnnotationVars() {
		if (withincodeAnnotationVars != null) {
			return;
		}
		withincodeAnnotationVars = new HashMap<>();

		// For some shadow we are interested in annotations on the method containing that shadow.
		ResolvedType[] annotations = getEnclosingMethod().getMemberView().getAnnotationTypes();
		for (ResolvedType ann : annotations) {
			Kind k = (getEnclosingMethod().getMemberView().getKind() == Member.CONSTRUCTOR ? Shadow.ConstructorExecution
					: Shadow.MethodExecution);
			withincodeAnnotationVars.put(ann, new AnnotationAccessVar(this, k, ann, getEnclosingType(),
					getEnclosingCodeSignature(), true));
		}
	}

	// ---- weave methods

	void weaveBefore(BcelAdvice munger) {
		range.insert(munger.getAdviceInstructions(this, null, range.getRealStart()), Range.InsideBefore);
	}

	public void weaveAfter(BcelAdvice munger) {
		weaveAfterThrowing(munger, UnresolvedType.THROWABLE);
		weaveAfterReturning(munger);
	}

	/**
	 * The basic strategy here is to add a set of instructions at the end of the shadow range that dispatch the advice, and then
	 * return whatever the shadow was going to return anyway.
	 *
	 * To achieve this, we note all the return statements in the advice, and replace them with code that: 1) stores the return value
	 * on top of the stack in a temp var 2) jumps to the start of our advice block 3) restores the return value at the end of the
	 * advice block before ultimately returning
	 *
	 * We also need to bind the return value into a returning parameter, if the advice specified one.
	 */
	public void weaveAfterReturning(BcelAdvice munger) {
		List<InstructionHandle> returns = findReturnInstructions();
		boolean hasReturnInstructions = !returns.isEmpty();

		// list of instructions that handle the actual return from the join point
		InstructionList retList = new InstructionList();

		// variable that holds the return value
		BcelVar returnValueVar = null;

		if (hasReturnInstructions) {
			returnValueVar = generateReturnInstructions(returns, retList);
		} else {
			// we need at least one instruction, as the target for jumps
			retList.append(InstructionConstants.NOP);
		}

		// list of instructions for dispatching to the advice itself
		InstructionList advice = getAfterReturningAdviceDispatchInstructions(munger, retList.getStart());

		if (hasReturnInstructions) {
			InstructionHandle gotoTarget = advice.getStart();
			for (InstructionHandle ih : returns) {
				retargetReturnInstruction(munger.hasExtraParameter(), returnValueVar, gotoTarget, ih);
			}
		}

		range.append(advice);
		range.append(retList);
	}

	/**
	 * @return a list of all the return instructions in the range of this shadow
	 */
	private List<InstructionHandle> findReturnInstructions() {
		List<InstructionHandle> returns = new ArrayList<>();
		for (InstructionHandle ih = range.getStart(); ih != range.getEnd(); ih = ih.getNext()) {
			if (ih.getInstruction().isReturnInstruction()) {
				returns.add(ih);
			}
		}
		return returns;
	}

	/**
	 * Given a list containing all the return instruction handles for this shadow, finds the last return instruction and copies it,
	 * making this the ultimate return. If the shadow has a non-void return type, we also create a temporary variable to hold the
	 * return value, and load the value from this var before returning (see pr148007 for why we do this - it works around a JRockit
	 * bug, and is also closer to what javac generates)
	 *
	 * Sometimes the 'last return' isnt the right one - some rogue code can include the real return from the body of a subroutine
	 * that exists at the end of the method. In this case the last return is RETURN but that may not be correct for a method with a
	 * non-void return type... pr151673
	 *
	 * @param returns list of all the return instructions in the shadow
	 * @param returnInstructions instruction list into which the return instructions should be generated
	 * @return the variable holding the return value, if needed
	 */
	private BcelVar generateReturnInstructions(List<InstructionHandle> returns, InstructionList returnInstructions) {
		BcelVar returnValueVar = null;
		if (this.hasANonVoidReturnType()) {
			// Find the last *correct* return - this is a method with a non-void return type
			// so ignore RETURN
			Instruction newReturnInstruction = null;
			int i = returns.size() - 1;
			while (newReturnInstruction == null && i >= 0) {
				InstructionHandle ih = returns.get(i);
				if (ih.getInstruction().opcode != Constants.RETURN) {
					newReturnInstruction = Utility.copyInstruction(ih.getInstruction());
				}
				i--;
			}
			returnValueVar = genTempVar(this.getReturnType());
			returnValueVar.appendLoad(returnInstructions, getFactory());
			returnInstructions.append(newReturnInstruction);
		} else {
			InstructionHandle lastReturnHandle = returns.get(returns.size() - 1);
			Instruction newReturnInstruction = Utility.copyInstruction(lastReturnHandle.getInstruction());
			returnInstructions.append(newReturnInstruction);
		}
		return returnValueVar;
	}

	/**
	 * @return true, iff this shadow returns a value
	 */
	private boolean hasANonVoidReturnType() {
		return !this.getReturnType().equals(UnresolvedType.VOID);
	}

	/**
	 * Get the list of instructions used to dispatch to the after advice
	 *
	 * @param munger
	 * @param firstInstructionInReturnSequence
	 * @return
	 */
	private InstructionList getAfterReturningAdviceDispatchInstructions(BcelAdvice munger,
			InstructionHandle firstInstructionInReturnSequence) {
		InstructionList advice = new InstructionList();

		BcelVar tempVar = null;
		if (munger.hasExtraParameter()) {
			tempVar = insertAdviceInstructionsForBindingReturningParameter(advice);
		}
		advice.append(munger.getAdviceInstructions(this, tempVar, firstInstructionInReturnSequence));
		return advice;
	}

	/**
	 * If the after() returning(Foo f) form is used, bind the return value to the parameter. If the shadow returns void, bind null.
	 *
	 * @param advice
	 * @return
	 */
	private BcelVar insertAdviceInstructionsForBindingReturningParameter(InstructionList advice) {
		BcelVar tempVar;
		UnresolvedType tempVarType = getReturnType();
		if (tempVarType.equals(UnresolvedType.VOID)) {
			tempVar = genTempVar(UnresolvedType.OBJECT);
			advice.append(InstructionConstants.ACONST_NULL);
			tempVar.appendStore(advice, getFactory());
		} else {
			tempVar = genTempVar(tempVarType);
			advice.append(InstructionFactory.createDup(tempVarType.getSize()));
			tempVar.appendStore(advice, getFactory());
		}
		return tempVar;
	}

	/**
	 * Helper method for weaveAfterReturning
	 *
	 * Each return instruction in the method body is retargeted by calling this method. The return instruction is replaced by up to
	 * three instructions: 1) if the shadow returns a value, and that value is bound to an after returning parameter, then we DUP
	 * the return value on the top of the stack 2) if the shadow returns a value, we store it in the returnValueVar (it will be
	 * retrieved from here when we ultimately return after the advice dispatch) 3) if the return was the last instruction, we add a
	 * NOP (it will fall through to the advice dispatch), otherwise we add a GOTO that branches to the supplied gotoTarget (start of
	 * the advice dispatch)
	 */
	private void retargetReturnInstruction(boolean hasReturningParameter, BcelVar returnValueVar, InstructionHandle gotoTarget,
			InstructionHandle returnHandle) {
		// pr148007, work around JRockit bug
		// replace ret with store into returnValueVar, followed by goto if not
		// at the end of the instruction list...
		InstructionList newInstructions = new InstructionList();
		if (returnValueVar != null) {
			if (hasReturningParameter) {
				// we have to dup the return val before consuming it...
				newInstructions.append(InstructionFactory.createDup(this.getReturnType().getSize()));
			}
			// store the return value into this var
			returnValueVar.appendStore(newInstructions, getFactory());
		}
		if (!isLastInstructionInRange(returnHandle, range)) {
			newInstructions.append(InstructionFactory.createBranchInstruction(Constants.GOTO, gotoTarget));
		}
		if (newInstructions.isEmpty()) {
			newInstructions.append(InstructionConstants.NOP);
		}
		Utility.replaceInstruction(returnHandle, newInstructions, enclosingMethod);
	}

	private boolean isLastInstructionInRange(InstructionHandle ih, ShadowRange aRange) {
		return ih.getNext() == aRange.getEnd();
	}

	public void weaveAfterThrowing(BcelAdvice munger, UnresolvedType catchType) {
		// a good optimization would be not to generate anything here
		// if the shadow is GUARANTEED empty (i.e., there's NOTHING, not even
		// a shadow, inside me).
		if (getRange().getStart().getNext() == getRange().getEnd()) {
			return;
		}
		InstructionFactory fact = getFactory();
		InstructionList handler = new InstructionList();
		BcelVar exceptionVar = genTempVar(catchType);
		exceptionVar.appendStore(handler, fact);

		// pr62642
		// I will now jump through some firey BCEL hoops to generate a trivial bit of code:
		// if (exc instanceof ExceptionInInitializerError)
		// throw (ExceptionInInitializerError)exc;
		if (this.getEnclosingMethod().getName().equals("<clinit>")) {
			ResolvedType eiieType = world.resolve("java.lang.ExceptionInInitializerError");
			ObjectType eiieBcelType = (ObjectType) BcelWorld.makeBcelType(eiieType);
			InstructionList ih = new InstructionList(InstructionConstants.NOP);
			handler.append(exceptionVar.createLoad(fact));
			handler.append(fact.createInstanceOf(eiieBcelType));
			InstructionBranch bi = InstructionFactory.createBranchInstruction(Constants.IFEQ, ih.getStart());
			handler.append(bi);
			handler.append(exceptionVar.createLoad(fact));
			handler.append(fact.createCheckCast(eiieBcelType));
			handler.append(InstructionConstants.ATHROW);
			handler.append(ih);
		}

		InstructionList endHandler = new InstructionList(exceptionVar.createLoad(fact));
		handler.append(munger.getAdviceInstructions(this, exceptionVar, endHandler.getStart()));
		handler.append(endHandler);
		handler.append(InstructionConstants.ATHROW);
		InstructionHandle handlerStart = handler.getStart();

		if (isFallsThrough()) {
			InstructionHandle jumpTarget = handler.append(InstructionConstants.NOP);
			handler.insert(InstructionFactory.createBranchInstruction(Constants.GOTO, jumpTarget));
		}
		InstructionHandle protectedEnd = handler.getStart();
		range.insert(handler, Range.InsideAfter);

		enclosingMethod.addExceptionHandler(range.getStart().getNext(), protectedEnd.getPrev(), handlerStart,
				(ObjectType) BcelWorld.makeBcelType(catchType), // ???Type.THROWABLE,
				// high priority if our args are on the stack
				getKind().hasHighPriorityExceptions());
	}

	// ??? this shares a lot of code with the above weaveAfterThrowing
	// ??? would be nice to abstract that to say things only once
	public void weaveSoftener(BcelAdvice munger, UnresolvedType catchType) {
		// a good optimization would be not to generate anything here
		// if the shadow is GUARANTEED empty (i.e., there's NOTHING, not even
		// a shadow, inside me).
		if (getRange().getStart().getNext() == getRange().getEnd()) {
			return;
		}

		InstructionFactory fact = getFactory();
		InstructionList handler = new InstructionList();
		InstructionList rtExHandler = new InstructionList();
		BcelVar exceptionVar = genTempVar(catchType);

		handler.append(fact.createNew(NameMangler.SOFT_EXCEPTION_TYPE));
		handler.append(InstructionFactory.createDup(1));
		handler.append(exceptionVar.createLoad(fact));
		handler.append(fact.createInvoke(NameMangler.SOFT_EXCEPTION_TYPE, "<init>", Type.VOID, new Type[] { Type.THROWABLE },
				Constants.INVOKESPECIAL)); // ??? special
		handler.append(InstructionConstants.ATHROW);

		// ENH 42737
		exceptionVar.appendStore(rtExHandler, fact);
		// aload_1
		rtExHandler.append(exceptionVar.createLoad(fact));
		// instanceof class java/lang/RuntimeException
		rtExHandler.append(fact.createInstanceOf(new ObjectType("java.lang.RuntimeException")));
		// ifeq go to new SOFT_EXCEPTION_TYPE instruction
		rtExHandler.append(InstructionFactory.createBranchInstruction(Constants.IFEQ, handler.getStart()));
		// aload_1
		rtExHandler.append(exceptionVar.createLoad(fact));
		// athrow
		rtExHandler.append(InstructionFactory.ATHROW);

		InstructionHandle handlerStart = rtExHandler.getStart();

		if (isFallsThrough()) {
			InstructionHandle jumpTarget = range.getEnd();// handler.append(fact.NOP);
			rtExHandler.insert(InstructionFactory.createBranchInstruction(Constants.GOTO, jumpTarget));
		}

		rtExHandler.append(handler);

		InstructionHandle protectedEnd = rtExHandler.getStart();
		range.insert(rtExHandler, Range.InsideAfter);

		enclosingMethod.addExceptionHandler(range.getStart().getNext(), protectedEnd.getPrev(), handlerStart,
				(ObjectType) BcelWorld.makeBcelType(catchType),
				// high priority if our args are on the stack
				getKind().hasHighPriorityExceptions());
	}

	public void weavePerObjectEntry(final BcelAdvice munger, final BcelVar onVar) {
		final InstructionFactory fact = getFactory();

		InstructionList entryInstructions = new InstructionList();
		InstructionList entrySuccessInstructions = new InstructionList();
		onVar.appendLoad(entrySuccessInstructions, fact);

		entrySuccessInstructions
				.append(Utility.createInvoke(fact, world, AjcMemberMaker.perObjectBind(munger.getConcreteAspect())));

		InstructionList testInstructions = munger.getTestInstructions(this, entrySuccessInstructions.getStart(),
				range.getRealStart(), entrySuccessInstructions.getStart());

		entryInstructions.append(testInstructions);
		entryInstructions.append(entrySuccessInstructions);

		range.insert(entryInstructions, Range.InsideBefore);
	}

	// PTWIMPL Create static initializer to call the aspect factory
	/**
	 * Causes the aspect instance to be *set* for later retrievable through localAspectof()/aspectOf()
	 */
	public void weavePerTypeWithinAspectInitialization(final BcelAdvice munger, UnresolvedType t) {
		ResolvedType tResolved = t.resolve(world);
		if (tResolved.isInterface()) {
			return; // Don't initialize statics in interfaces
		}
		ResolvedType aspectRT = munger.getConcreteAspect();
		BcelWorld.getBcelObjectType(aspectRT);

		// Although matched, if the visibility rules prevent the aspect from seeing this type, don't
		// insert any code (easier to do it here than try to affect the matching logic, unfortunately)
		if (!(tResolved.canBeSeenBy(aspectRT) || aspectRT.isPrivilegedAspect())) {
			return;
		}

		final InstructionFactory fact = getFactory();

		InstructionList entryInstructions = new InstructionList();
		InstructionList entrySuccessInstructions = new InstructionList();

		String aspectname = munger.getConcreteAspect().getName();

		String ptwField = NameMangler.perTypeWithinFieldForTarget(munger.getConcreteAspect());
		entrySuccessInstructions.append(InstructionFactory.PUSH(fact.getConstantPool(), t.getName()));

		entrySuccessInstructions.append(fact.createInvoke(aspectname, "ajc$createAspectInstance", new ObjectType(aspectname),
				new Type[] { new ObjectType("java.lang.String") }, Constants.INVOKESTATIC));
		entrySuccessInstructions.append(fact.createPutStatic(t.getName(), ptwField, new ObjectType(aspectname)));

		entryInstructions.append(entrySuccessInstructions);

		range.insert(entryInstructions, Range.InsideBefore);
	}

	public void weaveCflowEntry(final BcelAdvice munger, final Member cflowField) {
		final boolean isPer = munger.getKind() == AdviceKind.PerCflowBelowEntry || munger.getKind() == AdviceKind.PerCflowEntry;
		if (!isPer && getKind() == PreInitialization) {
			return;
		}
		final Type objectArrayType = new ArrayType(Type.OBJECT, 1);
		final InstructionFactory fact = getFactory();

		final BcelVar testResult = genTempVar(UnresolvedType.BOOLEAN);

		InstructionList entryInstructions = new InstructionList();
		{
			InstructionList entrySuccessInstructions = new InstructionList();

			if (munger.hasDynamicTests()) {
				entryInstructions.append(Utility.createConstant(fact, 0));
				testResult.appendStore(entryInstructions, fact);

				entrySuccessInstructions.append(Utility.createConstant(fact, 1));
				testResult.appendStore(entrySuccessInstructions, fact);
			}

			if (isPer) {
				entrySuccessInstructions.append(fact.createInvoke(munger.getConcreteAspect().getName(),
						NameMangler.PERCFLOW_PUSH_METHOD, Type.VOID, Type.NO_ARGS, Constants.INVOKESTATIC));
			} else {
				BcelVar[] cflowStateVars = munger.getExposedStateAsBcelVars(false);

				if (cflowStateVars.length == 0) {
					// This should be getting managed by a counter - lets make sure.
					if (!cflowField.getType().getName().endsWith("CFlowCounter")) {
						throw new RuntimeException("Incorrectly attempting counter operation on stacked cflow");
					}
					entrySuccessInstructions.append(Utility.createGet(fact, cflowField));
					// arrayVar.appendLoad(entrySuccessInstructions, fact);
					entrySuccessInstructions.append(fact.createInvoke(NameMangler.CFLOW_COUNTER_TYPE, "inc", Type.VOID,
							Type.NO_ARGS, Constants.INVOKEVIRTUAL));
				} else {
					BcelVar arrayVar = genTempVar(UnresolvedType.OBJECTARRAY);

					int alen = cflowStateVars.length;
					entrySuccessInstructions.append(Utility.createConstant(fact, alen));
					entrySuccessInstructions.append(fact.createNewArray(Type.OBJECT, (short) 1));
					arrayVar.appendStore(entrySuccessInstructions, fact);

					for (int i = 0; i < alen; i++) {
						arrayVar.appendConvertableArrayStore(entrySuccessInstructions, fact, i, cflowStateVars[i]);
					}

					entrySuccessInstructions.append(Utility.createGet(fact, cflowField));
					arrayVar.appendLoad(entrySuccessInstructions, fact);

					entrySuccessInstructions.append(fact.createInvoke(NameMangler.CFLOW_STACK_TYPE, "push", Type.VOID,
							new Type[] { objectArrayType }, Constants.INVOKEVIRTUAL));
				}
			}

			InstructionList testInstructions = munger.getTestInstructions(this, entrySuccessInstructions.getStart(),
					range.getRealStart(), entrySuccessInstructions.getStart());
			entryInstructions.append(testInstructions);
			entryInstructions.append(entrySuccessInstructions);
		}

		BcelAdvice exitAdvice = new BcelAdvice(null, null, null, 0, 0, 0, null, munger.getConcreteAspect()) {
			@Override
			public InstructionList getAdviceInstructions(BcelShadow s, BcelVar extraArgVar, InstructionHandle ifNoAdvice) {
				InstructionList exitInstructions = new InstructionList();
				if (munger.hasDynamicTests()) {
					testResult.appendLoad(exitInstructions, fact);
					exitInstructions.append(InstructionFactory.createBranchInstruction(Constants.IFEQ, ifNoAdvice));
				}
				exitInstructions.append(Utility.createGet(fact, cflowField));
				if (munger.getKind() != AdviceKind.PerCflowEntry && munger.getKind() != AdviceKind.PerCflowBelowEntry
						&& munger.getExposedStateAsBcelVars(false).length == 0) {
					exitInstructions.append(fact.createInvoke(NameMangler.CFLOW_COUNTER_TYPE, "dec", Type.VOID, Type.NO_ARGS,
							Constants.INVOKEVIRTUAL));
				} else {
					exitInstructions.append(fact.createInvoke(NameMangler.CFLOW_STACK_TYPE, "pop", Type.VOID, Type.NO_ARGS,
							Constants.INVOKEVIRTUAL));
				}
				return exitInstructions;
			}
		};
//		if (getKind() == PreInitialization) {
//			weaveAfterReturning(exitAdvice);
//		}
//		else {
			weaveAfter(exitAdvice);
//		}

		range.insert(entryInstructions, Range.InsideBefore);
	}

	/*
	 * Implementation notes:
	 *
	 * AroundInline still extracts the instructions of the original shadow into an extracted method. This allows inlining of even
	 * that advice that doesn't call proceed or calls proceed more than once.
	 *
	 * It extracts the instructions of the original shadow into a method.
	 *
	 * Then it extracts the instructions of the advice into a new method defined on this enclosing class. This new method can then
	 * be specialized as below.
	 *
	 * Then it searches in the instructions of the advice for any call to the proceed method.
	 *
	 * At such a call, there is stuff on the stack representing the arguments to proceed. Pop these into the frame.
	 *
	 * Now build the stack for the call to the extracted method, taking values either from the join point state or from the new
	 * frame locs from proceed. Now call the extracted method. The right return value should be on the stack, so no cast is
	 * necessary.
	 *
	 * If only one call to proceed is made, we can re-inline the original shadow. We are not doing that presently.
	 *
	 * If the body of the advice can be determined to not alter the stack, or if this shadow doesn't care about the stack, i.e.
	 * method-execution, then the new method for the advice can also be re-lined. We are not doing that presently.
	 */
	public void weaveAroundInline(BcelAdvice munger, boolean hasDynamicTest) {
		// !!! THIS BLOCK OF CODE SHOULD BE IN A METHOD CALLED weaveAround(...);
		Member mungerSig = munger.getSignature();
		// Member originalSig = mungerSig; // If mungerSig is on a parameterized type, originalSig is the member on the generic type
		if (mungerSig instanceof ResolvedMember) {
			ResolvedMember rm = (ResolvedMember) mungerSig;
			if (rm.hasBackingGenericMember()) {
				mungerSig = rm.getBackingGenericMember();
			}
		}
		ResolvedType declaringAspectType = world.resolve(mungerSig.getDeclaringType(), true);
		if (declaringAspectType.isMissing()) {
			world.getLint().cantFindType.signal(
					new String[] { WeaverMessages.format(WeaverMessages.CANT_FIND_TYPE_DURING_AROUND_WEAVE,
							declaringAspectType.getClassName()) }, getSourceLocation(),
					new ISourceLocation[] { munger.getSourceLocation() });
		}

		// ??? might want some checks here to give better errors
		ResolvedType rt = (declaringAspectType.isParameterizedType() ? declaringAspectType.getGenericType() : declaringAspectType);
		BcelObjectType ot = BcelWorld.getBcelObjectType(rt);
		LazyMethodGen adviceMethod = ot.getLazyClassGen().getLazyMethodGen(mungerSig);
		if (!adviceMethod.getCanInline()) {
			weaveAroundClosure(munger, hasDynamicTest);
			return;
		}

		// specific test for @AJ proceedInInners
		if (isAnnotationStylePassingProceedingJoinPointOutOfAdvice(munger, hasDynamicTest, adviceMethod)) {
			return;
		}

		// We can't inline around methods if they have around advice on them, this
		// is because the weaving will extract the body and hence the proceed call.

		// TODO should consider optimizations to recognize simple cases that don't require body extraction

		enclosingMethod.setCanInline(false);

		LazyClassGen shadowClass = getEnclosingClass();

		// Extract the shadow into a new method. For example:
		// "private static final void method_aroundBody0(M, M, String, org.aspectj.lang.JoinPoint)"
		// Parameters are: this if there is one, target if there is one and its different to this, then original arguments
		// at the shadow, then tjp
		String extractedShadowMethodName = NameMangler.aroundShadowMethodName(getSignature(), shadowClass.getNewGeneratedNameTag());
		List<String> parameterNames = new ArrayList<>();
		boolean shadowClassIsInterface = shadowClass.isInterface();
		LazyMethodGen extractedShadowMethod = extractShadowInstructionsIntoNewMethod(extractedShadowMethodName,
				shadowClassIsInterface?Modifier.PUBLIC:Modifier.PRIVATE,
				munger.getSourceLocation(), parameterNames,shadowClassIsInterface);

		List<BcelVar> argsToCallLocalAdviceMethodWith = new ArrayList<>();
		List<BcelVar> proceedVarList = new ArrayList<>();
		int extraParamOffset = 0;

		// Create the extra parameters that are needed for passing to proceed
		// This code is very similar to that found in makeCallToCallback and should
		// be rationalized in the future

		if (thisVar != null) {
			argsToCallLocalAdviceMethodWith.add(thisVar);
			proceedVarList.add(new BcelVar(thisVar.getType(), extraParamOffset));
			extraParamOffset += thisVar.getType().getSize();
		}

		if (targetVar != null && targetVar != thisVar) {
			argsToCallLocalAdviceMethodWith.add(targetVar);
			proceedVarList.add(new BcelVar(targetVar.getType(), extraParamOffset));
			extraParamOffset += targetVar.getType().getSize();
		}
		for (int i = 0, len = getArgCount(); i < len; i++) {
			argsToCallLocalAdviceMethodWith.add(argVars[i]);
			proceedVarList.add(new BcelVar(argVars[i].getType(), extraParamOffset));
			extraParamOffset += argVars[i].getType().getSize();
		}
		if (thisJoinPointVar != null) {
			argsToCallLocalAdviceMethodWith.add(thisJoinPointVar);
			proceedVarList.add(new BcelVar(thisJoinPointVar.getType(), extraParamOffset));
			extraParamOffset += thisJoinPointVar.getType().getSize();
		}

		// We use the munger signature here because it allows for any parameterization of the mungers pointcut that
		// may have occurred ie. if the pointcut is p(T t) in the super aspect and that has become p(Foo t) in the sub aspect
		// then here the munger signature will have 'Foo' as an argument in it whilst the adviceMethod argument type will be
		// 'Object' - since it represents the advice method in the superaspect which uses the erasure of the type variable p(Object
		// t) - see pr174449.

		Type[] adviceParameterTypes = BcelWorld.makeBcelTypes(munger.getSignature().getParameterTypes());

		// forces initialization ... dont like this but seems to be required for some tests to pass, I think that means there
		// is a LazyMethodGen method that is not correctly setup to call initialize() when it is invoked - but I dont have
		// time right now to discover which
		adviceMethod.getArgumentTypes();

		Type[] extractedMethodParameterTypes = extractedShadowMethod.getArgumentTypes();

		Type[] parameterTypes = new Type[extractedMethodParameterTypes.length + adviceParameterTypes.length + 1];
		int parameterIndex = 0;
		System.arraycopy(extractedMethodParameterTypes, 0, parameterTypes, parameterIndex, extractedMethodParameterTypes.length);
		parameterIndex += extractedMethodParameterTypes.length;
		parameterTypes[parameterIndex++] = BcelWorld.makeBcelType(adviceMethod.getEnclosingClass().getType());
		System.arraycopy(adviceParameterTypes, 0, parameterTypes, parameterIndex, adviceParameterTypes.length);

		// Extract the advice into a new method. This will go in the same type as the shadow
		// name will be something like foo_aroundBody1$advice
		String localAdviceMethodName = NameMangler.aroundAdviceMethodName(getSignature(), shadowClass.getNewGeneratedNameTag());
		int localAdviceMethodModifiers = Modifier.PRIVATE | (world.useFinal() & !shadowClassIsInterface ? Modifier.FINAL : 0) | Modifier.STATIC;
		LazyMethodGen localAdviceMethod = new LazyMethodGen(localAdviceMethodModifiers, BcelWorld.makeBcelType(mungerSig.getReturnType()), localAdviceMethodName, parameterTypes,
				NoDeclaredExceptions, shadowClass);

		// Doesnt work properly, so leave it out:
		// String aspectFilename = adviceMethod.getEnclosingClass().getInternalFileName();
		// String shadowFilename = shadowClass.getInternalFileName();
		// if (!aspectFilename.equals(shadowFilename)) {
		// localAdviceMethod.fromFilename = aspectFilename;
		// shadowClass.addInlinedSourceFileInfo(aspectFilename, adviceMethod.highestLineNumber);
		// }

		shadowClass.addMethodGen(localAdviceMethod);

		// create a map that will move all slots in advice method forward by extraParamOffset
		// in order to make room for the new proceed-required arguments that are added at
		// the beginning of the parameter list
		int nVars = adviceMethod.getMaxLocals() + extraParamOffset;
		IntMap varMap = IntMap.idMap(nVars);
		for (int i = extraParamOffset; i < nVars; i++) {
			varMap.put(i - extraParamOffset, i);
		}

		final InstructionFactory fact = getFactory();

		localAdviceMethod.getBody().insert(
				BcelClassWeaver.genInlineInstructions(adviceMethod, localAdviceMethod, varMap, fact, true));

		localAdviceMethod.setMaxLocals(nVars);

		// the shadow is now empty. First, create a correct call
		// to the around advice. This includes both the call (which may involve
		// value conversion of the advice arguments) and the return
		// (which may involve value conversion of the return value). Right now
		// we push a null for the unused closure. It's sad, but there it is.

		InstructionList advice = new InstructionList();
		// InstructionHandle adviceMethodInvocation;
		{
			for (BcelVar var : argsToCallLocalAdviceMethodWith) {
				var.appendLoad(advice, fact);
			}
			// ??? we don't actually need to push NULL for the closure if we take care
			boolean isAnnoStyleConcreteAspect = munger.getConcreteAspect().isAnnotationStyleAspect();
			boolean isAnnoStyleDeclaringAspect = munger.getDeclaringAspect() != null ? munger.getDeclaringAspect().resolve(world)
					.isAnnotationStyleAspect() : false;

			InstructionList iList = null;
			if (isAnnoStyleConcreteAspect && isAnnoStyleDeclaringAspect) {
				iList = this.loadThisJoinPoint();
				iList.append(Utility.createConversion(getFactory(), LazyClassGen.tjpType, LazyClassGen.proceedingTjpType));
			} else {
				iList = new InstructionList(InstructionConstants.ACONST_NULL);
			}
			advice.append(munger.getAdviceArgSetup(this, null, iList));
			// adviceMethodInvocation =
			advice.append(Utility.createInvoke(fact, localAdviceMethod)); // (fact, getWorld(), munger.getSignature()));
			advice.append(Utility.createConversion(getFactory(), BcelWorld.makeBcelType(mungerSig.getReturnType()),
					extractedShadowMethod.getReturnType(), world.isInJava5Mode()));
			if (!isFallsThrough()) {
				advice.append(InstructionFactory.createReturn(extractedShadowMethod.getReturnType()));
			}
		}

		// now, situate the call inside the possible dynamic tests,
		// and actually add the whole mess to the shadow
		if (!hasDynamicTest) {
			range.append(advice);
		} else {
			InstructionList afterThingie = new InstructionList(InstructionConstants.NOP);
			InstructionList callback = makeCallToCallback(extractedShadowMethod);
			if (terminatesWithReturn()) {
				callback.append(InstructionFactory.createReturn(extractedShadowMethod.getReturnType()));
			} else {
				// InstructionHandle endNop = range.insert(fact.NOP, Range.InsideAfter);
				advice.append(InstructionFactory.createBranchInstruction(Constants.GOTO, afterThingie.getStart()));
			}
			range.append(munger.getTestInstructions(this, advice.getStart(), callback.getStart(), advice.getStart()));
			range.append(advice);
			range.append(callback);
			range.append(afterThingie);
		}

		// now search through the advice, looking for a call to PROCEED.
		// Then we replace the call to proceed with some argument setup, and a
		// call to the extracted method.

		// inlining support for code style aspects
		if (!munger.getDeclaringType().isAnnotationStyleAspect()) {
			String proceedName = NameMangler.proceedMethodName(munger.getSignature().getName());

			InstructionHandle curr = localAdviceMethod.getBody().getStart();
			InstructionHandle end = localAdviceMethod.getBody().getEnd();
			ConstantPool cpg = localAdviceMethod.getEnclosingClass().getConstantPool();
			while (curr != end) {
				InstructionHandle next = curr.getNext();
				Instruction inst = curr.getInstruction();
				if ((inst.opcode == Constants.INVOKESTATIC) && proceedName.equals(((InvokeInstruction) inst).getMethodName(cpg))) {

					localAdviceMethod.getBody().append(curr,
							getRedoneProceedCall(fact, extractedShadowMethod, munger, localAdviceMethod, proceedVarList));
					Utility.deleteInstruction(curr, localAdviceMethod);
				}
				curr = next;
			}
			// and that's it.
		} else {
			// ATAJ inlining support for @AJ aspects
			// [TODO document @AJ code rule: don't manipulate 2 jps proceed at the same time.. in an advice body]
			InstructionHandle curr = localAdviceMethod.getBody().getStart();
			InstructionHandle end = localAdviceMethod.getBody().getEnd();
			ConstantPool cpg = localAdviceMethod.getEnclosingClass().getConstantPool();
			while (curr != end) {
				InstructionHandle next = curr.getNext();
				Instruction inst = curr.getInstruction();
				if ((inst instanceof INVOKEINTERFACE) && "proceed".equals(((INVOKEINTERFACE) inst).getMethodName(cpg))) {
					final boolean isProceedWithArgs;
					if (((INVOKEINTERFACE) inst).getArgumentTypes(cpg).length == 1) {
						// proceed with args as a boxed Object[]
						isProceedWithArgs = true;
					} else {
						isProceedWithArgs = false;
					}
					InstructionList insteadProceedIl = getRedoneProceedCallForAnnotationStyle(fact, extractedShadowMethod, munger,
							localAdviceMethod, proceedVarList, isProceedWithArgs);
					localAdviceMethod.getBody().append(curr, insteadProceedIl);
					Utility.deleteInstruction(curr, localAdviceMethod);
				}
				curr = next;
			}
		}

		// if (parameterNames.size() == 0) {
		// On return we have inserted the advice body into the local advice method. We have remapped all the local variables
		// that were referenced in the advice as we did the copy, and so the local variable table for localAdviceMethod is
		// now lacking any information about all the initial variables.
		InstructionHandle start = localAdviceMethod.getBody().getStart();
		InstructionHandle end = localAdviceMethod.getBody().getEnd();

		// Find the real start and end
		while (start.getInstruction().opcode == Constants.IMPDEP1) {
			start = start.getNext();
		}
		while (end.getInstruction().opcode == Constants.IMPDEP1) {
			end = end.getPrev();
		}
		Type[] args = localAdviceMethod.getArgumentTypes();
		int argNumber = 0;
		for (int slot = 0; slot < extraParamOffset; argNumber++) { // slot will increase by the argument size each time
			String argumentName = null;
			if (argNumber >= args.length || parameterNames.size() == 0 || argNumber >= parameterNames.size()) {
				// this should be unnecessary as I think all known joinpoints and helper methods
				// propagate the parameter names around correctly - but just in case let us do this
				// rather than fail. If a bug is raised reporting unknown as a local variable name
				// then investigate the joinpoint giving rise to the ResolvedMember and why it has
				// no parameter names specified
				argumentName = new StringBuilder("unknown").append(argNumber).toString();
			} else {
				argumentName = parameterNames.get(argNumber);
			}
			String argumentSignature = args[argNumber].getSignature();
			LocalVariableTag lvt = new LocalVariableTag(argumentSignature, argumentName, slot, 0);
			start.addTargeter(lvt);
			end.addTargeter(lvt);
			slot += args[argNumber].getSize();
		}
	}

	/**
	 * Check if the advice method passes a pjp parameter out via an invoke instruction - if so we can't risk inlining.
	 */
	private boolean isAnnotationStylePassingProceedingJoinPointOutOfAdvice(BcelAdvice munger, boolean hasDynamicTest,
			LazyMethodGen adviceMethod) {
		if (munger.getConcreteAspect().isAnnotationStyleAspect()) {
			// if we can't find one proceed() we suspect that the call
			// is happening in an inner class so we don't inline it.
			// Note: for code style, this is done at Aspect compilation time.
			boolean canSeeProceedPassedToOther = false;
			InstructionHandle curr = adviceMethod.getBody().getStart();
			InstructionHandle end = adviceMethod.getBody().getEnd();
			ConstantPool cpg = adviceMethod.getEnclosingClass().getConstantPool();
			while (curr != end) {
				InstructionHandle next = curr.getNext();
				Instruction inst = curr.getInstruction();
				if ((inst instanceof InvokeInstruction)
						&& ((InvokeInstruction) inst).getSignature(cpg).indexOf("Lorg/aspectj/lang/ProceedingJoinPoint;") > 0) {
					// we may want to refine to exclude stuff returning jp ?
					// does code style skip inline if i write dump(thisJoinPoint) ?
					canSeeProceedPassedToOther = true;// we see one pjp passed around - dangerous
					break;
				}
				curr = next;
			}
			if (canSeeProceedPassedToOther) {
				// remember this decision to avoid re-analysis
				adviceMethod.setCanInline(false);
				weaveAroundClosure(munger, hasDynamicTest);
				return true;
			}
		}
		return false;
	}

	private InstructionList getRedoneProceedCall(InstructionFactory fact, LazyMethodGen callbackMethod, BcelAdvice munger,
			LazyMethodGen localAdviceMethod, List<BcelVar> argVarList) {
		InstructionList ret = new InstructionList();
		// we have on stack all the arguments for the ADVICE call.
		// we have in frame somewhere all the arguments for the non-advice call.

		BcelVar[] adviceVars = munger.getExposedStateAsBcelVars(true);
		IntMap proceedMap = makeProceedArgumentMap(adviceVars);

		// System.out.println(proceedMap + " for " + this);
		// System.out.println(argVarList);

		ResolvedType[] proceedParamTypes = world.resolve(munger.getSignature().getParameterTypes());
		// remove this*JoinPoint* as arguments to proceed
		if (munger.getBaseParameterCount() + 1 < proceedParamTypes.length) {
			int len = munger.getBaseParameterCount() + 1;
			ResolvedType[] newTypes = new ResolvedType[len];
			System.arraycopy(proceedParamTypes, 0, newTypes, 0, len);
			proceedParamTypes = newTypes;
		}

		// System.out.println("stateTypes: " + Arrays.asList(stateTypes));
		BcelVar[] proceedVars = Utility.pushAndReturnArrayOfVars(proceedParamTypes, ret, fact, localAdviceMethod);

		Type[] stateTypes = callbackMethod.getArgumentTypes();
		// System.out.println("stateTypes: " + Arrays.asList(stateTypes));

		for (int i = 0, len = stateTypes.length; i < len; i++) {
			Type stateType = stateTypes[i];
			ResolvedType stateTypeX = BcelWorld.fromBcel(stateType).resolve(world);
			if (proceedMap.hasKey(i)) {
				// throw new RuntimeException("unimplemented");
				proceedVars[proceedMap.get(i)].appendLoadAndConvert(ret, fact, stateTypeX);
			} else {
				argVarList.get(i).appendLoad(ret, fact);
			}
		}

		ret.append(Utility.createInvoke(fact, callbackMethod));
		ret.append(Utility.createConversion(fact, callbackMethod.getReturnType(),
				BcelWorld.makeBcelType(munger.getSignature().getReturnType()), world.isInJava5Mode()));
		return ret;
	}

	// private static boolean bindsThisOrTarget(Pointcut pointcut) {
	// ThisTargetFinder visitor = new ThisTargetFinder();
	// pointcut.accept(visitor, null);
	// return visitor.bindsThisOrTarget;
	// }

	// private static class ThisTargetFinder extends IdentityPointcutVisitor {
	// boolean bindsThisOrTarget = false;
	//
	// public Object visit(ThisOrTargetPointcut node, Object data) {
	// if (node.isBinding()) {
	// bindsThisOrTarget = true;
	// }
	// return node;
	// }
	//
	// public Object visit(AndPointcut node, Object data) {
	// if (!bindsThisOrTarget) node.getLeft().accept(this, data);
	// if (!bindsThisOrTarget) node.getRight().accept(this, data);
	// return node;
	// }
	//
	// public Object visit(NotPointcut node, Object data) {
	// if (!bindsThisOrTarget) node.getNegatedPointcut().accept(this, data);
	// return node;
	// }
	//
	// public Object visit(OrPointcut node, Object data) {
	// if (!bindsThisOrTarget) node.getLeft().accept(this, data);
	// if (!bindsThisOrTarget) node.getRight().accept(this, data);
	// return node;
	// }
	// }

	/**
	 * Annotation style handling for inlining.
	 *
	 * Note: The proceedingjoinpoint is already on the stack (since the user was calling pjp.proceed(...)
	 *
	 * The proceed map is ignored (in terms of argument repositioning) since we have a fixed expected format for annotation style.
	 * The aim here is to change the proceed() call into a call to the xxx_aroundBody0 method.
	 *
	 *
	 */
	private InstructionList getRedoneProceedCallForAnnotationStyle(InstructionFactory fact, LazyMethodGen callbackMethod,
			BcelAdvice munger, LazyMethodGen localAdviceMethod, List<BcelVar> argVarList, boolean isProceedWithArgs) {
		InstructionList ret = new InstructionList();

		// store the Object[] array on stack if proceed with args
		if (isProceedWithArgs) {

			// STORE the Object[] into a local variable
			Type objectArrayType = Type.OBJECT_ARRAY;
			int theObjectArrayLocalNumber = localAdviceMethod.allocateLocal(objectArrayType);
			ret.append(InstructionFactory.createStore(objectArrayType, theObjectArrayLocalNumber));

			// STORE the ProceedingJoinPoint instance into a local variable
			Type proceedingJpType = Type.getType("Lorg/aspectj/lang/ProceedingJoinPoint;");
			int pjpLocalNumber = localAdviceMethod.allocateLocal(proceedingJpType);
			ret.append(InstructionFactory.createStore(proceedingJpType, pjpLocalNumber));

			// Aim here initially is to determine whether the user will have provided a new
			// this/target in the object array and consume them if they have, leaving us the rest of
			// the arguments to process as regular arguments to the invocation at the original join point

			boolean pointcutBindsThis = bindsThis(munger);
			boolean pointcutBindsTarget = bindsTarget(munger);
			boolean targetIsSameAsThis = getKind().isTargetSameAsThis();

			int nextArgumentToProvideForCallback = 0;

			if (hasThis()) {
				if (!(pointcutBindsTarget && targetIsSameAsThis)) {
					if (pointcutBindsThis) {
						// they have supplied new this as first entry in object array, consume it
						ret.append(InstructionFactory.createLoad(objectArrayType, theObjectArrayLocalNumber));
						ret.append(Utility.createConstant(fact, 0));
						ret.append(InstructionFactory.createArrayLoad(Type.OBJECT));
						ret.append(Utility.createConversion(fact, Type.OBJECT, callbackMethod.getArgumentTypes()[0]));
					} else {
						// use local variable 0
						ret.append(InstructionFactory.createALOAD(0));
					}
					nextArgumentToProvideForCallback++;
				}
			}

			if (hasTarget()) {
				if (pointcutBindsTarget) {
					if (getKind().isTargetSameAsThis()) {
						ret.append(InstructionFactory.createLoad(objectArrayType, theObjectArrayLocalNumber));
						ret.append(Utility.createConstant(fact, pointcutBindsThis ? 1 : 0));
						ret.append(InstructionFactory.createArrayLoad(Type.OBJECT));
						ret.append(Utility.createConversion(fact, Type.OBJECT, callbackMethod.getArgumentTypes()[0]));
					} else {
						int position = (hasThis() && pointcutBindsThis)? 1 : 0;
						ret.append(InstructionFactory.createLoad(objectArrayType, theObjectArrayLocalNumber));
						ret.append(Utility.createConstant(fact, position));
						ret.append(InstructionFactory.createArrayLoad(Type.OBJECT));
						ret.append(Utility.createConversion(fact, Type.OBJECT, callbackMethod.getArgumentTypes()[nextArgumentToProvideForCallback]));
					}
					nextArgumentToProvideForCallback++;
				} else {
					if (getKind().isTargetSameAsThis()) {
						// ret.append(new ALOAD(0));
					} else {
						ret.append(InstructionFactory.createLoad(localAdviceMethod.getArgumentTypes()[0], hasThis() ? 1 : 0));
						nextArgumentToProvideForCallback++;
					}
				}
			}

			// Where to start in the object array in order to pick up arguments
			int indexIntoObjectArrayForArguments = (pointcutBindsThis ? 1 : 0) + (pointcutBindsTarget ? 1 : 0);

			int len = callbackMethod.getArgumentTypes().length;
			for (int i = nextArgumentToProvideForCallback; i < len; i++) {
				Type stateType = callbackMethod.getArgumentTypes()[i];
				BcelWorld.fromBcel(stateType).resolve(world);
				if ("Lorg/aspectj/lang/JoinPoint;".equals(stateType.getSignature())) {
					ret.append(new InstructionLV(Constants.ALOAD, pjpLocalNumber));
				} else {
					ret.append(InstructionFactory.createLoad(objectArrayType, theObjectArrayLocalNumber));
					ret.append(Utility
							.createConstant(fact, i - nextArgumentToProvideForCallback + indexIntoObjectArrayForArguments));
					ret.append(InstructionFactory.createArrayLoad(Type.OBJECT));
					ret.append(Utility.createConversion(fact, Type.OBJECT, stateType));
				}
			}

		} else {
			Type proceedingJpType = Type.getType("Lorg/aspectj/lang/ProceedingJoinPoint;");
			int localJp = localAdviceMethod.allocateLocal(proceedingJpType);
			ret.append(InstructionFactory.createStore(proceedingJpType, localJp));

			int idx = 0;
			for (int i = 0, len = callbackMethod.getArgumentTypes().length; i < len; i++) {
				Type stateType = callbackMethod.getArgumentTypes()[i];
				/* ResolvedType stateTypeX = */
				BcelWorld.fromBcel(stateType).resolve(world);
				if ("Lorg/aspectj/lang/JoinPoint;".equals(stateType.getSignature())) {
					ret.append(InstructionFactory.createALOAD(localJp));// from localAdvice signature
					// } else if ("Lorg/aspectj/lang/ProceedingJoinPoint;".equals(stateType.getSignature())) {
					// //FIXME ALEX?
					// ret.append(new ALOAD(localJp));// from localAdvice signature
					// // ret.append(fact.createCheckCast(
					// // (ReferenceType) BcelWorld.makeBcelType(stateTypeX)
					// // ));
					// // cast ?
					//
					idx++;
				} else {
					ret.append(InstructionFactory.createLoad(stateType, idx));
					idx += stateType.getSize();
				}
			}
		}

		// do the callback invoke
		ret.append(Utility.createInvoke(fact, callbackMethod));

		// box it again. Handles cases where around advice does return something else than Object
		if (!UnresolvedType.OBJECT.equals(munger.getSignature().getReturnType())) {
			ret.append(Utility.createConversion(fact, callbackMethod.getReturnType(), Type.OBJECT));
		}
		ret.append(Utility.createConversion(fact, callbackMethod.getReturnType(),
				BcelWorld.makeBcelType(munger.getSignature().getReturnType()), world.isInJava5Mode()));

		return ret;

		//
		//
		//
		// if (proceedMap.hasKey(i)) {
		// ret.append(new ALOAD(i));
		// //throw new RuntimeException("unimplemented");
		// //proceedVars[proceedMap.get(i)].appendLoadAndConvert(ret, fact, stateTypeX);
		// } else {
		// //((BcelVar) argVarList.get(i)).appendLoad(ret, fact);
		// //ret.append(new ALOAD(i));
		// if ("Lorg/aspectj/lang/JoinPoint;".equals(stateType.getSignature())) {
		// ret.append(new ALOAD(i));
		// } else {
		// ret.append(new ALOAD(i));
		// }
		// }
		// }
		//
		// ret.append(Utility.createInvoke(fact, callbackMethod));
		// ret.append(Utility.createConversion(fact, callbackMethod.getReturnType(),
		// BcelWorld.makeBcelType(munger.getSignature().getReturnType())));
		//
		// //ret.append(new ACONST_NULL());//will be POPed
		// if (true) return ret;
		//
		//
		//
		// // we have on stack all the arguments for the ADVICE call.
		// // we have in frame somewhere all the arguments for the non-advice call.
		//
		// BcelVar[] adviceVars = munger.getExposedStateAsBcelVars();
		// IntMap proceedMap = makeProceedArgumentMap(adviceVars);
		//
		// System.out.println(proceedMap + " for " + this);
		// System.out.println(argVarList);
		//
		// ResolvedType[] proceedParamTypes =
		// world.resolve(munger.getSignature().getParameterTypes());
		// // remove this*JoinPoint* as arguments to proceed
		// if (munger.getBaseParameterCount()+1 < proceedParamTypes.length) {
		// int len = munger.getBaseParameterCount()+1;
		// ResolvedType[] newTypes = new ResolvedType[len];
		// System.arraycopy(proceedParamTypes, 0, newTypes, 0, len);
		// proceedParamTypes = newTypes;
		// }
		//
		// //System.out.println("stateTypes: " + Arrays.asList(stateTypes));
		// BcelVar[] proceedVars =
		// Utility.pushAndReturnArrayOfVars(proceedParamTypes, ret, fact, localAdviceMethod);
		//
		// Type[] stateTypes = callbackMethod.getArgumentTypes();
		// // System.out.println("stateTypes: " + Arrays.asList(stateTypes));
		//
		// for (int i=0, len=stateTypes.length; i < len; i++) {
		// Type stateType = stateTypes[i];
		// ResolvedType stateTypeX = BcelWorld.fromBcel(stateType).resolve(world);
		// if (proceedMap.hasKey(i)) {
		// //throw new RuntimeException("unimplemented");
		// proceedVars[proceedMap.get(i)].appendLoadAndConvert(ret, fact, stateTypeX);
		// } else {
		// ((BcelVar) argVarList.get(i)).appendLoad(ret, fact);
		// }
		// }
		//
		// ret.append(Utility.createInvoke(fact, callbackMethod));
		// ret.append(Utility.createConversion(fact, callbackMethod.getReturnType(),
		// BcelWorld.makeBcelType(munger.getSignature().getReturnType())));
		// return ret;
	}

	private boolean bindsThis(BcelAdvice munger) {
		UsesThisVisitor utv = new UsesThisVisitor();
		munger.getPointcut().accept(utv, null);
		return utv.usesThis;
	}

	private boolean bindsTarget(BcelAdvice munger) {
		UsesTargetVisitor utv = new UsesTargetVisitor();
		munger.getPointcut().accept(utv, null);
		return utv.usesTarget;
	}

	private static class UsesThisVisitor extends AbstractPatternNodeVisitor {
		boolean usesThis = false;

		@Override
		public Object visit(ThisOrTargetPointcut node, Object data) {
			if (node.isThis() && node.isBinding()) {
				usesThis = true;
			}
			return node;
		}

		@Override
		public Object visit(AndPointcut node, Object data) {
			if (!usesThis) {
				node.getLeft().accept(this, data);
			}
			if (!usesThis) {
				node.getRight().accept(this, data);
			}
			return node;
		}

		@Override
		public Object visit(NotPointcut node, Object data) {
			if (!usesThis) {
				node.getNegatedPointcut().accept(this, data);
			}
			return node;
		}

		@Override
		public Object visit(OrPointcut node, Object data) {
			if (!usesThis) {
				node.getLeft().accept(this, data);
			}
			if (!usesThis) {
				node.getRight().accept(this, data);
			}
			return node;
		}
	}

	private static class UsesTargetVisitor extends AbstractPatternNodeVisitor {
		boolean usesTarget = false;

		@Override
		public Object visit(ThisOrTargetPointcut node, Object data) {
			if (!node.isThis() && node.isBinding()) {
				usesTarget = true;
			}
			return node;
		}

		@Override
		public Object visit(AndPointcut node, Object data) {
			if (!usesTarget) {
				node.getLeft().accept(this, data);
			}
			if (!usesTarget) {
				node.getRight().accept(this, data);
			}
			return node;
		}

		@Override
		public Object visit(NotPointcut node, Object data) {
			if (!usesTarget) {
				node.getNegatedPointcut().accept(this, data);
			}
			return node;
		}

		@Override
		public Object visit(OrPointcut node, Object data) {
			if (!usesTarget) {
				node.getLeft().accept(this, data);
			}
			if (!usesTarget) {
				node.getRight().accept(this, data);
			}
			return node;
		}
	}

	BcelVar aroundClosureInstance = null;

	public void weaveAroundClosure(BcelAdvice munger, boolean hasDynamicTest) {
		InstructionFactory fact = getFactory();

		enclosingMethod.setCanInline(false);

		int linenumber = getSourceLine();
		// MOVE OUT ALL THE INSTRUCTIONS IN MY SHADOW INTO ANOTHER METHOD!

		// callbackMethod will be something like: "static final void m_aroundBody0(I)"
		boolean shadowClassIsInterface = getEnclosingClass().isInterface();
		LazyMethodGen callbackMethod = extractShadowInstructionsIntoNewMethod(
				NameMangler.aroundShadowMethodName(getSignature(), getEnclosingClass().getNewGeneratedNameTag()), shadowClassIsInterface?Modifier.PUBLIC:0,
				munger.getSourceLocation(), new ArrayList<>(),shadowClassIsInterface);

		BcelVar[] adviceVars = munger.getExposedStateAsBcelVars(true);

		String closureClassName = NameMangler.makeClosureClassName(getEnclosingClass().getType(), getEnclosingClass()
				.getNewGeneratedNameTag());

		Member constructorSig = new MemberImpl(Member.CONSTRUCTOR, UnresolvedType.forName(closureClassName), 0, "<init>",
				"([Ljava/lang/Object;)V");

		BcelVar closureHolder = null;

		// This is not being used currently since getKind() == preinitializaiton
		// cannot happen in around advice
		if (getKind() == PreInitialization) {
			closureHolder = genTempVar(AjcMemberMaker.AROUND_CLOSURE_TYPE);
		}

		InstructionList closureInstantiation = makeClosureInstantiation(constructorSig, closureHolder);

		/* LazyMethodGen constructor = */
		makeClosureClassAndReturnConstructor(closureClassName, callbackMethod, makeProceedArgumentMap(adviceVars));

		InstructionList returnConversionCode;
		if (getKind() == PreInitialization) {
			returnConversionCode = new InstructionList();

			BcelVar stateTempVar = genTempVar(UnresolvedType.OBJECTARRAY);
			closureHolder.appendLoad(returnConversionCode, fact);

			returnConversionCode.append(Utility.createInvoke(fact, world, AjcMemberMaker.aroundClosurePreInitializationGetter()));
			stateTempVar.appendStore(returnConversionCode, fact);

			Type[] stateTypes = getSuperConstructorParameterTypes();

			returnConversionCode.append(InstructionConstants.ALOAD_0); // put "this" back on the stack
			for (int i = 0, len = stateTypes.length; i < len; i++) {
				UnresolvedType bcelTX = BcelWorld.fromBcel(stateTypes[i]);
				ResolvedType stateRTX = world.resolve(bcelTX, true);
				if (stateRTX.isMissing()) {
					world.getLint().cantFindType.signal(
							new String[] { WeaverMessages.format(WeaverMessages.CANT_FIND_TYPE_DURING_AROUND_WEAVE_PREINIT,
									bcelTX.getClassName()) }, getSourceLocation(),
							new ISourceLocation[] { munger.getSourceLocation() });
					// IMessage msg = new Message(
					// WeaverMessages.format(WeaverMessages.CANT_FIND_TYPE_DURING_AROUND_WEAVE_PREINIT,bcelTX.getClassName()),
					// "",IMessage.ERROR,getSourceLocation(),null,
					// new ISourceLocation[]{ munger.getSourceLocation()});
					// world.getMessageHandler().handleMessage(msg);
				}
				stateTempVar.appendConvertableArrayLoad(returnConversionCode, fact, i, stateRTX);
			}
		} else {
			// pr226201
			Member mungerSignature = munger.getSignature();
			if (munger.getSignature() instanceof ResolvedMember) {
				if (((ResolvedMember) mungerSignature).hasBackingGenericMember()) {
					mungerSignature = ((ResolvedMember) mungerSignature).getBackingGenericMember();
				}
			}
			UnresolvedType returnType = mungerSignature.getReturnType();
			returnConversionCode = Utility.createConversion(getFactory(), BcelWorld.makeBcelType(returnType),
					callbackMethod.getReturnType(), world.isInJava5Mode());
			if (!isFallsThrough()) {
				returnConversionCode.append(InstructionFactory.createReturn(callbackMethod.getReturnType()));
			}
		}

		// initialize the bit flags for this shadow
		int bitflags = 0x000000;
		if (getKind().isTargetSameAsThis()) {
			bitflags |= 0x010000;
		}
		if (hasThis()) {
			bitflags |= 0x001000;
		}
		if (bindsThis(munger)) {
			bitflags |= 0x000100;
		}
		if (hasTarget()) {
			bitflags |= 0x000010;
		}
		if (bindsTarget(munger)) {
			bitflags |= 0x000001;
		}

		closureVarInitialized = false;

		// ATAJ for @AJ aspect we need to link the closure with the joinpoint instance
		if (munger.getConcreteAspect() != null && munger.getConcreteAspect().isAnnotationStyleAspect()
				&& munger.getDeclaringAspect() != null && munger.getDeclaringAspect().resolve(world).isAnnotationStyleAspect()) {

			aroundClosureInstance = genTempVar(AjcMemberMaker.AROUND_CLOSURE_TYPE);
			closureInstantiation.append(fact.createDup(1));
			aroundClosureInstance.appendStore(closureInstantiation, fact);

			// stick the bitflags on the stack and call the variant of linkClosureAndJoinPoint that takes an int
			closureInstantiation.append(fact.createConstant(bitflags));
			if (needAroundClosureStacking) {
				closureInstantiation.append(Utility.createInvoke(getFactory(), getWorld(),
						new MemberImpl(Member.METHOD, UnresolvedType.forName("org.aspectj.runtime.internal.AroundClosure"),
								Modifier.PUBLIC, "linkStackClosureAndJoinPoint", String.format("%s%s", "(I)", "Lorg/aspectj/lang/ProceedingJoinPoint;"))));

			} else {
				closureInstantiation.append(Utility.createInvoke(getFactory(), getWorld(),
						new MemberImpl(Member.METHOD, UnresolvedType.forName("org.aspectj.runtime.internal.AroundClosure"),
								Modifier.PUBLIC, "linkClosureAndJoinPoint", String.format("%s%s", "(I)", "Lorg/aspectj/lang/ProceedingJoinPoint;"))));
			}

		}

		InstructionList advice = new InstructionList();
		advice.append(munger.getAdviceArgSetup(this, null, closureInstantiation));

		// invoke the advice
		InstructionHandle tryUnlinkPosition  = advice.append(munger.getNonTestAdviceInstructions(this));

		if (needAroundClosureStacking) {
			// Call AroundClosure.unlink() in a 'finally' block
			if (munger.getConcreteAspect() != null && munger.getConcreteAspect().isAnnotationStyleAspect()
					&& munger.getDeclaringAspect() != null
					&& munger.getDeclaringAspect().resolve(world).isAnnotationStyleAspect()
					&& closureVarInitialized) {

				// Call unlink when 'normal' flow occurring
				aroundClosureInstance.appendLoad(advice, fact);
				InstructionHandle unlinkInsn = advice.append(Utility.createInvoke(getFactory(), getWorld(), new MemberImpl(Member.METHOD, UnresolvedType
						.forName("org.aspectj.runtime.internal.AroundClosure"), Modifier.PUBLIC, "unlink",
						"()V")));

				BranchHandle jumpOverHandler = advice.append(new InstructionBranch(Constants.GOTO, null));
				// Call unlink in finally block

				// Do not POP the exception off, we need to rethrow it
				InstructionHandle handlerStart = advice.append(aroundClosureInstance.createLoad(fact));
				advice.append(Utility.createInvoke(getFactory(), getWorld(), new MemberImpl(Member.METHOD, UnresolvedType
						.forName("org.aspectj.runtime.internal.AroundClosure"), Modifier.PUBLIC, "unlink",
						"()V")));
				// After that exception is on the top of the stack again
				advice.append(InstructionConstants.ATHROW);
				InstructionHandle jumpTarget = advice.append(InstructionConstants.NOP);
				jumpOverHandler.setTarget(jumpTarget);
				enclosingMethod.addExceptionHandler(tryUnlinkPosition, unlinkInsn, handlerStart, null/* ==finally */, false);
			}
		}

		advice.append(returnConversionCode);
		if (getKind() == Shadow.MethodExecution && linenumber > 0) {
			advice.getStart().addTargeter(new LineNumberTag(linenumber));
		}

		if (!hasDynamicTest) {
			range.append(advice);
		} else {
			InstructionList callback = makeCallToCallback(callbackMethod);
			InstructionList postCallback = new InstructionList();
			if (terminatesWithReturn()) {
				callback.append(InstructionFactory.createReturn(callbackMethod.getReturnType()));
			} else {
				advice.append(InstructionFactory.createBranchInstruction(Constants.GOTO,
						postCallback.append(InstructionConstants.NOP)));
			}
			range.append(munger.getTestInstructions(this, advice.getStart(), callback.getStart(), advice.getStart()));
			range.append(advice);
			range.append(callback);
			range.append(postCallback);
		}
	}

	// exposed for testing
	InstructionList makeCallToCallback(LazyMethodGen callbackMethod) {
		InstructionFactory fact = getFactory();
		InstructionList callback = new InstructionList();
		if (thisVar != null) {
			callback.append(InstructionConstants.ALOAD_0);
		}
		if (targetVar != null && targetVar != thisVar) {
			callback.append(BcelRenderer.renderExpr(fact, world, targetVar));
		}
		callback.append(BcelRenderer.renderExprs(fact, world, argVars));
		// remember to render tjps
		if (thisJoinPointVar != null) {
			callback.append(BcelRenderer.renderExpr(fact, world, thisJoinPointVar));
		}
		callback.append(Utility.createInvoke(fact, callbackMethod));
		return callback;
	}

	/** side-effect-free */
	private InstructionList makeClosureInstantiation(Member constructor, BcelVar holder) {

		// LazyMethodGen constructor) {
		InstructionFactory fact = getFactory();
		BcelVar arrayVar = genTempVar(UnresolvedType.OBJECTARRAY);
		// final Type objectArrayType = new ArrayType(Type.OBJECT, 1);
		final InstructionList il = new InstructionList();
		int alen = getArgCount() + (thisVar == null ? 0 : 1) + ((targetVar != null && targetVar != thisVar) ? 1 : 0)
				+ (thisJoinPointVar == null ? 0 : 1);
		il.append(Utility.createConstant(fact, alen));
		il.append(fact.createNewArray(Type.OBJECT, (short) 1));
		arrayVar.appendStore(il, fact);

		int stateIndex = 0;
		if (thisVar != null) {
			arrayVar.appendConvertableArrayStore(il, fact, stateIndex, thisVar);
			thisVar.setPositionInAroundState(stateIndex);
			stateIndex++;
		}
		if (targetVar != null && targetVar != thisVar) {
			arrayVar.appendConvertableArrayStore(il, fact, stateIndex, targetVar);
			targetVar.setPositionInAroundState(stateIndex);
			stateIndex++;
		}
		for (int i = 0, len = getArgCount(); i < len; i++) {
			arrayVar.appendConvertableArrayStore(il, fact, stateIndex, argVars[i]);
			argVars[i].setPositionInAroundState(stateIndex);
			stateIndex++;
		}
		if (thisJoinPointVar != null) {
			arrayVar.appendConvertableArrayStore(il, fact, stateIndex, thisJoinPointVar);
			thisJoinPointVar.setPositionInAroundState(stateIndex);
			stateIndex++;
		}
		il.append(fact.createNew(new ObjectType(constructor.getDeclaringType().getName())));
		il.append(InstructionConstants.DUP);
		arrayVar.appendLoad(il, fact);
		il.append(Utility.createInvoke(fact, world, constructor));
		if (getKind() == PreInitialization) {
			il.append(InstructionConstants.DUP);
			holder.appendStore(il, fact);
		}
		return il;
	}

	private IntMap makeProceedArgumentMap(BcelVar[] adviceArgs) {
		// System.err.println("coming in with " + Arrays.asList(adviceArgs));

		IntMap ret = new IntMap();
		for (int i = 0, len = adviceArgs.length; i < len; i++) {
			BcelVar v = adviceArgs[i];
			if (v == null) {
				continue; // XXX we don't know why this is required
			}
			int pos = v.getPositionInAroundState();
			if (pos >= 0) { // need this test to avoid args bound via cflow
				ret.put(pos, i);
			}
		}
		// System.err.println("returning " + ret);

		return ret;
	}

	/**
	 *
	 * @param callbackMethod the method we will call back to when our run method gets called.
	 * @param proceedMap A map from state position to proceed argument position. May be non covering on state position.
	 */
	private LazyMethodGen makeClosureClassAndReturnConstructor(String closureClassName, LazyMethodGen callbackMethod,
			IntMap proceedMap) {
		String superClassName = "org.aspectj.runtime.internal.AroundClosure";
		Type objectArrayType = new ArrayType(Type.OBJECT, 1);

		LazyClassGen closureClass = new LazyClassGen(closureClassName, superClassName, getEnclosingClass().getFileName(),
				Modifier.PUBLIC, new String[] {}, getWorld());
		closureClass.setMajorMinor(getEnclosingClass().getMajor(), getEnclosingClass().getMinor());
		InstructionFactory fact = new InstructionFactory(closureClass.getConstantPool());

		// constructor
		LazyMethodGen constructor = new LazyMethodGen(Modifier.PUBLIC, Type.VOID, "<init>", new Type[] { objectArrayType },
				new String[] {}, closureClass);
		InstructionList cbody = constructor.getBody();
		cbody.append(InstructionFactory.createLoad(Type.OBJECT, 0));
		cbody.append(InstructionFactory.createLoad(objectArrayType, 1));
		cbody.append(fact
				.createInvoke(superClassName, "<init>", Type.VOID, new Type[] { objectArrayType }, Constants.INVOKESPECIAL));
		cbody.append(InstructionFactory.createReturn(Type.VOID));

		closureClass.addMethodGen(constructor);

		// Create the 'Object run(Object[])' method
		LazyMethodGen runMethod = new LazyMethodGen(Modifier.PUBLIC, Type.OBJECT, "run", new Type[] { objectArrayType },
				new String[] {}, closureClass);
		InstructionList mbody = runMethod.getBody();
		BcelVar proceedVar = new BcelVar(UnresolvedType.OBJECTARRAY.resolve(world), 1);
		// int proceedVarIndex = 1;
		BcelVar stateVar = new BcelVar(UnresolvedType.OBJECTARRAY.resolve(world), runMethod.allocateLocal(1));
		// int stateVarIndex = runMethod.allocateLocal(1);
		mbody.append(InstructionFactory.createThis());
		mbody.append(fact.createGetField(superClassName, "state", objectArrayType));
		mbody.append(stateVar.createStore(fact));
		// mbody.append(fact.createStore(objectArrayType, stateVarIndex));

		Type[] stateTypes = callbackMethod.getArgumentTypes();

		for (int i = 0, len = stateTypes.length; i < len; i++) {
			ResolvedType resolvedStateType = BcelWorld.fromBcel(stateTypes[i]).resolve(world);
			if (proceedMap.hasKey(i)) {
				mbody.append(proceedVar.createConvertableArrayLoad(fact, proceedMap.get(i), resolvedStateType));
			} else {
				mbody.append(stateVar.createConvertableArrayLoad(fact, i, resolvedStateType));
			}
		}

		mbody.append(Utility.createInvoke(fact, callbackMethod));

		if (getKind() == PreInitialization) {
			mbody.append(Utility.createSet(fact, AjcMemberMaker.aroundClosurePreInitializationField()));
			mbody.append(InstructionConstants.ACONST_NULL);
		} else {
			mbody.append(Utility.createConversion(fact, callbackMethod.getReturnType(), Type.OBJECT));
		}
		mbody.append(InstructionFactory.createReturn(Type.OBJECT));

		closureClass.addMethodGen(runMethod);

		// class
		getEnclosingClass().addGeneratedInner(closureClass);

		return constructor;
	}

	// ---- extraction methods

	/**
	 * Extract the instructions in the shadow to a new method.
	 *
	 * @param extractedMethodName name for the new method
	 * @param extractedMethodVisibilityModifier visibility modifiers for the new method
	 * @param adviceSourceLocation source location of the advice affecting the shadow
	 * @param beingPlacedInInterface is this new method going into an interface
	 */
	LazyMethodGen extractShadowInstructionsIntoNewMethod(String extractedMethodName, int extractedMethodVisibilityModifier,
			ISourceLocation adviceSourceLocation, List<String> parameterNames, boolean beingPlacedInInterface) {
		// LazyMethodGen.assertGoodBody(range.getBody(), extractedMethodName);
		if (!getKind().allowsExtraction()) {
			throw new BCException("Attempt to extract method from a shadow kind (" + getKind()
					+ ") that does not support this operation");
		}
		LazyMethodGen newMethod = createShadowMethodGen(extractedMethodName, extractedMethodVisibilityModifier, parameterNames, beingPlacedInInterface);
		IntMap remapper = makeRemap();
		range.extractInstructionsInto(newMethod, remapper, (getKind() != PreInitialization) && isFallsThrough());
		if (getKind() == PreInitialization) {
			addPreInitializationReturnCode(newMethod, getSuperConstructorParameterTypes());
		}
		getEnclosingClass().addMethodGen(newMethod, adviceSourceLocation);
		return newMethod;
	}

	private void addPreInitializationReturnCode(LazyMethodGen extractedMethod, Type[] superConstructorTypes) {
		InstructionList body = extractedMethod.getBody();
		final InstructionFactory fact = getFactory();

		BcelVar arrayVar = new BcelVar(world.getCoreType(UnresolvedType.OBJECTARRAY), extractedMethod.allocateLocal(1));

		int len = superConstructorTypes.length;

		body.append(Utility.createConstant(fact, len));

		body.append(fact.createNewArray(Type.OBJECT, (short) 1));
		arrayVar.appendStore(body, fact);

		for (int i = len - 1; i >= 0; i++) {
			// convert thing on top of stack to object
			body.append(Utility.createConversion(fact, superConstructorTypes[i], Type.OBJECT));
			// push object array
			arrayVar.appendLoad(body, fact);
			// swap
			body.append(InstructionConstants.SWAP);
			// do object array store.
			body.append(Utility.createConstant(fact, i));
			body.append(InstructionConstants.SWAP);
			body.append(InstructionFactory.createArrayStore(Type.OBJECT));
		}
		arrayVar.appendLoad(body, fact);
		body.append(InstructionConstants.ARETURN);
	}

	private Type[] getSuperConstructorParameterTypes() {
		// assert getKind() == PreInitialization
		InstructionHandle superCallHandle = getRange().getEnd().getNext();
		InvokeInstruction superCallInstruction = (InvokeInstruction) superCallHandle.getInstruction();
		return superCallInstruction.getArgumentTypes(getEnclosingClass().getConstantPool());
	}

	/**
	 * make a map from old frame location to new frame location. Any unkeyed frame location picks out a copied local
	 */
	private IntMap makeRemap() {
		IntMap ret = new IntMap(5);
		int reti = 0;
		if (thisVar != null) {
			ret.put(0, reti++); // thisVar guaranteed to be 0
		}
		if (targetVar != null && targetVar != thisVar) {
			ret.put(targetVar.getSlot(), reti++);
		}
		for (BcelVar argVar : argVars) {
			ret.put(argVar.getSlot(), reti);
			reti += argVar.getType().getSize();
		}
		if (thisJoinPointVar != null) {
			ret.put(thisJoinPointVar.getSlot(), reti++);
		}
		// we not only need to put the arguments, we also need to remap their
		// aliases, which we so helpfully put into temps at the beginning of this join
		// point.
		if (!getKind().argsOnStack()) {
			int oldi = 0;
			int newi = 0;
			// if we're passing in a this and we're not argsOnStack we're always
			// passing in a target too
			if (arg0HoldsThis()) {
				ret.put(0, 0);
				oldi++;
				newi += 1;
			}
			// assert targetVar == thisVar
			for (int i = 0; i < getArgCount(); i++) {
				UnresolvedType type = getArgType(i);
				ret.put(oldi, newi);
				oldi += type.getSize();
				newi += type.getSize();
			}
		}

		// System.err.println("making remap for : " + this);
		// if (targetVar != null) System.err.println("target slot : " + targetVar.getSlot());
		// if (thisVar != null) System.err.println("  this slot : " + thisVar.getSlot());
		// System.err.println(ret);

		return ret;
	}

	/**
	 * The new method always static. It may take some extra arguments: this, target. If it's argsOnStack, then it must take both
	 * this/target If it's argsOnFrame, it shares this and target. ??? rewrite this to do less array munging, please
	 */
	private LazyMethodGen createShadowMethodGen(String newMethodName, int visibilityModifier, List<String> parameterNames, boolean beingPlacedInInterface) {
		Type[] shadowParameterTypes = BcelWorld.makeBcelTypes(getArgTypes());
		int modifiers = (world.useFinal() && !beingPlacedInInterface ? Modifier.FINAL : 0) | Modifier.STATIC | visibilityModifier;
		if (targetVar != null && targetVar != thisVar) {
			UnresolvedType targetType = getTargetType();
			targetType = ensureTargetTypeIsCorrect(targetType);
			// see pr109728,pr229910 - this fixes the case when the declaring class is sometype 'X' but the (gs)etfield
			// in the bytecode refers to a subtype of 'X'. This makes sure we use the type originally
			// mentioned in the fieldget instruction as the method parameter and *not* the type upon which the
			// field is declared because when the instructions are extracted into the new around body,
			// they will still refer to the subtype.
			if ((getKind() == FieldGet || getKind() == FieldSet) && getActualTargetType() != null
					&& !getActualTargetType().equals(targetType.getName())) {
				targetType = UnresolvedType.forName(getActualTargetType()).resolve(world);
			}
			ResolvedMember resolvedMember = getSignature().resolve(world);

			// pr230075, pr197719
			if (resolvedMember != null && Modifier.isProtected(resolvedMember.getModifiers())
					&& !samePackage(resolvedMember.getDeclaringType().getPackageName(), getEnclosingType().getPackageName())
					&& !resolvedMember.getName().equals("clone")) {
				if (!hasThis()) { // pr197719 - static accessor has been created to handle the call
					if (Modifier.isStatic(enclosingMethod.getAccessFlags()) && enclosingMethod.getName().startsWith("access$")) {
						targetType = BcelWorld.fromBcel(enclosingMethod.getArgumentTypes()[0]);
					}
				} else {
					if (!targetType.resolve(world).isAssignableFrom(getThisType().resolve(world))) {
						throw new BCException("bad bytecode");
					}
					targetType = getThisType();
				}
			}
			parameterNames.add("target");
			// There is a 'target' and it is not the same as 'this', so add it to the parameter list
			shadowParameterTypes = addTypeToFront(BcelWorld.makeBcelType(targetType), shadowParameterTypes);
		}

		if (thisVar != null) {
			UnresolvedType thisType = getThisType();
			parameterNames.add(0, "ajc$this");
			shadowParameterTypes = addTypeToFront(BcelWorld.makeBcelType(thisType), shadowParameterTypes);
		}

		if (this.getKind() == Shadow.FieldSet || this.getKind() == Shadow.FieldGet) {
			parameterNames.add(getSignature().getName());
		} else {
			String[] pnames = getSignature().getParameterNames(world);
			if (pnames != null) {
				for (int i = 0; i < pnames.length; i++) {
					if (i == 0 && pnames[i].equals("this")) {
						parameterNames.add("ajc$this");
					} else {
						parameterNames.add(pnames[i]);
					}
				}
			}
		}

		// We always want to pass down thisJoinPoint in case we have already woven
		// some advice in here. If we only have a single piece of around advice on a
		// join point, it is unnecessary to accept (and pass) tjp.
		if (thisJoinPointVar != null) {
			parameterNames.add("thisJoinPoint");
			shadowParameterTypes = addTypeToEnd(LazyClassGen.tjpType, shadowParameterTypes);
		}

		UnresolvedType returnType;
		if (getKind() == PreInitialization) {
			returnType = UnresolvedType.OBJECTARRAY;
		} else {
			if (getKind() == ConstructorCall) {
				returnType = getSignature().getDeclaringType();
			} else if (getKind() == FieldSet) {
				returnType = UnresolvedType.VOID;
			} else {
				returnType = getSignature().getReturnType().resolve(world);
				// returnType = getReturnType(); // for this and above lines, see pr137496
			}
		}
		return new LazyMethodGen(modifiers, BcelWorld.makeBcelType(returnType), newMethodName, shadowParameterTypes,
				NoDeclaredExceptions, getEnclosingClass());
	}

	private boolean samePackage(String p1, String p2) {
		if (p1 == null) {
			return p2 == null;
		}
		if (p2 == null) {
			return false;
		}
		return p1.equals(p2);
	}

	private Type[] addTypeToFront(Type type, Type[] types) {
		int len = types.length;
		Type[] ret = new Type[len + 1];
		ret[0] = type;
		System.arraycopy(types, 0, ret, 1, len);
		return ret;
	}

	private Type[] addTypeToEnd(Type type, Type[] types) {
		int len = types.length;
		Type[] ret = new Type[len + 1];
		ret[len] = type;
		System.arraycopy(types, 0, ret, 0, len);
		return ret;
	}

	public BcelVar genTempVar(UnresolvedType utype) {
		ResolvedType rtype = utype.resolve(world);
		return new BcelVar(rtype, genTempVarIndex(rtype.getSize()));
	}

	// public static final boolean CREATE_TEMP_NAMES = true;

	public BcelVar genTempVar(UnresolvedType typeX, String localName) {
		BcelVar tv = genTempVar(typeX);

		// if (CREATE_TEMP_NAMES) {
		// for (InstructionHandle ih = range.getStart(); ih != range.getEnd(); ih = ih.getNext()) {
		// if (Range.isRangeHandle(ih)) continue;
		// ih.addTargeter(new LocalVariableTag(typeX, localName, tv.getSlot()));
		// }
		// }
		return tv;
	}

	// eh doesn't think we need to garbage collect these (64K is a big number...)
	private int genTempVarIndex(int size) {
		return enclosingMethod.allocateLocal(size);
	}

	public InstructionFactory getFactory() {
		return getEnclosingClass().getFactory();
	}

	@Override
	public ISourceLocation getSourceLocation() {
		int sourceLine = getSourceLine();
		if (sourceLine == 0 || sourceLine == -1) {
			// Thread.currentThread().dumpStack();
			// System.err.println(this + ": " + range);
			return getEnclosingClass().getType().getSourceLocation();
		} else {
			// For staticinitialization, if we have a nice offset, don't build a new source loc
			if (getKind() == Shadow.StaticInitialization && getEnclosingClass().getType().getSourceLocation().getOffset() != 0) {
				return getEnclosingClass().getType().getSourceLocation();
			} else {
				int offset = 0;
				Kind kind = getKind();
				if ((kind == MethodExecution) || (kind == ConstructorExecution) || (kind == AdviceExecution)
						|| (kind == StaticInitialization) || (kind == PreInitialization) || (kind == Initialization)) {
					if (getEnclosingMethod().hasDeclaredLineNumberInfo()) {
						offset = getEnclosingMethod().getDeclarationOffset();
					}
				}
				return getEnclosingClass().getType().getSourceContext().makeSourceLocation(sourceLine, offset);
			}
		}
	}

	public Shadow getEnclosingShadow() {
		return enclosingShadow;
	}

	public LazyMethodGen getEnclosingMethod() {
		return enclosingMethod;
	}

	public boolean isFallsThrough() {
		return !terminatesWithReturn();
	}

	public void setActualTargetType(String className) {
		this.actualInstructionTargetType = className;
	}

	public String getActualTargetType() {
		return actualInstructionTargetType;
	}
}
