blob: 587d19c1564caff902c174809536dcdd190bb359 [file] [log] [blame]
/* *******************************************************************
* 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 v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* PARC initial implementation
* ******************************************************************/
package org.aspectj.weaver;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.PartialOrder;
import org.aspectj.util.TypeSafeEnum;
import org.aspectj.weaver.ast.Var;
/*
* The superclass of anything representing a the shadow of a join point. A shadow represents
* some bit of code, and encompasses both entry and exit from that code. All shadows have a kind
* and a signature.
*/
public abstract class Shadow {
// every Shadow has a unique id, doesn't matter if it wraps...
private static int nextShadowID = 100; // easier to spot than zero. // OPTIMIZE is this a bug? static?
private final Kind kind;
private final Member signature;
private Member matchingSignature;
private ResolvedMember resolvedSignature;
protected final Shadow enclosingShadow;
protected List<ShadowMunger> mungers = Collections.emptyList();
public int shadowId = nextShadowID++; // every time we build a shadow, it gets a new id
// ----
protected Shadow(Kind kind, Member signature, Shadow enclosingShadow) {
this.kind = kind;
this.signature = signature;
this.enclosingShadow = enclosingShadow;
}
// ----
public abstract World getIWorld();
public List<ShadowMunger> getMungers() {
return mungers;
}
/**
* could this(*) pcd ever match
*/
public final boolean hasThis() {
if (getKind().neverHasThis()) {
return false;
} else if (getKind().isEnclosingKind()) {
return !Modifier.isStatic(getSignature().getModifiers());
} else if (enclosingShadow == null) {
return false;
} else {
return enclosingShadow.hasThis();
}
}
/**
* the type of the this object here
*
* @throws IllegalStateException if there is no this here
*/
public final UnresolvedType getThisType() {
if (!hasThis()) {
throw new IllegalStateException("no this");
}
if (getKind().isEnclosingKind()) {
return getSignature().getDeclaringType();
} else {
return enclosingShadow.getThisType();
}
}
/**
* a var referencing this
*
* @throws IllegalStateException if there is no target here
*/
public abstract Var getThisVar();
/**
* could target(*) pcd ever match
*/
public final boolean hasTarget() {
if (getKind().neverHasTarget()) {
return false;
} else if (getKind().isTargetSameAsThis()) {
return hasThis();
} else {
return !Modifier.isStatic(getSignature().getModifiers());
}
}
/**
* the type of the target object here
*
* @throws IllegalStateException if there is no target here
*/
public final UnresolvedType getTargetType() {
if (!hasTarget()) {
throw new IllegalStateException("no target");
}
return getSignature().getDeclaringType();
}
/**
* a var referencing the target
*
* @throws IllegalStateException if there is no target here
*/
public abstract Var getTargetVar();
public UnresolvedType[] getArgTypes() {
if (getKind() == FieldSet) {
return new UnresolvedType[] { getSignature().getReturnType() };
}
return getSignature().getParameterTypes();
}
public boolean isShadowForArrayConstructionJoinpoint() {
return (getKind() == ConstructorCall && signature.getDeclaringType().isArray());
}
public boolean isShadowForMonitor() {
return (getKind() == SynchronizationLock || getKind() == SynchronizationUnlock);
}
// will return the right length array of ints depending on how many dimensions the array has
public ResolvedType[] getArgumentTypesForArrayConstructionShadow() {
String s = signature.getDeclaringType().getSignature();
int pos = s.indexOf("[");
int dims = 1;
while (pos < s.length()) {
pos++;
if (pos < s.length()) {
dims += (s.charAt(pos) == '[' ? 1 : 0);
}
}
ResolvedType intType = UnresolvedType.INT.resolve(this.getIWorld());
if (dims == 1) {
return new ResolvedType[] { intType };
}
ResolvedType[] someInts = new ResolvedType[dims];
for (int i = 0; i < dims; i++) {
someInts[i] = intType;
}
return someInts;
}
public UnresolvedType[] getGenericArgTypes() {
if (isShadowForArrayConstructionJoinpoint()) {
return getArgumentTypesForArrayConstructionShadow();
}
if (isShadowForMonitor()) {
return UnresolvedType.ARRAY_WITH_JUST_OBJECT;
}
if (getKind() == FieldSet) {
return new UnresolvedType[] { getResolvedSignature().getGenericReturnType() };
}
return getResolvedSignature().getGenericParameterTypes();
}
public UnresolvedType getArgType(int arg) {
if (getKind() == FieldSet) {
return getSignature().getReturnType();
}
return getSignature().getParameterTypes()[arg];
}
public int getArgCount() {
if (getKind() == FieldSet) {
return 1;
}
return getSignature().getParameterTypes().length;
}
// /**
// * Return name of the argument at position 'i' at this shadow. This does not make sense for all shadows - but can be useful in
// * the case of, for example, method-execution.
// *
// * @return null if it cannot be determined
// */
// public String getArgName(int i, World w) {
// String[] names = getSignature().getParameterNames(w);
// if (names == null || i >= names.length)
// return null;
// return names[i];
// }
public abstract UnresolvedType getEnclosingType();
public abstract Var getArgVar(int i);
public abstract Var getThisJoinPointVar();
public abstract Var getThisJoinPointStaticPartVar();
public abstract Var getThisEnclosingJoinPointStaticPartVar();
public abstract Var getThisAspectInstanceVar(ResolvedType aspectType);
// annotation variables
public abstract Var getKindedAnnotationVar(UnresolvedType forAnnotationType);
public abstract Var getWithinAnnotationVar(UnresolvedType forAnnotationType);
public abstract Var getWithinCodeAnnotationVar(UnresolvedType forAnnotationType);
public abstract Var getThisAnnotationVar(UnresolvedType forAnnotationType);
public abstract Var getTargetAnnotationVar(UnresolvedType forAnnotationType);
public abstract Var getArgAnnotationVar(int i, UnresolvedType forAnnotationType);
public abstract Member getEnclosingCodeSignature();
/**
* returns the kind of shadow this is, representing what happens under this shadow
*/
public Kind getKind() {
return kind;
}
/**
* returns the signature of the thing under this shadow
*/
public Member getSignature() {
return signature;
}
/**
* returns the signature of the thing under this shadow, with any synthetic arguments removed
*/
public Member getMatchingSignature() {
return matchingSignature != null ? matchingSignature : signature;
}
public void setMatchingSignature(Member member) {
this.matchingSignature = member;
}
/**
* returns the resolved signature of the thing under this shadow
*
*/
public ResolvedMember getResolvedSignature() {
if (resolvedSignature == null) {
resolvedSignature = signature.resolve(getIWorld());
}
return resolvedSignature;
}
public UnresolvedType getReturnType() {
if (kind == ConstructorCall) {
return getSignature().getDeclaringType();
} else if (kind == FieldSet) {
return UnresolvedType.VOID;
} else if (kind == SynchronizationLock || kind == SynchronizationUnlock) {
return UnresolvedType.VOID;
}
return getResolvedSignature().getGenericReturnType();
}
public static String METHOD_EXECUTION = "method-execution";
public static String METHOD_CALL = "method-call";
public static String CONSTRUCTOR_EXECUTION = "constructor-execution";
public static String CONSTRUCTOR_CALL = "constructor-call";
public static String FIELD_GET = "field-get";
public static String FIELD_SET = "field-set";
public static String STATICINITIALIZATION = "staticinitialization";
public static String PREINITIALIZATION = "preinitialization";
public static String INITIALIZATION = "initialization";
public static String EXCEPTION_HANDLER = "exception-handler";
public static String SYNCHRONIZATION_LOCK = "lock";
public static String SYNCHRONIZATION_UNLOCK = "unlock";
public static String ADVICE_EXECUTION = "adviceexecution";
/**
* These names are the ones that will be returned by thisJoinPoint.getKind() Those need to be documented somewhere
*/
public static final Kind MethodCall = new Kind(METHOD_CALL, 1, true);
public static final Kind ConstructorCall = new Kind(CONSTRUCTOR_CALL, 2, true);
public static final Kind MethodExecution = new Kind(METHOD_EXECUTION, 3, false);
public static final Kind ConstructorExecution = new Kind(CONSTRUCTOR_EXECUTION, 4, false);
public static final Kind FieldGet = new Kind(FIELD_GET, 5, true);
public static final Kind FieldSet = new Kind(FIELD_SET, 6, true);
public static final Kind StaticInitialization = new Kind(STATICINITIALIZATION, 7, false);
public static final Kind PreInitialization = new Kind(PREINITIALIZATION, 8, false);
public static final Kind AdviceExecution = new Kind(ADVICE_EXECUTION, 9, false);
public static final Kind Initialization = new Kind(INITIALIZATION, 10, false);
public static final Kind ExceptionHandler = new Kind(EXCEPTION_HANDLER, 11, true);
public static final Kind SynchronizationLock = new Kind(SYNCHRONIZATION_LOCK, 12, true);
public static final Kind SynchronizationUnlock = new Kind(SYNCHRONIZATION_UNLOCK, 13, true);
// Bits here are 1<<(Kind.getKey()) - and unfortunately keys didn't start at zero so bits here start at 2
public static final int MethodCallBit = 0x002;
public static final int ConstructorCallBit = 0x004;
public static final int MethodExecutionBit = 0x008;
public static final int ConstructorExecutionBit = 0x010;
public static final int FieldGetBit = 0x020;
public static final int FieldSetBit = 0x040;
public static final int StaticInitializationBit = 0x080;
public static final int PreInitializationBit = 0x100;
public static final int AdviceExecutionBit = 0x200;
public static final int InitializationBit = 0x400;
public static final int ExceptionHandlerBit = 0x800;
public static final int SynchronizationLockBit = 0x1000;
public static final int SynchronizationUnlockBit = 0x2000;
public static final int MAX_SHADOW_KIND = 13;
public static final Kind[] SHADOW_KINDS = new Kind[] { MethodCall, ConstructorCall, MethodExecution, ConstructorExecution,
FieldGet, FieldSet, StaticInitialization, PreInitialization, AdviceExecution, Initialization, ExceptionHandler,
SynchronizationLock, SynchronizationUnlock };
public static final int ALL_SHADOW_KINDS_BITS;
public static final int NO_SHADOW_KINDS_BITS;
static {
ALL_SHADOW_KINDS_BITS = 0x3ffe;
NO_SHADOW_KINDS_BITS = 0x0000;
}
/**
* Return count of how many bits set in the supplied parameter.
*/
public static int howMany(int i) {
int count = 0;
for (int j = 0; j < SHADOW_KINDS.length; j++) {
if ((i & SHADOW_KINDS[j].bit) != 0) {
count++;
}
}
return count;
}
/**
* A type-safe enum representing the kind of shadows
*/
public static final class Kind extends TypeSafeEnum {
// private boolean argsOnStack; //XXX unused
public int bit;
public Kind(String name, int key, boolean argsOnStack) {
super(name, key);
bit = 1 << key;
// this.argsOnStack = argsOnStack;
}
public String toLegalJavaIdentifier() {
return getName().replace('-', '_');
}
public boolean argsOnStack() {
return !isTargetSameAsThis();
}
// false for handlers
public boolean allowsExtraction() {
return true;
}
public boolean isSet(int i) {
return (i & bit) != 0;
}
// XXX revisit along with removal of priorities
public boolean hasHighPriorityExceptions() {
return !isTargetSameAsThis();
}
private final static int hasReturnValueFlag = MethodCallBit | ConstructorCallBit | MethodExecutionBit | FieldGetBit
| AdviceExecutionBit;
/**
* These shadow kinds have return values that can be bound in after returning(Dooberry doo) advice.
*
* @return
*/
public boolean hasReturnValue() {
return (bit & hasReturnValueFlag) != 0;
}
private final static int isEnclosingKindFlag = MethodExecutionBit | ConstructorExecutionBit | AdviceExecutionBit
| StaticInitializationBit | InitializationBit;
/**
* These are all the shadows that contains other shadows within them and are often directly associated with methods.
*/
public boolean isEnclosingKind() {
return (bit & isEnclosingKindFlag) != 0;
}
private final static int isTargetSameAsThisFlag = MethodExecutionBit | ConstructorExecutionBit | StaticInitializationBit
| PreInitializationBit | AdviceExecutionBit | InitializationBit;
public boolean isTargetSameAsThis() {
return (bit & isTargetSameAsThisFlag) != 0;
}
private final static int neverHasTargetFlag = ConstructorCallBit | ExceptionHandlerBit | PreInitializationBit
| StaticInitializationBit | SynchronizationLockBit | SynchronizationUnlockBit;
public boolean neverHasTarget() {
return (bit & neverHasTargetFlag) != 0;
}
private final static int neverHasThisFlag = PreInitializationBit | StaticInitializationBit;
public boolean neverHasThis() {
return (bit & neverHasThisFlag) != 0;
}
public String getSimpleName() {
int dash = getName().lastIndexOf('-');
if (dash == -1) {
return getName();
} else {
return getName().substring(dash + 1);
}
}
public static Kind read(DataInputStream s) throws IOException {
int key = s.readByte();
switch (key) {
case 1:
return MethodCall;
case 2:
return ConstructorCall;
case 3:
return MethodExecution;
case 4:
return ConstructorExecution;
case 5:
return FieldGet;
case 6:
return FieldSet;
case 7:
return StaticInitialization;
case 8:
return PreInitialization;
case 9:
return AdviceExecution;
case 10:
return Initialization;
case 11:
return ExceptionHandler;
case 12:
return SynchronizationLock;
case 13:
return SynchronizationUnlock;
}
throw new BCException("unknown kind: " + key);
}
}
/**
* Only does the check if the munger requires it (@AJ aspects don't)
*
* @param munger
* @return
*/
protected boolean checkMunger(ShadowMunger munger) {
if (munger.mustCheckExceptions()) {
for (Iterator<ResolvedType> i = munger.getThrownExceptions().iterator(); i.hasNext();) {
if (!checkCanThrow(munger, i.next())) {
return false;
}
}
}
return true;
}
protected boolean checkCanThrow(ShadowMunger munger, ResolvedType resolvedTypeX) {
if (getKind() == ExceptionHandler) {
// XXX much too lenient rules here, need to walk up exception handlers
return true;
}
if (!isDeclaredException(resolvedTypeX, getSignature())) {
getIWorld().showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CANT_THROW_CHECKED, resolvedTypeX, this), // from
// advice
// in
// \
// '"
// +
// munger
// .
// +
// "\'"
// ,
getSourceLocation(), munger.getSourceLocation());
}
return true;
}
private boolean isDeclaredException(ResolvedType resolvedTypeX, Member member) {
ResolvedType[] excs = getIWorld().resolve(member.getExceptions(getIWorld()));
for (int i = 0, len = excs.length; i < len; i++) {
if (excs[i].isAssignableFrom(resolvedTypeX)) {
return true;
}
}
return false;
}
public void addMunger(ShadowMunger munger) {
if (checkMunger(munger)) {
if (mungers == Collections.EMPTY_LIST) {
mungers = new ArrayList<ShadowMunger>();
}
this.mungers.add(munger);
}
}
public final void implement() {
sortMungers();
if (mungers == null) {
return;
}
prepareForMungers();
implementMungers();
}
private void sortMungers() {
List sorted = PartialOrder.sort(mungers);
// Bunch of code to work out whether to report xlints for advice that isn't ordered at this Joinpoint
possiblyReportUnorderedAdvice(sorted);
if (sorted == null) {
// this means that we have circular dependencies
for (ShadowMunger m : mungers) {
getIWorld().getMessageHandler().handleMessage(
MessageUtil.error(WeaverMessages.format(WeaverMessages.CIRCULAR_DEPENDENCY, this), m.getSourceLocation()));
}
}
mungers = sorted;
}
// not quite optimal... but the xlint is ignore by default
private void possiblyReportUnorderedAdvice(List sorted) {
if (sorted != null && getIWorld().getLint().unorderedAdviceAtShadow.isEnabled() && mungers.size() > 1) {
// Stores a set of strings of the form 'aspect1:aspect2' which indicates there is no
// precedence specified between the two aspects at this shadow.
Set<String> clashingAspects = new HashSet<String>();
int max = mungers.size();
// Compare every pair of advice mungers
for (int i = max - 1; i >= 0; i--) {
for (int j = 0; j < i; j++) {
Object a = mungers.get(i);
Object b = mungers.get(j);
// Make sure they are the right type
if (a instanceof Advice && b instanceof Advice) {
Advice adviceA = (Advice) a;
Advice adviceB = (Advice) b;
if (!adviceA.concreteAspect.equals(adviceB.concreteAspect)) {
AdviceKind adviceKindA = adviceA.getKind();
AdviceKind adviceKindB = adviceB.getKind();
// make sure they are the nice ones (<6) and not any synthetic advice ones we
// create to support other features of the language.
if (adviceKindA.getKey() < (byte) 6 && adviceKindB.getKey() < (byte) 6
&& adviceKindA.getPrecedence() == adviceKindB.getPrecedence()) {
// Ask the world if it knows about precedence between these
Integer order = getIWorld().getPrecedenceIfAny(adviceA.concreteAspect, adviceB.concreteAspect);
if (order != null && order.equals(new Integer(0))) {
String key = adviceA.getDeclaringAspect() + ":" + adviceB.getDeclaringAspect();
String possibleExistingKey = adviceB.getDeclaringAspect() + ":" + adviceA.getDeclaringAspect();
if (!clashingAspects.contains(possibleExistingKey)) {
clashingAspects.add(key);
}
}
}
}
}
}
}
for (Iterator<String> iter = clashingAspects.iterator(); iter.hasNext();) {
String element = iter.next();
String aspect1 = element.substring(0, element.indexOf(":"));
String aspect2 = element.substring(element.indexOf(":") + 1);
getIWorld().getLint().unorderedAdviceAtShadow.signal(new String[] { this.toString(), aspect1, aspect2 },
this.getSourceLocation(), null);
}
}
}
/**
* Prepare the shadow for implementation. After this is done, the shadow should be in such a position that each munger simply
* needs to be implemented.
*/
protected void prepareForMungers() {
throw new RuntimeException("Generic shadows cannot be prepared");
}
/** Actually implement the (non-empty) mungers associated with this shadow */
private void implementMungers() {
World world = getIWorld();
for (ShadowMunger munger : mungers) {
if (munger.implementOn(this)) {
world.reportMatch(munger, this);
}
}
}
public abstract ISourceLocation getSourceLocation();
// ---- utility
public String toString() {
return getKind() + "(" + getSignature() + ")"; // + getSourceLines();
}
public String toResolvedString(World world) {
StringBuffer sb = new StringBuffer();
sb.append(getKind());
sb.append("(");
Member m = getSignature();
if (m == null) {
sb.append("<<missing signature>>");
} else {
ResolvedMember rm = world.resolve(m);
if (rm == null) {
sb.append("<<unresolvableMember:").append(m).append(">>");
} else {
String genString = rm.toGenericString();
if (genString == null) {
sb.append("<<unableToGetGenericStringFor:").append(rm).append(">>");
} else {
sb.append(genString);
}
}
}
sb.append(")");
return sb.toString();
// was: return getKind() + "(" + world.resolve(getSignature()).toGenericString() + ")";
}
/**
* Convert a bit array for the shadow kinds into a set of them... should only be used for testing - mainline code should do bit
* manipulation!
*/
public static Set<Kind> toSet(int i) {
Set<Kind> results = new HashSet<Kind>();
for (int j = 0; j < Shadow.SHADOW_KINDS.length; j++) {
Kind k = Shadow.SHADOW_KINDS[j];
if (k.isSet(i)) {
results.add(k);
}
}
return results;
}
}