/* ******************************************************************* * 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.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 mungers = Collections.emptyList(); protected boolean needAroundClosureStacking = false; 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 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 (Kind shadowKind : SHADOW_KINDS) { if ((i & shadowKind.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 (ResolvedType resolvedType : munger.getThrownExceptions()) { if (!checkCanThrow(munger, resolvedType)) { 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 (ResolvedType exc : excs) { if (exc.isAssignableFrom(resolvedTypeX)) { return true; } } return false; } public void addMunger(ShadowMunger munger) { if (checkMunger(munger)) { if (mungers == Collections.EMPTY_LIST) { mungers = new ArrayList<>(); } 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 clashingAspects = new HashSet<>(); 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(0)) { String key = adviceA.getDeclaringAspect() + ":" + adviceB.getDeclaringAspect(); String possibleExistingKey = adviceB.getDeclaringAspect() + ":" + adviceA.getDeclaringAspect(); if (!clashingAspects.contains(possibleExistingKey)) { clashingAspects.add(key); } } } } } } } for (String element : clashingAspects) { 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(); needAroundClosureStacking = false; int annotationStyleWithAroundAndProceedCount = 0; for (ShadowMunger munger: mungers) { if (munger.getDeclaringType()!= null && munger.getDeclaringType().isAnnotationStyleAspect() && munger.isAroundAdvice() && munger.bindsProceedingJoinPoint()) { annotationStyleWithAroundAndProceedCount++; if (annotationStyleWithAroundAndProceedCount>1) { needAroundClosureStacking = true; break; } } } 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("<>"); } else { ResolvedMember rm = world.resolve(m); if (rm == null) { sb.append("<>"); } else { String genString = rm.toGenericString(); if (genString == null) { sb.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 toSet(int i) { Set results = new HashSet<>(); 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; } }